Addresses #85 (WIP — needs the thermal-mesh benchmark to verify).
identification_error was reported as a raw nodal sum sum((T-T*)²) in every
heat solver's forward, while the adjoint-based solvers (Firedrake, FEniCS)
differentiated the area-weighted integral ∫(T-T*)²dΩ and multiplied the
result by an approximate n_nodes/domain_vol scalar to bridge the two. That
scalar is only ~5% accurate, so the returned gradient was not the exact
derivative of the reported objective; L-BFGS accumulates these inconsistent
gradients into a corrupted curvature estimate and converges to a suboptimal
point.
Make ∫(T-T*)²dΩ the reported objective everywhere, so the gradient is the
exact derivative of what is reported:
- firedrake-heat, fenics-heat (adjoint): the forward now reports the same
assemble(inner(T-T*,T-T*)*dx) the adjoint already differentiates, and the
n_nodes/domain_vol correction factor is removed (both rho and source
gradients). Internally exact.
- jax-fem, torch-fem-thermal (autodiff): the forward objective is weighted by
lumped nodal volumes derived from the shared hex mesh; since AD runs
straight through the objective, the gradient is automatically consistent.
- dealii-heat (forward-only): same lumped-volume weighting applied to the
reported scalar.
The lumped-volume helper splits each axis-aligned hex cell's bounding-box
volume equally among its 8 nodes (verified: total weight = domain volume).
Firedrake/FEniCS use the consistent mass matrix while the other three use
lumped mass; these differ at O(h²) and both converge to the same continuous
objective. Reconciling to one convention (and adding a deal.II adjoint) is
left as follow-up.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Root cause
identification_errorwas the raw nodal sumΣ(T-T*)²in every forward, but the adjoint solvers (Firedrake, FEniCS) differentiated the area-weighted integral∫(T-T*)²dΩand multiplied by an approximaten_nodes/domain_volscalar. Per the paper excerpt in the issue, that scalar is only ~5% accurate, so the gradient isn't the exact derivative of the reported objective — and L-BFGS folds the inconsistency into a corrupted Hessian estimate, converging to a suboptimal point.Fix: make
∫(T-T*)²dΩthe reported objective everywhereassemble(inner(T-T*,T-T*)*dx)(plumbed through_solve_heat); removednodal_correctionon both rho & source gradsjax.vjpthrough the objectivetorch.autogradThe shared
_lumped_node_weightshelper splits each axis-aligned hex cell's bounding-box volume equally among its 8 nodes. Unit-tested: total weight = domain volume; interior nodes (shared by 2 cells) weighted 2× corners.Known limitations (why it's a draft)
*dx); the other three use lumped mass. These agree to O(h²) and converge to the same continuum objective, but aren't bit-identical. Reconciling to one convention is follow-up.identification_error/final_ic_errornumbers in published results.