This page was generated from unit-3.3.1-wavedg/wavedg.ipynb.

3.3.1 DG for the acoustic wave equation

We consider the acoustic problem tucp=0 in Ω×I,tpcdiv(u)=0 in Ω×I,p=p0 on Ω×{0},u=0 on Ω×{0}.

Here p is the acoustic pressure (the local deviation from the ambient pressure) and u is the local velocity.

+ suitable boundary conditions

A simple grid:

[1]:
from ngsolve import *
# import netgen.gui
from ngsolve.webgui import Draw
from netgen.geom2d import SplineGeometry
geo = SplineGeometry()
geo.AddRectangle( (-1, -1), (1, 1), bcs = ("bottom", "right", "top", "left"))
mesh = Mesh( geo.GenerateMesh(maxh=0.1))
Draw(mesh)
[1]:
BaseWebGuiScene

Find p:[0,T]TThPk+1(T) and u:[0,T]TTh[Pk(T)]d so that

(tu,v)=bh(p,v)v,(tp,q)=bh(q,u)q

with the centered flux approximation:

bh(p,v)=TTpv+T(p^p)vn

Here p^ is the centered approximation i.e. p^={{p}}.

[2]:
order = 6
fes_p = L2(mesh, order=order+1, all_dofs_together=True)
fes_u = VectorL2(mesh, order=order, piola=True, all_dofs_together=True)
fes = fes_p*fes_u
gfu = GridFunction(fes)
sceneu = Draw(gfu.components[1], mesh, "u")
scenep = Draw(gfu.components[0], mesh, "p", min=-0.02, max=0.08, autoscale=False, order=3)

What is the flag piola doing?

The VectorL2 space uses the following definition of basis functions on the mesh.

Let φ^(x^) be a (vectorial) basis function on the reference element T^, Φ:T^T with x=Φ(x^) and F=DΦ, then

