Mesh-coordinate compliance derivative versus central finite differences.
The same clamped plate is first given a gentle smooth bend, then differentiated with
respect to the z coordinate at three interior mesh nodes. Geometry is made live with
hm.geometry(domain, node_disp=shape); the CSDL total therefore includes both the
shape derivative of the shell solve and that of compliance. Central finite
differences use a fixed step study, reporting the best agreement rather than hiding
the truncation/cancellation trade-off.
The node perturbations are deliberately small. Reissner–Mindlin penalty systems become ill-conditioned for large local warps, and UFL omits the non-smooth derivative of the stabilization cell-diameter scale; neither effect is an adjoint accuracy test.
"""Mesh-coordinate compliance derivative versus central finite differences.
The same clamped plate is first given a gentle smooth bend, then differentiated with
respect to the ``z`` coordinate at three interior mesh nodes. Geometry is made live with
``hm.geometry(domain, node_disp=shape)``; the CSDL total therefore includes both the
shape derivative of the shell solve and that of compliance. Central finite
differences use a fixed step study, reporting the best agreement rather than hiding
the truncation/cancellation trade-off.
The node perturbations are deliberately small. Reissner--Mindlin penalty systems
become ill-conditioned for large local warps, and UFL omits the non-smooth derivative
of the stabilization cell-diameter scale; neither effect is an adjoint accuracy test.
"""
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 rect_plate # noqa: E402
from _harness import Case, main # noqa: E402
LENGTH, WIDTH = 4.0, 1.0
E, NU, THICKNESS, PRESSURE = 4.32e8, 0.0, 0.05, 1.0
STEPS = (1e-2, 1e-3, 1e-4, 1e-5)
def _compliance(mesh, shape, want_gradient=False):
rec = csdl.Recorder(inline=True)
rec.start()
domain = hm.ShellDomain(mesh, element="CG2CG1")
nd = csdl.Variable(value=shape.copy(), name="node_disp")
material = hm.isotropic(domain, E=E, nu=NU, thickness=THICKNESS, density=1.0)
state = hm.solve(domain, material, hm.pressure(domain, PRESSURE),
hm.clamp(domain, where=lambda x: np.isclose(x[0], 0.0)),
geometry=hm.geometry(domain, node_disp=nd))
output = hm.compliance(state)
value = float(np.asarray(output.value).ravel()[0])
grad = None
if want_gradient:
grad = np.asarray(csdl.experimental.PySimulator(rec).compute_totals([output], [nd])[output, nd])
rec.stop()
return value, grad
def solve_at(n):
"""Return best agreement for individual interior ``z`` coordinate derivatives."""
mesh = rect_plate(LENGTH, WIDTH, nx=4 * n, ny=n, cell="quad")
nn = mesh.geometry.x.shape[0]
# At a perfectly flat configuration these individual out-of-plane derivatives
# vanish by reflection symmetry. A gentle, smooth baseline bend makes the local
# coordinate sensitivities nonzero without entering the penalty-conditioning
# regime warned about in the shape-derivative documentation.
shape0 = np.zeros((nn, 3))
shape0[:, 2] = 0.10 * (mesh.geometry.x[:, 0] / LENGTH) ** 2
shape0[:, 0] = 0.005 * mesh.geometry.x[:, 0] / LENGTH
_, gradient = _compliance(mesh, shape0, want_gradient=True)
xyz = mesh.geometry.x
targets = ([LENGTH / 4, WIDTH / 2, 0.0], [LENGTH / 2, WIDTH / 2, 0.0],
[3 * LENGTH / 4, WIDTH / 2, 0.0])
nodes = [int(np.argmin(np.linalg.norm(xyz - p, axis=1))) for p in targets]
best = np.inf
print(" absolute step max relative error at nodes", nodes, "(z coordinate)")
for step in STEPS:
errors = []
for k in nodes:
plus, minus = shape0.copy(), shape0.copy()
plus[k, 2] += step
minus[k, 2] -= step
cp, _ = _compliance(mesh, plus)
cm, _ = _compliance(mesh, minus)
fd = (cp - cm) / (2.0 * step)
errors.append(abs(gradient.reshape(nn, 3)[k, 2] - fd) / max(abs(fd), 1e-14))
worst = max(errors)
best = min(best, worst)
print(f" {step:13.1e} {worst:.3e}")
print(f" best relative agreement = {best:.3e}")
return 1.0 - best
CASE = Case(
name="Shape derivative: nodal coordinates versus central finite differences",
quantity="1 - best relative CSDL/FD shape-gradient error",
reference=1.0,
tolerance=5e-3,
citation="UFL CoordinateDerivative; central differences evaluated by this script",
levels=(2, 3),
quick_level=2,
solve=solve_at,
monotone=False,
notes="small local warps avoid penalty-conditioning artifacts; tolerance is FD-limited",
)
if __name__ == "__main__":
main(CASE)