2.1.6. Multigrid for high order finite element spaces

Officially released with NGSolve-2504, most high order spaces provide high order polynomial preserving prolongation operators. Current restrictions:

  • require simplicial meshes

  • refinement by bisection

  • require uniform polynomial order

[1]:
from ngsolve import *
from ngsolve.webgui import Draw
from ngsolve.la import EigenValues_Preconditioner

Many FESpaces provide now a high-order accurate prolongation operator. It has to be enabled by the flag hoprolongation=True, maybe becoming default in future.

[2]:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))
gfu = GridFunction(H1(mesh, order=10, hoprolongation=True))
gfu.Set (sin(50*x*y))
Draw (gfu, order=10);
[3]:
for l in range(2):
    mesh.Refine()
Draw (gfu, order=5);

Multigrid preconditioners for high order spaces

If the high order prolongation is enabled, the multigrid preconditioner uses the high order discretization on the mesh hierarchy. If not, the coarse grid spaces use the lowest order spaces in the background.

[4]:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))

fes = H1(mesh, order=5, hoprolongation=True, dirichlet=".*")
u,v = fes.TnT()

a = BilinearForm(grad(u)*grad(v)*dx).Assemble()
pre = preconditioners.MultiGrid(a, blocktype=["vertexpatch"])
# pre = preconditioners.MultiGrid(a, smoother="block", blocktype=["vertexedge","face"])
# pre = preconditioners.MultiGrid(a, smoother="block", blocktype=["vertpatch"])

for l in range(4):
    mesh.Refine()
    a.Assemble()
    lam = EigenValues_Preconditioner(a.mat, pre)
    print (mesh.levels, fes.ndof, lam[0], lam[-1])
2 2801 0.9737767048916046 0.9999650900459265
3 11001 0.9731893341714941 0.9999569893694966
4 43601 0.9738508899616769 0.9999593243766862
5 173601 0.9740047337504241 0.9999581308391128
[5]:
f = LinearForm(x*v*dx).Assemble()
gfu = GridFunction(fes)

Solve(a * gfu == f, dirichlet=x*(1-x), lin_solver=solvers.CGSolver, pre=pre, printrates=True)
CG iteration 1, residual = 2.500665990770517
CG iteration 2, residual = 0.024383987764233152
CG iteration 3, residual = 0.00018799791819350655
CG iteration 4, residual = 1.1484794277901268e-06
CG iteration 5, residual = 9.252209912710351e-09
CG iteration 6, residual = 6.018344974750805e-11
CG iteration 7, residual = 4.580233475866812e-13
[6]:
ea = { "euler_angles" : (-70, 0,-55) }
Draw (gfu, deformation=True, **ea);

High order with static condensation

For high order methods, static condensation may save a lot of computation. However, canonical prolongation for the skeleton variables only does not preserve high order polynomials. Here the HarmonicProlongation comes into play: It prolongates functions on the boundaries of the coarse grid elements, and then solves local Dirichlet problems for the dofs on the fine-grid skeleton inside the coarse grid elements.

The Dirichlet problem is solved for the problem-specific bilinear form, which has to be provided when enabling the HarmonicProlongation:

[7]:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))

fes = H1(mesh, order=5, hoprolongation=True, dirichlet=".*")
u,v = fes.TnT()

a = BilinearForm(grad(u)*grad(v)*dx, condense=True).Assemble()
fes.SetHarmonicProlongation(a)
pre = preconditioners.MultiGrid(a, blocktype="vertexpatch")

for l in range(5):
    mesh.Refine()
    a.Assemble()
    lam = EigenValues_Preconditioner(a.mat, pre)
    print (mesh.levels, fes.ndof, lam[0], lam[-1])
2 2801 0.8873455539760278 0.9998975910633985
3 11001 0.8957401274225056 0.9998894014559885
4 43601 0.8945786078982365 0.9998863285007136
5 173601 0.894386169292487 0.9998960435342532
6 692801 0.8953852278217878 0.9998858093863445
[8]:
f = LinearForm(x*v*dx).Assemble()
gfu = GridFunction(fes)

Solve(a * gfu == f, dirichlet=x*(1-x), lin_solver=solvers.CGSolver, pre=pre, printrates=True)
CG iteration 1, residual = 3.4886087977865676
CG iteration 2, residual = 0.12345444219082062
CG iteration 3, residual = 0.0036390880918837146
CG iteration 4, residual = 9.08036702501544e-05
CG iteration 5, residual = 2.7831282126523136e-06
CG iteration 6, residual = 7.696308282558869e-08
CG iteration 7, residual = 2.172768027790556e-09
CG iteration 8, residual = 6.173415107275903e-11
CG iteration 9, residual = 1.8887182888427588e-12
[9]:
Draw (gfu, deformation=True, **ea);

