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.9737767048280397 0.9999650900445778
3 11001 0.973189334188707 0.9999569893696341
4 43601 0.9738508900104403 0.9999593243769744
5 173601 0.9740047343009597 0.9999581308422725
[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.500665990770516
CG iteration 2, residual = 0.024383987764233117
CG iteration 3, residual = 0.0001879979181935082
CG iteration 4, residual = 1.1484794277895824e-06
CG iteration 5, residual = 9.252209913153161e-09
CG iteration 6, residual = 6.018344879733152e-11
CG iteration 7, residual = 4.580222394727665e-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.887345553976028 0.9998975910633989
3 11001 0.8957401274225061 0.999889401455988
4 43601 0.8945786078982367 0.999886328500714
5 173601 0.8943861692924857 0.9998960435342528
6 692801 0.8953852278217918 0.9998858093863472
[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.4886087977865685
CG iteration 2, residual = 0.12345444219082093
CG iteration 3, residual = 0.0036390880918837575
CG iteration 4, residual = 9.080367025015569e-05
CG iteration 5, residual = 2.7831282126523055e-06
CG iteration 6, residual = 7.696308282558776e-08
CG iteration 7, residual = 2.1727680277905314e-09
CG iteration 8, residual = 6.173415107275892e-11
CG iteration 9, residual = 1.888718288842756e-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.31325680892611085 0.9999007841866485
3 14146 0.197501810323512 0.9999100509629464
4 55938 0.19549161267814535 0.9999047978307443
5 222466 0.19549539298199847 0.9998999587479498
6 887298 0.19556232072452417 0.9998955800582687

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.5451163631463511 1.2567450360091899
3 3618 0.35880823306081083 1.429313946531482
4 14146 0.3055401019239755 1.5320915176949343
5 55938 0.27985898251758545 1.69468341085315
[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.020553570901516632
CG iteration 2, residual = 0.002450501713491592
CG iteration 3, residual = 0.0006507984597072646
CG iteration 4, residual = 0.00020038866210214928
CG iteration 5, residual = 7.105863986203141e-05
CG iteration 6, residual = 3.0287581800428845e-05
CG iteration 7, residual = 1.3957421250950989e-05
CG iteration 8, residual = 6.026536683658645e-06
CG iteration 9, residual = 2.743921037302033e-06
CG iteration 10, residual = 1.2186305324357992e-06
CG iteration 11, residual = 4.964351575149758e-07
CG iteration 12, residual = 1.9398388083217042e-07
CG iteration 13, residual = 7.686678702204675e-08
CG iteration 14, residual = 3.3046832271810455e-08
CG iteration 15, residual = 1.4352549305221053e-08
CG iteration 16, residual = 6.021165087796305e-09
CG iteration 17, residual = 2.5562823884669277e-09
CG iteration 18, residual = 1.091584364671456e-09
CG iteration 19, residual = 4.5195816561895847e-10
CG iteration 20, residual = 1.7588626620381997e-10
CG iteration 21, residual = 6.957906924119465e-11
CG iteration 22, residual = 2.8502107548586754e-11
CG iteration 23, residual = 1.1345979806439238e-11
CG iteration 24, residual = 4.169162119309453e-12
CG iteration 25, residual = 1.5090611126847465e-12
CG iteration 26, residual = 5.394782139774904e-13
CG iteration 27, residual = 1.987123416673933e-13
CG iteration 28, residual = 7.249987931618582e-14
CG iteration 29, residual = 2.7920178283258032e-14
CG iteration 30, residual = 1.107782882314275e-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

from Dissertation J. Schöberl, 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.02169771865226
CG iteration 2, residual = 4.696993518465428
CG iteration 3, residual = 0.44126979160721574
CG iteration 4, residual = 0.06908617512440603
CG iteration 5, residual = 0.02943344480046653
CG iteration 6, residual = 0.014480167237825943
CG iteration 7, residual = 0.008734149812569682
CG iteration 8, residual = 0.005471780853206843
CG iteration 9, residual = 0.00204064046650003
CG iteration 10, residual = 0.0008675227735594886
CG iteration 11, residual = 0.0003033426394324715
CG iteration 12, residual = 0.0001495584892548056
CG iteration 13, residual = 5.9516150943703965e-05
CG iteration 14, residual = 2.71179649021834e-05
CG iteration 15, residual = 1.0442357021319547e-05
CG iteration 16, residual = 5.0955310217706835e-06
CG iteration 17, residual = 2.342587632652588e-06
CG iteration 18, residual = 9.935176562707475e-07
CG iteration 19, residual = 3.81653954604812e-07
CG iteration 20, residual = 1.5921102516905162e-07
CG iteration 21, residual = 7.002462032466278e-08
CG iteration 22, residual = 3.0241428336713374e-08
CG iteration 23, residual = 1.4336012133504587e-08
CG iteration 24, residual = 6.296140098311719e-09
CG iteration 25, residual = 2.509039553579678e-09
CG iteration 26, residual = 1.0887999965340932e-09
CG iteration 27, residual = 4.765297107009801e-10
CG iteration 28, residual = 2.1085634784578303e-10
CG iteration 29, residual = 1.1199062841888853e-10
[18]:
Draw (gfu);
[ ]: