Fix EE-mollifier shape derivative (two bugs)#239
Conversation
8e6cee6 to
174674f
Compare
174674f to
8663296
Compare
There was a problem hiding this comment.
Pull request overview
This PR fixes two correctness bugs in IPC’s mollified edge-edge (EE) branch for NormalPotential::shape_derivative, and adds a regression test intended to exercise a near-parallel, mollified EE pair that existing shape-derivative tests do not cover.
Changes:
- Fix
edge_edge_mollifier_gradient_wrt_x()to use the correct threshold-gradient factor (rest-space) when differentiating througheps_x. - Fix
NormalPotential::shape_derivative()(mollified branch) by adding the missing outer-product term∇ᵤm (∇ᵤf)ᵀ. - Add a new FD regression test for mollified EE
shape_derivativevs. centered FD ofgradient.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/ipc/distance/edge_edge_mollifier.cpp |
Corrects the rest-space factor used in the mollifier gradient w.r.t. rest positions. |
src/ipc/potentials/normal_potential.cpp |
Adds the missing symmetric outer-product term in the mollified shape_derivative expansion. |
tests/src/tests/distance/test_edge_edge_mollifier.cpp |
Introduces a new regression test intended to validate mollified EE shape derivatives via finite differences. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #239 +/- ##
==========================================
+ Coverage 95.77% 96.51% +0.73%
==========================================
Files 159 163 +4
Lines 16521 16651 +130
Branches 927 920 -7
==========================================
+ Hits 15823 16070 +247
+ Misses 698 581 -117
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
…ᵤm)(∇ᵤf)ᵀ
Two bugs in the per-pair NormalPotential::shape_derivative when
collision.is_mollified() == true (near-parallel EE pair):
1. edge_edge_mollifier_gradient_wrt_x returns ∇_rest m, computed as
∂m/∂ε · ∇_rest ε. The second factor was using
edge_edge_mollifier_gradient(positions, eps_x) (returns ∇_position m)
instead of edge_edge_mollifier_threshold_gradient(rest) (returns
∇_rest ε). Sister function edge_edge_mollifier_gradient_jacobian_wrt_x
already used the correct factor.
2. NormalPotential::shape_derivative (normal_potential.cpp:392)
summed only four outer-product / Hessian terms in local_hess. The
correct chain-rule expansion of ∇ₓ(f∇ᵤm + m∇ᵤf) — with
∇ₓm = ∇ᵤm + (∂m/∂ε·∇_rest ε), ∇ₓf = ∇ᵤf, ∇ₓ∇ᵤf = ∇ᵤ²f — has FIVE terms:
f · ∇ₓ∇ᵤm (jac_m)
+ (∂m/∂ε · ∇_rest ε) · (∇ᵤf)ᵀ (gradx_m · gradu_fᵀ)
+ (∇ᵤf)(∇ᵤm)ᵀ + (∇ᵤm)(∇ᵤf)ᵀ symmetric pair
+ m · ∇ᵤ²f (hessu_f)
The (∇ᵤm)(∇ᵤf)ᵀ term was missing; it is the "∇ᵤm contribution" of
(∇ₓm)(∇ᵤf)ᵀ that gets split off from the rest contribution that goes
into the gradx_m·gradu_fᵀ term.
Adds a regression test "Edge-Edge Mollifier Shape Derivative" in
tests/src/tests/distance/test_edge_edge_mollifier.cpp that constructs
a 4-vertex / 2-edge scene with a single mollified EE pair and FD-checks
the full 12x12 analytic BarrierPotential::shape_derivative against
centered FD of gradient at fixed collision set (same convention as
test_barrier_potential.cpp:377-391).
Without either fix, rel >= 2.5e-2. With both fixes, rel reaches FD
precision (~3.5e-6). Test threshold 1e-5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
ee9b47a to
7b97031
Compare
Summary
Two bugs in the per-pair
NormalPotential::shape_derivativewhencollision.is_mollified() == true(near-parallel EE pair). Both surface only on the mollified branch, which IPC's existingshape_derivativetest (stacked cubes,test_barrier_potential.cpp:248) does not exercise.1.
edge_edge_mollifier_gradient_wrt_xused the wrong second factorReturns
∇_rest m = ∂m/∂ε · ∇_rest ε. The second factor was usingedge_edge_mollifier_gradient(positions, eps_x)(returns∇_position m) instead ofedge_edge_mollifier_threshold_gradient(rest)(returns∇_rest ε). Sister functionedge_edge_mollifier_gradient_jacobian_wrt_xalready used the correct factor.if (ee_cross_norm_sqr < eps_x) { return edge_edge_mollifier_derivative_wrt_eps_x( ee_cross_norm_sqr, eps_x) - * edge_edge_mollifier_gradient(ea0, ea1, eb0, eb1, eps_x); + * edge_edge_mollifier_threshold_gradient( + ea0_rest, ea1_rest, eb0_rest, eb1_rest); }2.
NormalPotential::shape_derivativewas missing one outer-product termThe correct chain-rule expansion of
∇ₓ(f∇ᵤm + m∇ᵤf)— with∇ₓm = ∇ᵤm + (∂m/∂ε·∇_rest ε),∇ₓf = ∇ᵤf,∇ₓ∇ᵤf = ∇ᵤ²f— has five terms:The code summed only four (the
(∇ᵤm)(∇ᵤf)ᵀterm was missing). That term is the "∇ᵤmcontribution" of(∇ₓm)(∇ᵤf)ᵀ— its rest counterpart was being correctly accumulated intogradx_m · gradu_fᵀ, but its position counterpart was being forgotten.Why it matters
In a polyfem shape-optimization transient adjoint, these two bugs combined produced a ~400× per-pair shape-derivative inflation at the touchdown frame and a ~10⁸× sign-flipped end-to-end gradient. The existing IPC
shape_derivativetest uses a non-mollified scene so neither bug appeared there.Test plan
New regression test
"Edge-Edge Mollifier Shape Derivative"intests/src/tests/distance/test_edge_edge_mollifier.cppconstructs a minimal 4-vertex / 2-edge scene with a single mollified EE pair (y_sep = 9.99e-4just insidedhat = 1e-3,z_twist = 2e-2so|A × B|² ≪ eps_x). FD-checks the full 12×12 analyticshape_derivativeagainst centered FD ofgradientat fixed collision set witheps = 1e-7(same convention astest_barrier_potential.cpp:377-391).~7000× discrimination, essentially FD precision after both fixes. All 122 existing mollifier-suite assertions still pass.
🤖 Generated with Claude Code