End-to-end optimization: measured run record
Status: runs end to end (2026-09-01). The chain and the discrete/continuous split are documented in README.md § “End-to-end optimization”; the showcase is examples/fem_bracket_optimization.py on the L-bracket from scenes/bracket.py. This note keeps what the README does not carry: the measured run record, the box-bound rationale, and a known CalculiX deck fragility. Full build history in git/PR #19.
Details beyond the README’s summary:
- With the default backend the compliance term is the total squared displacement; with ccx it is classical compliance (
f . u, twice the strain energy). recompute_pointsre-projects the snapped boundary vertices onto the traced SDF’s zero set with total motion clamped to half a cell diagonal (the same clamp used at extraction); every--remesh-everysteps (default 6) topology is re-extracted. The final reported number is evaluated on a freshly extracted mesh so it does not depend on the last frozen topology.
Measured numbers
Default 30-step run (Apple Silicon CPU, jax-fem backend, 2026-09-01; reproducible — two independent runs produced identical objective traces):
| quantity | initial | final | change |
|---|---|---|---|
| objective | 17.4416 | 4.6924 | -73.1% |
| compliance | 16.1642 | 3.0729 | -81.0% |
| mass | 1.2774 | 1.6195 | +26.8% |
| web_thickness | 0.1600 | 0.1666 | +4.1% |
| rib_height | 0.8800 | 1.1064 | +25.7% |
| plate_thickness | 0.2000 | 0.3000 (upper bound) | +50.0% |
- FD vs adjoint at the initial design (central differences, eps 1e-5): rel 2.3e-5 (
web_thickness), 2.0e-3 (rib_height), 3.3e-7 (plate_thickness). - Mesh size at default resolution (30, 21, 16): 1714 hexes / 2735 nodes.
- Per-step timing: 4.7 s median warm eval (value + gradient), 11-42 s on re-extraction steps (retrace + XLA recompile). Some frozen-mesh phases run slower per eval (up to ~36 s) — the iterative solve needs more iterations when the thinned web conditions the system badly.
- Total wall time for the default 30-step run: ~9.5 min (360 s of objective/gradient evals plus FD check, meshing, and exports).
plate_thicknessruns to its upper bound and stays there (its raw gradient component remains ~-40); the projected gradient norm decays 272 -> 8.4.- The remesh jumps are visible and material at this resolution: the step-12 re-extraction raised the objective 6.02 -> 8.86 and the step-18 one lowered it 7.99 -> 5.53 — the web is only ~2 cells thick, so a one-cell-layer change in the loaded wall shifts the discrete compliance substantially.
Box bounds and projection
| parameter | lower | upper |
|---|---|---|
web_thickness |
0.12 | 0.26 |
rib_height |
0.35 | 1.15 |
plate_thickness |
0.14 | 0.30 |
The lower thickness bounds keep the thin walls above one grid cell, so re-extraction never drops them from the topology; the upper bounds keep the part inside the sampling lattice. Projection is a jnp.clip after optax.apply_updates. The recorded projected gradient norm zeroes components pushing against an active bound — that is the stationarity measure that should shrink, while the raw norm can stay large at a bound-constrained optimum.
Artifacts and guards
examples/output/collects the convergence history CSV, the convergence figure (PNG), and before/after VTU exports for ParaView. The VTUs are per-run artifacts and gitignored viaexamples/output/.gitignore; CSV and PNG are commit-worthy.examples/test_fem_bracket_optimization.pyis collected by a barepytestrun with no configuration, runs the smoke path in under a minute, and restores the x64 flag so the rest of the suite does not inherit it.--smokeruns 2 optimizer steps at resolution (14, 10, 8), asserts the objective descends, and asserts adjoint/FD agreement on the web-thickness component.
CalculiX deck fragility (found here, worked around, not fixed)
The --backend calculix path works at both the smoke and the default resolution (single eval + FD check verified: objective 1.3769 at (30, 21, 16), web-thickness FD rel 2.3e-3 through the ccx *SENSITIVITY adjoint), but only after the example flushes float-noise coordinates to exact zero before the solve. The underlying issue sits in cadjoint/fem/calculix.py’s deck writer (write_elastic_deck, {x:.17g} node fields): ccx’s free-format reader rejects any field wider than 20 characters, and a snapped node at ~1e-17 — float noise on a coordinate plane — prints as 23 (also at risk: 17-significant- digit negatives in (-0.1, -0.01), which print as 21). HEAD’s validated configuration passed by luck of its grid values. The proper fix (bounded-width formatting in the deck writer) belongs to the fem layer, which is being reworked concurrently; the example’s jnp.where(|points| < 1e-9, 0, points) shim is gradient-neutral because the affected coordinates are exactly zero for every nearby design.