Scordelis-Lo roof
The first of the two “shell obstacle course” barrel-vault benchmarks. A cylindrical
roof of radius 25, length 50 and thickness 0.25 spans two rigid end diaphragms; the
straight edges are free. It carries its own weight, 90 per unit area, acting in
global -z. The quantity everyone quotes is the vertical deflection at the midspan
of a free edge: 0.3024.
The case is membrane-dominated with a significant bending boundary layer near the free edges, so it punishes an element that cannot represent in-plane stretching and bending together. It converges from below.
conda activate hermit
python examples/verification/ex_scordelis_lo.py # convergence sweep
python examples/verification/ex_scordelis_lo.py --quick # single cheap level
"""Scordelis-Lo roof
The first of the two "shell obstacle course" barrel-vault benchmarks. A cylindrical
roof of radius 25, length 50 and thickness 0.25 spans two rigid end diaphragms; the
straight edges are free. It carries its own weight, 90 per unit area, acting in
global ``-z``. The quantity everyone quotes is the vertical deflection at the midspan
of a free edge: **0.3024**.
The case is membrane-dominated with a significant bending boundary layer near the
free edges, so it punishes an element that cannot represent in-plane stretching and
bending together. It converges from below.
conda activate hermit
python examples/verification/ex_scordelis_lo.py # convergence sweep
python examples/verification/ex_scordelis_lo.py --quick # single cheap level
"""
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 cylinder_sector # noqa: E402
from _harness import Case, main, node_nearest # noqa: E402
RADIUS, LENGTH, HALF_ANGLE = 25.0, 50.0, 40.0
E, NU, THICKNESS = 4.32e8, 0.0, 0.25
SELF_WEIGHT = 90.0 # force per unit area, -z
REFERENCE = 0.3024
def solve_at(n, *, cell="triangle"):
"""Vertical deflection at the free-edge midspan on an ``n x n`` mesh."""
mesh = cylinder_sector(RADIUS, LENGTH, HALF_ANGLE, nx=n, nt=n, cell=cell)
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)
# Rigid diaphragms: the two curved ends are held in the plane of the diaphragm
# (v = w = 0) and left free along the axis.
diaphragms = hm.pin(
domain,
where=lambda x: np.isclose(x[0], 0.0) | np.isclose(x[0], LENGTH),
dofs=("uy", "uz"),
)
# Self weight is a global -z body force per unit area, not a pressure along the
# shell normal -- on a curved roof those are very different loads.
state = hm.solve(domain, material,
hm.traction(domain, [0.0, 0.0, -SELF_WEIGHT]), diaphragms)
u = hm.nodal_displacement(state).value.reshape(-1, 3)
rec.stop()
y_edge = RADIUS * np.sin(np.radians(HALF_ANGLE))
k, dist = node_nearest(domain, [LENGTH / 2, y_edge, RADIUS * np.cos(np.radians(HALF_ANGLE))])
if dist > 1e-9:
print(f" (note: sample node is {dist:.2e} from the nominal point at n={n})")
return abs(u[k, 2])
def make_case(cell="triangle"):
"""Return the Scordelis-Lo case for either supported surface cell type."""
if cell not in {"triangle", "quad"}:
raise ValueError(f"unsupported cell type {cell!r}")
label = ("Scordelis-Lo roof" if cell == "triangle"
else "Scordelis-Lo roof (quadrilaterals)")
return Case(
name=label,
quantity="vertical deflection at free-edge midspan",
reference=REFERENCE,
tolerance=0.02,
citation="MacNeal & Harder, 'A proposed standard set of problems to test finite "
"element accuracy', Finite Elements in Analysis and Design 1(1), 1985",
levels=(8, 16, 24, 32),
quick_level=24,
solve=lambda n: solve_at(n, cell=cell),
notes=("developable cylindrical surface: its structured quadrilaterals are "
"planar" if cell == "quad" else
"converges from below; 0.3086 is the alternative deep-shell-theory value"),
)
CASE = make_case()
if __name__ == "__main__":
main(CASE)