33. Boundary Conditions#
Usually, Dirichlet boundary conditions (essential bc) are built into the space: find \(u \in H^1\) such that \(u = u_D\) on \(\Gamma_D\). Now, we want to pose the Dirichlet bc by an extra equation. For this, we start from the strong form
multiply the equation by a test-function in \(H^1\), integate by parts, and keep the boundary term:
We introduce a new variable \(\lambda\) for \(-\frac{\partial u}{\partial n}\) on the Dirichlet boundary. Natural bc are treated as usual, we assume we only have homogeneous Neumann bc for ease of notation:
The Dirichlet bc \(u = u_D\) is now enforced also by a test function \(\mu\) living on the Dirichlet boundary. Thus, the whole equation is now:
Find \(u \in V := H^1(\Omega)\) and \(\lambda \in Q := H^{-1/2}(\Gamma_D)\) such that
Why do we have the space \(H^{-1/2}(\Gamma_D)\) ? Functions from \(H^1(\Omega)\) have boundary values (the so called trace) exactly in the space \(H^{1/2}(\Gamma_D)\). We can pair these functions with elements from its dual space, called \(H^{-1/2}(\Gamma_D)\). To be precise, the integral is a convenient notation for the duality pairing:
The space \(H^{-1/2}\) is weaker (i.e. larger) than \(L_2\). Thus, we can use discontinuous \(L_2\) finite elements for its discretization.
from ngsolve import *
from ngsolve.webgui import Draw
# mesh = Mesh(unit_square.GenerateMesh(maxh=0.2))
mesh = Mesh(unit_cube.GenerateMesh(maxh=0.2))
order=3
V = H1(mesh, order=order, orderface=order+1)
# Q = SurfaceL2(mesh, order=order-2)
Q = H1(mesh, order=order, definedon=mesh.Boundaries(".*"))
X = V*Q
print ("V.ndof =", V.ndof, "Q.ndof =", Q.ndof)
V.ndof = 6542 Q.ndof = 1652
WARNING: kwarg 'orderface' is an undocumented flags option for class <class 'ngsolve.comp.H1'>, maybe there is a typo?
u,lam = X.TrialFunction()
v,mu = X.TestFunction()
a = BilinearForm(X)
a += grad(u)*grad(v)*dx + (u*mu+v*lam)*ds
f = LinearForm(X)
f += 10*x*v*dx + y*mu*ds
a.Assemble()
f.Assemble()
gfu = GridFunction(X)
gfu.vec.data = a.mat.Inverse(X.FreeDofs()) * f.vec
sol_u, sol_lam = gfu.components
Draw (sol_u, mesh, clipping={ "function" : True })
Draw (sol_lam, mesh);
Choosing the test-function \(v = 1\) in the first equation $\( \int_\Omega \nabla u \nabla 1 + \int_{\Gamma_D} \lambda 1 = \int_\Omega f 1 \)$ we observe that the total flux is exactly in balance with the total source
We compute the integral over the whole boundary as
Integrate(sol_lam, mesh, BND)
4.999999999999967
bndparts = Integrate(sol_lam, mesh, BND, region_wise=True)
print ("boundary parts:", bndparts)
print ("sum: ", sum(bndparts))
boundary parts: 0.31624
1.34946
1.81158
-0.145481
0.8347
0.833505
sum: 4.999999999999967
Can we use continuous finite elements for the normal derivative \(\lambda\) ?
33.1. Interface conditions#
Instead of enforcing Dirichlet boundary conditions, one can use the Lagrange multiplier also to enforce continuity across interfaces. Let the domain be \(\Omega_1 \cup \Omega_2\), and \(\gamma\) the common interface.
The interface conditions are continuity of the function, and continuity of the normal derivative:
Define \(V = H^1 (\Omega_1 \cup \Omega_2)\), and \(Q = H^{-1/2}(\gamma)\), and search for \(u \in V\) and \(\lambda \in Q\) such that:
The continuity of \(u\) is enforced by the second equation. The continuity of the normal derivative is obtained weakly from the first equation: Choosing a arbitrary test function \(v_1\), and setting \(v_2 = 0\) we obtain
Which implies \(\lambda = - \frac{\partial u}{\partial n_1}\). The same we get from \(v_2\), i.e. \(\lambda = -\frac{\partial u_2}{\partial n_2}\). The opposite sign comes from opposite outer normal vectors \(n_1 = -n_2\).
Imagine a rotating part of machine:
from netgen.occ import *
from ngsolve import *
from ngsolve.webgui import Draw
square = MoveTo(0,0).Rectangle(1,1).Face()
circo = Circle((0.5,0.5), 0.3).Face()
circ = Circle((0.5,0.5), 0.3).Face()
bar = MoveTo(0.3,0.45).Rectangle(0.4,0.1).Face()
square.edges.name="outer"
circ.edges.name="gammai"
circo.edges.name="gammao"
outer = square-circo
outer.faces.name = "outer"
circ.faces.name = "inner"
bar.faces.name = "bar"
inner = circ-bar
both = Compound([outer, inner, bar])
mesh = Mesh(OCCGeometry(both, dim=2).GenerateMesh(maxh=0.05)).Curve(3)
print (mesh.GetMaterials(), mesh.GetBoundaries())
Draw (mesh);
('outer', 'inner', 'bar') ('outer', 'outer', 'outer', 'outer', 'gammao', 'gammai', 'default', 'default', 'default', 'default')
We can virtually rotate the inner domain by setting a deformation function:
def MeshRotation(angle):
mesh.UnsetDeformation()
deform = GridFunction(VectorH1(mesh, order=3))
rotmat = CF( (cos(angle), -sin(angle), sin(angle), cos(angle))).Reshape( (2,2))
center = CF( (0.5, 0.5) )
pos = CF( (x,y) )
deform.Set( (rotmat-Id(2))*(pos-center), definedon=mesh.Materials("inner|bar"))
return deform
from time import sleep
scene = Draw (mesh)
for i in range(30):
mesh.SetDeformation(MeshRotation(i/30))
scene.Redraw()
sleep(0.03)
Forming the integrals of \(\lambda\) defined on one side, against an \(v\) from the other side is tricky. This is a very recent feature in NGSolve called ContactBoundary
:
mesh.SetDeformation(MeshRotation(0.8))
fesu = H1(mesh, order=3, dirichlet="outer")
feslam = H1(mesh, order=3, definedon=mesh.Boundaries("gammao"))
fes = fesu*feslam
(u,lam),(v,mu) = fes.TnT()
a = BilinearForm(grad(u)*grad(v)*dx)
contact = ContactBoundary(mesh.Boundaries("gammao"), mesh.Boundaries("gammai"), volume=False)
contact.AddIntegrator (mu*(u-u.Other()) + lam*(v-v.Other()))
contact.Update (bf=a, intorder=20)
a.Assemble()
mesh.SetDeformation(MeshRotation(0))
f = LinearForm(1e3*(x-0.5)*v*dx("bar")).Assemble()
mesh.SetDeformation(MeshRotation(0.8))
gfu = GridFunction(fes)
gfu.vec.data = a.mat.Inverse(freedofs=fes.FreeDofs())*f.vec
Draw (gfu.components[0]);
Draw (grad(gfu.components[0]), mesh);