Tangential displacement normal normal stress continuous (TDNNS) for linear elasticity#
In this section we present and use the TDNNS formulation for linear elasticity. The TDNNS method fits in between the primal and dual form of the Hellinger-Reissner formulation. The degrees of freedom for the TDNNS method are:
tangential component of displacement vector
normal-normal component of stress tensor
Three dofs on one edge fix rigid body modes for triangle, and six dofs on one face of a tetrahedra fix rigid body modes


Quadrilateral/prism elements: The dofs decouple into stretching components and bending components




The divergence of \(nn\)-continuous piece-wise smooth functions#
Let \(\sigma\) be a \(nn\)-continuous finite element function, and \(f = \mathrm{div} \sigma\) be the distributional divergence, i.e.
We observe that the distribution \(f\) consists of element-terms and facet-terms
Thus, the expression
makes sense for functions where the tangential trace on an edge (respectively face in 3D) is well defined. This is (we cheat an \(\epsilon\) on regularity) the space \(H(\operatorname{curl})\). The canonical finite elements are the (tangential continuous) Nédélec elements (edge elements).
We take another distributional divergence of \(f = \mathrm{div} \sigma\)
Setting \(g = \mathrm{div} f\) we observe that \(g\) is a distribution of the form
with element, edge, and vertex terms
This functional \(g\) is well defined on the space \(H^{1+\epsilon}(\Omega)\), i.e. nearly on \(H^1(\Omega)\), which motivates the space
Normal-normal continuous symmetric matrix finite elements are (a slightly non-conforming) discretization of \(H(\mathrm{div} \mathrm{div})\).
TDNNS Variational formulation#
The primal linear elasticity problem: Find \(u\in H^1_{\Gamma_D}(\Omega,\mathbb{R}^2)\) such that for all \(v\in H^1_{\Gamma_D}(\Omega,\mathbb{R}^2)\)
can be rewritten by using the stress \(\sigma=\mathbb{C}\varepsilon(u)\) as additional unknown. Therefore the stress-strain relation is inverted
where \(d\) is is the spatial dimension of the domain \(\Omega\subset\mathbb{R}^d\), \(\mathrm{tr}(A)\) denotes the trace and \(\mathrm{dev}(A)=A-\frac{1}{d}\mathrm{tr}(A)I_{d\times d}\) the deviatoric part of the matrix \(A\). With this we obtain the formulation: Find \((\sigma,u)\) such that for all \((\tau,v)\)
Depending on the regularity of \(\sigma\) and \(v\) the duality pairing \(\langle\cdot,\cdot\rangle\) has to be interpreted differently.
Let \(\Sigma_h\) be an \(nn\)-continuous symmetric matrix valued finite element space, and \(V_h\) a tangential continuous vector finite element space.
The TDNNS method for linear elasticity is: Find: \(\sigma_h \in \Sigma_h\) and \(u_h \in V_h\) such that \(\sigma_{h,nn}=g_n\) on \(\Gamma_N\) and
The \(b(\cdot,\cdot)\) bilinear form can be interpreted as distributional divergence:
References:
Error estimates#
First results were in discrete norms [Pechstein, Schöberl. Tangential-Displacement and Normal-Normal-Stress Continuous Mixed Finite Elements for Elasticity. Math. Models Methods Appl. Sci. , (2011).]
Recent results [Pechstein, Schöberl. An analysis of the TDNNS method using natural norms. Numerische Mathematik , (2018).] give error estimates in natural norms:
from ngsolve import *
from netgen.occ import *
from ngsolve.webgui import Draw
rect = Rectangle(10, 1).Face()
rect.edges.Min(X).name = "left"
rect.edges.Max(X).name = "right"
rect.edges.Min(Y).name = "bottom"
rect.edges.Max(Y).name = "top"
mesh = Mesh(OCCGeometry(rect, dim=2).GenerateMesh(maxh=0.5))
order = 3
V = HDivDiv(mesh, order=order, dirichlet="bottom|right|top")
Q = HCurl(mesh, order=order, dirichlet="left")
X = V * Q
(sigma, u), (tau, v) = X.TnT()
n = specialcf.normal(2)
def tang(u):
return u - (u * n) * n
a = BilinearForm(X, symmetric=True)
a += (InnerProduct(sigma, tau) + div(sigma) * v + div(tau) * u) * dx
a += (-(sigma * n) * tang(v) - (tau * n) * tang(u)) * dx(element_boundary=True)
a.Assemble()
f = LinearForm(X)
f += -2e-4 * v[1] * dx
f.Assemble()
gf_solution = GridFunction(X)
gf_solution.vec.data = a.mat.Inverse(X.FreeDofs(), inverse="") * f.vec
gf_sigma, gf_u = gf_solution.components
Draw(gf_u, mesh, name="displacement", deformation=True)
Draw(gf_sigma[0][0], mesh, name="s11");
Hybridized TDNNS method for linear elasticity#
We can apply hybridization techniques NGSolve docu - Static condensation to retain a positive definite problem. Therefore, the continuity condition on the stress space is broken and gets reinforced weakly by means of a Lagrange multiplier. Note, that essential and natural boundary conditions again swap. The hybridized TDNNS method for linear elasticity is: Find \(\sigma_h \in \Sigma_h\), \(u_h \in V_h\), and \(\hat{u}_h\in \Lambda_h\) such that:
where \(\Lambda_h\) is a finite element space living only on the skeleton (edges in 2D) in normal direction such that the normal-normal continuity is forced
We can write down the system matrix
From the first equation \(\underline{\sigma}\) can be explicitly expressed in terms of \(\underline{u}\) and \(\underline{\alpha}\). This identity is inserted into the second equation leading to the Schur-complement matrix \(-BA^{-1}B^\top\). Note that \(A\) is a block diagonal matrix and thus cheap to invert
from ngsolve import *
from netgen.occ import *
from ngsolve.webgui import Draw
rect = Rectangle(10, 1).Face()
rect.edges.Min(X).name = "left"
rect.edges.Max(X).name = "right"
rect.edges.Min(Y).name = "bottom"
rect.edges.Max(Y).name = "top"
mesh = Mesh(OCCGeometry(rect, dim=2).GenerateMesh(maxh=0.5))
order = 3
V = Discontinuous(HDivDiv(mesh, order=order))
Q = HCurl(mesh, order=order, dirichlet="left")
Qh = NormalFacetFESpace(mesh, order=order, dirichlet="left")
X = V * Q * Qh
(sigma, u, uh), (tau, v, vh) = X.TnT()
n = specialcf.normal(2)
def tang(u):
return u - (u * n) * n
a = BilinearForm(X, symmetric=True, condense=True)
a += (InnerProduct(sigma, tau) + div(sigma) * v + div(tau) * u) * dx
a += (-(sigma * n) * tang(v) - (tau * n) * tang(u)) * dx(element_boundary=True)
a += ((uh * n) * tau * n * n + (vh * n) * sigma * n * n) * dx(element_boundary=True)
a.Assemble()
f = LinearForm(X)
f += -2e-4 * v[1] * dx
f.Assemble()
gf_solution = GridFunction(X)
res = f.vec.CreateVector()
res.data = f.vec - a.mat * gf_solution.vec
if a.condense:
res.data += a.harmonic_extension_trans * res
gf_solution.vec.data += (
a.mat.Inverse(X.FreeDofs(coupling=True), inverse="sparsecholesky") * res
)
gf_solution.vec.data += a.harmonic_extension * gf_solution.vec
gf_solution.vec.data += a.inner_solve * res
else:
gf_solution.vec.data += a.mat.Inverse(X.FreeDofs(coupling=False), inverse="") * res
gf_sigma, gf_u, gf_uh = gf_solution.components
Draw(gf_u, mesh, name="displacement", deformation=True)
Draw(gf_sigma[0], mesh, name="s11");