Pinched hemisphere with an 18-degree hole (quadrilaterals)
The MacNeal-Harder pinched hemisphere benchmark, with the same geometry, material,
loading, published radial-deflection reference (0.0924), and symmetry reduction
as ex_pinched_hemisphere.py. Unlike a cylinder, a sphere is doubly curved: its
structured quadrilaterals are warped. It measures the global response alongside the
triangle case after the constant-normal curvature fix for LSDOlab/Hermit#7.
"""Pinched hemisphere with an 18-degree hole (quadrilaterals)
The MacNeal-Harder pinched hemisphere benchmark, with the same geometry, material,
loading, published radial-deflection reference (**0.0924**), and symmetry reduction
as ``ex_pinched_hemisphere.py``. Unlike a cylinder, a sphere is doubly curved: its
structured quadrilaterals are warped. It measures the global response alongside the
triangle case after the constant-normal curvature fix for LSDOlab/Hermit#7.
"""
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 sphere_octant # noqa: E402
from _harness import Case, main, node_nearest # noqa: E402
RADIUS, THICKNESS, HOLE_ANGLE = 10.0, 0.04, 18.0
E, NU, LOAD = 6.825e7, 0.3, 2.0
REFERENCE = 0.0924
def solve_at(n):
"""Radial deflection under the load on an ``n x n`` warped-quad octant mesh."""
mesh = sphere_octant(RADIUS, HOLE_ANGLE, 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)
bcs = (
hm.symmetry(domain, where=lambda x: np.isclose(x[1], 0.0), normal=[0, 1, 0])
+ hm.symmetry(domain, where=lambda x: np.isclose(x[0], 0.0), normal=[1, 0, 0])
)
top_z = RADIUS * np.cos(np.radians(HOLE_ANGLE))
bcs = bcs + hm.pin(
domain,
where=lambda x: np.isclose(x[2], top_z) & np.isclose(x[1], 0.0),
dofs=("uz",),
)
load_in = hm.point_load(domain, at=[RADIUS, 0.0, 0.0], force=[-LOAD / 2, 0.0, 0.0])
load_out = hm.point_load(domain, at=[0.0, RADIUS, 0.0], force=[0.0, LOAD / 2, 0.0])
state = hm.solve(domain, material, load_in + load_out, bcs)
u = hm.nodal_displacement(state).value.reshape(-1, 3)
rec.stop()
k, dist = node_nearest(domain, [RADIUS, 0.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, 0]
CASE = Case(
name="Pinched hemisphere with an 18-degree hole (warped quadrilaterals)",
quantity="radial deflection under the load",
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=(4, 8, 16, 24, 32, 48, 64, 96),
quick_level=32,
solve=solve_at,
monotone=False,
notes="same physical benchmark as the triangular case; spherical quad cells are warped. "
"The constant-normal formulation remains within the published tolerance; "
"direct energy and recovered-stress objectivity gates are in "
"tests/test_prescribed_bcs.py.",
)
if __name__ == "__main__":
main(CASE)