5.2. DG Operators in NGSolve#

We want to efficiently implement the mass and gradient bilinear forms from the previous section.

Spaces#

We use L2 and VectorL2 spaces

from ngsolve import *
from netgen.occ import *
from ngsolve.webgui import Draw

geo = OCCGeometry(unit_square.shape, dim = 2)
mesh = Mesh(geo.GenerateMesh(maxh = 0.01))


order = 6
fes_p = L2(mesh, order = order)
fes_v = VectorL2(mesh, order = order-1)

p,p_ = fes_p.TnT()
v,v_ = fes_v.TnT()

print(fes_p.ndof, fes_v.ndof)
649656 974484
gfp = GridFunction(fes_p)
gfp.vec[100]=1.
Draw(gfp)
BaseWebGuiScene

Mass matrices#

Mass Operators can be assembled

from time import time

now = time()
mass_p_inv = BilinearForm(p*p_*dx).Assemble().mat.Inverse()
mass_v_inv = BilinearForm(v*v_*dx).Assemble().mat.Inverse()
print("Assembling and factorization: ",time()-now)

tmp_p = mass_p_inv.CreateVector()
tmp_v = mass_v_inv.CreateVector()

tmp_p.SetRandom()
tmp_v.SetRandom()
now = time()
for i in range(20):
    tmp_p.data = mass_p_inv*tmp_p
    tmp_v.data = mass_v_inv*tmp_v
print("20 matrix applications: ",time()-now)
Assembling and factorization:  7.4811577796936035
20 matrix applications:  10.61510157585144

alternatively they can be applied matrix-free

from time import time
now = time()

mass_p_inv = fes_p.Mass(1).Inverse()
mass_v_inv = fes_v.Mass(1).Inverse()
print("Assembling and factorization: ",time()-now)
tmp_p = mass_p_inv.CreateVector()
tmp_v = mass_v_inv.CreateVector()
tmp_p.SetRandom()
tmp_v.SetRandom()
now = time()
for i in range(20):
    tmp_p.data = mass_p_inv*tmp_p
    tmp_v.data = mass_v_inv*tmp_v
    
print("20 matrix applications: ",time()-now)
Assembling and factorization:  0.05927157402038574
20 matrix applications:  0.5783483982086182

Gradients#

Version with .Other()#

fes = fes_p*fes_v

gf = GridFunction(fes)
gf.vec.SetRandom()


(P,V),(P_,V_) = fes.TnT()


n = specialcf.normal(mesh.dim)


now = time()
B = BilinearForm(fes, nonassemble=True)
B += grad(P)*V_ * dx + 0.5*(P.Other()-P)*(V_*n) * dx(element_boundary=True)
BT = BilinearForm(fes, nonassemble=True)
BT += grad(P_)*V * dx + 0.5*(P_.Other()-P_)*(V*n) * dx(element_boundary=True)
print("Assembling and factorization: ",time()-now)


now = time()
for i in range(20):
    gf.vec.data = B.mat*gf.vec
    
print("20 matrix applications: ",time()-now)
Assembling and factorization:  0.0006272792816162109
20 matrix applications:  3.8412270545959473

Version with TraceOperator#

fes_tr = FacetFESpace(mesh,order=order)
pT = fes_tr.TrialFunction()


now = time()
traceop = fes_p.TraceOperator(fes_tr,average=True)



Bel = BilinearForm(trialspace=fes_p, testspace=fes_v)
Bel += grad(p)*v_ * dx -p*(v_*n) * dx(element_boundary=True)
Bel.Assemble()

Btr = BilinearForm(trialspace=fes_tr, testspace=fes_v)
Btr += pT * (v_*n) * dx(element_boundary=True)
Btr.Assemble()

emb_p = fes.embeddings[0]
emb_v = fes.embeddings[1]

B = emb_v @ (Bel.mat + Btr.mat @ traceop) @ emb_p.T
print("Assembling and factorization: ",time()-now)

now = time()
for i in range(20):
    gf.vec.data = B*gf.vec
    
print("20 matrix applications: ",time()-now)
Assembling and factorization:  1.4064881801605225
20 matrix applications:  1.1851904392242432

Geometry free TraceOperator#

fes_v = VectorL2(mesh, order = order-1, piola = True)

v,v_ = fes_v.TnT()
now = time()
Bel = BilinearForm(trialspace=fes_p, testspace=fes_v, geom_free=True)
Bel += grad(p)*v_ * dx -p*(v_*n) * dx(element_boundary=True)
Bel.Assemble()

Btr = BilinearForm(trialspace=fes_tr, testspace=fes_v, geom_free=True)
Btr += 0.5 * pT * (v_*n) * dx(element_boundary=True)
Btr.Assemble()

emb_p = fes.embeddings[0]
emb_v = fes.embeddings[1]

B = emb_v @ (Bel.mat + Btr.mat @ traceop) @ emb_p.T
print("Assembling and factorization: ",time()-now)

now = time()
for i in range(20):
    gf.vec.data = B*gf.vec
    
print("20 matrix applications: ",time()-now)
Assembling and factorization:  0.043317556381225586
20 matrix applications:  0.2828328609466553