Skip to content

Rotated free-slip: prescribed-normal datum through the SNES path; one unified Newton loop (#438, #403) - #458

Draft
lmoresi wants to merge 9 commits into
developmentfrom
feature/rotated-datum-snes
Draft

Rotated free-slip: prescribed-normal datum through the SNES path; one unified Newton loop (#438, #403)#458
lmoresi wants to merge 9 commits into
developmentfrom
feature/rotated-datum-snes

Conversation

@lmoresi

@lmoresi lmoresi commented Jul 28, 2026

Copy link
Copy Markdown
Member

What this is

Workstream B of the free-surface roadmap (joint session with @lmoresi): the prescribed wall-normal datum u·n̂ = ũ_n now runs through the full nonlinear rotated machinery, and the separate linear/nonlinear rotated solve paths are unified into one Newton/Picard loop. Closes #438's acceptance checklist and #403 items 2 & 4.

Design

  • Datum through Newton = feasible iterates. The constraint is affine, so accepted iterates carry v_n = ũ_n exactly (an affine snap in the rotated frame) and every increment satisfies the homogeneous constraint n̂·δ = 0 — the datum never touches the tangent, the increment RHS, the FMG prolongation, or the null-space handling.
  • Cold starts take the affine lift, not the snap. Snapping a zero state onto a datum manufactures a boundary-strip strain state whose shear-thinning tangent produces a first Newton step ~4 orders too large (measured; no line-searched step descends). The first increment instead carries the datum jump via zeroRowsColumns(x̂, b̂) at the rest-state tangent — which is exactly the old linear path's operation sequence, and is what makes unification natural:
  • One path. A linear model converges after that first increment; the loop self-terminates, so the up-front _residual_is_nonlinear probe leaves the dispatch (two Jacobian assemblies saved per linear rotated solve; the probe survives as a lazy guard in the picard+pure-Newton corner). Direct LU becomes a per-increment diagnostic option. One result-dict shape (reaction = F(u); the linear-only A/b keys retire). Linear solutions change at solver-tolerance level only (bit-preservation deliberately relaxed — maintainer decision in-session).
  • API: add_rotated_freeslip_bc(conds, boundary, normal=) accepts the non-zero scalar datum (value-first, per the Charter); the FreeSurface manager uses it and its penalty-fallback catch is gone — consistent_constraint="strong" works for nonlinear rheologies.

Three latent bugs found and fixed (each was masking the next)

  1. Rank-local datum branching — both paths branched on the rank-local datum_map; a rank owning no datum boundary nodes desyncs the collective sequences (np>1 deadlock class; latent in the linear path since the datum landed). The activity flag now rides a collective PETSc Vec norm of the lift vector.
  2. Essential-pinned rotation modes admitted to the null space_mode_satisfies_constraints checked only the rotated constraint rows, so a rigid-rotation mode pinned by an essential BC on another boundary was projected out of every increment RHS: Newton converged superlinearly to rel ~2e-5 and floored there, irreducibly. Modes are now also verified as null vectors of the assembled operator (‖J·m‖ against the velocity diagonal scale); after the fix the same solve reaches rel 2e-13 in 11 iterations and correctly leaves the gauge in place.
  3. Step-norm false convergence — a tiny Newton step was reported as converged even at rel |F̂| = 0.85 (stiff tangents make tiny steps far from the solution). The exit is now verified against the reference scale max(r0, ‖F̂(0)‖), which also fixes the warm-start rtol trap (rtol relative to a good warm start's own small r0 demands ever-more absolute accuracy).

Plus: FreeSurface's consistent solve warm-starts from the free solve's converged fields (a cold start at the power-law regularisation floor stalls at rel 0.13 while the free solve converges beside it).

Evidence

Acceptance runs in ~/+Simulations/FreeSurface/annulus_fs_convection/b_snes_datum_acceptance/ (README carries the full table and attempt history):

  • Power-law annulus FS convection, consistent_constraint="strong", matched steps: material-boundary error 5.3e-3 (strong-linear reference 7.8e-3; penalty 3.7e-2), net surface flux 1.4e-4 (penalty 2.0e-3).
  • TI fault-bearing smoke (power-law background, weak-plane band ratio 0.01, cos θ datum): converges, 24 its to rel 6e-8, σ_nn recovery clean.
  • Nonlinear cos θ datum imposed to 3e-16 through genuine Newton iteration (new regression in test_1018); nonlinear datum at np2/np4 (new test in test_1066).

Tests: test_1018 18/18 serial · test_1066 np2 + np4 · test_1070 serial + np2 (7) · test_1071 serial.

Known limitations / follow-ups (all recorded)

New subsystem documentation: docs/developer/subsystems/rotated-freeslip.md; changelog entry included.

An adversarial review follows as the first comment, per house practice. Draft until @lmoresi signs off.

🤖 Generated with Claude Code

Underworld development team with AI support from Claude Code

lmoresi added 8 commits July 27, 2026 22:58
…rotated path (#438)

- v_n = u_n now carried through the manual Newton/Picard loop: accepted
  iterates are kept feasible (affine snap in _impose_normal_constraint),
  so increments stay homogeneous (n.delta = 0) and the datum never touches
  the tangent, the increment RHS, the custom-FMG prolongation or the
  nullspace handling.
- A COLD start imposes the datum through the FIRST increment's affine lift
  (zeroRowsColumns x/b at the rest-state tangent, exactly the linear
  one-shot's treatment): snapping the zero state onto the datum manufactures
  a boundary-strip strain state whose shear-thinning tangent cannot descend
  (measured: first Newton step 4 orders too large, no line-searched alpha
  improves). Warm starts take the exact affine snap directly.
- Collective datum-activity flag in BOTH solve paths: branching on the
  rank-local datum_map desyncs the ranks' collective sequences at np>1
  (the two zeroRowsColumns variants scatter differently; the lift skips
  the line-search collectives). The linear path carried the same latent
  pattern, tolerated only because annulus partitions happen to give every
  rank a piece of the outer arc.
- Regression: nonlinear power-law annulus with a u.n = cos(theta) datum
  imposed to machine precision through genuine Newton iteration
  (test_1018).

Tests: test_1018 serial 18/18; test_1066 np2; test_1070 serial + np2 (7);
test_1071 serial.

Underworld development team with AI support from Claude Code
…t bare MPI

The rank-consistent datum-activity flag now rides the x-hat lift vector's
collective norm (PETSc-before-MPI house rule): x-hat carries the prescribed
rotated-frame datum for the zeroRowsColumns lift anyway, so one object does
both jobs and the hand-rolled ownership loop collapses into _set_rows_local.
Removes the mpi4py allreduce introduced in the previous commit.

The deeper cure is a surface submesh (the 1D-manifold gap, #202 lineage):
a boundary-field datum would make this flag a field norm and retire the
hand-rolled surface bookkeeping in this layer - workstream C.

Underworld development team with AI support from Claude Code
…d solves; datum via conds (#438, #403)

One constraint, one path: the linear one-shot is deleted and every rotated
solve runs the manual Newton/Picard loop. A linear model converges after its
first increment (the cold-start affine lift IS the linear solve), so the
up-front _residual_is_nonlinear probe leaves the dispatch entirely - two
Jacobian assemblies saved per linear solve (test_1018: 60s -> 55s). The probe
survives only as a lazy guard in the picard + pure-Newton corner, where a
linear model now ignores the meaningless warmup instead of raising.

- solver._rotated_use_lu becomes an increment-solver choice (direct LU per
  Newton increment, serial PC-free diagnostic; naive pressure pin extracted
  to _naive_pressure_pin with its TODO(BUG)). Verified against the iterative
  path to 6e-10.
- One result-dict shape: reaction = F(u) always (equals A.u - b exactly for
  linear residuals); the linear-only A/b keys and their consumer fallback in
  boundary_normal_traction retire. ksp_its is always the per-increment list
  (test_1018 3D assertion moved to max-per-increment).
- add_rotated_freeslip_bc(conds, ...) accepts the non-zero wall-normal datum
  (number or scalar expression, value-first; #403 item 2) and stores it in
  _rotated_freeslip_datum; vector values rejected. Tests and FreeSurface now
  use the public argument instead of poking the attribute.
- FreeSurface: the penalty-fallback NotImplementedError catch in
  _solve_consistent is gone - consistent_constraint="strong" now runs through
  the SNES rotated path for nonlinear rheologies.

Linear solutions change at solver-tolerance level only (agreed relaxation of
bit-preservation); an occasional cheap polish increment can follow the lift
when the first residual check sits at the tolerance boundary (free-surface
plume wall-time unchanged).

Tests: test_1018 serial 18/18; test_1066 np2; test_1070 serial + np2 (7);
test_1071 serial; LU-vs-iterative and lazy-probe smokes.

Underworld development team with AI support from Claude Code
…ll modes

Two defects surfaced by the TI fault smoke (#438 acceptance):

1. The step-norm exit reported converged=True on tiny increments while the
   residual was still O(1) (a stiff power-law tangent at the regularisation
   floor produces tiny steps far from the solution). The exit is now VERIFIED
   against the problem's intrinsic scale ||F(0)||: a warm start at the
   solution passes; a stagnating crawl goes back through the line search and
   either progresses (bounded by max_it) or ends on the stall exit honestly.

2. _mode_satisfies_constraints admitted rigid-rotation modes that are PINNED
   by an essential BC on another boundary (it only checked the rotated
   constraint rows). The admitted mode was projected out of every increment
   RHS, leaving an irreducible residual component: Newton converged
   superlinearly to rel ~2e-5 and floored there (measured on the
   essential-inner + rotated-outer power-law annulus). Modes are now also
   verified as null vectors of the ASSEMBLED operator (||J.m|| against the
   velocity diagonal scale), which catches any pinning without enumerating BC
   types; the null space is built after the first Jacobian assembly to make
   that possible. Same fix protects the post-solve rotation-gauge removal.
   With the fix the same solve converges to rel 2e-13 in 11 iterations and
   correctly leaves the gauge in place.

TI fault-bearing smoke (power-law background, weak-plane band, cos(theta)
datum) now passes: 24 its to rel 6e-8, datum at the quadrature floor,
sigma_nn recovery clean.

Tests: test_1018 serial 18/18 (incl. gauge-removal cases), test_1066 np2
(reference unchanged - the linear-case imprint of the spurious projection is
below the 1e-6 test tolerance), test_1070 serial (7) + test_1071.

Underworld development team with AI support from Claude Code
…scale convergence for warm starts

- FreeSurface._solve_consistent now warm-starts from the free solve's fields:
  the consistent solution is the free solution with a small material-boundary
  datum correction, and the free solve has already converged this step.
  Starting there keeps a power-law tangent at physical strain rates - a cold
  start puts it at the regularisation floor, where the Newton line search
  stalls at O(0.1) relative residual (measured, acceptance attempt 2). With
  the warm start the first acceptance step converges in 3 iterations.

- The rotated loop's convergence reference is now max(r0, ||F(0)||): rtol
  relative to a warm start's own (small) initial residual demands ever-more
  absolute accuracy the better the guess (the warm-start rtol trap). The
  rest-state residual is the intrinsic forcing scale; cold starts unchanged
  (their r0 IS that scale). The step-norm verification and the non-convergence
  warning read the same reference.

Acceptance (#438) evidence in
~/+Simulations/FreeSurface/annulus_fs_convection/b_snes_datum_acceptance/:
power-law annulus FS convection with consistent_constraint="strong" through
the SNES rotated path holds the material boundary at the strong-datum level
(err 5.3e-3 vs penalty 3.7e-2; net flux 1.4e-4 vs 2.0e-3) at matched steps;
TI fault-bearing smoke passes (24 its to rel 6e-8). Open finding recorded
there: deformed-ring datum compatibility floors the interior residual at rel
~2e-3 (honestly reported unconverged; suspected arc-length-vs-FE-quadrature
demean gap - the principled fix is a trace-mass-weighted demean).

Tests: test_1018 18/18 serial; test_1070 serial+np2 (7); test_1071;
test_1066 np2.

Underworld development team with AI support from Claude Code
…ith the PETSc Jacobian checker

Regroup note (#438 B-session): TI power-law under consistent_jacobian=True
shows an early trajectory identical to the frozen tangent and Picard-paced
convergence (24 its vs ~11 isotropic) - consistent with the dC/d(eps_II)
director-orientation terms being dropped or mis-oriented in the JIT-lowered
tangent, while the isotropic terms survive (isotropic Newton is superlinear
through the same rotated machinery, which also argues the rotation algebra
itself is transparent). Discriminating harness (native -snes_test_jacobian
vs rotated directional-derivative probe, with an isotropic control) is
staged in the #438 acceptance run directory; not yet run - joint item.

Underworld development team with AI support from Claude Code
#457

The PETSc Jacobian checker (bounded-curvature protocol; at 1e-12
regularisation the FD reference is invalid for power-law and every model
reads the same large mismatch) convicts the TI anisotropy tangent on the
NATIVE path: ||J-Jfd||/||J|| ~ 2e-3-5e-3 in developed flow vs an FD-limited
4e-6 isotropic control. The rotated machinery is exonerated. Evidence and
harness recorded in #457.

Underworld development team with AI support from Claude Code
…inear datum test

- docs/developer/subsystems/rotated-freeslip.md: the unified rotated path -
  constraint semantics, datum-through-Newton design (feasible iterates,
  cold-start lift), convergence reference scale, operator-verified null
  modes, parallel rules, reaction/sigma_nn hand-off. Linked from the
  developer index.
- Changelog entry for the #438/#403 work at the conceptual level.
- tests/parallel/test_1066: nonlinear (power-law) rotated datum at np>=2 -
  guards the collective-sequence class through the Newton loop (lift,
  line-search residuals, mode verification). Passes np2 and np4.

Underworld development team with AI support from Claude Code
@lmoresi

lmoresi commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Adversarial review (pre-ready, per house practice)

Ordered by how much each item should worry a reviewer.

1. The unconditional lift-step acceptance has no safety net. On a cold start with a datum, iteration 0 is accepted without any line search. For the tested rheologies the lift lands on a smooth state, but a rheology whose rest-state tangent is a bad global model (e.g. strongly pressure-dependent viscosity, or yield already active at rest) could get a first iterate far uphill, and the loop would then need the line search to walk back from a worse place than zero. Mitigations: bounded by max_it + stall exit + honest reporting; no observed failure. But the reviewer should know this is argued-safe, not proven-safe.

2. converged=True semantics changed twice in one PR. The reference scale is now max(r0, ‖F̂(0)‖) and the step-norm exit verifies against it. This is more honest, but it means: (a) a warm-started solve can report converged at an absolute residual that is rtol·‖F̂(0)‖ even if its own start was much better — by design, but it is a contract change; (b) any downstream code that compared rnorm/rnorm0 to rtol will see different verdicts. I audited the in-repo consumers (_capture_rotated_report, FreeSurface) — clean — but out-of-repo scripts reading _rotated_freeslip_info will see behaviour change.

3. Linear rotated solves are no longer bit-identical to development. Deliberate (maintainer ruling in-session: within-tolerance is the acceptance), and test_1066's hardcoded ∫v·v reference still passes at 1e-6 — but that pass is luck of magnitude for the nullspace fix (the spurious projection's imprint on the linear isoviscous case happened to be < 1e-6). A different fixture could have needed a re-baseline; anyone bisecting a downstream tolerance-level change will land on this PR.

4. The occasional polish increment is a real cost on linear solves. When the first residual check sits at the tolerance boundary the loop pays a second (cheap, but nonzero) KSP solve. Measured: FS plume wall-time unchanged, test_1018 faster overall (probe removal wins). But a user with many marginal linear solves could see it; the mitigation (inner KSP slightly tighter than outer) was considered and deferred.

5. The nullspace mode verification depends on an assembled J. The build was moved inside the loop to guarantee that, but _finalize_rotated_solution's gauge-removal check can run with an unassembled J on one exit path (trivially-converged warm start before any Jacobian assembly this call) — it then skips the operator test (treat-as-pass, matching old behaviour). In practice J persists from a previous solve; still, that path is weaker than the main one.

6. The FreeSurface warm-start copies fields via .array every step. Correct and cheap relative to the solves, but it assumes the free solve converged — if the free solve diverged, the consistent solve now inherits its garbage rather than starting clean. The manager already warns on free-solve divergence; the coupling is new though.

7. Parallel coverage is honest but not exhaustive. np2/np4 pass for both datum tests; np4 on an annulus still gives every rank a boundary segment, so the true empty-datum-rank path (a 3D cap datum at high rank counts) is exercised only by construction (collective flags), not by test. The _eval_datum empty-rank + #432 3D exposure is documented, unresolved.

8. Deleted code / behaviour surface. The linear one-shot's removal takes with it: the Uhat result key (nobody consumed it), the internal nsp/custom_Pl self-build in _solve_rotated_iterative (callers audited: one), and the A/b fallback in boundary_normal_traction (dead by construction). If an external script called solve_rotated_freeslip_nonlinear by name, it breaks loudly (name gone) — accepted, the pyx dispatch is the only in-repo caller.

9. What we could not test. The 3D spherical datum (no 3D FreeSurface yet — the guard stands); TI Newton-rate claims (blocked by #457, found during this work); behaviour under _rotated_use_lu at np>1 (still guarded by the naive-pin TODO(BUG), serial-only as before).

Net: the riskiest items are (1) and (2), both semantic rather than correctness regressions, both documented in the subsystem page. The three fixed bugs each have a regression guard (deadlock class → np2/np4 tests; nullspace floor → the 1018 nonlinear datum test converges to tolerance, which the floor made impossible; false convergence → the TI smoke asserts the honest verdict).

Underworld development team with AI support from Claude Code

… refusal

Conflict in the rotated free-slip dispatch only: development added the armed
wall-clock-guard refusal (#442) around the old two-path dispatch; this branch
had unified the dispatch into one Newton loop. Resolution keeps BOTH: the
guard re-check (an armed guard would be silently inert on the rotated path,
which runs its own Krylov loop outside self.snes) followed by the single
unified call. Development's _record_convergence_status hook after the rotated
report (#441 warm-start flag) auto-merged and stands.

Underworld development team with AI support from Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant