This page was generated from unit-2.1.7-facetspace-mg/facetspace-mg.ipynb.

2.1.7 Multigrid for hybrid methods

Mixed methods for second order problems can often be reduced to the mesh facet, so called hybrid mixed methods. Simiar, hybrid DG methods introduce new variables on the facets, such that the bulk of element variables can be condensed out.

We show how to setup a multigrid preconditioner for hybrid methods. Interesting applications are nearly incompressible materials, or Stokes, discretized by \(H(\operatorname{div})\)-conforming HDG or hybrid mixed methods.

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

The hybrid DG method:

Alternative text

On \(V_h = V_T \times V_F = P^k({\mathcal T}) \times P^k ({\mathcal F})\) we define the bilinear form

\[\DeclareMathOperator{\Div}{div} \sum_T \int_T \nabla u \nabla v - \sum_T \int_{\partial T} \tfrac{\partial u}{\partial n} (v-\widehat v) - \sum_T \int_{\partial T} \tfrac{\partial v}{\partial n} (u-\widehat u) + \frac{\alpha p^2}{h} \sum_F \int_F (u-\widehat u)(v-\widehat v)\]

Element variables can be condensed out, which leads to a system reduced to the Skeleton.

When splitting a large triangle \(T_H\) into small trianles, the functions on \(\partial T_H\) have a canonical representation on the facets of the fine triangles. However, facet variables on internal edges of \(T_H\) are not defined by embedding. The HarmonicProlongation provides the energy optimal extension to the internal edges. To define energy optimal we need the energy defined by a bilinear form.

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

order = 3
fes = L2(mesh, order=order) * FacetFESpace(mesh, order=order, hoprolongation=True, dirichlet=".*")
(u,uhat), (v,vhat) = fes.TnT()

n = specialcf.normal(2)
h = specialcf.mesh_size
dS = dx(element_vb=BND)

HDGform = u*v*dx+ grad(u)*grad(v)*dx - n*grad(u)*(v-vhat)*dS - n*grad(v)*(u-uhat)*dS + 5*(order+1)**2/h*(u-uhat)*(v-vhat)*dS
bfa = BilinearForm(HDGform, condense=True).Assemble()
fes.SetHarmonicProlongation(bfa, inverse="sparsecholesky")
pre = preconditioners.MultiGrid(bfa, blocktype=["vertexpatch"], cycle=1)
[3]:
with TaskManager():
    for l in range(7):
        mesh.Refine()
        bfa.Assemble()
        # pre.Update()
        lam = EigenValues_Preconditioner(bfa.mat, pre)
        print ("l =", l, "ndof =", fes.ndof, "lam_min/lam_max = ", lam[0], lam[-1])
l = 0 ndof = 144 lam_min/lam_max =  0.9999999999999993 0.9999999999999993
l = 1 ndof = 544 lam_min/lam_max =  0.9573848616699543 0.9999918601826863
l = 2 ndof = 2112 lam_min/lam_max =  0.9261189386281288 0.9999692236038735
l = 3 ndof = 8320 lam_min/lam_max =  0.9325349652513784 0.9998939800629592
l = 4 ndof = 33024 lam_min/lam_max =  0.92945510895588 0.9997230751035944
l = 5 ndof = 131584 lam_min/lam_max =  0.9282844459834527 0.9996349409051806
l = 6 ndof = 525312 lam_min/lam_max =  0.9280114490358107 0.9995334765161776
[4]:
f = LinearForm (x*v*dx).Assemble()
gfu = GridFunction(fes)
gfu.vec[:]=0
with TaskManager():
    Solve (bfa*gfu==f, pre, lin_solver=solvers.CGSolver, printrates=True)
CG iteration 1, residual = 0.09459461645928992
CG iteration 2, residual = 0.0005944516866628238
CG iteration 3, residual = 7.695100604718358e-06
CG iteration 4, residual = 2.0699972197723346e-07
CG iteration 5, residual = 3.8945463326402356e-09
CG iteration 6, residual = 4.9504671345225695e-11
CG iteration 7, residual = 1.0763303055548899e-12
CG iteration 8, residual = 2.096450968451275e-14
[5]:
Draw (gfu.components[0]);

Hybrid-mixed methods:

Find \(\sigma, u, \widehat u \in \Sigma_h \times V_h \times F_h\):

\[\begin{split}\DeclareMathOperator{\Div}{div} \begin{array}{ccccccll} \int a \sigma \tau & + & \sum_T \int_T \Div \tau \, u & + & \sum_F \int_F [\tau_n] \widehat u & = & 0 & \forall \, \tau \in \Sigma \\ \int \Div \sigma \, v &&&&& = & \int f v & \forall \, v \in V_h \\ \int [ \sigma_n ] \, \widehat v &&&&& = & \int_{\Gamma_n} g \widehat v & \forall \, \widehat v \in F_h \end{array}\end{split}\]

