Stokes uu_G3 transposed tangent + TI un-frozen Picard: fix issue #457 at source - #493
Conversation
…; TI c-tensors un-froze the Picard tangent (#457) Two independent defects, both invisible behind major-symmetric tangents until the transverse-isotropic Newton tangent (the first in-repo tangent WITHOUT major symmetry) exposed them via -snes_test_jacobian and a velocity-block symmetry probe. 1. SNES_Stokes_SaddlePt Jacobian layout. sympy.derive_by_array is dx-FIRST, but the uu/up/natural-BC blocks translated it with permutedims((0,2,1,3)) — valid only for the dx-LAST convention this repo's layout doc wrongly recorded. The result: dF1[gc,dg]/dL[fc,df] landed in PETSc's [fc,gc,df,dg] slot — the major transpose. Every isotropic rheology has a major-symmetric tangent (frozen C is symmetric; the isotropic Newton term is a rank-one e^ (x) e^), so the whole test suite passed over it; the 2026-04-21 audit "verified" the Stokes path against the mis-stated convention while SNES_Vector was being fixed for the same bug. All blocks now use the same explicit-index loops as SNES_Vector/SNES_MultiComponent, writing each entry directly into PETSc's [fc,gc,df,dg] layout. The block-constraint boundary G0 keeps its form (shape-protected and r*(n(x)n) symmetric). F0/F1 sources are shape-normalised (spherical templates deliver (dim,1) F0). 2. TI constitutive freezing. TransverseIsotropicFlowModel._build_c_tensor baked the UNWRAPPED .sym contents of eta_0/eta_1 into the c-tensor, so any strain-rate dependence was visible to sympy.diff and the default (Picard) tangent was silently NOT frozen — it ran the (transposed) Newton derivative instead. Now bakes the wrapped Parameter atoms exactly like the isotropic path. TransverseIsotropicVEPFlowModel gets the same treatment plus a persistent _eta1_yield_eff UWexpression container (the ViscoPlasticFlowModel._plastic_eff_viscosity pattern) so the yield-limited weak-plane viscosity freezes under Picard and is recovered by _jacobian_unwrap under Newton. Measured on the #457 reproduction (annulus-class TI, eta_1/eta_0 = 0.01, shear-thinning eta_0): - Picard velocity-block asymmetry: 1.089e-01 -> 4.0e-17 (machine zero) - Newton -snes_test_jacobian: 1.09e-01 -> 1.3e-4 (FD-limited; isotropic control 1.06e-4); Newton converges in 9 its where the transposed tangent stalled at Picard pace. Docs: petsc-jacobian-layout.md corrected (dx-first convention, warning about the earlier mis-audit, Stokes migration recorded, permutedims banned for new Jacobian code). Tests: test_1066_stokes_jacobian_layout.py — (1) assembled uu_G3 vs an FD oracle from the residual flux with a built-in guard that the oracle tangent is genuinely major-asymmetric; (2) TI Picard G3 has zero velocity-gradient dependence (frozen) + major symmetry, with a Newton positive control; (3) TI-VEP c-tensor coefficient freezing with a Newton-unwrap positive control. All three fail on the pre-fix build at their primary assertions (0.313 rel FD mismatch; L-dependence everywhere) and pass on this build. Full level_1 tier_a gate: 554 passed, 0 failed. Closes #457. Underworld development team with AI support from Claude Code
There was a problem hiding this comment.
🟡 Not ready to approve
The natural-BC gradient-flux up_G2/up_G3 Jacobian currently differentiates the raw boundary flux instead of the Newton-consistent _jacobian_source() version, which can reintroduce the very kink/unwrap inconsistency the new path is meant to avoid.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Fixes the root causes of issue #457 by correcting Stokes Jacobian block assembly to match PETSc’s [fc,gc,df,dg] layout under SymPy’s dx-first derive_by_array convention, and by restoring the intended Picard “frozen-coefficient” contract for transverse-isotropic (TI / TI-VEP) tangents via wrapped-atom coefficient handling.
Changes:
- Replaces
derive_by_array + permutedimsinSNES_Stokes_SaddlePtwith explicit-index Jacobian construction foruu,up, and natural-BC blocks to avoid major-transpose assembly. - Updates TI and TI-VEP c-tensor coefficient construction to keep viscosity/yield coefficients inside wrapped atoms under Picard, with explicit Newton unwrapping.
- Adds targeted regression tests and corrects/strengthens the Jacobian layout documentation to match the dx-first convention.
File summaries
| File | Description |
|---|---|
tests/test_1066_stokes_jacobian_layout.py |
Adds regression tests for Stokes uu_G3 layout vs FD oracle and for TI/TI-VEP Picard coefficient-freezing behavior. |
src/underworld3/cython/petsc_generic_snes_solvers.pyx |
Migrates Stokes + natural-BC Jacobian assembly to explicit-index loops matching PETSc layout (removes permutedims path for these blocks). |
src/underworld3/constitutive_models.py |
Fixes TI/TI-VEP c-tensor construction to use wrapped Parameter atoms (and adds persistent yield-limited η₁ container) so Picard tangents remain frozen. |
docs/developer/subsystems/petsc-jacobian-layout.md |
Corrects SymPy convention documentation (dx-first) and records the Stokes migration/ban on new permutedims Jacobian code. |
Review details
Suppressed comments (1)
src/underworld3/cython/petsc_generic_snes_solvers.pyx:7583
- Similarly,
up_G3for the natural-BC gradient term differentiatesbd_F1instead of the Newton-consistentbd_F1_jacthat was just constructed. If_jacobian_source()is smoothing/unwrappingbd_F1for Newton,up_G3should use the same source to keep the block Jacobian consistent with the residual linearisation policy.
G3[fc * dim + df, dg] = sympy.diff(
bd_F1[fc, df], Gp[0, dg]
)
- Files reviewed: 4/4 changed files
- Comments generated: 1
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
| G2 = sympy.zeros(dim, dim) | ||
| for fc in range(dim): | ||
| for df in range(dim): | ||
| G2[fc, df] = sympy.diff(bd_F1[fc, df], p_scalar) |
…nt uu_G0), align G1 index naming to dg The layout doc's 'no remaining permutedims' claim is now literally true: the block-constrained boundary uu_G0 (content r*(n(x)n), symmetric and shape-protected — numerically unaffected) moves to the same explicit [fc, gc] loops as every other block. Trial-side derivative index in the two G1 loops renamed df -> dg to match the layout doc's convention. Retest: layout + constrained free-slip + constrained SolCx + Stokes suites, 25 passed. Underworld development team with AI support from Claude Code
Adversarial review — live probes at the PR head (isolated worktree, own build)We attempted to break both fixes independently. Verdict: clean, two nits (response commit below). Findings
Attacks that failed (independently measured)
|
Fixes #457 at source. Two independent defects, both hidden by major-symmetric tangents until the transverse-isotropic Newton tangent — the first in-repo tangent without major symmetry — exposed them.
1. The Stokes velocity-block Jacobian was the major transpose of the true tangent
sympy.derive_by_arrayis dx-first, butSNES_Stokes_SaddlePttranslated it withpermutedims((0,2,1,3))— correct only under the dx-last convention thatpetsc-jacobian-layout.mdwrongly recorded. The assembleduu_G3therefore helddF1[gc,dg]/dL[fc,df]in PETSc's[fc,gc,df,dg]slot. Frozen constitutive tensors and isotropic Newton terms are major-symmetric, so nothing in the suite could see it; the 2026-04-21 layout audit signed the Stokes path off against the mis-stated convention at the same time SNES_Vector was being fixed for the identical bug.All
uu,up, and natural-BC (including Nitsche G2/G3) blocks now use the explicit-index loops SNES_Vector and SNES_MultiComponent already use. The block-constraint boundaryG0keeps its form — shape-protected, andr(n⊗n)is symmetric.2. The TI c-tensor builds silently un-froze the Picard tangent
TransverseIsotropicFlowModel._build_c_tensorbaked the unwrapped.symcontents of η₀/η₁ into the tensor, exposing their strain-rate dependence tosympy.diff— so "Picard" for TI was the (transposed) Newton derivative, never a frozen-coefficient tangent. It now bakes the wrapped Parameter atoms exactly like the isotropic path.TransverseIsotropicVEPFlowModelgets the same discipline plus a persistent_eta1_yield_effUWexpression container (theViscoPlasticFlowModelpattern) so the yield-limited weak-plane viscosity freezes under Picard and is recovered by_jacobian_unwrapunder Newton.Measured (unpatched, this branch, on the #457 reproduction)
-snes_test_jacobianrelTests
tests/test_1066_stokes_jacobian_layout.py, validated both ways (all three fail on the pre-fix build at their primary assertions — 0.313 rel FD mismatch, velocity-gradient dependence throughout — and pass on this branch):uu_G3vs a central-difference oracle built from the residual flux, with a built-in guard that the oracle tangent is genuinely major-asymmetric (the test cannot pass by symmetry).uu_G3has zero velocity-gradient dependence (frozen) and exact major symmetry; Newton positive control.Full
level_1 and tier_agate: 554 passed, 0 failed.Docs:
petsc-jacobian-layout.mdcorrected — dx-first convention, a warning recording the earlier mis-audit, the Stokes migration, and a ban onpermutedimsfor new Jacobian code.Out of scope, noted for the record: #463 (TI-VEP
viscosityproperty discards the yield-limited η₁ — orthogonal property-level defect) and the isotropic VEP_build_c_tensor's own.symuse (its flux path runs throughstress(), not this tensor, and its tangents are symmetric either way; it should be tidied with #463).Underworld development team with AI support from Claude Code