This page was generated from notebooks/Meshes.ipynb.
PETSc DMPlex
In this tutorial we will have an in-depth look in to the way we transform NGSolve/Netgen Mesh
to a PETSc DMPlex. In particular we will show to create a PETSc DMPlex
from an NGSolve/Netgen and vice-versa, using the MeshMapping
class. We will also show how to use PETSc DMPlexTransform
to construct purely quad mesh and Alfeld split mesh.
[1]:
from ipyparallel import Cluster
c = await Cluster().start_and_connect(n=1, activate=True)
Starting 1 engines with <class 'ipyparallel.cluster.launcher.LocalEngineSetLauncher'>
Let’s test if the cluster has been initialized correctly by checking the size of the COMM_WORLD
.
[2]:
%%px
from mpi4py.MPI import COMM_WORLD
COMM_WORLD.Get_size()
Out[0:1]: 1
First we need to construct the distributed mesh that will be interested in dealing with in our map to a PETSc DMPlex
.
[3]:
%%px
from ngsolve import Mesh
from netgen.geom2d import unit_square
if COMM_WORLD.rank == 0:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.2).Distribute(COMM_WORLD))
else:
mesh = Mesh(ngm.Mesh.Receive(COMM_WORLD))
We can now convert this matrix in a PETSc DMPlex
, to do this we will initialize a MeshMapping
class and then access the mapped PETSc DMPlex
as the attribute petscPlex
.
[4]:
%%px
from ngsPETSc import MeshMapping
Map = MeshMapping(mesh)
Map.petscPlex.view()
[stdout:0] DM Object: Default 1 MPI process
type: plex
Default in 2 dimensions:
Number of 0-cells per rank: 37
Number of 1-cells per rank: 88
Number of 2-cells per rank: 52
Labels:
celltype: 3 strata with value/size (0 (37), 3 (52), 1 (88))
depth: 3 strata with value/size (0 (37), 1 (88), 2 (52))
Face Sets: 4 strata with value/size (1 (5), 2 (5), 3 (5), 4 (5))
We can use any PETSc DMPlex
function that is wrapped in petsc4py on the Map.petscPlex
object. For example, we can apply any PETSc DMPlexTransform
. We will now apply different PETSc DMPlexTransform
and check using the view
method that we have the mesh was transformed correctly. We begin with the PETSc DMPlexTransformType.REFINEREGULAR
which will create split a triangle in four subs triangles connecting the middle points of each vertex of the triangle to create a new
triangle in the center.
[5]:
%%px
from petsc4py import PETSc
tr = PETSc.DMPlexTransform().create(comm=PETSc.COMM_WORLD)
tr.setType(PETSc.DMPlexTransformType.REFINEREGULAR)
tr.setDM(Map.petscPlex)
tr.setUp()
newplex = tr.apply(Map.petscPlex)
newplex.view()
[stdout:0] DM Object: 1 MPI process
type: plex
DM_0x55baeedcda70_1 in 2 dimensions:
Number of 0-cells per rank: 125
Number of 1-cells per rank: 332
Number of 2-cells per rank: 208
Labels:
celltype: 3 strata with value/size (1 (332), 3 (208), 0 (125))
depth: 3 strata with value/size (0 (125), 1 (332), 2 (208))
Face Sets: 4 strata with value/size (1 (15), 2 (15), 3 (15), 4 (15))
We can easily verify that the number of 2-cells
elements, i.e. triangles in the mesh has quadrupled. We can also create a new MeshMapping
class to convert the new PETSc DMPlex
into a Netgen Mesh
and visualize it.
[6]:
%%px
from ngsolve import Mesh
Map = MeshMapping(newplex)
from ngsolve.webgui import Draw
Draw(Mesh(Map.ngMesh))
[output:0]
Out[0:5]: BaseWebGuiScene
We can experiment also with other PETSc DMPlexTransformation
for example the REFINETOBOX
transformation which will split each triangle in the mesh into three quadrilateral by joining the midpoints of each edge. This will allow to obtain a purely quadrilateral mesh from a Netgen mesh.
[7]:
%%px
tr = PETSc.DMPlexTransform().create(comm=PETSc.COMM_WORLD)
tr.setType(PETSc.DMPlexTransformType.REFINETOBOX)
tr.setDM(Map.petscPlex)
tr.setUp()
newplex = tr.apply(Map.petscPlex)
Map = MeshMapping(newplex)
Draw(Mesh(Map.ngMesh))
[output:0]
Out[0:6]: BaseWebGuiScene
Example (Alfeld Splittings and Scott-Vogelious)
In this example we would like to show that the finite element pair \(P^2-P^1_{disc}\) known as the low-order Scott-Vogelious pair, which is known to verify the Brezzi-Babuska condition for the Stokes problem on Alfeld split mesh, can be easily implemented in NGSolve using a PETSc DMPlexTransformation
. First we construct a mesh and use the MeshMapping
class to obtain an PETSc DMPlex
that we proceed to split to obtain an Alfeld refinement.
[8]:
%%px
from netgen.geom2d import SplineGeometry
if COMM_WORLD.rank == 0:
geo = SplineGeometry()
geo.AddRectangle( (0, 0), (2, 0.41), bcs = ("wall", "outlet", "wall", "inlet"))
geo.AddCircle ( (0.2, 0.2), r=0.05, leftdomain=0, rightdomain=1, bc="cyl")
mesh = Mesh( geo.GenerateMesh(maxh=0.05))
mesh.Curve(3)
else:
mesh = Mesh(ngm.Mesh.Receive(COMM_WORLD))
Map = MeshMapping(mesh)
tr = PETSc.DMPlexTransform().create(comm=PETSc.COMM_WORLD)
tr.setType(PETSc.DMPlexTransformType.REFINEALFELD)
tr.setDM(Map.petscPlex)
tr.setUp()
newplex = tr.apply(Map.petscPlex)
mesh = Mesh(MeshMapping(newplex).ngMesh)
Draw(mesh)
[output:0]
Out[0:7]: BaseWebGuiScene
We now proceed constructing the finite element space we are interested in and distressing the Stokes equation in variational form, i.e. find \((\vec{u}_h,p_h)\in [P^2(\mathcal{T}_h)]^2\times P^1_{disc}(\mathcal{T}_h)\) such that for any \((\vec{v}_h,q_h)\in [P^2(\mathcal{T}_h)]^2\times P^1_{disc}(\mathcal{T}_h)\) the following equations hold,
[9]:
%%px
from ngsolve import VectorH1, L2, H1, BilinearForm, InnerProduct, GridFunction
from ngsolve import grad, div, x,y, dx, CoefficientFunction, Norm, SetVisualization, TRIG
V = VectorH1(mesh, order=2,dirichlet=[4,1,3,5,6,7,8])
Q = L2(mesh, order=1)
X = V*Q
u,p = X.TrialFunction()
v,q = X.TestFunction()
a = BilinearForm(X)
a += (InnerProduct(grad(u),grad(v))+div(u)*q-div(v)*p)*dx
a.Assemble()
gfu = GridFunction(X)
uin = CoefficientFunction( (1.5*4*y*(0.41-y)/(0.41*0.41), 0) )
gfu.components[0].Set(uin, definedon=mesh.Boundaries([3]))
res = gfu.vec.CreateVector()
res.data = -a.mat * gfu.vec
inv = a.mat.Inverse(freedofs=X.FreeDofs(), inverse="umfpack")
gfu.vec.data += inv * res
Draw(Norm(gfu.components[0]), mesh, "|vel|")
SetVisualization(max=2)
[stdout:0] warning: Boundaries( [int list] ) is deprecated, pls generate Region
[output:0]