Skip to content

Fix force_field x/y transpose; extract a ComplianceEvaluator seam (0.3.0) - #30

Merged
jihoonkim888 merged 5 commits into
mainfrom
claude/affectionate-noether-8094df
Jun 16, 2026
Merged

jihoonkim888 merged 5 commits into
mainfrom
claude/affectionate-noether-8094df

Conversation

@jihoonkim888

Copy link
Copy Markdown
Owner

Summary

Two pieces of 0.3.0 foundation work, with a correctness fix as the headline.

1. Fix a force_field x/y transpose (breaking, but narrow)

force_field components Fx and Fy were swapped: a user-supplied Fx acted along the y (nely) axis and Fy along x. The root cause is a build_edof rewrite — the same change that added custom force_field support and moved the default load from -y to -z — which silently swapped the H8 element's first two local axes.

Because the new default load is -z (unaffected by an x↔y swap), every shipped cantilever example and the golden master kept working, so the regression stayed hidden. It only affects force_field loads carrying distinct Fx vs Fy.

Evidence. On a beam long in x, Fx should be axial (stiff) while Fy/Fz bend it (much softer). Before the fix, Fy was the stiff one — i.e. Fy acted along x. The fix restores the correct local-node ordering in build_edof so the element's first axis aligns with the grid's x (nelx).

Impact. Breaking only for force_field loads with both x and y components. The default load, -z-only loads, supports, obstacle/geometry positions, and the golden-master result are unchanged (max abs difference ~4e-13). The README force_field cantilever recipe now behaves as documented. A one-time notice fires for affected loads; pin the previous release to reproduce the old behaviour.

2. Extract a ComplianceEvaluator seam (behaviour-preserving)

Pull the per-iteration FE physics (assemble K(xPhys), solve, compliance, sensitivities) out of the optimizer's CPU loop into a reusable evaluator, so a future MMA optimizer backend can call the same physics. top3d() output is byte-identical (array_equal, max diff 0.0).

Tests

Test What it locks
test_force_direction.py The public (x, y, z) force convention (Fx axial on an x-long beam) — mutation-verified red→green; plus the one-time notice
test_gradient_check.py Finite-difference check that the analytic sensitivity is the true gradient
test_physics_anchor.py Closed-form absolute anchor: single-element energy patch U = 1/2, uniaxial bar C = N

Full CPU suite green.

Roadmap

0.3.0 becomes the force-direction-fix release (mirroring 0.2.0 = the STL coordinate fix); Core Functionality & Interface (mass minimization, GUI) moves to 0.4.0. Design notes for the 0.3.0 optimizer decision (adopt MMA via NLopt) are under docs/literature-reviews/.

🤖 Generated with Claude Code

jihoonkim888 and others added 4 commits June 16, 2026 14:55
…nt anchor

M0 of the 0.3.0 optimizer work. Pull the per-iteration FE physics (assemble K(xPhys),
solve, compliance, sensitivities) out of the optimizer's CPU loop into a reusable
ComplianceEvaluator, so a future NLopt MMA backend can call the same physics. The CPU
branch now calls the evaluator; the GPU branch is left inline. Behaviour-preserving:
top3d() output is byte-identical (array_equal, max diff 0.0; full suite green).

Adds tests/test_gradient_check.py (R1): a finite-difference check that the analytic
sensitivity dc is the true gradient of compliance through the full design -> filter ->
physics chain (mutation-proven: a sign flip or a 1% scaling fails it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anchor

A build_edof rewrite (the commit that added custom force_field support and moved the
default load from -y to -z) silently swapped the H8 element's first two local axes, so a
user-supplied Fx acted along the y (nely) axis and Fy along x. This was invisible to the
default -z load, the shipped cantilever examples, and the golden master (all -z /
x<->y-symmetric), so only force_field loads with distinct Fx vs Fy were affected.

Restore the correct local node ordering in build_edof (swap local nodes 1<->3 and 5<->7) so
the element's first axis aligns with the grid's x (nelx). The golden-master result is
unchanged (max abs difference ~4e-13) and the README force_field recipe now behaves as
documented (a -y tip load bending in the x-y plane).

- tests/test_force_direction.py: locks the public (x, y, z) force convention (Fx is axial on
  an x-long beam, Fy/Fz transverse) so the transpose cannot regress; also covers the one-time
  notice (fires for Fx/Fy loads, silent for Fz-only).
- tests/test_physics_anchor.py (R2): closed-form absolute anchor -- single-element energy
  patch (U = 1/2) and a uniaxial bar through the evaluator (C = N) -- under the corrected
  convention; mutation-proven (KE*2 trips both).
- One-time transitional notice for force_field loads carrying Fx/Fy (removed in 0.4.0).
- CHANGELOG entry under [Unreleased].

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…design notes

The breaking force-direction fix takes the next release number 0.3.0 (mirroring 0.2.0 =
the STL coordinate fix); Core Functionality & Interface (mass minimization, GUI,
convergence) moves to 0.4.0 and Pre-release Stabilization to 0.5.0, per RELEASING.md (a
correctness release takes a number independently of the roadmap; on a collision the
roadmap milestone is pushed up). The convergence item is also marked conditional (coupled
to mass minimization, not a standalone change).

Adds the design notes that drove the 0.3.0 optimizer decision under docs/literature-reviews/:
an optimizer survey / decision memo (adopt MMA via NLopt; reject GPL ports and
NN/autodiff-as-optimizer) and the implementation plan plus validation strategy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… docstring

- README: the roadmap renumber left the table of contents pointing at the old headings
  (dead anchors for 0.3.0/0.4.0, missing 0.5.0); sync it with the renamed sections.
- 0.3.0-implementation-plan.md: note the renumber (this mass-minimization milestone is now
  0.4.0; the force-fix took 0.3.0) so the title no longer contradicts the roadmap.
- ComplianceEvaluator docstring: stop calling the returned dv the "true gradient" -- with
  obstacles it is not the exact volume-constraint gradient (cells zeroed after, not before,
  the filter). Pre-existing behaviour; flagged for the future MMA backend.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jihoonkim888
jihoonkim888 merged commit 57abbba into main Jun 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant