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 \begin{align} \partial_{t} \mathbf{u} - c \nabla p &= 0 \quad \text{ in } \Omega \times I, \\ \partial_{t} p - c \operatorname{div}(\mathbf{u}) &= 0 \quad \text{ in } \Omega \times I, \\ % p (0,\cdot) &= p(1,\cdot), \quad % p (\cdot,0) = p(\cdot,1), \label{eq:per2b}\\ % \mathbf{q} (0,\cdot) &= \mathbf{q}(1,\cdot), \quad % \mathbf{q} (\cdot,0) = \mathbf{q}(\cdot,1), \label{eq:per2bb}\\ p &= p_0 \!\!\! \quad \text{ on } \Omega \times \{0\},\\ u &= 0 \quad \text{ on } \Omega \times \{0\}. \end{align}
Here \(p\) is the acoustic pressure (the local deviation from the ambient pressure) and \(\mathbf{u}\) is the local velocity.
+
suitable boundary conditions
A simple grid:
[1]:
from ngsolve import *
import netgen.gui
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)
Generate Mesh from spline geometry
Boundary mesh done, np = 80
CalcLocalH: 80 Points 0 Elements 0 Surface Elements
Meshing domain 1 / 1
load internal triangle rules
Surface meshing done
Edgeswapping, topological
Smoothing
Split improve
Combine improve
Smoothing
Edgeswapping, metric
Smoothing
Split improve
Combine improve
Smoothing
Edgeswapping, metric
Smoothing
Split improve
Combine improve
Smoothing
Update mesh topology
Update clusters
Find \(p: [0,T] \to \bigoplus_{T\in\mathcal{T}_h} \mathcal{P}^{k+1}(T)\) and \(\mathbf{u}: [0,T] \to \bigoplus_{T\in\mathcal{T}_h} [\mathcal{P}^k(T)]^d\) so that
\begin{align*} (\partial_t \mathbf{u}, v) &= b_h(p,v) & &&& \forall v,\\ (\partial_t p, q) &= & - b_h(q,\mathbf{u}) &&& \forall q\\ \end{align*}
with the centered flux approximation:
Here \(\hat{p}\) is the centered approximation i.e. \(\hat{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 = FESpace( [fes_p,fes_u] )
gfu = GridFunction(fes)
Draw(gfu.components[1], mesh, "u")
Draw(gfu.components[0], mesh, "p", min=-0.02, max=0.08, autoscale=False, sd=3)
What is the flag piola
doing?¶
The VectorL2
space uses the following definition of basis functions on the mesh.
Let \(\hat{\varphi}(\hat{x})\) be a (vectorial) basis function on the reference element \(\hat{T}\), \(\Phi: \hat{T} \to T\) with \(x = \Phi(\hat{x})\) and \(F= D\Phi\), then
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
:
[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
:
[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
\(\times\) fes_u
\(\to\)(fes_p
\(\times\) fes_u
)\('\) corresponding to \(B_h((u,p),(v,q)=b_h(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="")
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
Redraw()
setvalues element 930/930
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.45 s, sys: 125 ms, total: 5.57 s
Wall time: 1.39 s
t = -6.938893903907228e-17
CPU times: user 5.31 s, sys: 96 ms, total: 5.41 s
Wall time: 1.35 s
Version 2:¶
Using the TraceOperator
and assembling of \(b_h\)¶
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 \(b_h\) 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
assemble mixed bilinearform
CPU times: user 122 ms, sys: 8.2 ms, total: 130 ms
Wall time: 128 ms
assemble mixed bilinearform
CPU times: user 40.7 ms, sys: 12.1 ms, total: 52.9 ms
Wall time: 51.4 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.75 s, sys: 2.11 s, total: 6.86 s
Wall time: 1.71 s
t = -6.938893903907228e-17
CPU times: user 4.67 s, sys: 2.02 s, total: 6.69 s
Wall time: 1.66 s
Version 3:¶
Using the TraceOperator
and geom_free=True
with assembling¶
With \(p(x) = \hat{p}(\hat{x})\) and \(\mathbf{v}(x) = \frac{1}{|\operatorname{det}(F)|} F \cdot \hat{\mathbf{v}}(\hat{x})\) (Piola mapped) there holds
\begin{align} \int_T \mathbf{v} \cdot \nabla p = \int_{\hat{T}} \hat{\mathbf{v}} \cdot \hat{\nabla} \hat{p} \end{align}
and similarly for the facet integrals, cf. unit-2.11 and
Integrals are independent of the “physical element” (up to ordering)
\(\leadsto\) 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.33 ms, sys: 349 µs, total: 1.68 ms
Wall time: 877 µs
CPU times: user 2.24 ms, sys: 0 ns, total: 2.24 ms
Wall time: 1.77 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.24 s, sys: 109 ms, total: 1.35 s
Wall time: 334 ms
t = -6.938893903907228e-17
CPU times: user 1.13 s, sys: 43.5 ms, total: 1.18 s
Wall time: 293 ms
References:
[Hesthaven, Warburton. Nodal Discontinuous Galerkin Methods—Algorithms, Analysis and Applications, 2007]