This example shows the result of a HarmonicProlongation:

[10]:
mesh = Mesh(unit_square.GenerateMesh(maxh=2))

fes = H1(mesh, order=8, hoprolongation=True)
u,v = fes.TnT()

a = BilinearForm(grad(u)*grad(v)*dx, condense=True).Assemble()
fes.SetHarmonicProlongation(a)
pre = preconditioners.MultiGrid(a, smoother="block", blocktype="vertexpatch")
[11]:
gfu = GridFunction(fes)
gfu.Set(sin(10*x))
Draw(gfu, order=5)
mesh.Refine()
Draw(gfu, order=5)
gfu.vec.data += a.harmonic_extension * gfu.vec
Draw (gfu, order=5);

Nearly incompressible elasticity and Stokes

The Scott-Vogelius element:

[12]:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))

fes = VectorH1(mesh, order=4, hoprolongation=True, dirichlet=".*")
u,v = fes.TnT()

eq = InnerProduct(Grad(u),Grad(v)) * dx + 1e4 * div(u)*div(v)* dx
a = BilinearForm(eq).Assemble()

# a = BilinearForm( (Grad(u)|Grad(v)) * dx).Assemble()

pre = preconditioners.MultiGrid(a, blocktype=["vertexpatch"])

with TaskManager():
    for l in range(5):
        mesh.Refine()
        a.Assemble()
        lam = EigenValues_Preconditioner(a.mat, pre)
        print (mesh.levels, fes.ndof, lam[0], lam[-1])
2 3618 0.3132568089265143 0.9999007841863647
3 14146 0.19750181032333813 0.999910050962943
4 55938 0.19549161267811255 0.9999047978307942
5 222466 0.1954953929820305 0.9998999587479214
6 887298 0.19556232072416013 0.9998955800582523

A robust method with reduced integration

We define the bilinear form as

\[A(u,v) = \int_\Omega \nabla u : \nabla v \, dx + \frac{1}{\varepsilon} \int_\Omega P_{L_2}^Q \operatorname{div} u \operatorname{div} v \, dx,\]

where \(P_{L_2}^Q\) is an element-wise \(L_2\)-projector into a lower order space.

[13]:
mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))

fesp = L2(mesh, order=0)
fes = VectorH1(mesh, order=2, hoprolongation=True, dirichlet=".*")
u,v = fes.TnT()

eq = InnerProduct(Grad(u),Grad(v)) * dx + 1e4 * Interpolate(div(u),fesp)*div(v)* dx
a = BilinearForm(eq).Assemble()

# a = BilinearForm( (Grad(u)|Grad(v)) * dx).Assemble()
fes.SetHarmonicProlongation(a)
pre = preconditioners.MultiGrid(a, blocktype=["vertexpatch"])

for l in range(4):
    mesh.Refine()
    a.Assemble()
    lam = EigenValues_Preconditioner(a.mat, pre)
    print (mesh.levels, fes.ndof, lam[0], lam[-1])
