Embedded Trefftz
Classical Trefftz methods need an explicit local basis of functions satisfying the PDE. These are difficult to construct. Embedded Trefftz works differently:
start with a standard discontinuous polynomial space,
assemble elementwise a local version of the PDE,
compute the elementwise nullspace of that residual,
assemble the usual DG formulation on that nullspace.
Moreover, we relax the Trefftz condition in a way that generalizes to operators that do not have a kernel in the polynomials.
Constructing an Embedding¶
The embedding represents the unknown Trefftz basis inside the standard discontinuous polynomial basis .
If is the ordinary DG matrix on the full polynomial space, then we solve is the projected system
The embedding is constructed from the residual equations.
The local Trefftz coefficients are the nullspace of .
Numerically, TrefftzEmbedding computes that nullspace element by element with an SVD (or equivalently a QR):
On each mesh element, use an SVD :
Hence, the local embedding is given by
Conditioning of the embedded Trefftz method.¶
Source
from ngsolve import *
from netgen.occ import *
from ngsolve_book import Draw
from ngstrefftz import *
SetNumThreads(4)
import matplotlib.pyplot as plt
import scipy.sparse as sp
Lap = lambda u: sum(Trace(u.Operator("hesse")))A first Embedding¶
Since
choosing yields
mesh = Mesh(unit_square.GenerateMesh(maxh=0.35))
p = 4
base = L2(mesh, order=p, dgjumps=True)
test = L2(mesh, order=p - 2, dgjumps=True)
u = base.TrialFunction()
q = test.TestFunction()
op = (-Lap(u)) * q * dx
emb = TrefftzEmbedding(op)
print(f"full polynomial dofs: {base.ndof}")
print(f"embedded Trefftz dofs: {emb.GetEmbedding().shape[1]}")full polynomial dofs: 270
embedded Trefftz dofs: 162
The embedded unknown lives in the small Trefftz space. emb.Embed(...)
converts it back to the polynomial space for visualization or post-processing.
ut = emb.GetEmbedding().CreateRowVector()
up = GridFunction(base, multidim=len(ut))
for i, vec in enumerate(up.vecs):
ut[:] = 0
ut[i] = 1
vec.data = emb.Embed(ut)
Draw(up,mesh,"basis",animate=True,interpolate_multidim=False,min=-1, max=1, deformation=True, scale=.3, euler_angles=[-70,0.4,2],)WebGLSceneSetting up the global embedded DG system¶
We can use again the standard SIPDG
Source
def sip_laplace(fes, bndc, rhs=0, alpha=4):
mesh = fes.mesh
order = fes.globalorder
n = specialcf.normal(mesh.dim)
h = specialcf.mesh_size
u = fes.TrialFunction()
v = fes.TestFunction()
jump_u = (u - u.Other()) * n
jump_v = (v - v.Other()) * n
mean_dudn = 0.5 * (grad(u) + grad(u.Other()))
mean_dvdn = 0.5 * (grad(v) + grad(v.Other()))
a = BilinearForm(fes, symmetric=True)
a += grad(u) * grad(v) * dx
a += alpha * order**2 / h * jump_u * jump_v * dx(skeleton=True)
a += (-mean_dudn * jump_v - mean_dvdn * jump_u) * dx(skeleton=True)
a += alpha * order**2 / h * u * v * ds(skeleton=True)
a += (-n * grad(u) * v - n * grad(v) * u) * ds(skeleton=True)
f = LinearForm(fes)
f += rhs * v * dx
f += alpha * order**2 / h * bndc * v * ds(skeleton=True)
f += -n * grad(v) * bndc * ds(skeleton=True)
with TaskManager():
a.Assemble()
f.Assemble()
return a, fFirst, the explicit matrix projection mirrors the derivation:
exact = exp(x) * sin(y)
a_full, f_full = sip_laplace(base, exact)
T = emb.GetEmbedding()
TT = T.CreateTranspose()
with TaskManager():
A_t = TT @ a_full.mat @ T
b_t = TT * f_full.vec
sol_t = A_t.Inverse(inverse="sparsecholesky") * b_t
gfu = GridFunction(base)
gfu.vec.data = T * sol_t
print(f"error: {sqrt(Integrate((gfu - exact) ** 2, mesh)):.3e}")
Draw(gfu, mesh, "projected embedded solve");error: 9.955e-07
EmbeddedTrefftzFES¶
For production code, EmbeddedTrefftzFES avoids assembling the larger full
matrix first:
tfes = EmbeddedTrefftzFES(emb)
a_emb, f_emb = sip_laplace(tfes, exact)
gfu_t = GridFunction(tfes)
with TaskManager():
gfu_t.vec.data = a_emb.mat.Inverse(inverse="sparsecholesky") * f_emb.vec
gfu = GridFunction(base)
gfu.vec.data = emb.Embed(gfu_t.vec)
print(f"error: {sqrt(Integrate((gfu - exact) ** 2, mesh)):.3e}")error: 9.955e-07
Inhomogeneous Problems¶
For the unknown is split into a local particular part and a homogeneous Trefftz correction:
The local equations determine locally, while the global DG solve determines only :
p = 4
base = L2(mesh, order=p, dgjumps=True)
test = L2(mesh, order=p - 2, dgjumps=True)
u = base.TrialFunction()
q = test.TestFunction()
exact = sin(pi * x) * sin(pi * y)
rhs = 2 * pi**2 * exact
op = Lap(u) * q * dx
lop = -rhs * q * dx
emb = TrefftzEmbedding(op, lop)
up = emb.GetParticularSolution()
a, f = sip_laplace(base, exact, rhs=rhs)
T = emb.GetEmbedding()
TT = T.CreateTranspose()
with TaskManager():
A_t = TT @ a.mat @ T
b_t = TT * (f.vec - a.mat * up)
sol_t = A_t.Inverse(inverse="sparsecholesky") * b_t
gfu = GridFunction(base)
gfu.vec.data = T * sol_t + up
print(f"Poisson embedded error: {sqrt(Integrate((gfu - exact) ** 2, mesh)):.3e}")
print(f"full dofs: {base.ndof}, Trefftz dofs: {T.width}")
Draw(gfu, mesh, "gfu");Poisson embedded error: 1.021e-04
full dofs: 270, Trefftz dofs: 162
Local-Global Coupling¶
The embedded view also separates the global DG coupling from the local residual constraints. Recall the weak Trefftz space
For inhomogeneous problems, the algebraic problem can be written as a coupled local-global system: find such that
The crucial decomposition is
Here is a local lifting complement. In the block picture below, the lower-left block is zero because Trefftz trial functions satisfy the local residual equations. The lower-right block is local and determines the lifting or particular part; the upper blocks are the globally coupled DG equations.