This page was generated from unit-8.2-intlset/intlset.ipynb.
8.2 Integration on level set domains¶
This unit gives a basic introduction to a few fundamental concepts in ngsxfem
. Especially, we treat:
level set geometries and its (P1) approximation
Cut differential symbols to conveniently define integrals on cut domain
for higher order level set geometry approximations
simple example: calculate area of the unit circle using level set representations
background domain: \([-1.5, 1.5]^2 \subset \mathbb{R}^2\).
Let \(\phi\) be a continuous level set function. We recall:
[1]:
# basic geometry features (for the background mesh)
from netgen.geom2d import SplineGeometry
# ngsolve
from ngsolve import *
# basic xfem functionality
from xfem import *
# for isoparametric mapping
from xfem.lsetcurv import *
# visualisation
from ngsolve.webgui import *
# the contant pi
from math import pi
importing ngsxfem-2.1.2405.dev0
We generate the background mesh of the domain and use a simplicial triangulation
[2]:
square = SplineGeometry()
square.AddRectangle([-1.5,-1.5], [1.5,1.5], bc=1)
mesh = Mesh(square.GenerateMesh(maxh=0.8, quad_dominated=False))
Draw(mesh)
[2]:
BaseWebGuiScene
On the background mesh we define the level set function. In this example we choose a levelset function which measures the distance to the origin and substracts a constant \(r\), resulting in an circle of radius \(r\) as \(\Omega_{-}\):
[3]:
r = 1
levelset = sqrt(x**2+y**2) - r
DrawDC(levelset, -3.5, 2.5, mesh, 'levelset')
[3]:
BaseWebGuiScene
Area of the unit circle:
\[A = \pi = \int_{\Omega_-(\phi)} 1 \, \mathrm{d}x\]
Piecewise linear geometry approximation¶
The ngsxfem integration on implicitly defined geometries is only able to handle functions \(\phi\) which are in \(\mathcal{P}^1(T)\) for all \(T\) of the triangulation.
Therefore we replace the levelset function \(\phi\) with an approximation \(\phi^\ast \in \mathcal{P}^1(T), T \subset \Omega\).
[4]:
V = H1(mesh,order=1,autoupdate=True)
lset_approx = GridFunction(V,autoupdate=True)
InterpolateToP1(levelset, lset_approx)
DrawDC(lset_approx, -3.5, 2.5, mesh, 'levelset_p1')
[4]:
BaseWebGuiScene
Notice that this replacement \(\phi \mapsto \phi^\ast\) will introduce a geometry error of order \(h^2\). We will investigate this numerically later on in this notebook.
As a next step we need to define the integrand:
[5]:
f = CoefficientFunction(1)
As in NGSolve, we integrate by using the Integrate
function. For this to be aware of the fact that we are integration over a level set domain we use the dCut
differential symbol, to which we need to pass:
levelset
: level set function which describes the geometry (best choice: P1-GridFunction)domain_type
: decision on which part of the geometry the integration should take place (NEG
/POS
/IF
)order
: The order of integration rule to be used
[6]:
order = 2
integral = Integrate(f * dCut(levelset=lset_approx, domain_type=NEG, order=order), mesh=mesh)
error = abs(integral - pi)
print("Result of the integration: ", integral)
print("Error of the integration: ", error)
Result of the integration: 2.8532268778347776
Error of the integration: 0.28836577575501554
Convergence study¶
To get more accurate results
save the previous error and execute a refinement
repeat the steps from above
Repeatedly execute the lower block to add more refinement steps.
[7]:
errors = []
def AppendResultAndPostProcess(integral):
error = abs(integral - pi)
print("Result of the integration: ", integral)
print("Error of the integration: ", error)
errors.append(error)
eoc = [log(errors[i+1]/errors[i])/log(0.5) for i in range(len(errors)-1)]
print("Collected errors:", errors)
print("experimental order of convergence (L2):", eoc)
AppendResultAndPostProcess(integral)
Result of the integration: 2.8532268778347776
Error of the integration: 0.28836577575501554
Collected errors: [0.28836577575501554]
experimental order of convergence (L2): []
[8]:
scene = Draw(lset_approx, mesh, "lsetp1", min=0, max=0.0)
def RefineAndIntegrate():
# refine cut elements only:
RefineAtLevelSet(gf=lset_approx)
mesh.Refine()
InterpolateToP1(levelset,lset_approx)
scene.Redraw()
integral = Integrate(f * dCut(levelset=lset_approx, domain_type=NEG, order=order), mesh=mesh)
AppendResultAndPostProcess(integral)
[9]:
RefineAndIntegrate()
Result of the integration: 3.033381503911132
Error of the integration: 0.1082111496786613
Collected errors: [0.28836577575501554, 0.1082111496786613]
experimental order of convergence (L2): [1.4140507939031013]
We observe only a second order convergence which results from the piecewise linear geometry approximation.
Higher order geometry approximation with an isoparametric mapping¶
In order to get higher order convergence we can use the isoparametric mapping functionality of xfem.
We apply a mesh transformation technique in the spirit of isoparametric finite elements:
Video of the mesh transformation
[10]:
%%html
<iframe width=100% height=600px src="https://www.youtube.com/embed/Mst_LvfgPCg?rel=0" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
To compute the corresponding mapping we use the LevelSetMeshAdaptation class
[11]:
mesh = Mesh(square.GenerateMesh(maxh=0.4, quad_dominated=False))
lsetmeshadap = LevelSetMeshAdaptation(mesh, order=2, threshold=1000, discontinuous_qn=True)
deformation = lsetmeshadap.CalcDeformation(levelset)
Draw(deformation,mesh,"deformation")
[11]:
BaseWebGuiScene
We can observe the geometrical improvement in the following sequence:
[12]:
scene1 = Draw(deformation, mesh, "deformation", autoscale=False, min=0.0, max=0.028)
[13]:
scene2 = DrawDC(lsetmeshadap.lset_p1, -3.5, 2.5, mesh, "lsetp1_ho", deformation=deformation)
[14]:
N=100
deformation.vec.data = 1.0/N*deformation.vec
for i in range (1, N+1):
deformation.vec.data = (i+1)/i * deformation.vec
scene1.Redraw()
scene2.Redraw()
Convergence study with isoparametric mapping applied¶
We now apply the mesh deformation (2nd order mapping) to improve the accuracy of the integration problem.
error tables:
[15]:
order = 2
mesh = Mesh (square.GenerateMesh(maxh=0.7, quad_dominated=False))
levelset = sqrt(x*x+y*y)-1
referencevals = { POS : 9-pi, NEG : pi, IF : 2*pi }
lsetmeshadap = LevelSetMeshAdaptation(mesh, order=order, threshold=0.2, discontinuous_qn=True)
lsetp1 = lsetmeshadap.lset_p1
errors_uncurved = dict()
errors_curved = dict()
eoc_uncurved = dict()
eoc_curved = dict()
for key in [NEG,POS,IF]:
errors_curved[key] = []
errors_uncurved[key] = []
eoc_curved[key] = []
eoc_uncurved[key] = []
refinements:
[16]:
f = CoefficientFunction (1.0)
refinements = 5
for reflevel in range(refinements):
if(reflevel > 0):
mesh.Refine()
for key in [NEG,POS,IF]:
# Applying the mesh deformation
deformation = lsetmeshadap.CalcDeformation(levelset)
integrals_uncurved = Integrate(f * dCut(levelset=lsetp1, domain_type=key, order=order), mesh=mesh)
# dCut deals with the mesh deformation internally
integrals_curved = Integrate(f * dCut(levelset=lsetp1, domain_type=key, order=order, deformation=deformation), mesh=mesh)
errors_curved[key].append(abs(integrals_curved - referencevals[key]))
errors_uncurved[key].append(abs(integrals_uncurved - referencevals[key]))
# refine cut elements:
RefineAtLevelSet(gf=lsetmeshadap.lset_p1)
Convergence rates:
[17]:
for key in [NEG,POS,IF]:
eoc_curved[key] = [log(a/b)/log(2) for (a,b) in zip (errors_curved[key][0:-1],errors_curved[key][1:]) ]
eoc_uncurved[key] = [log(a/b)/log(2) for (a,b) in zip (errors_uncurved[key][0:-1],errors_uncurved[key][1:]) ]
print("errors ( curved): \n{}\n".format( errors_curved))
print(" eoc ( curved): \n{}\n".format( eoc_curved))
errors ( curved):
{<DOMAIN_TYPE.NEG: 0>: [0.001710337974651921, 8.945537152715843e-05, 8.112397148263284e-06, 1.932554796191255e-06, 1.0494129565330468e-07], <DOMAIN_TYPE.POS: 1>: [0.11418698405525696, 8.945537152715843e-05, 8.112397149595552e-06, 1.9325547979676116e-06, 1.049413373976904e-07], <DOMAIN_TYPE.IF: 2>: [0.0009223373587694539, 0.0003690527231654528, 2.455580523097467e-05, 7.995576201125232e-07, 1.6669838220906286e-07]}
eoc ( curved):
{<DOMAIN_TYPE.NEG: 0>: [4.256969515213352, 3.4629679270340263, 2.0696189609453226, 4.202854908813071], <DOMAIN_TYPE.POS: 1>: [10.317942475320116, 3.4629679267970985, 2.069618959856161, 4.202854336252534], <DOMAIN_TYPE.IF: 2>: [1.321467599035857, 3.9096908984681504, 4.940718316506891, 2.261961902073998]}
Note: The integration on level set domains (and the mapping) can be extended to bilinear and linear form integrators with the same syntax.