This page was generated from unit-3.6-opsplit/opsplit.ipynb.

3.6 DG/HDG splitting

When solving unsteady problems with an operator-splitting method it might be benefitial to consider different space discretizations for different operators.

For a problem of the form

tu+Au+Cu=0

We consider the operator splitting:

  • 1.Step: Given u0, solve tntn+1: tu+Cu=0u12

  • 2.Step: Given u12, solve tntn+1: tu+Au=0u1

This splitting is only first order accurate but allows to take different time discretizations for the substeps 1 and 2.

In this example we consider the Navier-Stokes problem again (cf. unit 3.2) and want to combine

  • an H(div) -conforming Hybrid DG method (which is a very good discretization for Stokes-type problems) and

  • a standard upwind DG method for the discretization of the convection

The weak form is: Find (u,p):[0,T](H0,D1)d×L2, s.t. Ωtuv+Ωνuv+uu vΩdiv(v)p=fvv(H0,D1)d,Ωdiv(u)q=0qL2,u(t=0)=u0

Again, we consider the benchmark setup from http://www.featflow.de/en/benchmarks/cfdbenchmarking/flow/dfg_benchmark2_re100.html . The geometry:

image0

The viscosity is set to ν=103.

[1]:
from ngsolve import *
import netgen.gui
from netgen.geom2d import SplineGeometry
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.08) ); Draw(mesh)
order = 3
mesh.Curve(order)

For the HDG formulation we use the product space with

  • BDMk: H(div) conforming FE space (degree k)

  • Vector facet space: facet functions of degree k (vector valued and only in tangential direction)

  • piecewise polynomials up to degree k1 for the pressure

image0

HDG spaces

[2]:
V1 = HDiv ( mesh, order = order, dirichlet = "wall|cyl|inlet" )
V2 = FESpace ( "vectorfacet", mesh, order = order,
              dirichlet = "wall|cyl|inlet" )
Q = L2( mesh, order = order-1)
V = FESpace ([V1,V2,Q])

u, uhat, p = V.TrialFunction()
v, vhat, q = V.TestFunction()

Parameter and directions (normal / tangential projection):

[3]:
nu = 0.001 # viscosity
n = specialcf.normal(mesh.dim)
h = specialcf.mesh_size
def tang(vec):
    return vec - (vec*n)*n

Stokes discretization

