3.3. Complex-valued waves#
In NGSolve finite element spaces can be built over the complex field and the resulting complex matrix systems can be solved. This tutorial shows how to compute the solution of the Helmholtz equation with impedance boundary conditions in complex arithmetic. The boundary value problem is to find \(u\) satisfying
together with the impedance (outgoing) boundary condition
where \(i =\) 1j
is the imaginary unit.
from ngsolve import *
from ngsolve.webgui import Draw
from netgen.occ import *
air: Face = Circle((0.5, 0.5), 0.8).Face()
air.edges.name = 'outer'
scatterer = MoveTo(0.7, 0.3).Rectangle(0.05, 0.4).Face()
scatterer.edges.name = 'scat'
geo = OCCGeometry(air - scatterer, dim=2)
mesh = Mesh(geo.GenerateMesh(maxh=0.05))
Draw(mesh);
3.3.1. Declare a complex-valued finite element space#
fes = H1(mesh, order=5, complex=True)
u, v = fes.TnT()
omega = 10
pulse = 5e4*exp(-(40**2)*((x-0.5)*(x-0.5) + (y-0.5)*(y-0.5)))
Draw(10**-5*pulse, mesh, order=3);
3.3.2. Forming the system#
The weak form for \(u \in H^1\):
for all \(v\) in \(H^1\).
# Forms
a = BilinearForm(fes)
a += grad(u)*grad(v)*dx - omega**2*u*v*dx
a += -omega*1j*u*v * ds("outer")
a.Assemble()
f = LinearForm(pulse * v * dx).Assemble();
3.3.3. Solve#
gfu = GridFunction(fes, name="u")
gfu.vec.data = a.mat.Inverse() * f.vec
Draw(gfu, mesh, min=-10, max=10, order=3, animate_complex=True);
Open controls and explore webgui’s further viewing options such as viewing real and imaginary parts, viewing absolute value, and viewing with deformation turned on.
3.3.4. Add a PML#
Consider the same problem as before
together with the Sommerfeld (outgoing) radiation condition at infinity
define now an outer region called for the PML
outer = Circle((0.5, 0.5), 1).Face()
outer.edges.name = 'outer'
air: Face = Circle((0.5, 0.5), 0.8).Face()
#air.edges.name = 'outer'
scatterer = MoveTo(0.7, 0.3).Rectangle(0.05, 0.4).Face()
scatterer.edges.name = 'scat'
pmlregion = outer - air
pmlregion.faces.name = 'pmlregion'
geo = OCCGeometry(Glue([air-scatterer, pmlregion]), dim=2)
mesh = Mesh(geo.GenerateMesh(maxh=0.05))
Draw(mesh);
The PML facility in NGSolve implements a complex coordinate transformation on a given mesh region (which in this example is pmlregion
). When this complex variable change is applied to the outgoing solution in the PML region, it becomes a a function that decays exponentially as \(r \to \infty\), allowing us to truncate the unbounded domain.
With the following single line, we tell NGSolve to turn on this coordinate transformation.
mesh.SetPML(pml.Radial(rad=0.8, alpha=1j,origin=(0.5,0.5)), "pmlregion")
Then a radial PML is set in the exterior of a disk
centered at origin
of radius rad
. In addition to
origin
and rad
, the keyword argument alpha
may be used to set
the PML strength, which determines the rate of increase in the imaginary
part of the coordinate map as radius increases.
Having set the PML, the rest of the code now looks very much like that in Unit 1.7:
fes = H1(mesh, order=4, complex=True)
u = fes.TrialFunction()
v = fes.TestFunction()
a = BilinearForm(fes)
a += grad(u)*grad(v)*dx - omega**2*u*v*dx
a += -1j*omega*u*v*ds("outerbnd")
a.Assemble()
f = LinearForm(pulse * v * dx).Assemble()
gfu = GridFunction(fes)
gfu.vec.data = a.mat.Inverse() * f.vec
settings = {}
Draw(gfu, animate_complex=True, deformation=False, scale = 0.01);