You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Emit drops basis_change on one product-rule term for a repeated (aliased) diff leaf — ∂(FᵀF)/∂F half-wrong #112
When emitting the tangent diff(expr, X) where the leaf X appears more than
once (e.g. C = XᵀX via inner_product(X,{1},X,{1})), numsim-codegen lowers
one of the two product-rule terms to tmech without its basis_change index
permutation, so the emitted rank-4 is in the wrong index layout and the tangent
comes out wrong (exactly half for d(FᵀF)/dF).
The numsim-cas symbolic tree is correct — cas's own tensor_evaluator
evaluates the same diff(FᵀF, F) to the exact two-term result (err 1.1e-16 vs dFᵀF + FᵀdF). The defect is purely in the codegen emit/lowering of that tree.
Was briefly mis-filed as numsim-cas#331 (now retracted/closed).
Reproduction
Recipe: input F (declare non-symmetric — roles::Other — so dF/dF is the
full identity), output C = inner_product(F, sequence{1}, F, sequence{1}), add_algorithmic_tangent("dC_dF", "C", "F"). Emitted body:
auto t0 = inner_product<seq<1>,seq<1>>(F, F); // C = FᵀFauto t1 = otimesu(eye, eye);
auto t2 = inner_product<seq<1>,seq<1>>(F, t1); // term A — NO basis_changeauto t3 = otimesu(eye, eye);
auto t4 = inner_product<seq<1>,seq<1>>(t3, F);
auto t5 = basis_change<seq<1,3,4,2>>(t4); // term B — has basis_changeauto t6 = t2 + t5;
dC_dF_out = t6;
Contracting each term with an arbitrary dF and comparing to the analytic dFᵀF / FᵀdF:
t5 : dF == dFᵀF (exact — term B correct)
t2 : dF == neither dFᵀF nor FᵀdF (err 9.75e-1 to both — term A wrong layout)
So t2 should carry a basis_change (it is FᵀdF in the wrong slot order) and
doesn't. (t2+t5):dF is off by 9.75e-1 → the whole tangent is wrong.
Contrast — distinct leaves are fine:G = AᵀB, diff(G, A) emits a single
term with its basis_change and FD-matches to 7e-11. The bug is specific to
the repeated/aliased leaf, where the product rule yields two terms and one of
them loses its permutation during emit.
Where to look
The tensor code-emit index-permutation logic (code_emit/tensor_code_emit.h /
the inner_product + basis_change lowering). It looks like the permutation for
one product-rule term is computed as identity (and thus skipped) when it should
be non-identity. Compare the two summands' index sequences coming out of cas::diff and ensure each summand's basis_change is emitted.
Impact
Blocks correct finite-strain tangents ∂stress/∂F where C = FᵀF (or b=FFᵀ). ∂stress/∂C (single symmetric leaf) is unaffected and FD-verified. Same
silent-wrong-tangent risk class as the FD spine (#90) exists to catch —
recommend an FD golden with an aliased-leaf kinematic map once fixed.
The mis-emitted term evaluates to exactly zero, not merely a wrong layout. term_rhs = inner_product<seq<1>,seq<1>>(F, otimesu(I,I)) emitted without a basis_change produces the zero rank-4 in tmech (|t2|max = 0), so the
generated ∂(FᵀF)/∂F collapses to dFᵀF alone — the half-correct result. term_lhs survives only because cas emits an explicit basis_change for it.
Use roles::DeformationGradient (recipe.h:93, is_symmetric=false) for the
input F — the separate symmetric-leaf 𝕀ˢ issue is a non-issue with the right
role. The emit bug here is present with the General leaf regardless.
cas is exonerated every way:diff(FᵀF,F) matches dFᵀF+FᵀdF to ~2e-16 and
a pure FD of C(F) to ~3e-10 through cas's own evaluator; same for FFᵀ, trace(FᵀF)→2F, otimes(F,F).
Fix site:TensorCodeEmit's inner_product_wrapper (and likely outer_product_wrapper) emit must reproduce the evaluator's free-index ordering
— emit the normalizing basis_change for both product-rule terms, not just
the permuted one. Gate with an FD golden on ∂(FᵀF)/∂F (both terms).
(A parallel investigation posted a full write-up on the now-closed numsim-cas#331
— same conclusion; this codegen issue is the canonical tracker.)
Resolved upstream — not a codegen bug. Root cause is tmech: inner_product returned a zero rank-4 for a lazy operand needing a basis change (the otimesu(eye,eye) identity cas emits for d(leaf)/d(leaf)). Fixed in tmech #29 / v1.1.1 (791c460). Verified: the ∂(FᵀF)/∂F generated code matches cas's evaluator to 0 once tmech is v1.1.1; no codegen change needed. Fix lands via bumping the tmech pin in numsim-cas (+ the renamed TMECH_BUILD_* toggles to avoid a g++-14 ICE in tmech's examples). My earlier 'missing basis_change in emit' diagnosis was wrong.
Activity
petlenz commentedon Jul 22, 2026
Sharper diagnosis (independently re-verified)
term_rhs = inner_product<seq<1>,seq<1>>(F, otimesu(I,I))emitted without abasis_changeproduces the zero rank-4 in tmech (|t2|max = 0), so thegenerated
∂(FᵀF)/∂Fcollapses todFᵀFalone — the half-correct result.term_lhssurvives only because cas emits an explicitbasis_changefor it.roles::DeformationGradient(recipe.h:93,is_symmetric=false) for theinput F — the separate symmetric-leaf
𝕀ˢissue is a non-issue with the rightrole. The emit bug here is present with the General leaf regardless.
diff(FᵀF,F)matchesdFᵀF+FᵀdFto ~2e-16 anda pure FD of
C(F)to ~3e-10 through cas's own evaluator; same forFFᵀ,trace(FᵀF)→2F,otimes(F,F).TensorCodeEmit'sinner_product_wrapper(and likelyouter_product_wrapper) emit must reproduce the evaluator's free-index ordering— emit the normalizing
basis_changefor both product-rule terms, not justthe permuted one. Gate with an FD golden on
∂(FᵀF)/∂F(both terms).(A parallel investigation posted a full write-up on the now-closed numsim-cas#331
— same conclusion; this codegen issue is the canonical tracker.)
petlenz commentedon Jul 22, 2026
Resolved upstream — not a codegen bug. Root cause is tmech:
inner_productreturned a zero rank-4 for a lazy operand needing a basis change (theotimesu(eye,eye)identity cas emits ford(leaf)/d(leaf)). Fixed in tmech #29 / v1.1.1 (791c460). Verified: the ∂(FᵀF)/∂F generated code matches cas's evaluator to 0 once tmech is v1.1.1; no codegen change needed. Fix lands via bumping the tmech pin in numsim-cas (+ the renamedTMECH_BUILD_*toggles to avoid a g++-14 ICE in tmech's examples). My earlier 'missing basis_change in emit' diagnosis was wrong.