The bilinear form to the HDG (see unit-2.8-DG for scalar HDG) discretized Stokes problem is: Kh((uT,uF,p),(vT,vF,q):=TT{TνuT:vT dxΩdiv(u)q dxΩdiv(v)p dxTνuTn[v]t dsTνvTn[u]t ds+Tναk2h[u]t[v]t ds}

where []t is the tangential projection of the jump ()T()F.

[4]:
alpha = 4  # SIP parameter
dS = dx(element_boundary=True)
a = BilinearForm ( V, symmetric=True)
a += nu * InnerProduct ( Grad(u), Grad(v) ) * dx
a += nu * InnerProduct ( Grad(u)*n, tang(vhat-v) ) * dS
a += nu * InnerProduct ( Grad(v)*n, tang(uhat-u) ) * dS
a += nu * alpha*order*order/h * InnerProduct(tang(vhat-v), tang(uhat-u)) * dS
a += (-div(u)*q - div(v)*p) * dx
a.Assemble()
[4]:
<ngsolve.comp.BilinearForm at 0x7ff9680060b0>

The mass matrix is simply

Mh((uT,uF,p),(vT,vF,q):=ΩuTvTdx

[5]:
m = BilinearForm(V , symmetric=True)
m += u * v * dx
m.Assemble()
[5]:
<ngsolve.comp.BilinearForm at 0x7ff970081670>

Initial conditions

As initial condition we solve the Stokes problem:

[6]:
gfu = GridFunction(V)

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

invstokes = a.mat.Inverse(V.FreeDofs(), inverse="umfpack")
gfu.vec.data += invstokes @ -a.mat * gfu.vec

Draw( gfu.components[0], mesh, "velocity" )
Draw( gfu.components[2], mesh, "pressure" )
Draw( Norm(gfu.components[0]), mesh, "absvel(hdiv)")

Application of convection

In the operator splitted approach we want to apply only operator applications for the convection part. Further, we want to do this in a usual DG setting. As a model problem we use the following procedure:

  • Given (u,p) in HDG space: project into u^ in usual DG space

  • Solve tu^=Cu^ by explicit scheme (involves convection evaluations and mass matrix operations only)

  • Solve Unsteady Stokes step with r.h.s. from convection sub-problem. To this end evaluate mixed mass matrix u^v to obtain a functional on the HDG space

For the projection steps we use mixed mass matrices:

  • Mm : HDG×DGR

  • MmT : DG×HDGR

  • MDG : DG×DGR (block diagonal)

To set up mixed mass matrices we use a bilinear form with two different FESpaces.

We do not assemble the matrices as we will only need the matrix-vector applications of Mm, MmT and MDG1.

There is a more elegant version (ConvertL2Operator, similar to TraceOperator) that we discuss below.

mixed mass matrices

[7]:
VL2 = VectorL2(mesh, order=order, piola=True)
uL2, vL2 = VL2.TnT()
bfmixed = BilinearForm ( V, VL2, nonassemble=True )
bfmixed += vL2*u*dx
bfmixedT = BilinearForm ( VL2, V, nonassemble=True)
bfmixedT += uL2*v*dx

convection operator

  • convection operation with standard Upwinding

  • No set up of the matrix (only interested in operator applications)

  • for the advection velocity we use the H(div)-conforming velocity (which is pointwise divergence free).

[8]:
vel = gfu.components[0]
convL2 = BilinearForm(VL2, nonassemble=True )
convL2 += (-InnerProduct(Grad(vL2) * vel, uL2)) * dx
un = InnerProduct(vel,n)
upwindL2 = IfPos(un, un*uL2, un*uL2.Other(bnd=uin))

dskel_inner  = dx(skeleton=True)
dskel_bound  = ds(skeleton=True)

convL2 += InnerProduct (upwindL2, vL2-vL2.Other()) * dskel_inner
convL2 += InnerProduct (upwindL2, vL2) * dskel_bound

Solution of convection steps as an operator (BaseMatrix)

We now define the solution of the convection problem for an initial data u in the HDG space. The return value (“res”) is Mmu^ where u^ is the solution of several explicit Euler steps of the convection problem

tu^=MDG1Cu^
[9]:
class PropagateConvection(BaseMatrix):
    def __init__(self,tau,steps):
        super(PropagateConvection, self).__init__()
        self.tau = tau; self.steps = steps
        self.h = V.ndof; self.w = V.ndof # operator domain and range
        self.mL2 = VL2.Mass(Id(mesh.dim)); self.invmL2 = self.mL2.Inverse()
        self.vecL2 = bfmixed.mat.CreateColVector() # temp vector
    def Mult(self, x, y):
        self.vecL2.data = self.invmL2 @ bfmixed.mat * x # <- project from Hdiv to L2
        for i in range(self.steps):
            self.vecL2.data -= self.tau/self.steps * self.invmL2 @ convL2.mat * self.vecL2
        y.data = bfmixedT.mat * self.vecL2
    def CreateColVector(self):
        return CreateVVector(self.h)

The operator splitted method (Yanenko splitting)

Setup of M:

[10]:
t = 0; tend = 0
tau = 0.01; substeps = 10

mstar = m.mat.CreateMatrix()
mstar.AsVector().data = m.mat.AsVector() + tau * a.mat.AsVector()
inv = mstar.Inverse(V.FreeDofs(), inverse="umfpack")

The time loop:

[11]:
%%time
tend += 1
res = gfu.vec.CreateVector()
convpropagator = PropagateConvection(tau,substeps)
while t < tend:
    gfu.vec.data += inv @ (convpropagator - mstar) * gfu.vec
    t += tau
    print ("\r  t =", t, end="")
    Redraw(blocking=True)
  t = 1.0000000000000007CPU times: user 9.53 s, sys: 19.2 ms, total: 9.55 s
Wall time: 9.48 s

ConvertL2Operator

Instead of doing the conversion between the spaces “by hand”, we can use the convenience operator ConvertL2Operator that realizes $ M_{DG}^{-1} :nbsphinx-math:`cdot `M_m $:

[12]:
HDG_to_L2 = V1.ConvertL2Operator(VL2) @ Embedding(V.ndof, V.Range(0)).T

The transpose of $ M_{DG}^{-1} \cdot `M_m is M_m^T :nbsphinx-math:cdot M_{DG}^{-1}$ * ``V1.ConvertL2Operator(VL2)`: V1 VL2 * V1.ConvertL2Operator(VL2).T: VL2 V1

Overwrite the application and use the ConvertL2Operator instead ot the mixed mass matrices:

[13]:
def NewMult(self, x, y):
    self.vecL2.data = HDG_to_L2 * x # <- project from Hdiv to L2
    for i in range(self.steps):
        self.vecL2.data -= tau/self.steps*self.invmL2@convL2.mat*self.vecL2
    y.data = HDG_to_L2.T @ self.mL2 * self.vecL2

PropagateConvection.Mult = NewMult

The time loop:

[14]:
%%time
tend += 1
convpropagator = PropagateConvection(tau,substeps)
while t < tend:
    gfu.vec.data += inv @ (convpropagator - mstar) * gfu.vec
    t += tau
    print ("\r  t =", t, end="")
    Redraw(blocking=True)
  t = 2.0000000000000013CPU times: user 9.36 s, sys: 28.6 ms, total: 9.39 s
Wall time: 9.34 s