where \(\Sigma_h\) is an discontinuous \(H(div)\) finite element space, \(V_h\) a sub-space of \(L_2\), and \(F_h\) consists of polynomials on every facet.

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

order = 2

fesSigma = PrivateSpace(Discontinuous(HDiv(mesh, order=order, RT=True)))
fesL2 = L2(mesh, order=order)
fesFacet = FacetFESpace(mesh, order=order, hoprolongation=True, dirichlet=".*")
fes = fesSigma*fesL2*fesFacet

(sigma, u,uhat), (tau, v,vhat) = fes.TnT()
n = specialcf.normal(2)
dS = dx(element_vb=BND)
mixedform = -sigma*tau*dx
mixedform += div(sigma)*v*dx - sigma*n*vhat*dS
mixedform += div(tau)*u*dx - tau*n*uhat*dS

bfa = BilinearForm(mixedform, condense=True).Assemble()
fes.SetHarmonicProlongation(bfa, inverse="sparsecholesky")
pre = preconditioners.MultiGrid(bfa, blocktype=["vertexpatch"], cycle=1)

for l in range(5):
    mesh.Refine()
    bfa.Assemble()
    lam = EigenValues_Preconditioner(bfa.mat, pre)
    print ("l =", l, "ndof =", fes.ndof, "lam_min/lam_max = ", lam[0], lam[-1])
l = 0 ndof = 2328 lam_min/lam_max =  0.8830690135570511 1.000082660916307
l = 1 ndof = 9192 lam_min/lam_max =  0.8695401255366382 0.9995622254215344
l = 2 ndof = 36528 lam_min/lam_max =  0.8480639402311638 0.9985740487773203
l = 3 ndof = 145632 lam_min/lam_max =  0.830706421034568 0.9979298221317969
l = 4 ndof = 581568 lam_min/lam_max =  0.8389819990459044 0.9976337192728126
[7]:
f = LinearForm(x*v*dx).Assemble()
gfu = GridFunction(fes)
Solve(bfa*gfu==f, pre, solvers.CGSolver, printrates=True)
Draw (gfu.components[1]);
CG iteration 1, residual = 0.09771383816444208
CG iteration 2, residual = 0.0011964806411029606
CG iteration 3, residual = 3.13755506370263e-05
CG iteration 4, residual = 9.5216727952247e-07
CG iteration 5, residual = 3.248449810413196e-08
CG iteration 6, residual = 1.649970071284961e-09
CG iteration 7, residual = 8.248735706462719e-11
CG iteration 8, residual = 3.983130055402826e-12
CG iteration 9, residual = 2.0772868157657282e-13
CG iteration 10, residual = 1.2019646043967861e-14

Nearly incompressible materials, H(div)-conforming HDG

[Lehrenfeld+Schöberl, 2016]

[8]:
ngmesh = unit_square.GenerateMesh(maxh=0.3)
mesh = Mesh(ngmesh)

order = 3

fesT = HDiv(mesh, order=order, hoprolongation=True, dirichlet=".*")
fesF = TangentialFacetFESpace(mesh, order=order, hoprolongation=True, highest_order_dc=True, dirichlet=".*")
fes = fesT*fesF

(u,uhat), (v,vhat) = fes.TnT()
n = specialcf.normal(2)
def tang(v): return v-(v*n)*n
h = specialcf.mesh_size
dS = dx(element_vb=BND)

HDGform = InnerProduct(Grad(u),Grad(v))*dx - (Grad(u)*n)*tang(v-vhat)*dS - (Grad(v)*n)*tang(u-uhat)*dS \
    + 1*(order+1)**2/h*tang(u-uhat)*tang(v-vhat)*dS

bfa = BilinearForm(HDGform + 1e3*div(u)*div(v)*dx, condense=True).Assemble()
fes.SetHarmonicProlongation(bfa)
pre = preconditioners.MultiGrid(bfa, smoother="block", smoothingsteps=1, blocktype=["vertexpatch"], cycle=1)
[9]:
with TaskManager():
    for l in range(4):
        mesh.Refine()
        bfa.Assemble()
        lam = EigenValues_Preconditioner(bfa.mat, pre)
        print ("l =", l, "ndof =", fes.ndof, lam[0], lam[-1])
l = 0 ndof = 2190 0.8031555871568468 1.0389867675724136
l = 1 ndof = 8622 0.6881351203081549 1.0480292176047623
l = 2 ndof = 34158 0.6819544900463768 1.0702830065295164
l = 3 ndof = 135918 0.6926495255892722 1.0752680621326927
[10]:
with TaskManager():
    f = LinearForm ((0.5-y)*v[0]*dx).Assemble()
