# Hyperbolic-paraboloid cantilever: restored warped-quad consistency This rectangular hyperbolic paraboloid is a deliberately controlled #7 fixture. Its mid-surface is ``z = c*x*(y-W/2)``: it has negative Gaussian curvature, and, unlike the sphere, the mixed-curvature (warp) rate ``c`` does not disappear when the structured mesh is refined. A clamp at ``x=0`` and a total global-z tip force give a simple, reproducible scalar response at the centre of the free edge. The physical target is a converged *triangular* solve on exactly this geometry. Triangles have planar cells and are objective for each candidate curvature formulation, so this is an independently computed reference rather than an assumed published number. At ``n = 32, 48, 64`` (where the mesh is ``2n x n``), the triangle answers are ``0.92081419, 0.92107296, 0.92117998``. The final two differ by only ``1.16e-4`` relatively, so the ``n=64`` result is used as the reference. The old warped-quad sequence was ``1.1166539, 1.1233583, 1.1257320`` at ``n=8,16,32``: it converged 22.2% above, and away from, that target. With Hermit's constant-normal curvature the sequence is ``0.9171184, 0.9199326, 0.9208417``; the quad solution now converges to the independently computed triangle result. ```python """Hyperbolic-paraboloid cantilever: restored warped-quad consistency This rectangular hyperbolic paraboloid is a deliberately controlled #7 fixture. Its mid-surface is ``z = c*x*(y-W/2)``: it has negative Gaussian curvature, and, unlike the sphere, the mixed-curvature (warp) rate ``c`` does not disappear when the structured mesh is refined. A clamp at ``x=0`` and a total global-z tip force give a simple, reproducible scalar response at the centre of the free edge. The physical target is a converged *triangular* solve on exactly this geometry. Triangles have planar cells and are objective for each candidate curvature formulation, so this is an independently computed reference rather than an assumed published number. At ``n = 32, 48, 64`` (where the mesh is ``2n x n``), the triangle answers are ``0.92081419, 0.92107296, 0.92117998``. The final two differ by only ``1.16e-4`` relatively, so the ``n=64`` result is used as the reference. The old warped-quad sequence was ``1.1166539, 1.1233583, 1.1257320`` at ``n=8,16,32``: it converged 22.2% above, and away from, that target. With Hermit's constant-normal curvature the sequence is ``0.9171184, 0.9199326, 0.9208417``; the quad solution now converges to the independently computed triangle result. """ import pathlib import sys import csdl_alpha as csdl import numpy as np import hermit as hm sys.path.insert(0, str(pathlib.Path(__file__).parent)) from _geometry import hyperbolic_paraboloid # noqa: E402 from _harness import Case, main, node_nearest # noqa: E402 LENGTH, WIDTH, WARP = 4.0, 2.0, 0.5 E, NU, THICKNESS, LOAD = 1.0e5, 0.25, 0.1, 1.0 def solve_response(n, *, cell, warp=WARP, thickness=THICKNESS): """Free-edge-centre z deflection on a ``2n x n`` hypar mesh.""" mesh = hyperbolic_paraboloid(LENGTH, WIDTH, warp, nx=2 * n, ny=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) xyz = np.asarray(domain.node_coords) tip = np.flatnonzero(np.isclose(xyz[:, 0], LENGTH)) loads = sum((hm.point_load(domain, at=xyz[k], force=[0.0, 0.0, LOAD / len(tip)]) for k in tip), start=hm.Loads(domain)) state = hm.solve(domain, material, loads, hm.clamp(domain, where=lambda x: np.isclose(x[0], 0.0))) u = hm.nodal_displacement(state).value.reshape(-1, 3) rec.stop() k, distance = node_nearest(domain, [LENGTH, WIDTH / 2, 0.0]) if distance > 1e-10: raise RuntimeError(f"free-edge sample moved by {distance:.3e}") return float(u[k, 2]) def solve_at(n): """Warped-quad response, compared with the separately converged triangle target.""" return solve_response(n, cell="quad") # Independent triangle result at n=64; see the module docstring for the convergence # evidence. This is deliberately a literal result of a separate solve, not fitted # from the quad sequence or a target adjusted to its answer. REFERENCE = 0.9211799767764 CASE = Case( name="Hyperbolic-paraboloid cantilever (warped quadrilaterals)", quantity="global-z deflection at the centre of the free edge", reference=REFERENCE, tolerance=0.02, citation="Computed in this file: triangle n=64 = 0.92117998; n=48 differs by " "1.16e-4 relative on the same geometry. Triangles are planar and " "objective under the compared curvature formulations", levels=(8, 16, 32), quick_level=8, solve=solve_at, monotone=False, notes="the former #7 result was 1.1257320 at n=32 (22.2% high); the " "constant-normal result is 0.9208417 (0.037% low).", ) if __name__ == "__main__": main(CASE) ```