Cook’s membrane
A tapered panel with corners (0,0), (48,44), (48,60), and (0,44). E = 1.0, nu = 1/3, thickness = 1.0. The panel is clamped on the left edge (x=0) and loaded by a total shear force P=1 distributed along the right edge (x=48). The reference vertical displacement at the midpoint of the right edge (48,52) is approximately 23.9.
This is a membrane-dominated in-plane bending problem. For a shell element, it specifically exercises the in-plane response (membrane behavior) and the drilling stabilization. The drilling rotation (rot_z) and out-of-plane DOFs do not require manual constraining; the element formulation natively provides sufficient drilling stabilization for the solve to succeed.
conda activate hermit
python examples/verification/ex_cooks_membrane.py
python examples/verification/ex_cooks_membrane.py --quick
"""Cook's membrane
A tapered panel with corners (0,0), (48,44), (48,60), and (0,44).
E = 1.0, nu = 1/3, thickness = 1.0.
The panel is clamped on the left edge (x=0) and loaded by a total shear force P=1
distributed along the right edge (x=48).
The reference vertical displacement at the midpoint of the right edge (48,52) is approximately 23.9.
This is a membrane-dominated in-plane bending problem. For a shell element, it specifically
exercises the in-plane response (membrane behavior) and the drilling stabilization.
The drilling rotation (rot_z) and out-of-plane DOFs do not require manual constraining;
the element formulation natively provides sufficient drilling stabilization for the solve to succeed.
conda activate hermit
python examples/verification/ex_cooks_membrane.py
python examples/verification/ex_cooks_membrane.py --quick
"""
import pathlib
import sys
import numpy as np
import csdl_alpha as csdl
import hermit as hm
sys.path.insert(0, str(pathlib.Path(__file__).parent))
from _geometry import structured_surface # noqa: E402
from _harness import Case, main, node_nearest # noqa: E402
E, NU, THICKNESS = 1.0, 1.0 / 3.0, 1.0
LOAD = 1.0
REFERENCE = 23.9
def cook_map(u, v):
"""Bilinear map of the unit square to the Cook membrane geometry."""
x = 48.0 * u
y_bot = 44.0 * u
y_top = 44.0 + 16.0 * u
y = y_bot * (1 - v) + y_top * v
return x, y, np.zeros_like(x)
def solve_at(n):
"""Vertical displacement at the top right corner on an ``n x n`` quad mesh."""
mesh = structured_surface(cook_map, n, n, cell="quad")
rec = csdl.Recorder(inline=True)
rec.start()
domain = hm.ShellDomain(mesh, element="CG2CG1")
material = hm.isotropic(domain, E=E, nu=NU, thickness=THICKNESS, density=1.0)
# Clamped on the left edge
bcs = hm.clamp(domain, where=lambda x: np.isclose(x[0], 0.0))
# Apply statically equivalent total shear force P=1.0 along the right edge (x=48).
# We use equal splitting over the edge vertices (domain.node_coords only contains vertices).
# This is a statically equivalent distribution, rather than the exactly consistent CG2
# weights (1/6, 4/6, 1/6) which would require locating the mid-edge DOFs explicitly.
xyz = domain.node_coords
right_edge = np.flatnonzero(np.isclose(xyz[:, 0], 48.0))
load = None
for k in right_edge:
p = hm.point_load(domain, at=xyz[k], force=[0.0, LOAD / len(right_edge), 0.0])
load = p if load is None else load + p
state = hm.solve(domain, material, load, bcs)
u = hm.nodal_displacement(state).value.reshape(-1, 3)
rec.stop()
# Tip displacement at the midpoint of the right edge
k, dist = node_nearest(domain, [48.0, 52.0, 0.0])
if dist > 1e-9:
print(f" (note: sample node is {dist:.2e} from the nominal point at n={n})")
return u[k, 1]
CASE = Case(
name="Cook's membrane",
quantity="vertical displacement at mid-right edge",
reference=REFERENCE,
tolerance=0.02,
citation="Cook, 'Some elements for analysis of plane solid structures', "
"Int. J. Numer. Methods Eng., 1974",
levels=(4, 8, 16, 24, 32),
quick_level=32,
solve=solve_at,
notes="converges smoothly to ~23.95 (error ~0.2%).",
monotone=False,
)
if __name__ == "__main__":
main(CASE)