Skip to content

Emit drops basis_change on one product-rule term for a repeated (aliased) diff leaf — ∂(FᵀF)/∂F half-wrong #112

Closed as not planned

Description

@petlenz

Summary

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ᵀF
auto t1 = otimesu(eye, eye);
auto t2 = inner_product<seq<1>,seq<1>>(F, t1);               // term A — NO basis_change
auto 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_change
auto 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.

Activity

  1. petlenz commented on Jul 22, 2026

    @petlenz
    MemberAuthor

    Sharper diagnosis (independently re-verified)

    • 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.)

  2. petlenz commented on Jul 22, 2026

    @petlenz
    MemberAuthor

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions