86. Multi-level Extension#
The goal is to extend boundary data (e.g. Dirichlet values) onto the domain, such that the resulting energy norm is optimal (up to a constant factor). We do so by a multi-level decomposition of the boundary data.
86.1. Efficiently computable multi-level decomposition#
We are given a nested sequence of spaces \(V_0 \subset V_1 \subset \ldots V_L\). For given \(u \in V_L\), we want to compute a decomposition
with \(u_0 \in V_0\) and \(w_l \in V_l\) such that
We know that there exist such decompositions, but do not yet have a cheap algorithm for computing one. This we define next.
Let \(N_l = \operatorname{dim}{V_l}\), and \(\varphi_{l,i}\) be the hat-basis-functions of \(V_l\). Nodal interpolation of \(u\) onto level \(l\) is not stable, but local averaging with the hat-basis is a good choice. We define
If \(u\) is a constant function, than \(\Pi_l u\) reproduces the same constant function.
For \(u \in V_L\) we define
which is a decomposition \(u = u_0 + \sum w_l\).
Theorem There holds
\[ \| u_0 \|_{H^1}^2 + \sum_{l=1}^L h_l^{-2} \| w_l \|_{L_2}^2 \preceq L^2 \, \| u \|_{H^1}^2 \]
Proof: We use that there exists a stable decomposition \(u = \tilde u_0 + \sum_{k=1}^L \tilde w_k\). The computable choice is
For \(k \leq l\) there holds
for \(k \geq l\) there holds
Using the triangle inequality, the proven estimates and Cauchy-Schwarz:
86.2. Algorithm#
The quasi-interpolant of \(u \in V_L\) to \(u_l \in V_l\),
can be implemented as follows. Let \(M_l\) be the mass-matrix on level \(l\), and \(\tilde P_l = P_L P_{L-1} \cdots P_{l+1}\) be the cumulated prolongation operators from level \(l\) up to the final level \(L\). Then
\( (u, \varphi_{l,i})_{L_2} = (\tilde P_l^T M_L u)_i \)
The values \((1, \varphi_{l,i})_{L_2}\) are the entries of the vector \(M_l {\mathbb 1}\), where \({\mathbb 1} = (1,\ldots, 1)\).
86.3. Extending boundary data#
We use this coarse-level interpolants to extend Dirichlet data onto the domain. On every level we have a trivial extension
by just setting all interior nodal values to zero. Thus, the function decays very quickly from the boundary. If the mesh gets more and more refined, the decay gets steeper and steeper.
from ngsolve import *
from ngsolve.webgui import Draw
mesh = Mesh(unit_square.GenerateMesh(maxh=0.3))
fes = H1(mesh, order=1, dirichlet="left|bottom", autoupdate=True)
u,v = fes.TnT()
gfu = GridFunction(fes, autoupdate=True)
for i in range(4):
mesh.Refine()
a = BilinearForm(grad(u)*grad(v)*dx).Assemble()
copy mesh complete
bnd = mesh.Boundaries("left|bottom")
gfu.Set (1-x-y+0.3*sin(10*pi*x), definedon=bnd)
Draw (gfu, deformation=True)
print ("Norm(u) = ", InnerProduct((a.mat*gfu.vec).Evaluate(), gfu.vec))
Norm(u) = 38.983324464566756
The method is now to decompose the function into different levels, and use the trivial extension on every level to obtain a better extension:
class MLExtension:
def __init__ (self, fes, level, bndmass, dofs):
self.fes = fes
self.level = level
self.bndmass = bndmass
self.dofs = dofs
ones = bndmass.CreateRowVector()
ones[:] = 1
Mones = (bndmass*ones).Evaluate()
self.inv = DiagonalMatrix(Mones).Inverse(dofs)
if level > 0:
self.prol = fes.Prolongation().CreateMatrix(level)
self.rest = self.prol.CreateTranspose()
coarsebndmass = self.rest @ bndmass @ self.prol # multiply matrices
coarsedofs = BitArray(self.prol.width)
coarsedofs[:] = False
for i in range(len(coarsedofs)):
coarsedofs[i] = dofs[i]
self.coarseext = MLExtension(fes, level-1, coarsebndmass, coarsedofs)
def Extend (self, x):
Mx = (self.bndmass * x).Evaluate()
ext = self.ExtendRec(Mx)
x[~self.dofs] = ext
def ExtendRec (self, Mx):
sol = (self.inv * Mx).Evaluate()
if self.level == 0:
return sol
if self.level > 0:
self.fes.Prolongation().Restrict(self.level, Mx)
xc = self.coarseext.ExtendRec(Mx)
pxc = (self.fes.Prolongation().Operator(self.level) * xc).Evaluate()
pxc[self.dofs] = sol
return pxc
bndmass = BilinearForm(u*v*ds(bnd), check_unused=False).Assemble().mat
ext = MLExtension(fes, fes.mesh.levels-1, bndmass, fes.GetDofs(bnd))
ext.Extend(gfu.vec)
Draw (gfu, deformation=True)
print ("Norm(uext) = ", InnerProduct(a.mat*gfu.vec, gfu.vec))
Norm(uext) = 3.541248535042474