I am currently implementing Maxwell’s equations with a Cohen Monk perfectly matched layer in a two-dimensional time domain using a rectangular grid. The integration points for error calculation are only taken at the center of each element. Theoretically, the convergence order should be 2, but I consistently obtain an order of 1. I am not sure where the issue lies.Here is my code:
from mpi4py import MPI
from petsc4py import PETSc
import numpy as np
import ufl
import basix
from basix import CellType
from pathlib import Path
from dolfinx import fem, default_real_type, mesh
from dolfinx.fem import (
Constant,
Function,
extract_function_spaces,
functionspace,
assemble_scalar,
dirichletbc,
form,
Expression,
locate_dofs_geometrical,
locate_dofs_topological,
)
from dolfinx.fem.petsc import (
assemble_matrix,
assemble_vector,
apply_lifting,
create_vector,
set_bc,
)
from dolfinx.io import VTXWriter,XDMFFile
from dolfinx.mesh import create_rectangle, compute_midpoints, locate_entities_boundary, locate_entities, CellType, create_unit_square
from dolfinx.plot import vtk_mesh
from basix.ufl import element, quadrature_element
from ufl import (
FacetNormal,
Identity,
TestFunction,
TrialFunction,
div,
curl,
dot,
ds,
dx,
inner,
lhs,
nabla_grad,
rhs,
sym,
interpolate,
SpatialCoordinate,
as_vector,
Measure,
exp,
sin,
cos,
pi,
as_matrix
)
def l2_error(solution, exact_expr, space_type="E"):
# Dx = dx(metadata={'quadrature_degree':1})
if space_type == "E":
# 计算误差表达式
error_expr = form(inner(solution - exact_expr, solution - exact_expr) * dx)
else:
# 对于标量磁场
error_expr = form((solution - exact_expr) * (solution - exact_expr) * dx)
error = np.sqrt(MPI.COMM_WORLD.allreduce(assemble_scalar(error_expr), op=MPI.SUM))
return error
def maxwell_simulation(N, dt, T=1.0, output_dir="results"):
output_folder = Path(output_dir) / f"N{N}"
output_folder.mkdir(exist_ok=True, parents=True)
ax0 = 0;ay0 = 0
bx0 = 1;by0 = 1
Nx , Ny=N, N
points = np.array([[ax0,ay0],[bx0,by0]], dtype=np.float64)
n = [Nx, Ny]
mesh = create_rectangle(MPI.COMM_WORLD, points, n, CellType.quadrilateral)
V = element("N1curl", mesh.basix_cell(), 1)
U = element("DG", mesh.basix_cell(), 0)
Vs = functionspace(mesh, V)
Us = functionspace(mesh, U)
V_lagrange = functionspace(mesh, ("Lagrange", 1, (mesh.geometry.dim,)))
U_lagrange = functionspace(mesh, ("Lagrange", 1))
x = SpatialCoordinate(mesh)
# 使用 ufl 定义表达式
def curl_2d_E(E):
return ufl.Dx(E[1], 0) - ufl.Dx(E[0], 1)
# 精确解 - 使用 ufl 表达式
def E_exact_expr(x, t):
return as_vector([
ufl.exp(-ufl.pi * t) * ufl.cos(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1]),
-ufl.exp(-ufl.pi * t) * ufl.sin(ufl.pi * x[0]) * ufl.cos(ufl.pi * x[1])
])
# 用于插值的 numpy 函数(需要传入时间参数)
def E_exact_np(x, t):
return np.array([
np.exp(-np.pi * t) * np.cos(np.pi * x[0]) * np.sin(np.pi * x[1]),
-np.exp(-np.pi * t) * np.sin(np.pi * x[0]) * np.cos(np.pi * x[1])
])
def H_exact_expr(x, t):
return ufl.exp(-ufl.pi * t) * ufl.cos(ufl.pi * x[0]) * ufl.cos(ufl.pi * x[1])
def H_exact_np(x, t):
return np.exp(-np.pi * t) * np.cos(np.pi * x[0]) * np.cos(np.pi * x[1])
def E_star_exact_expr(x, t):
return as_vector([ufl.exp(-pi * t) * cos(pi * x[0]) * sin(pi * x[1]),
-ufl.exp(-pi * t) * sin(pi * x[0]) * cos(pi * x[1])])
def E_star_exact_np(x, t):
return np.array([
np.exp(-np.pi * t) * np.cos(np.pi * x[0]) * np.sin(np.pi * x[1]),
-np.exp(-np.pi * t) * np.sin(np.pi * x[0]) * np.cos(np.pi * x[1])
])
def K_exact_expr(x, t):
return -(1 / ufl.pi) * ufl.exp(-ufl.pi * t) * ufl.cos(ufl.pi * x[0]) * ufl.cos(ufl.pi * x[1])
def K_exact_np(x, t):
return -(1 / np.pi) * np.exp(-np.pi * t) * np.cos(np.pi * x[0]) * np.cos(np.pi * x[1])
def H_star_exact_np(x_val, t_val):
return np.exp(-np.pi * t_val) * np.cos(np.pi * x_val[0]) * np.cos(np.pi * x_val[1])
def f_exact_np(x, t):
return np.array([
np.cos(np.pi * x[0]) * np.sin(np.pi * x[1]) *
((np.sin(np.pi * x[1]))**2 - (np.sin(np.pi * x[0]))**2),
np.sin(np.pi * x[0]) * np.cos(np.pi * x[1]) *
((np.sin(np.pi * x[1]))**2 - (np.sin(np.pi * x[0]))**2)
])
# 源项 hat_f
def f_hat_expr(x,t):
return as_vector([
(ufl.exp(-pi * t) - 1) * ufl.cos(ufl.pi * x[0]) * ufl.sin(ufl.pi * x[1]) *
((ufl.sin(ufl.pi * x[1]))**2 - (ufl.sin(pi * x[0]))**2),
(ufl.exp(-pi * t) - 1) * sin(pi * x[0]) * cos(pi * x[1]) *
((ufl.sin(pi * x[1]))**2 - (ufl.sin(ufl.pi * x[0]))**2)
])
def f_hat_np(x, t):
return np.array([
(np.exp(-np.pi * t) - 1) * np.cos(np.pi * x[0]) * np.sin(np.pi * x[1]) *
((np.sin(np.pi * x[1]))**2 - (np.sin(np.pi * x[0]))**2),
(np.exp(-np.pi * t) - 1) * np.sin(np.pi * x[0]) * np.cos(np.pi * x[1]) *
((np.sin(np.pi * x[1]))**2 - (np.sin(np.pi * x[0]))**2)
])
# 源项 g_z
g_exact_expr = cos(pi * x[0]) * cos(pi * x[1]) * ((sin(pi * x[0]))**2 + (sin(pi * x[1]))**2)
def g_exact_np(x, t):
return np.cos(np.pi * x[0]) * np.cos(np.pi * x[1]) * ((np.sin(np.pi * x[0]))**2 + (np.sin(np.pi * x[1]))**2)
# 源项 hat_g_z
def g_hat_expr(t):
return (ufl.exp(-pi * t) * cos(pi * x[0]) * cos(pi * x[1]) *
(((sin(pi * x[0]))**2 + (sin(pi * x[1]))**2) -
(1 / pi) * (sin(pi * x[0]))**2 * sin(pi * x[1])**2) -
cos(pi * x[0]) * cos(pi * x[1]) * ((sin(pi * x[0]))**2 + (sin(pi * x[1]))**2))
def g_hat_np(x, t):
return (np.exp(-np.pi * t) * np.cos(np.pi * x[0]) * np.cos(np.pi * x[1]) *
(((np.sin(np.pi * x[0]))**2 + (np.sin(np.pi * x[1]))**2) -
(1 / np.pi) * (np.sin(np.pi * x[0]))**2 * (np.sin(np.pi * x[1]))**2) -
np.cos(np.pi * x[0]) * np.cos(np.pi * x[1]) *
((np.sin(np.pi * x[0]))**2 + (np.sin(np.pi * x[1]))**2))
# 源项 g_z^*
def g_star_expr(t):
return -3 * pi * ufl.exp(-pi * t) * cos(pi * x[0]) * cos(pi * x[1])
def g_star_np(x, t):
return -3 * np.pi * np.exp(-np.pi * t) * np.cos(np.pi * x[0]) * np.cos(np.pi * x[1])
# sigma 函数
sigma_x_expr = (sin(pi * x[0]))**2
sigma_y_expr = (sin(pi * x[1]))**2
sigma_x = sigma_x_expr
sigma_y = sigma_y_expr
# 矩阵系数
C_2d = as_matrix([[sigma_y, 0],
[0, sigma_x]])
G_2d = as_matrix([[sigma_x, 0],
[0, sigma_y]])
C_1d = sigma_x + sigma_y
D_1d = sigma_x * sigma_y
# PEC边界条件
def pec_boundary(x):
tol = 1e-10
return (np.abs(x[0] - 0) < tol) | (np.abs(x[0] - 1) < tol) | \
(np.abs(x[1] - 0) < tol) | (np.abs(x[1] - 1) < tol)
boundary_facets = locate_entities_boundary(mesh, mesh.topology.dim - 1, pec_boundary)
boundary_dofs_E = locate_dofs_topological(Vs, mesh.topology.dim - 1, boundary_facets)
Ebc = dirichletbc(Function(Vs), boundary_dofs_E)
Estarbc = dirichletbc(Function(Vs), boundary_dofs_E)
# 初始值
eps0 = 1.0 # ε0
mu0 = 1.0 # μ0
E_n = Function(Vs)
E_n.interpolate(lambda x: E_exact_np(x, 0))
E_starn = Function(Vs)
E_starn.interpolate(lambda x: E_star_exact_np(x, 0))
H_nhalf = Function(Us)
H_nhalf.interpolate(lambda x: H_exact_np(x, 0.5 * dt))
H_starn = Function(Us)
H_starn.interpolate(lambda x: H_star_exact_np(x, 0.5 * dt))
K_nhalf = Function(Us)
K_nhalf.interpolate(lambda x: K_exact_np(x, 0.5 * dt))
# 源项函数
f_n = Function(Vs)
f_n.interpolate(lambda x: f_exact_np(x, 0))
f_hat = Function(Vs)
f_hat.interpolate(lambda x: f_hat_np(x, 0.5 * dt))
g_n = Function(Us)
g_n.interpolate(lambda x: g_exact_np(x, 0))
g_star = Function(Us)
g_star.interpolate(lambda x: g_star_np(x, dt))
g_hat = Function(Us)
g_hat.interpolate(lambda x: g_hat_np(x, dt))
#变分形式
E_star = TrialFunction(Vs)
psi = TestFunction(Vs)
a1 = form(ufl.inner(E_star, psi) * dx )
L1 = form(ufl.inner(E_starn, psi) * dx
+ dt * ufl.inner(H_nhalf, curl(psi)) * dx)
A1 = assemble_matrix(a1)
A1.assemble()
b1 = create_vector(Vs)
E_starsolution = Function(Vs)
E = TrialFunction(Vs)
psi_hat = TestFunction(Vs)
a2 = form(ufl.inner(E, psi_hat) * dx + 0.5 * dt * ufl.inner(C_2d * E, psi_hat) * dx )
L2 = form(ufl.inner(E_n, psi_hat) * dx
- 0.5 * dt * ufl.inner(C_2d * E_n, psi_hat) * dx
+ ufl.inner(E_starsolution, psi_hat) * dx
- ufl.inner(E_starn, psi_hat) * dx
+ 0.5 * dt * ufl.inner(G_2d * E_starsolution, psi_hat) * dx
+ 0.5 * dt * ufl.inner(G_2d * E_starn, psi_hat) * dx
+ dt * ufl.inner(f_n, psi_hat) * dx
+ dt * ufl.inner(f_hat, psi_hat) * dx)
A2 = assemble_matrix(a2)
A2.assemble()
b2 = create_vector(Vs)
Esolution = Function(Vs)
H_star = TrialFunction(Us)
phi = TestFunction(Us)
a3 = form(ufl.inner(H_star, phi) * dx )
L3 = form(ufl.inner(H_starn, phi) * dx
- dt * ufl.inner(curl(Esolution), phi) * dx
+ dt * ufl.inner(g_star, phi) * dx)
A3 = assemble_matrix(a3)
A3.assemble()
b3 = create_vector(Us)
H_starsolution = Function(Us)
H = TrialFunction(Us)
phi_hat = TestFunction(Us)
a4 = form(ufl.inner(H, phi_hat) * dx
+ 0.5 * dt * ufl.inner(C_1d * H, phi_hat) * dx
+ 0.25 * (dt**2) * ufl.inner(D_1d * H, phi_hat) * dx)
L4 = form(ufl.inner(H_nhalf, phi_hat) * dx
- 0.5 * dt * ufl.inner(C_1d * H_nhalf, phi_hat) * dx
- dt * ufl.inner(D_1d * K_nhalf, phi_hat) * dx
- 0.25 * (dt**2) * ufl.inner(D_1d * H_nhalf, phi_hat) * dx
+ ufl.inner(H_starsolution,phi_hat) * dx
- ufl.inner(H_starn, phi_hat) * dx
+ dt * ufl.inner(g_n, phi_hat) * dx
+ dt * ufl.inner(g_hat, phi_hat) * dx)
A4 = assemble_matrix(a4)
A4.assemble()
b4 = create_vector(Us)
Hsolution = Function(Us)
K = TrialFunction(Us)
phi_wide = TestFunction(Us)
a5 = form(ufl.inner(D_1d * K, phi_wide) * dx)
L5 = form(ufl.inner(D_1d * K_nhalf, phi_wide) * dx
+ 0.5 * dt * ufl.inner(D_1d * Hsolution, phi_wide) * dx
+ 0.5 * dt * ufl.inner(D_1d * H_nhalf, phi_wide) * dx)
A5 = assemble_matrix(a5)
A5.assemble()
b5 = create_vector(Us)
Ksolution = Function(Us)
solver1 = PETSc.KSP().create(mesh.comm)
solver1.setOperators(A1)
solver1.setType(PETSc.KSP.Type.PREONLY)
pc1 = solver1.getPC()
pc1.setType(PETSc.PC.Type.LU)
solver2 = PETSc.KSP().create(mesh.comm)
solver2.setOperators(A2)
solver2.setType(PETSc.KSP.Type.PREONLY)
pc2 = solver2.getPC()
pc2.setType(PETSc.PC.Type.LU)
solver3 = PETSc.KSP().create(mesh.comm)
solver3.setOperators(A3)
solver3.setType(PETSc.KSP.Type.PREONLY)
pc3 = solver3.getPC()
pc3.setType(PETSc.PC.Type.LU)
solver4 = PETSc.KSP().create(mesh.comm)
solver4.setOperators(A4)
solver4.setType(PETSc.KSP.Type.PREONLY)
pc4 = solver4.getPC()
pc4.setType(PETSc.PC.Type.LU)
solver5 = PETSc.KSP().create(mesh.comm)
solver5.setOperators(A5)
solver5.setType(PETSc.KSP.Type.PREONLY)
pc5 = solver5.getPC()
pc5.setType(PETSc.PC.Type.LU)
E_for_output = Function(V_lagrange)
H_for_output = Function(U_lagrange)
folder = output_folder
# 写入网格文件
with XDMFFile(mesh.comm, folder / "mesh.xdmf", "w") as xdmf_file:
xdmf_file.write_mesh(mesh)
E_for_output = Function(V_lagrange)
xdmf_E = XDMFFile(mesh.comm, folder / "Esolution_over_time.xdmf", "w")
xdmf_H = XDMFFile(mesh.comm, folder / "Hsolution_over_time.xdmf", "w")
xdmf_E.write_mesh(mesh)
xdmf_H.write_mesh(mesh)
t_E = 0
t_half = 0.5 * dt
Nt = 1000
for n in range(Nt):
t_E_new = t_E + dt
t_next_half = t_half + dt
with b1.localForm() as loc_1:
loc_1.set(0)
assemble_vector(b1, L1)
apply_lifting(b1, [a1], [[Estarbc]])
b1.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b1, [Estarbc])
solver1.solve(b1, E_starsolution.x.petsc_vec)
E_starsolution.x.scatter_forward()
f_hat.interpolate(lambda x: np.array([
(np.exp(-pi * t_half) - 1) * np.cos(np.pi * x[0]) * np.sin(np.pi * x[1]) * ((np.sin(np.pi * x[1]))**2 - (np.sin(np.pi * x[0]))**2),
(np.exp(-pi * t_half) - 1) * np.sin(np.pi * x[0]) * np.cos(np.pi * x[1]) * ((np.sin(np.pi * x[1]))**2 - (np.sin(np.pi * x[0]))**2)
]))
with b2.localForm() as loc_2:
loc_2.set(0)
assemble_vector(b2, L2)
apply_lifting(b2, [a2], [[Ebc]])
b2.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b2, [Ebc])
solver2.solve(b2, Esolution.x.petsc_vec)
Esolution.x.scatter_forward()
g_star.interpolate(lambda x: g_star_np(x, t_E_new))
with b3.localForm() as loc_3:
loc_3.set(0)
assemble_vector(b3, L3)
apply_lifting(b3, [a3], [[]])
b3.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b3, [])
solver3.solve(b3, H_starsolution.x.petsc_vec)
H_starsolution.x.scatter_forward()
g_hat.interpolate(lambda x: g_hat_np(x, t_E_new))
with b4.localForm() as loc_4:
loc_4.set(0)
assemble_vector(b4, L4)
apply_lifting(b4, [a4], [[]])
b4.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b4, [])
solver4.solve(b4, Hsolution.x.petsc_vec)
Hsolution.x.scatter_forward()
with b5.localForm() as loc_5:
loc_5.set(0)
assemble_vector(b5, L5)
b5.ghostUpdate(addv=PETSc.InsertMode.ADD_VALUES, mode=PETSc.ScatterMode.REVERSE)
set_bc(b5, [])
solver5.solve(b5, Ksolution.x.petsc_vec)
Ksolution.x.scatter_forward()
# 每100步或最后一步保存XDMF文件
if n % 100 == 0 or n == Nt - 1:
E_for_output.interpolate(Esolution)
H_for_output.interpolate(Hsolution)
E_for_output.name = "E_field"
# Hsolution.name = "H_field"
H_for_output.name = "H_field"
# Hsolution.name = "H_field"
xdmf_E.write_function(E_for_output, t_E_new)
xdmf_H.write_function(H_for_output, t_next_half)
# vtk_E.write_function(E_for_output, t_E_new)
# vtk_H.write_function(Hsolution, t_next_half)
print(f" -> Data written at t={t_E_new:.6e} s (={t_E_new*1e9:.3f} ns)")
H_nhalf.x.array[:] = Hsolution.x.array[:]
E_n.x.array[:] = Esolution.x.array[:]
H_starn.x.array[:] = H_starsolution.x.array[:]
E_starn.x.array[:] = E_starsolution.x.array[:]
K_nhalf.x.array[:] = Ksolution.x.array[:]
E_n.x.scatter_forward()
H_nhalf.x.scatter_forward()
H_starn.x.scatter_forward()
E_starn.x.scatter_forward()
K_nhalf.x.scatter_forward()
t_E = t_E_new
t_half = t_next_half
# 计算最终时刻误差
E_expr_final = E_exact_expr(x, t_E)
H_expr_final = H_exact_expr(x, t_half)
final_E_error = l2_error(Esolution, E_expr_final, space_type="E")
final_H_error = l2_error(Hsolution, H_expr_final, space_type="H")
xdmf_E.close()
xdmf_H.close()
b1.destroy()
b2.destroy()
b3.destroy()
b4.destroy()
b5.destroy()
solver1.destroy()
solver2.destroy()
solver3.destroy()
solver4.destroy()
h = 1.0/N # 网格尺寸
return {
'N': N,
'h': h,
'dt': dt,
'final_time_E': t_E,
'final_time_H': t_half,
'error_E_abs': final_E_error,
'error_H_abs': final_H_error,
'num_time_steps': Nt
}
def calculate_convergence_rates(results):
print("空间收敛性分析")
# 提取数据
N_values = [r['N'] for r in results]
h_values = [r['h'] for r in results]
# dt_values = [r['dt'] for r in results]
E_errors = [r['error_E_abs'] for r in results]
H_errors = [r['error_H_abs'] for r in results]
print(f"{'N':>6} {'h':>10} {'E_err(绝对)':>15} {'H_err(绝对)':>15} ")
print("-" * 85)
for i in range(len(results)):
print(f"{N_values[i]:6d} {h_values[i]:10.6f} "
f"{E_errors[i]:15.6e} {H_errors[i]:15.6e} ")#{dt_values[i]:12.6e}
# 计算收敛阶
print("\n收敛阶计算:")
print("-" * 50)
convergence_rates = []
for i in range(1, len(h_values)):
rate_E = np.log(E_errors[i-1] / E_errors[i]) / np.log(h_values[i-1] / h_values[i])
rate_H = np.log(H_errors[i-1] / H_errors[i]) / np.log(h_values[i-1] / h_values[i])
print(f"h: {h_values[i-1]:.4f} -> {h_values[i]:.4f} (细化比: {h_values[i-1]/h_values[i]:.2f})")
print(f" E场绝对误差收敛阶: {rate_E:.4f}")
print(f" H场绝对误差收敛阶: {rate_H:.4f}")
return {
'N_values': N_values,
'h_values': h_values,
'E_errors': E_errors,
'H_errors': H_errors,
'convergence_rates': convergence_rates
}
def spatial_convergence_test():
print("="*70)
print("开始空间收敛性测试")
print("="*70)
T = 0.01 # 总时间
dt = 1e-5# 固定时间步长
N_values = [10, 20, 40, 80, 160]
results = []
for N in N_values:
h = 1/N
# dt = 0.01 * h
print(f"\n运行 N={N}, h={1.0/N:.4f}, dt={dt:.6e}, T={T}")
print("-" * 40)
result = maxwell_simulation(
N=N,
dt=dt,
T=T,
output_dir=f"cr2"
)
results.append(result)
print(f"完成! 最终误差:")
print(f" E场绝对误差: {result['error_E_abs']:.6e}")
print(f" H场绝对误差: {result['error_H_abs']:.6e}")
# 计算收敛阶
convergence_data = calculate_convergence_rates(results)
return {
'results': results,
'convergence_data': convergence_data
}
if __name__ == "__main__":
results = spatial_convergence_test()
Here are my results.
Estimated Convergence Rates
Refinement E field order H field order
0.1000 → 0.0500 1.4940 0.5751
0.0500 → 0.0250 1.3871 0.6573
0.0250 → 0.0125 1.0953 0.9593
0.0125 → 0.0063 0.9845 1.2414