9.2. Linear Kirchhoff-Love and Reissner-Mindlin shells#
In this section we discretize the linear Kirchhoff-Love shell equations with the Hellan-Herrmann-Johnson (HHJ) method and the linear Reissner-Mindlin shell equation by adding shearing degrees of freedom on top of the HHJ method in terms of the tangential-displacement-normal-normal-stress (TDNNS) method. Further, we will observe and discuss the problem of membrane locking of shells and how to cure it by using an interpolation operator into the so-called Regge finite elements following [Neunteufel, Schöberl. Avoiding membrane locking with Regge interpolation. Comput. Methods Appl. Mech. Eng. 373, (2021)].
9.2.1. HHJ for Kirchhoff-Love shells#
We show how linear Koiter (Kirchhoff-Love) shells can be handled with the Hellan-Herrmann-Johnson method and discuss the effect of membrane locking. The Kirchhoff-Love shell energy consists of the membrane and bending term and reads
where
yielding the Lagrangian [Neunteufel, Schöberl. The Hellan-Herrmann-Johnson and TDNNS method for linear and nonlinear shells. arXiv, (2023)].
where
reduces for plates to the HHJ pairing (noting that
We consider a hyperboloid with free ends as a benchmark example. The shell described by the equation
is loaded by a periodic volume force
We consider as reference value the radial deflection at point
from ngsolve import *
import netgen.meshing as meshing
from ngsolve.webgui import Draw
ref_value = {0.1: -0.1856305, 0.01: -0.1502913, 0.001: -0.1498749}
thickness = 0.001
radius = 1
order = 2
E = 2.85e4
nu = 0.3
mu = E / (2 * (1 + nu))
lam = E * nu / (1 - nu**2)
free = "right"
symmetry = "left|top|bottom"
e_n = -specialcf.normal(3)
force = 1e4 * thickness * cos(2 * IfPos(y, atan(z / y), pi / 2)) * e_n * thickness**2
mapping = lambda x, y, z: (
x,
sqrt(1 + x**2) * cos(pi / 2 * y),
sqrt(1 + x**2) * sin(pi / 2 * y),
)
geom = meshing.SurfaceGeometry(mapping)
mesh = Mesh(geom.GenerateMesh(quads=False, nx=5, ny=5)).Curve(order)
Draw(mesh)
# point of interest
P = (0, 0, radius)
For implementation we use a hybridization technique to eliminate
First we try out this method without further adaptions. We observe that for small thicknesses
To circumvent membrane locking we interpolate the membrane strains into a finite element space of lower polynomial degree. This will give the finite element solution more freedom to fulfill the “weakened” constraints without being zero. However, projecting too much allows for so-called spurious zero-energy modes making the method unstable in the membrane dominated regime (where no membrane locking occurs as membrane modes are dominant). A good balance is to interpolate into the tangential-tangential continuous Regge finite element space of one polynomial order less than the displacement field. We therefore replace the membrane energy
# try out False and True
interpolateMembrane = False
def MaterialStress(mat):
return E / (1 - nu**2) * ((1 - nu) * mat + nu * Trace(mat) * Id(3))
def MaterialStressInv(mat):
return (1 + nu) / E * (mat - nu / (nu + 1) * Trace(mat) * Id(3))
fesU = VectorH1(
mesh,
order=order,
dirichletx_bbnd="left",
dirichlety_bbnd="top",
dirichletz_bbnd="bottom",
)
fesS = HDivDivSurface(mesh, order=order - 1, discontinuous=True)
fesH = NormalFacetSurface(mesh, order=order - 1, dirichlet_bbnd="left|top|bottom")
fes = fesU * fesS * fesH
(u, sigma, uh), (du, dsigma, duh) = fes.TnT()
# Need to take traces as we are on the surface
sigma, dsigma, uh, duh = sigma.Trace(), dsigma.Trace(), uh.Trace(), duh.Trace()
fesRegge = HCurlCurl(mesh, order=order - 1)
# normal, tangential, and co-normal vectors
nv = specialcf.normal(3)
tv = specialcf.tangential(3)
cnv = Cross(nv, tv)
# Projection to tangent space and surface derivatives
Ptau = Id(3) - OuterProduct(nv, nv)
gradu = Grad(u).Trace()
graddu = Grad(du).Trace()
gf_solution = GridFunction(fes)
# sum_{i=1}^3 hesse(u_i) nv_i
Hn = (u.Operator("hesseboundary").trans * nv).Reshape((3, 3))
dHn = (du.Operator("hesseboundary").trans * nv).Reshape((3, 3))
bfa = BilinearForm(fes, symmetric=True, condense=True)
# membrane part
if interpolateMembrane:
bfa += (
thickness
* InnerProduct(
MaterialStress(Interpolate(Sym(Ptau * gradu), fesRegge)),
Interpolate(Sym(Ptau * graddu), fesRegge),
)
) * ds
else:
bfa += (
thickness * InnerProduct(MaterialStress(Sym(Ptau * gradu)), Sym(Ptau * graddu))
) * ds
# bending part
bfa += (-12 / thickness**3 * InnerProduct(MaterialStressInv(sigma), dsigma)) * ds
bfa += (InnerProduct(dsigma, Hn) + InnerProduct(sigma, dHn)) * ds
bfa += (
sigma[cnv, cnv] * (duh - graddu[nv, :]) * cnv
+ dsigma[cnv, cnv] * (uh - gradu[nv, :]) * cnv
) * ds(element_boundary=True)
f = LinearForm(force * du * ds)
with TaskManager():
bfa.Assemble()
f.Assemble()
if bfa.condense:
f.vec.data += bfa.harmonic_extension_trans * f.vec
inv = bfa.mat.Inverse(fes.FreeDofs(True), inverse="sparsecholesky")
gf_solution.vec.data = inv * f.vec
gf_solution.vec.data += bfa.harmonic_extension * gf_solution.vec
gf_solution.vec.data += bfa.inner_solve * f.vec
else:
inv = bfa.mat.Inverse(fes.FreeDofs(), inverse="")
gf_solution.vec.data = inv * f.vec
gf_u, gf_sigma, _ = gf_solution.components
print(f"value = {gf_u(mesh(*P, BND))[2]}, reference = {ref_value[thickness]}")
Draw(gf_u, mesh, deformation=True);
value = -0.0024043926800899175, reference = -0.1498749
9.2.2. TDNNS method for Reissner-Mindlin shells#
Next, we add shearing degrees of freedom to allow for shear deformations. The additional shearing field
The primal energy including shearing energy would read
where
We again add the bending moment tensor as unknown giving us the following Lagrangian [Neunteufel, Schöberl. The Hellan-Herrmann-Johnson and TDNNS method for linear and nonlinear shells. arXiv, (2023).]
To avoid possible membrane locking we can again add the interpolant into Regge finite elements in the membrane energy term. Due to the hierarchical approach of adding the shearing field
The reference value of the hyperboloid benchmark now change to
from ngsolve import *
import netgen.meshing as meshing
from ngsolve.webgui import Draw
ref_value = {0.1: -0.18954566, 0.01: -0.15046617, 0.001: -0.1498902}
thickness = 0.001
radius = 1
order = 2
E = 2.85e4
nu = 0.3
mu = E / (2 * (1 + nu))
lam = E * nu / (1 - nu**2)
G = E / (2 * (1 + nu))
kappa = 5 / 6
free = "right"
symmetry = "left|top|bottom"
e_n = -specialcf.normal(3)
force = 1e4 * thickness * cos(2 * IfPos(y, atan(z / y), pi / 2)) * e_n * thickness**2
mapping = lambda x, y, z: (
x,
sqrt(1 + x**2) * cos(pi / 2 * y),
sqrt(1 + x**2) * sin(pi / 2 * y),
)
geom = meshing.SurfaceGeometry(mapping)
mesh = Mesh(geom.GenerateMesh(quads=False, nx=5, ny=5)).Curve(order)
# point of interest
P = (0, 0, radius)
def MaterialStress(mat):
return E / (1 - nu**2) * ((1 - nu) * mat + nu * Trace(mat) * Id(3))
def MaterialStressInv(mat):
return (1 + nu) / E * (mat - nu / (nu + 1) * Trace(mat) * Id(3))
fesU = VectorH1(
mesh,
order=order,
dirichletx_bbnd="left",
dirichlety_bbnd="top",
dirichletz_bbnd="bottom",
)
fesB = HCurl(mesh, order=order - 1)
fesS = HDivDivSurface(mesh, order=order - 1, discontinuous=True)
fesH = NormalFacetSurface(mesh, order=order - 1, dirichlet_bbnd="left|top|bottom")
fes = fesU * fesB * fesS * fesH
fesRegge = HCurlCurl(mesh, order=order - 1)
(u, gamma, sigma, uh), (du, dgamma, dsigma, duh) = fes.TnT()
# Need to take traces as we are on the surface
sigma, dsigma, uh, duh, gamma, dgamma = (
sigma.Trace(),
dsigma.Trace(),
uh.Trace(),
duh.Trace(),
gamma.Trace(),
dgamma.Trace(),
)
nv = specialcf.normal(3)
tv = specialcf.tangential(3)
cnv = Cross(nv, tv)
Ptau = Id(3) - OuterProduct(nv, nv)
gradu = Grad(u).Trace()
graddu = Grad(du).Trace()
gf_solution = GridFunction(fes)
Hn = (u.Operator("hesseboundary").trans * nv).Reshape((3, 3))
dHn = (du.Operator("hesseboundary").trans * nv).Reshape((3, 3))
bfa = BilinearForm(fes, symmetric=True, condense=True)
# membrane part
bfa += (
thickness
* InnerProduct(
MaterialStress(Interpolate(Sym(Ptau * gradu), fesRegge)),
Interpolate(Sym(Ptau * graddu), fesRegge),
)
) * ds
# bending part
bfa += (-12 / (thickness**3) * InnerProduct(MaterialStressInv(sigma), dsigma)) * ds
bfa += (
InnerProduct(dsigma, Hn - Grad(gamma)) + InnerProduct(sigma, dHn - Grad(dgamma))
) * ds
bfa += (
sigma[cnv, cnv] * (duh - graddu[nv, :] + dgamma) * cnv
+ dsigma[cnv, cnv] * (uh - gradu[nv, :] + gamma) * cnv
) * ds(element_boundary=True)
# shear part
bfa += (thickness * kappa * G * InnerProduct(gamma, dgamma)) * ds
f = LinearForm(force * du * ds)
with TaskManager():
bfa.Assemble()
f.Assemble()
if bfa.condense:
f.vec.data += bfa.harmonic_extension_trans * f.vec
inv = bfa.mat.Inverse(fes.FreeDofs(True), inverse="sparsecholesky")
gf_solution.vec.data = inv * f.vec
gf_solution.vec.data += bfa.harmonic_extension * gf_solution.vec
gf_solution.vec.data += bfa.inner_solve * f.vec
else:
inv = bfa.mat.Inverse(fes.FreeDofs(), inverse="")
gf_solution.vec.data = inv * f.vec
gf_u, gf_beta, gf_sigma, _ = gf_solution.components
print(f"value = {gf_u(mesh(*P, BND))[2]}, reference = {ref_value[thickness]}")
Draw(gf_u, mesh, deformation=True);
value = -0.14998434211853243, reference = -0.1498902