2 946 0.5451163631463438 1.2567450360089776
3 3618 0.35880823306083937 1.4293139465317155
4 14146 0.30554010192394687 1.5320915176928378
5 55938 0.27985898251764896 1.6946834108546738
[14]:
f = LinearForm((x-0.5)*v[1]*dx).Assemble()
gfu = GridFunction(fes)
Solve (a*gfu==f, pre, solvers.CGSolver, printrates=True)
CG iteration 1, residual = 0.02055357090150823
CG iteration 2, residual = 0.0024505017134408657
CG iteration 3, residual = 0.0006507984596997669
CG iteration 4, residual = 0.0002003886621020541
CG iteration 5, residual = 7.105863986319757e-05
CG iteration 6, residual = 3.028758180150971e-05
CG iteration 7, residual = 1.3957421251522062e-05
CG iteration 8, residual = 6.026536683895633e-06
CG iteration 9, residual = 2.743921037426196e-06
CG iteration 10, residual = 1.2186305324858967e-06
CG iteration 11, residual = 4.964351575295287e-07
CG iteration 12, residual = 1.9398388083721275e-07
CG iteration 13, residual = 7.686678702373776e-08
CG iteration 14, residual = 3.304683227273379e-08
CG iteration 15, residual = 1.4352549305598802e-08
CG iteration 16, residual = 6.021165087929649e-09
CG iteration 17, residual = 2.5562823885201097e-09
CG iteration 18, residual = 1.0915843646850842e-09
CG iteration 19, residual = 4.5195816562362506e-10
CG iteration 20, residual = 1.7588626620381772e-10
CG iteration 21, residual = 6.957906924129525e-11
CG iteration 22, residual = 2.8502107548717794e-11
CG iteration 23, residual = 1.1345979806499508e-11
CG iteration 24, residual = 4.1691621193654924e-12
CG iteration 25, residual = 1.509061112717019e-12
CG iteration 26, residual = 5.394782139923496e-13
CG iteration 27, residual = 1.9871234167359908e-13
CG iteration 28, residual = 7.249987931847989e-14
CG iteration 29, residual = 2.7920178284341328e-14
CG iteration 30, residual = 1.107782882354085e-14
[15]:
Draw (gfu);
[16]:
from netgen.occ import *
shape = MoveTo(0,0).LineTo(1,0,"in").LineTo(1,1).LineTo(2,1).LineTo(3,0).LineTo(4,1).LineTo(5,1) \
    .LineTo(5,2,"out").LineTo(4,2).LineTo(3,1).LineTo(2,2).LineTo(1,2).Rotate(180).Arc(1,90).Close().Face()
mesh = shape.GenerateMesh(dim=2, maxh=0.25).Curve(3)
Draw (mesh)
print (mesh.GetBoundaries())

fesp = L2(mesh, order=0)
fes = VectorH1(mesh, order=2, hoprolongation=True, dirichlet="in|default")
u,v = fes.TnT()

eq = InnerProduct(Grad(u),Grad(v)) * dx + 1e4 * Interpolate(div(u),fesp)*div(v)* dx
a = BilinearForm(eq).Assemble()

# a = BilinearForm( (Grad(u)|Grad(v)) * dx).Assemble()
fes.SetHarmonicProlongation(a)
pre = preconditioners.MultiGrid(a, blocktype=["vertexpatch"])

for l in range(4):
    mesh.Refine()
    a.Assemble()
    print (mesh.levels, fes.ndof)
('in', 'default', 'default', 'default', 'default', 'default', 'out', 'default', 'default', 'default', 'default', 'default', 'default')
2 3482
3 13426
4 52706
5 208834

see this Dissertation, page 116.

[17]:
f = LinearForm(fes)
gfu = GridFunction(fes)
Solve (a*gfu==f, pre, solvers.CGSolver, dirichlet=CF((0,x*(1-x))) | mesh.Boundaries("in"), printrates=True)
CG iteration 1, residual = 156.0216977186488
CG iteration 2, residual = 4.696993518461107
CG iteration 3, residual = 0.4412697915994829
CG iteration 4, residual = 0.06908617512166873
CG iteration 5, residual = 0.029433444799497818
CG iteration 6, residual = 0.014480167237941906
CG iteration 7, residual = 0.008734149812494384
CG iteration 8, residual = 0.005471780853160841
CG iteration 9, residual = 0.002040640466432749
CG iteration 10, residual = 0.0008675227735453143
CG iteration 11, residual = 0.0003033426394231655
CG iteration 12, residual = 0.00014955848925133776
CG iteration 13, residual = 5.951615094254987e-05
CG iteration 14, residual = 2.71179649008438e-05
CG iteration 15, residual = 1.0442357021264973e-05
CG iteration 16, residual = 5.0955310215879336e-06
CG iteration 17, residual = 2.342587632617851e-06
CG iteration 18, residual = 9.935176562326827e-07
CG iteration 19, residual = 3.8165395459554503e-07
CG iteration 20, residual = 1.5921102516544734e-07
CG iteration 21, residual = 7.0024620322799e-08
CG iteration 22, residual = 3.0241428336654446e-08
CG iteration 23, residual = 1.4336012134117808e-08
CG iteration 24, residual = 6.296140099473285e-09
CG iteration 25, residual = 2.50903955509807e-09
CG iteration 26, residual = 1.088799998290404e-09
CG iteration 27, residual = 4.765297125284482e-10
CG iteration 28, residual = 2.108563495896235e-10
CG iteration 29, residual = 1.1199063002443609e-10
[18]:
Draw (gfu);
[ ]: