Thickness optimization
Minimise the compliance of a cantilever plate at fixed mass. Mirrors the optimization
block of femo_alpha ex_simple_shell.py: nodal (CG1) thickness design variables, an
equality mass constraint at the baseline mass, PySLSQP.
What this example demonstrates is the plumbing – CSDL total derivatives through the shell solve driving a real optimiser. It is deliberately not a demonstration of a well-posed design problem, and the result is worth understanding before you copy the setup.
A free per-node thickness field is the classical ill-posed thickness design problem.
Bending stiffness goes as int t**3 while mass goes as int t, so at fixed mass
an oscillating thickness genuinely raises the stiffness: the optimum is a
mesh-dependent checkerboard that really does beat any smooth profile in the discrete
objective. The optimiser is right; the design space is the problem. Do not expect the
root-thick / tip-thin wedge that beam theory gives – see
examples/verification/ex_optimal_thickness_taper.py, which measures the gap (at
equal mass on its fixture: uniform 0.056, analytic wedge 0.017, free-nodal 0.0037) and
recovers the analytic answer by restricting the design space instead.
The usual remedies are a filter, a perimeter penalty, or a restricted design space.
conda activate hermit
python examples/advanced_examples/ex_thickness_opt.py
"""Thickness optimization
Minimise the compliance of a cantilever plate at fixed mass. Mirrors the optimization
block of femo_alpha ``ex_simple_shell.py``: nodal (CG1) thickness design variables, an
equality mass constraint at the baseline mass, PySLSQP.
What this example demonstrates is the *plumbing* -- CSDL total derivatives through the
shell solve driving a real optimiser. It is deliberately **not** a demonstration of a
well-posed design problem, and the result is worth understanding before you copy the
setup.
A free per-node thickness field is the classical ill-posed thickness design problem.
Bending stiffness goes as ``int t**3`` while mass goes as ``int t``, so at fixed mass
an *oscillating* thickness genuinely raises the stiffness: the optimum is a
mesh-dependent checkerboard that really does beat any smooth profile in the discrete
objective. The optimiser is right; the design space is the problem. Do not expect the
root-thick / tip-thin wedge that beam theory gives -- see
``examples/verification/ex_optimal_thickness_taper.py``, which measures the gap (at
equal mass on its fixture: uniform 0.056, analytic wedge 0.017, free-nodal 0.0037) and
recovers the analytic answer by restricting the design space instead.
The usual remedies are a filter, a perimeter penalty, or a restricted design space.
conda activate hermit
python examples/advanced_examples/ex_thickness_opt.py
"""
import pathlib
import numpy as np
import csdl_alpha as csdl
import hermit as hm
MESH = pathlib.Path(__file__).parents[2] / "tests" / "meshes" / "plate_2x10_quad_4x20.xdmf"
E_VAL, NU_VAL, H_VAL, RHO_VAL = 4.32e8, 0.0, 0.2, 1.0
PRESSURE_Z = 2.0
WIDTH, LENGTH = 2.0, 10.0
def clamped_at_x0(x):
return np.less(x[0], 1e-12)
def main():
mesh = hm.read_mesh(MESH)
rec = csdl.Recorder(inline=True)
rec.start()
domain = hm.ShellDomain(mesh, element="CG2CG1")
thickness = csdl.Variable(value=H_VAL * np.ones(domain.n_nodes), name="thickness")
material = hm.isotropic(domain, E=E_VAL, nu=NU_VAL,
thickness=hm.from_nodal(domain, thickness), density=RHO_VAL)
state = hm.solve(domain, material, hm.pressure(domain, PRESSURE_Z),
hm.clamp(domain, where=clamped_at_x0))
compliance, mass = hm.compliance(state), hm.mass(state)
print(f"baseline: compliance={float(np.ravel(compliance.value)[0]):.6e} "
f"mass={float(np.ravel(mass.value)[0]):.6e}")
mass_0 = RHO_VAL * H_VAL * WIDTH * LENGTH
thickness.set_as_design_variable(lower=1e-2, upper=10.0)
mass.set_as_constraint(lower=mass_0, upper=mass_0)
compliance.set_as_objective()
from modopt import CSDLAlphaProblem, PySLSQP
sim = csdl.experimental.PySimulator(rec)
prob = CSDLAlphaProblem(problem_name="hermit_plate_thickness", simulator=sim)
optimizer = PySLSQP(prob, solver_options={"maxiter": 200, "acc": 1e-9})
optimizer.solve()
optimizer.print_results()
rec.stop()
print("optimized:")
print(f" compliance : {float(np.ravel(compliance.value)[0]):.6e}")
print(f" mass : {float(np.ravel(mass.value)[0]):.6e} (target {mass_0})")
print(f" thickness : min {thickness.value.min():.4f} max {thickness.value.max():.4f}")
if __name__ == "__main__":
main()