gfu = GridFunction(fes)
gfu.vec[:]=0
with TaskManager(pajetrace=10**8):
    solvers.BVP(bfa, f, gfu, pre, print=True)
CG iteration 1, residual = 0.01974650523880611
CG iteration 2, residual = 0.0002077035687087128
CG iteration 3, residual = 1.354162972035853e-05
CG iteration 4, residual = 1.2095124817465959e-06
CG iteration 5, residual = 1.1795588285891222e-07
CG iteration 6, residual = 1.2588391516132443e-08
CG iteration 7, residual = 1.4899585820052177e-09
CG iteration 8, residual = 1.670719075759744e-10
[11]:
Draw (gfu.components[0])
[11]:
BaseWebGuiScene

Nearly incompressible materials / Stokes in 3D

[12]:
ngmesh = unit_cube.GenerateMesh(maxh=2)
mesh = Mesh(ngmesh)

order = 2

fesT = HDiv(mesh, order=order, hoprolongation=True, dirichlet=".*")
fesF = TangentialFacetFESpace(mesh, order=order, hoprolongation=True, highest_order_dc=True, dirichlet=".*")
fes = fesT*fesF

(u,uhat), (v,vhat) = fes.TnT()
n = specialcf.normal(3)
def tang(v): return v-(v*n)*n
h = specialcf.mesh_size
dS = dx(element_vb=BND)

HDGform = 0.001*u*v*dx+InnerProduct(Grad(u),Grad(v))*dx - (Grad(u)*n)*tang(v-vhat)*dS - (Grad(v)*n)*tang(u-uhat)*dS \
    + 5*(order+1)**2/h*tang(u-uhat)*tang(v-vhat)*dS

bfa = BilinearForm(HDGform + 1e3*div(u)*div(v)*dx, condense=True).Assemble()
fes.SetHarmonicProlongation(bfa)
pre = preconditioners.MultiGrid(bfa, smoother="block", smoothingsteps=3, blocktype=["edgepatch"], cycle=1)
[13]:
with TaskManager():
    for l in range(3):
        mesh.Refine()
        bfa.Assemble()
        # pre.Update()
        lam = EigenValues_Preconditioner(bfa.mat, pre)
        print ("l =", l, "ndof =", fes.ndof, "lam min/max = ", lam[0], lam[-1])
l = 0 ndof = 5574 lam min/max =  0.5060508739232081 1.2112630153266326
l = 1 ndof = 43470 lam min/max =  0.258069675460347 1.2905858121466474
l = 2 ndof = 342894 lam min/max =  0.23767448244474496 1.4487474122388466
[14]:
with TaskManager():
    f = LinearForm ((0.5-y)*v[0]*dx).Assemble()
gfu = GridFunction(fes)
gfu.vec[:]=0

with TaskManager():
    Solve(bfa*gfu==f, pre, solvers.CGSolver, printrates=True)
CG iteration 1, residual = 0.016109906113163888
CG iteration 2, residual = 0.0036631197303150544
CG iteration 3, residual = 0.001376384118013166
CG iteration 4, residual = 0.0005505700530464624
CG iteration 5, residual = 0.0002366794621813538
CG iteration 6, residual = 0.00010014395420474696
CG iteration 7, residual = 4.349404812113172e-05
CG iteration 8, residual = 1.6590186209439522e-05
CG iteration 9, residual = 6.187485725235242e-06
CG iteration 10, residual = 2.3330442207174118e-06
CG iteration 11, residual = 9.509298878739721e-07
CG iteration 12, residual = 3.7770775958300927e-07
CG iteration 13, residual = 1.6068815303131638e-07
CG iteration 14, residual = 6.952194561489457e-08
CG iteration 15, residual = 2.7823124594669592e-08
CG iteration 16, residual = 1.0924337753512887e-08
CG iteration 17, residual = 4.487871076209828e-09
CG iteration 18, residual = 1.862723025370629e-09
CG iteration 19, residual = 7.402872804341473e-10
CG iteration 20, residual = 2.994232200089316e-10
CG iteration 21, residual = 1.272693311791219e-10
CG iteration 22, residual = 5.430254195363792e-11
CG iteration 23, residual = 2.2027345474568758e-11
CG iteration 24, residual = 8.93408629673281e-12
CG iteration 25, residual = 3.6784422792092155e-12
CG iteration 26, residual = 1.457910563468592e-12
CG iteration 27, residual = 5.886329949349116e-13
CG iteration 28, residual = 2.568065072284821e-13
CG iteration 29, residual = 1.0908752440989971e-13
CG iteration 30, residual = 4.42217449741758e-14
CG iteration 31, residual = 1.8045747212340023e-14
CG iteration 32, residual = 7.616932076775313e-15
[15]:
clipping = { "function" : True,  "pnt" : (0.5,0.5,0.51), "vec" : (0,0,-1) }
Draw (gfu.components[0], order=2, clipping=clipping);