φ(x):={φ^(x^),piola=False,covariant=False,(default)1|detF|Fφ^(x^),piola=True,covariant=False,FTφ^(x^),piola=False,covariant=True.

Inverse mass matrix operations:

Combining Embedding and the inverse mass matrix operation for fes_p allows to realize the following block inverse operations acting on fes:

(Mp1000)invp=(I0)emb_pMp1invmassp(I0)emb_p.T
[3]:
pdofs = fes.Range(0);
emb_p = Embedding(fes.ndof, pdofs)
invmassp = fes_p.Mass(1).Inverse()
invp = emb_p @ invmassp @ emb_p.T

Analogously, combining Embedding and the inverse mass matrix operation for fes_u allows to realize the following block inverse operations on fes:

(000Mu1)invu=(I0)emb_uMu1invmassu(I0)emb_u.T
[4]:
udofs = fes.Range(1)
emb_u = Embedding(fes.ndof, udofs)
invmassu = fes_u.Mass(Id(mesh.dim)).Inverse()
invu = emb_u @ invmassu @ emb_u.T

Time loop

Assuming the operators B,BT: fes_p× fes_u (fes_p× fes_u ) corresponding to Bh((u,p),(v,q)=bh(p,v) are given, the symplectic Euler time stepping method takes the form:

[5]:
def Run(B, BT, t0 = 0, tend = 0.1, tau = 1e-3, backward = False):
    t = 0
    with TaskManager():
        while t < (tend-t0) - tau/2:
            t += tau
            if not backward:
                gfu.vec.data += -tau * invp @ BT * gfu.vec
                gfu.vec.data += tau * invu @ B * gfu.vec
                print("\r t = {:}".format(t0 + t),end="")
            else:
                gfu.vec.data += -tau * invu @ B * gfu.vec
                gfu.vec.data += tau * invp @ BT * gfu.vec
                print("\r t = {:}".format(tend - t),end="")
            sceneu.Redraw() # blocking=False)
            scenep.Redraw() # blocking=False)
        print("")

Initial values (density ring):

[6]:
gfu.components[0].Set (exp(-50*(x**2+y**2))-exp(-100*(x**2+y**2)))
gfu.components[1].vec[:] = 0
scenep.Redraw()

Version 1:

The bilinear form for application on the full space fes

[7]:
n = specialcf.normal(mesh.dim)
(p,u),(q,v) = fes.TnT()
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(q)*u * dx + 0.5*(q.Other()-q)*(u*n) * dx(element_boundary=True)
[8]:
%time Run(B.mat, BT.mat, backward=False)
%time Run(B.mat, BT.mat, backward=True)
 t = 0.10000000000000007
CPU times: user 5.49 s, sys: 61 ms, total: 5.55 s
Wall time: 1.39 s
 t = -6.938893903907228e-17
CPU times: user 5.3 s, sys: 30.9 ms, total: 5.33 s
Wall time: 1.33 s

Version 2:

Using the TraceOperator and assembling of bh

We want to use the TraceOperator again:

[9]:
fes_tr = FacetFESpace(mesh, order=order+1)
traceop = fes_p.TraceOperator(fes_tr, False)

We want to assemble the sub-block matrix and need test/trial functions for single spaces (not the product spaces):

[10]:
p = fes_p.TrialFunction()
v = fes_u.TestFunction()
phat = fes_tr.TrialFunction()

We split the operator to bh into * volume contributions (local) * and couplings between the trace (obtained through the trace op) and the volume:

[11]:
Bel = BilinearForm(trialspace=fes_p, testspace=fes_u)
Bel += grad(p)*v * dx -p*(v*n) * dx(element_boundary=True)
%time Bel.Assemble()

Btr = BilinearForm(trialspace=fes_tr, testspace=fes_u)
Btr += 0.5 * phat * (v*n) * dx(element_boundary=True)
%time Btr.Assemble()

B = emb_u @ (Bel.mat + Btr.mat @ traceop) @ emb_p.T
CPU times: user 117 ms, sys: 16.2 ms, total: 134 ms
Wall time: 132 ms
CPU times: user 44.2 ms, sys: 7.7 ms, total: 51.9 ms
Wall time: 51.6 ms

Version 2 in action (assembled matrices, TraceOperators and Embeddings can do transposed multiply):

[12]:
%time Run(B, B.T, backward=False)
%time Run(B, B.T, backward=True)
 t = 0.10000000000000007
CPU times: user 4.76 s, sys: 2 s, total: 6.76 s
Wall time: 1.69 s
 t = -6.938893903907228e-17
CPU times: user 4.7 s, sys: 1.96 s, total: 6.66 s
Wall time: 1.66 s

Version 3:

Using the TraceOperator and geom_free=True with assembling

With p(x)=p^(x^) and v(x)=1|det(F)|Fv^(x^) (Piola mapped) there holds

Tvp=T^v^^p^

and similarly for the facet integrals, cf. unit-2.11 and

Integrals are independent of the “physical element” (up to ordering)

same element matrices for a large class of elements

[13]:
Bel = BilinearForm(trialspace=fes_p, testspace=fes_u, geom_free = True)
Bel += grad(p)*v * dx -p*(v*n) * dx(element_boundary=True)
%time Bel.Assemble()

Btr = BilinearForm(trialspace=fes_tr, testspace=fes_u, geom_free = True)
Btr += 0.5 * phat * (v*n) *dx(element_boundary=True)
%time Btr.Assemble()

B = emb_u @ (Bel.mat + Btr.mat @ traceop) @ emb_p.T
CPU times: user 1.55 ms, sys: 0 ns, total: 1.55 ms
Wall time: 870 µs
CPU times: user 1.63 ms, sys: 0 ns, total: 1.63 ms
Wall time: 1.37 ms

Version 3 in action:

[14]:
%time Run(B, B.T, backward=False)
%time Run(B, B.T, backward=True)
 t = 0.10000000000000007
CPU times: user 1.22 s, sys: 60.9 ms, total: 1.28 s
Wall time: 319 ms
 t = -6.938893903907228e-17
CPU times: user 1.24 s, sys: 44.2 ms, total: 1.28 s
Wall time: 320 ms

References: