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 V0V1VL. For given uVL, we want to compute a decomposition

u=u0+l=1Lwl

with u0V0 and wlVl such that

u0A2+hl2wlL22uH12

We know that there exist such decompositions, but do not yet have a cheap algorithm for computing one. This we define next.

Let Nl=dimVl, and φl,i be the hat-basis-functions of Vl. Nodal interpolation of u onto level l is not stable, but local averaging with the hat-basis is a good choice. We define

ul:=Πlu:=i=1Nl(u,φl,i)L2(1,φl,i)L2φl,i

If u is a constant function, than Πlu reproduces the same constant function.

For uVL we define

u0:=Π0uwl=ΠluΠl1u1lL1wL=uΠL1u,

which is a decomposition u=u0+wl.

Theorem There holds

u0H12+l=1Lhl2wlL22L2uH12

Proof: We use that there exists a stable decomposition u=u~0+k=1Lw~k. The computable choice is

wl=ΠluΠl1u=(ΠlΠl1)u~0+k=1L(ΠlΠl1)w~k

For kl there holds

(ΠlΠl1)w~kL2hlw~kH1hlhkw~kL2,

for kl there holds

(ΠlΠl1)w~kL2w~kL2hlhkw~kL2.

Using the triangle inequality, the proven estimates and Cauchy-Schwarz:

l=1Lhl2wlL22l=1Lhl2(k=1L(ΠlΠl1)w~kL2)2l=1Lhl2(k=1Lhlhkw~kL2)2=l=1L(k=1L1hkw~kL2)2=L(k=1L11hkw~kL2)2Lk=1L12k=1L1hk2w~kL22=L2k=1L1hk2w~kL22L2uH12

86.2. Algorithm#

The quasi-interpolant of uVL to ulVl,

Πlu:=i=1Nl(u,φl,i)L2(1,φl,i)L2φl,i

can be implemented as follows. Let Ml be the mass-matrix on level l, and P~l=PLPL1Pl+1 be the cumulated prolongation operators from level l up to the final level L. Then

  • (u,φl,i)L2=(P~lTMLu)i

  • The values (1,φl,i)L2 are the entries of the vector Ml1, where 1=(1,,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

El0:H1(Ω)H1(Ω)

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:

El=El1Πl1u+El0(uΠl1u)
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