Flow channel in 3D

[16]:
from ngsolve import *
from netgen.occ import *
from ngsolve.webgui import Draw
from ngsolve.krylovspace import CGSolver

box = Box((0,0,0), (2.5,0.41,0.41))
box.faces.name="wall"
box.faces.Min(X).name="inlet"
box.faces.Max(X).name="outlet"
cyl = Cylinder((0.5,0.2,0), Z, h=0.41,r=0.05)
cyl.faces.name="cyl"
shape = box-cyl

mesh = shape.GenerateMesh(maxh=0.2).Curve(3)
Draw (mesh);
[17]:
order = 2

fesT = HDiv(mesh, order=order, hoprolongation=True, dirichlet="wall|inlet|cyl")
fesF = TangentialFacetFESpace(mesh, order=order, hoprolongation=True, highest_order_dc=True, dirichlet=".*")
fes = fesT*fesF

(u,uhat), (v,vhat) = fes.TnT()
n = specialcf.normal(3)
def tang(v): return v-(v*n)*n
h = specialcf.mesh_size
dS = dx(element_vb=BND)

HDGform = 0.001*u*v*dx+InnerProduct(Grad(u),Grad(v))*dx - (Grad(u)*n)*tang(v-vhat)*dS - (Grad(v)*n)*tang(u-uhat)*dS \
    + 5*(order+1)**2/h*tang(u-uhat)*tang(v-vhat)*dS

with TaskManager():
    bfa = BilinearForm(HDGform + 1e3*div(u)*div(v)*dx, condense=True).Assemble()
    fes.SetHarmonicProlongation(bfa)
    pre = preconditioners.MultiGrid(bfa, inverse="sparsecholesky", smoother="block", smoothingsteps=1, blocktype=["edgepatch"], cycle=1)
[18]:
with TaskManager():
    for l in range(1):
        mesh.Refine()
        bfa.Assemble()
        lam = EigenValues_Preconditioner(bfa.mat, pre)
        print ("l =", l, "ndof =", fes.ndof, "lam min/max = ", lam[0], lam[-1])
l = 0 ndof = 1779310 lam min/max =  0.07395292018165678 3.336861388881034
[19]:
gfu = GridFunction(fes)

uin = (1.5*4*y*(0.41-y)/(0.41*0.41)*z*(0.41-z)/0.41**2,0, 0)
gfu.components[0].Set(uin, definedon=mesh.Boundaries("inlet"))

inv = CGSolver(bfa.mat, pre.mat, printrates=True, tol=1e-5)

with TaskManager():
    gfu.vec.data -= inv@bfa.mat * gfu.vec
gfu.vec.data += bfa.harmonic_extension * gfu.vec
CG iteration 1, residual = 14.705439018174088
CG iteration 2, residual = 0.6535781335123135
CG iteration 3, residual = 0.29491228099866235
CG iteration 4, residual = 0.1501249968250971
CG iteration 5, residual = 0.11534735471253388
CG iteration 6, residual = 0.07205075926983516
CG iteration 7, residual = 0.0519820898755217
CG iteration 8, residual = 0.03870922114839964
CG iteration 9, residual = 0.022859269501353415
CG iteration 10, residual = 0.016697123231946494
CG iteration 11, residual = 0.01089695408604915
CG iteration 12, residual = 0.007552851135391592
CG iteration 13, residual = 0.005539446504971075
CG iteration 14, residual = 0.004139996160135474
CG iteration 15, residual = 0.0030604930093082504
CG iteration 16, residual = 0.0021714417553217423
CG iteration 17, residual = 0.0015570730934652771
CG iteration 18, residual = 0.0011741926334635528
CG iteration 19, residual = 0.0008572928809047239
CG iteration 20, residual = 0.0006182722955593343
CG iteration 21, residual = 0.000510028652649261
CG iteration 22, residual = 0.00036400544226010645
CG iteration 23, residual = 0.00027695011270485905
CG iteration 24, residual = 0.00020615710374376038
CG iteration 25, residual = 0.00014504450758853936
[20]:
clipping = { "function" : True,  "pnt" : (1,0.2,0.2 ), "vec" : (0,0,-1.0) }
Draw (gfu.components[0], mesh, order=2, clipping=clipping);

Pressure:

[21]:
Draw (div(gfu.components[0]), mesh, order=2, clipping=clipping, draw_surf=False);
[ ]:

[ ]:

[ ]: