Maxwell with Mixed Conditions#
keys: PEC bvp with mixed conditions, MoM, Dirichlet data, Neumann data, manufactured solution (test)
from netgen.occ import *
import netgen.meshing as meshing
from ngsolve import *
from ngsolve.webgui import Draw
from ngbem import *
from ngsolve import Projector, Preconditioner
from ngsolve.krylovspace import CG, GMRes
Loading ngbem library
Consider the mixed problem:
\( \begin{array}{l rcl r} & \mathrm{curl}\,\mathrm{curl} \boldsymbol E - \kappa^2 \boldsymbol E &=& \boldsymbol 0 &\mathrm{in}\; \Omega^c \,,\\ \textnormal{Dirichlet condition} & \gamma_R \boldsymbol E &=& \boldsymbol m & \mathrm{on}\; \Gamma_0\,,\\ \textnormal{Neumann condition} & \gamma_N \boldsymbol E &=& \boldsymbol j & \mathrm{on}\; \Gamma_1\,, \\ \textnormal{radiation condition} & | \mathrm{curl}\boldsymbol E \times \dfrac{x}{|x|} - i \omega\epsilon \boldsymbol E(x)| &=& \mathcal O(1/|x|^2), & |x|\to \infty\,. \end{array} \) |
\(\quad\quad\quad\) |
lower half sphere: Dirichlet boundary with given \(\gamma_D \, E = \boldsymbol m\)
upper half sphere: Neumann boundary with given \(\gamma_N \, E = \boldsymbol j\)
Define geometry and mesh:
topsphere = Sphere((0,0,0), 1) * Box((-1,-1,0),(1,1,1))
botsphere = Sphere((0,0,0), 1) - Box((-1,-1,0),(1,1,1))
topsphere.faces.name = "neumann" # 1/kappa n x curl E
botsphere.faces.name = "dirichlet" # nxE
shape = Fuse( [topsphere,botsphere] )
h = 0.5
order = 3
mesh = Mesh( OCCGeometry(shape).GenerateMesh(maxh=h, perfstepsend=meshing.MeshingStep.MESHSURFACE)).Curve(order)
#Draw (mesh);
Define a manufactured solution \(\boldsymbol E\):
# manufactured solution: kernel with source in point s and direction e
kappa = 1. / h
s = CF((0., 0., 0.))
e = (1.192, -0.189, 2.745)
e = CF(e) / sqrt(e[0] ** 2 + e[1] ** 2 + e[2] ** 2)
xms = CF((x, y , z)) - s
r = Norm(xms)
f = (-kappa ** 2 * r ** 2 - 1j * kappa * r + 1.) / r ** 3
g = (e * xms) * (-kappa ** 2 * r ** 2 - 3. * 1j * kappa * r + 3.) / r ** 5
E = exp(1j * kappa * r) * (f * e - g * xms)
curlE = CF((E[2].Diff(y) - E[1].Diff(z), E[0].Diff(z) - E[2].Diff(x), E[1].Diff(x) - E[0].Diff(y)))
print("kappa = ", kappa)
kappa = 2.0
Define the finite element spaces, i.e., \(H^{-\frac12}(\mathrm{curl}_\Gamma, \Gamma)\) and \(H^{-\frac12}(\mathrm{div}_\Gamma, \Gamma)\) conforming spaces:
order = 3
fesHDiv = HDivSurface(mesh, order=order, complex=True)
uHDiv,vHDiv = fesHDiv.TnT()
fesHCurl = HCurl(mesh, order=order, complex=True)
uHCurl,vHCurl = fesHCurl.TnT()
Get the Dirichlet trace \(\boldsymbol m\) and exact Neumann data \(\boldsymbol j\) as grid functions defined everywhere on \(\Gamma\):
n = specialcf.normal(3)
m = GridFunction(fesHCurl)
m.Set( Cross( Cross(n, E), n) , definedon=mesh.Boundaries(".*"), dual=True) # Hcurl, gamma_R = (nxE)xn
j = GridFunction(fesHDiv)
j.Set( 1/kappa*Cross(n,curlE) , definedon=mesh.Boundaries(".*"), dual=True) # Hdiv, gamma_N = 1/k nx curl E
Define Dirichlet boundary condition on the lower half sphere \(\Gamma_0\) and have a look at it:
mDir = GridFunction(fesHCurl)
mDir.Set( m, definedon=mesh.Boundaries("dirichlet"), dual=True) # given Dirichlet data
Draw (Norm(mDir), mesh, draw_vol=False, order=3);
Define Neumann boundary condition on the upper half sphere \(\Gamma_1\) and have a look at it:
jNeu = GridFunction(fesHDiv)
jNeu.Set( j, definedon=mesh.Boundaries("neumann"), dual=True) # given Neumann data
Draw (Norm(jNeu), mesh, draw_vol=False, order=3);
Compute boundary integral operators \(\mathrm{V}\), \(\mathrm{K}\), \(\mathrm{D}\) and the mass matrix \(\mathrm M\)
# M, V, K and D
intorder = 2 * order + 6
with TaskManager():
M = BilinearForm( uHCurl.Trace() * vHDiv.Trace()* ds(bonus_intorder=3)).Assemble() # <Hcurl, Hdiv>
V = MaxwellSingleLayerPotentialOperator(fesHDiv, kappa, intorder=intorder, eps=1e-4)
K = MaxwellDoubleLayerPotentialOperator(fesHCurl, fesHDiv, kappa, intorder=intorder, eps=1e-4)
D = MaxwellSingleLayerPotentialOperatorCurl(fesHCurl, kappa, intorder=intorder, eps=1e-4)
Generate right hand side, block matrix and solve the linear system of equations:
eps = 1e-6
intorder = 2 * order + 6
with TaskManager():
fd = ((0.5 * M.mat - K.mat) * mDir.vec - V.mat * jNeu.vec).Evaluate()
fn = ((0.5 * M.mat.T + K.mat.T) * jNeu.vec + D.mat * mDir.vec).Evaluate()
fesHDivN = HDivSurface(mesh, order=order, dirichlet="neumann") # Dirichlet nodes free dofs
fesHCurlD = HCurl(mesh, order=order, dirichlet="dirichlet") # Neumann nodes free dofs
lhs = BlockMatrix([[V.mat, K.mat], [-K.mat.T, -D.mat]])
rhs = BlockVector([fd, fn])
preHDiv = BilinearForm( uHDiv.Trace() * vHDiv.Trace() *ds).Assemble()
preHCurl = BilinearForm( uHCurl.Trace() * vHCurl.Trace() *ds).Assemble()
preBlock = BlockMatrix ([[preHDiv.mat.Inverse(freedofs=fesHDivN.FreeDofs()), None], [None, preHCurl.mat.Inverse(freedofs=fesHCurlD.FreeDofs())]])
sol = GMRes(A=lhs, b=rhs, pre=preBlock, maxsteps=3000, tol=1e-8, printrates=False)
Check the numerical solution
Have a look at Neumann data on \(\Gamma_0\) and compute the \(L_2\)-error:
# compare with exact Dirichlet trace
gfdiv = GridFunction(fesHDiv)
gfdiv.vec[:] = sol[0]
print ("Neumann error =", sqrt(Integrate(Norm(gfdiv + jNeu - j)**2, mesh.Boundaries(".*"), BND)))
Draw (Norm(gfdiv), mesh.Boundaries("dirichlet"), order=3);
Neumann error = 0.0216215069008944
Have a look at Dirichlet data on \(\Gamma_1\) and compute the \(L_2\)-error:
# compare with the exact Neuman trace
gfcurl = GridFunction(fesHCurl)
gfcurl.vec[:] = sol[1]
print ("Dirichlet error =", sqrt(Integrate(Norm(gfcurl +mDir - m)**2, mesh.Boundaries(".*"), BND)))
Draw (Norm(gfcurl), mesh.Boundaries("neumann"), draw_vol=False, order=3);
Dirichlet error = 0.029447011579691797
References (theory results):
Note that the Calderon-projector in the referenced paper is slightly different from ours as only trace space \(H^{-\frac12}(\mathrm{div}_\Gamma,\Gamma)\) is used. Theoretical results stay the same.