3D Maxwell equations with PML#
Now, we consider a waveguide equipped with a PML (Perfectly Matched Layer).
Just recall that such a layer allows to absorb the waves without any reflection if the layer is infinite. When the PML is bounded, there is only some weak reflections due to a reflection on the back of the PML, more and more strong as the layer becomes thin.
The PML mechanism consisting in dilatating one coordinate with a complex factor, we need to apply a change of unknowns to obtain the following equivalent strong formulation (\(\mu = \varepsilon=1\)):
where \(\mathbb{A}\) represents either the change of unknowns and the PML dilatation (\(\alpha\in\mathbb{C}\)):
Let us introduce
and
The strong formulation has the following weak form:
Note that we have to deal with many essential conditions, in particular the transmission condition \(\mathbf{E_g}\times n=\mathbf{E_d}\times n\) on \(\Sigma_0\).
In the following example, a ‘Neumann’ mode of the wave-guide is used as exact solution. Using the first family Nedelec’s elements (NE11 or NE12) the XLiFE++ program looks like:
#include "xlife++.h"
using namespace xlifepp;
// Neumann modes and their tangential traces (n=[0 0 -1])
Vector<Complex> mode_Neumann(const Point&P,Real n,Real m,Parameters& pa=defaultParameters)
{
Real x = P(1), y = P(2), z = P(3), cst;
Real a = pa("a"), b = pa("b"), w = pa("w");
Vector<Complex> u(3);
Real lambda = pi_*pi_ * ( n*n / (a*a) + m*m / (b*b));
if ( (n == 0) || (m == 0) ) cst = sqrt(2.)/sqrt(lambda * a * b);
else cst = 2./sqrt(lambda * a * b);
Complex beta = sqrt(Complex(w*w-lambda));
u(1) = - m*pi_/b * cst * exp(i_*beta*z) * cos(n*pi_*x/a) * sin(m*pi_*y/b);
u(2) = n*pi_/a * cst * exp(i_*beta*z) * sin(n*pi_*x/a) * cos(m*pi_*y/b);
return u;
}
Vector<Complex> nug_mode_Neumann(const Point&P,Real n,Real m,Parameters& pa=defaultParameters)
{
Vector<Complex> ru = mode_Neumann(P,n,m,pa);
Complex t=ru(1); ru(1)=ru(2); ru(2)=-t;
return ru;
}
Vector<Complex> u_exa(const Point&P,Parameters& pa=defaultParameters)
{ return mode_Neumann(P,1,0,pa); }
Vector<Complex> nug_u_exa(const Point&P,Parameters& pa=defaultParameters)
{ return nug_mode_Neumann(P,1,0,pa); }
// PML matrices
xlifepp::Matrix<Complex> A_alpha(const Point&P,Parameters& pa=defaultParameters)
{
Complex alpha = pa("alpha");
xlifepp::Matrix<Complex> A(3,3);
A(1,1)=1./alpha; A(2,2)=1./alpha; A(3,3)=alpha;
return A;
}
xlifepp::Matrix<Complex> A_alpha_inv(const Point&P,Parameters& pa=defaultParameters)
{
Complex alpha = pa("alpha");
xlifepp::Matrix<Complex> A(3,3);
A(1,1)=alpha; A(2,2)=alpha; A(3,3)=1./alpha;
return A;
}
// null function
Vector<Real> f0 (const Point&P,Parameters& pa=defaultParameters )
{ return Vector<Real>(3);}
//------ main programm -----------
int main(int argc, char** argv)
{
init(argc, argv, _lang=en);
// Geometric data
Real a=1, b=1, w=sqrt(1.25)*pi_; // section lengths
Real h=3., hp=2.; // waveguide length and PML length
Real dx=2./10.; // mesh step
Complex alpha=exp(- i_*pi_/4); // PML coefficient
// mesh waveguide using gmsh
Cuboid C_a(_xmin=0.,_xmax=a,_ymin=0.,_ymax=b,_zmin=-h,_zmax=0.,_hsteps=dx,_domain_name="Omega_g",
_side_names=Strings("Sigma_g","Sigma_0","Gamma","Gamma","Gamma","Gamma")),
C_b(_xmin=0.,_xmax=a,_ymin=0.,_ymax=b,_zmin=0,_zmax=hp,_hsteps=dx,_domain_name="Omega_d",
_side_names=Strings("Sigma_0","Sigma_d","Gamma","Gamma","Gamma","Gamma"));
Mesh mail3d(C_a+C_b, _shape=tetrahedron, _generator=gmsh);
Domain Omega_g=mail3d.domain("Omega_g"), Omega_d=mail3d.domain("Omega_d"),
Sigma_0=mail3d.domain("Sigma_0"), Sigma_g=mail3d.domain("Sigma_g"),
Gamma =mail3d.domain("Gamma");
Sigma_g.setNormalOrientation (_outwardsDomain,Omega_g);
Sigma_0.updateParentOfSideElements();
Parameters pars;
pars<<Parameter(a,"a")<<Parameter(b,"b")<<Parameter(w,"w")<<Parameter(alpha,"alpha");
// Construct Nedelec Spaces (order 1 : NE11 or 2 : NE12)
Space Vg(_domain=Omega_g,_interpolation=NE1_1,_name="Hrot_Ned_g",_optimizeNumbering);
Unknown ug(Vg, _name="ug");TestFunction vg(ug, _name="vg");
Space Vd(_domain=Omega_d,_interpolation=NE1_1,_name="Hrot_Ned_d",_optimizeNumbering);
Unknown ud(Vd, _name="ud"); TestFunction vd(ud, _name="vd");
// Function data
Function uex(u_exa,pars),nuguex(nug_u_exa,pars),fzero(f0,pars);
Function fA(A_alpha,pars),fAinv(A_alpha_inv,pars);
nuguex.require(_n);
// Build system and solve it
BilinearForm auv = intg(Omega_g,rot(ug)|rot(vg)) - w*w*intg(Omega_g,ug|vg )
+ intg(Omega_d,fAinv*rot(ud)|rot(vd)) - w*w*intg(Omega_d,fA*ud|vd );
LinearForm bv = intg(Omega_g,fzero|vg) + intg(Omega_d,fzero|vd);
EssentialConditions ecs = (ncross(ug)|Gamma=0)&(ncross(ud)|Gamma=0)&(ncross(ug)|Sigma_g=nuguex)
& (ncross(ud)-ncross(ug)|Sigma_0=0);
TermMatrix A(auv, ecs, _name="A");
TermVector B(bv, _name="B");
TermVector U = directSolve(A,B);
//Project solution on Lagrange spaces P1 (or P2 if NE12 used) and export solution
Space Wg(_domain=Omega_g,_interpolation=P1,_name="Wg");
Unknown upg(Wg, _name="upg", _dim=3); TestFunction vpg(upg, _name="vpg");
Space Wd(_domain=Omega_d,_interpolation=P1,_name="Wd");
Unknown upd(Wd, _name="upd", _dim=3); TestFunction vpd(upd, _name="vpd");
TermVector Ug = projection(U(ug),Wg,3), Ud = projection(U(ud),Wd,3);
TermVector Up = Ug + Ud; Up.name("Up");
TermVector Uexag(upg,Omega_g,uex), Uexad(upd,Omega_d,uex);
TermVector Uexa = Uexag + Uexad; Uexa.name("Uexa");
saveToFile("solMaxwellPML.vtu",Uexa,Up);
//Compute L2 error on Omega_g (ug) in Nedelec space
TermMatrix M(intg(Omega_g, ug|vg ), _name="M");
TermVector Urot = projection(Uexag,Vg,1) ; //projection on Nedelec space
TermVector E1 = Urot - U(ug);
Real er=sqrt(abs((M*E1|E1))), norm=sqrt(abs((M*Urot|Urot)));
theCout << "L2 error (projection) = " << er << eol;
theCout << "relative L2 error (projection) : " << 100*er/norm << "% \n" << eol;
return 0;
}