#493 fixed #457 by replacing derive_by_array + permutedims with explicit-index
loops. It converted SNES_Vector and SNES_Stokes_SaddlePt. SNES_Scalar was
not converted and still carries the same construction, and the same transpose.
It is latent, not active — nothing reachable through the public API hits it
today. But it is latent for the same reason #457 was invisible for months, which
is why it is worth closing rather than leaving.
The transpose, demonstrated
petsc_generic_snes_solvers.pyx ~3732 assigns the result straight to _G3
with no reordering:
G3 = sympy.derive_by_array(F1_jac, L)
...
self._G3 = sympy.ImmutableMatrix(G3)
derive_by_array is dx-first, so for a non-symmetric flux tensor:
F1 = [L0 + 7*L1, 3*L0 + 2*L1] # k = [[1,7],[3,2]]
ImmutableMatrix(derive_by_array(F1, L)) = [[1, 3], [7, 2]] <- k TRANSPOSED
g3[i][j] should be dF1[i]/dL[j] = [[1, 7], [3, 2]]
Same root cause as #457: derive_by_array leads with the derivative indices, so
the assembled tangent is the transpose of the one PETSc's [fc, gc, df, dg]
layout expects.
Why nothing fails today
The only anisotropic scalar model, AnisotropicDiffusionModel, builds a
diagonal tensor — sympy.diag(*validated) from a (dim,1)/(1,dim)
vector. A diagonal matrix is its own transpose, so the index order cannot show.
Verified on the supported path: kappa = [1, 7] gives
_G3 = [[1, 0], [0, 7]], correct entry by entry.
That is precisely how #457 hid in Stokes — invisible under frozen C, isotropic
eta(edot) Newton and linear transverse isotropy, wrong exactly when the
tangent lost its major symmetry.
A second failure mode in the same idiom
Feeding a full (non-diagonal) tensor to the scalar DiffusionModel — misuse of
that model, but it is what a user reaches for first — does not raise a useful
error. The diffusivity is carried as one opaque symbol kappa, and
derive_by_array then returns an array that lies about its shape:
F1.shape (2,) backing len 2
G3.shape (2, 2) backing len 2 <- should be 4
.shape and contents disagree, nothing checks, and it surfaces three frames
later as a bare IndexError: list index out of range inside
sympy.ImmutableMatrix. The traceback names sympy internals and not the
diffusivity, so the cause is not recoverable from the error.
So the one idiom carries three hazards: dx-first index order, shape/content
divergence with no check, and silent success whenever the tangent happens to be
symmetric.
Suggested fix
Convert SNES_Scalar to the explicit-index loops already used by the other two
solvers, finishing #493. The layout contract is
docs/developer/subsystems/petsc-jacobian-layout.md.
The regression test needs a full tensor to exist first. A diagonal one
cannot catch a transpose. That means either AnisotropicDiffusionModel accepts
a full (dim, dim) conductivity, or the test builds F1 directly. The former
is worth having on its own account — a foliation- or fault-aligned conductivity
is a real modelling case and is the direction this project is heading — and it
would make the defect reachable, so it should land with the fix rather than
before it.
Found while checking that #457 was genuinely closed (it is). Related: #457, PR
#493, and Charter §8 on baselines tight enough to fail when a default changes.
Underworld development team with AI support from Claude Code
#493 fixed #457 by replacing
derive_by_array+permutedimswith explicit-indexloops. It converted
SNES_VectorandSNES_Stokes_SaddlePt.SNES_Scalarwasnot converted and still carries the same construction, and the same transpose.
It is latent, not active — nothing reachable through the public API hits it
today. But it is latent for the same reason #457 was invisible for months, which
is why it is worth closing rather than leaving.
The transpose, demonstrated
petsc_generic_snes_solvers.pyx~3732 assigns the result straight to_G3with no reordering:
derive_by_arrayis dx-first, so for a non-symmetric flux tensor:Same root cause as #457:
derive_by_arrayleads with the derivative indices, sothe assembled tangent is the transpose of the one PETSc's
[fc, gc, df, dg]layout expects.
Why nothing fails today
The only anisotropic scalar model,
AnisotropicDiffusionModel, builds adiagonal tensor —
sympy.diag(*validated)from a(dim,1)/(1,dim)vector. A diagonal matrix is its own transpose, so the index order cannot show.
Verified on the supported path:
kappa = [1, 7]gives_G3 = [[1, 0], [0, 7]], correct entry by entry.That is precisely how #457 hid in Stokes — invisible under frozen C, isotropic
eta(edot)Newton and linear transverse isotropy, wrong exactly when thetangent lost its major symmetry.
A second failure mode in the same idiom
Feeding a full (non-diagonal) tensor to the scalar
DiffusionModel— misuse ofthat model, but it is what a user reaches for first — does not raise a useful
error. The diffusivity is carried as one opaque symbol
kappa, andderive_by_arraythen returns an array that lies about its shape:.shapeand contents disagree, nothing checks, and it surfaces three frameslater as a bare
IndexError: list index out of rangeinsidesympy.ImmutableMatrix. The traceback names sympy internals and not thediffusivity, so the cause is not recoverable from the error.
So the one idiom carries three hazards: dx-first index order, shape/content
divergence with no check, and silent success whenever the tangent happens to be
symmetric.
Suggested fix
Convert
SNES_Scalarto the explicit-index loops already used by the other twosolvers, finishing #493. The layout contract is
docs/developer/subsystems/petsc-jacobian-layout.md.The regression test needs a full tensor to exist first. A diagonal one
cannot catch a transpose. That means either
AnisotropicDiffusionModelacceptsa full
(dim, dim)conductivity, or the test buildsF1directly. The formeris worth having on its own account — a foliation- or fault-aligned conductivity
is a real modelling case and is the direction this project is heading — and it
would make the defect reachable, so it should land with the fix rather than
before it.
Found while checking that #457 was genuinely closed (it is). Related: #457, PR
#493, and Charter §8 on baselines tight enough to fail when a default changes.
Underworld development team with AI support from Claude Code