from netgen.occ import *
from ngsolve import *
from ngsolve.webgui import Draw
from ngsolve.bem import *
from ngsolve import Projector, Preconditioner
from ngsolve.krylovspace import CG
keys: homogeneous Neumann bvp, hypersingular operator, unknown Dirichlet data
Neumann Laplace Direct Method#
Consider the Neumann boundary value problem
The unique solution \(u\in H^1(\Omega)\) can be written in the following form (representation forumula)
NGSolve Solution
Define the geometry \(\Omega \subset \mathbb R^3\) and create a mesh:
sp = Sphere( (0,0,0), 1)
mesh = Mesh( OCCGeometry(sp).GenerateMesh(maxh=0.2)).Curve(2)
Define conforming finite element spaces for \(H^{\frac12}(\Gamma)\) and \(H^{-\frac12}(\Gamma)\) respectively:
fesL2 = SurfaceL2(mesh, order=2, dual_mapping=False)
u,v = fesL2.TnT()
fesH1 = H1(mesh, order=2, definedon=mesh.Boundaries(".*"))
uH1,vH1 = fesH1.TnT()
print ("ndofL2 = ", fesL2.ndof, "ndof H1 = ", fesH1.ndof)
ndofL2 = 4332 ndof H1 = 1580
Project the given Neumann data \(u_1\) in finite element space and have a look at the boundary data:
uexa = 1/ sqrt( (x-1)**2 + (y-1)**2 + (z-1)**2 )
graduexa = CF( (uexa.Diff(x), uexa.Diff(y), uexa.Diff(z)) )
n = specialcf.normal(3)
j = GridFunction(fesL2)
j.Interpolate(graduexa*n, definedon=mesh.Boundaries(".*"))
Draw (j, mesh, draw_vol=False, order=3);
Boundary Integral Equation
Let’s carefully apply the Neumann trace \(\gamma_1\) to the representation formula and solve the resulting boundary integral equation for the Dirichlet trace \(u_0\) of \(u\) by discretisation of the following variational formulation:
The discretisation of the variational formulation leads to a system of linear equations, ie
where the linear operators are as follows
\(\mathrm{S}\) is a stabilisation matrix that removes the one dimensional kernel from hypersinbular operator.
\(\mathrm{D}\) is the hypersingular operator and the stabilisation \((\mathrm D + \mathrm{S})\) is regular and symmetric.
\(\mathrm{M}\) is a mass matrix.
\(\mathrm{K}'\) is the adjoint double layer operator.
Details
For any \(u_j, v_i \in H^{\frac12}(\Gamma)\) the hypersingular operator is implemented as follows (reference below)
This explicit represenation of \(D\) is the singular layer potential operator with vector valued surface curl operator applied on trial and test functions. You may assemble \(D\) it by
D = LaplaceSL(curl(u)*ds)*curl(v)*ds
Recall that for any \(v_i\in H^{-\frac12}(\Gamma)\) and \(w_j \in H^{\frac12}(\Gamma)\) the double layer potential operators \(\mathrm K\) has been given by
And this means that the adjoint double layer potential operator equals its transposed, \(\mathrm K^\intercal\).
You may assemble \(\mathrm V\) and \(\mathrm K\) it by
V = LaplaceSL(u*ds)*v*ds
K = LaplaceDL(w*ds)*v*ds
The stabilisation matrix \(S\) is needed to cope with the kernel of the hypersingular operator. The following stabilisation matrix is used here:
where \(v_i, v_j\) being \(H^{\frac12}(\Gamma)\)-conforming shape functions.
vH1m1 = LinearForm(vH1*1*ds(bonus_intorder=3)).Assemble()
S = (BaseMatrix(Matrix(vH1m1.vec.Reshape(1))))@(BaseMatrix(Matrix(vH1m1.vec.Reshape(fesH1.ndof))))
The assembly of the hypersingular operator is declarative and close to their actual implementations (explicit formula are given below).
with TaskManager():
D = LaplaceSL(curl(uH1)*ds)*curl(vH1)*ds
K = LaplaceDL(uH1*ds)*v*ds
M = BilinearForm( uH1 * v * ds(bonus_intorder=3)).Assemble()
Left and right hand side of linear system are computed and the solution is the Dirichlet data \(u_0\):
m = GridFunction(fesH1)
pre = BilinearForm(uH1*vH1*ds).Assemble().mat.Inverse(freedofs=fesH1.FreeDofs())
with TaskManager():
lhs = D.mat + S
rhs = ( (0.5 * M.mat.T - K.mat.T) * j.vec ).Evaluate()
CG(mat=lhs, pre=pre, rhs=rhs, sol=m.vec, tol=1e-8, maxsteps=200, initialize=False, printrates=True)
CG iteration 1, residual = 0.6734143938795208
CG iteration 2, residual = 0.3570586989565186
CG iteration 3, residual = 0.20905841633641048
CG iteration 4, residual = 0.17782730725965978
CG iteration 5, residual = 0.1240304646603016
CG iteration 6, residual = 0.09705555483495114
CG iteration 7, residual = 0.059686364640765925
CG iteration 8, residual = 0.048358396284000675
CG iteration 9, residual = 0.028887223089879264
CG iteration 10, residual = 0.02087591545709992
CG iteration 11, residual = 0.013576293553324575
CG iteration 12, residual = 0.008369259002401461
CG iteration 13, residual = 0.005519552288092554
CG iteration 14, residual = 0.003273842526139349
CG iteration 15, residual = 0.0021009042394241127
CG iteration 16, residual = 0.001230772030359319
CG iteration 17, residual = 0.0007221780720367607
CG iteration 18, residual = 0.00042551775688713883
CG iteration 19, residual = 0.0002320103253771422
CG iteration 20, residual = 0.00013264368940199342
CG iteration 21, residual = 7.176651710682202e-05
CG iteration 22, residual = 3.9139160079288725e-05
CG iteration 23, residual = 2.0404247412155772e-05
CG iteration 24, residual = 1.042677650806519e-05
CG iteration 25, residual = 5.204655489842847e-06
CG iteration 26, residual = 2.5870475716945516e-06
CG iteration 27, residual = 1.2572803365940249e-06
CG iteration 28, residual = 6.09225631215286e-07
CG iteration 29, residual = 2.8568758167594216e-07
CG iteration 30, residual = 1.2785128960165276e-07
CG iteration 31, residual = 5.716856867904242e-08
CG iteration 32, residual = 2.827875076787529e-08
CG iteration 33, residual = 1.8645284671804463e-08
CG iteration 34, residual = 1.8353008222638013e-08
CG iteration 35, residual = 2.1312773512214396e-08
CG iteration 36, residual = 1.684434996512971e-08
CG iteration 37, residual = 8.623457687244244e-09
CG iteration 38, residual = 3.8171683132670186e-09
Let’s have a look at the numerical and exact Dirichlet data and compare them quantitatively:
Draw (m, mesh, draw_vol=False, order=3);
mexa = GridFunction(fesH1)
mexa.Interpolate (uexa, definedon=mesh.Boundaries(".*"))
Draw (mexa, mesh, draw_vol=False, order=3);
print ("L2 error of surface gradients =", sqrt (Integrate ( (grad(mexa)-grad(m))**2, mesh, BND)))
L2 error of surface gradients = 0.004568783275440448
Evaluation Solution
screen = WorkPlane(Axes( (0,0,0), Z, X)).RectangleC(0.5,0.5).Face()
screen.faces.name="screen"
vismesh = screen.GenerateMesh(maxh=0.05)
LSPotential = LaplaceSL( u*ds )
LDPotential = LaplaceDL( uH1*ds )
repformula = LSPotential(j) - LDPotential(m)
fes_screen = H1(vismesh, order=5)
gf_screen = GridFunction(fes_screen)
with TaskManager():
gf_screen.Set(repformula, definedon=vismesh.Boundaries("screen"))
Draw(gf_screen)
Draw(repformula - uexa, vismesh) # exact up to a constant
BaseWebGuiScene
References:
For details on the explicit representation of the hypersingular operator have a look into Numerische Näherungsverfahren für elliptische Randwertprobleme, p.127, p.259 (1st edition).