| name | skill-debugging |
| description | Debugging a failing MDOODZ model โ the three failure families (CHOLMOD non-positive-definite, Powell-Hestenes non-convergence, first-iteration NaN), the error-string lookup table, the eta-contrast reality, the two anisotropy recipes, the single-variable bisection cookbook, and the known MDLIB anisotropy bugs with their fix commits. Use when a run crashes, won't factor, won't converge, NaNs, or stalls mid-production. |
Debugging a Failing MDOODZ Model
Distilled from ~/.claude/reading/cooking-mdoodz-models/primer.md. When a run breaks you get a red error and a Killed log โ but there are three distinct failure families with three root causes and three fixes. This skill is the diagnostic ladder.
First move โ diff against the nearest working SET, do NOT tune
The single most important rule. Before twiddling any solver knob, answer: "how does this SET differ from the closest working SET in SETS/?" If you cannot articulate "this differs from CollisionAnisotropy.txt (or RiftingCheninAniso.txt, or AniFstrainQuartzBlackford.txt) only in axes X, Y, Z" โ you are already lost. One grep of SETS/ for your parameter combination ends most investigations in minute one. Unguided max_its_PH / min_eta / Newton / penalty sweeps produce nothing actionable.
Error-string lookup table
Search the log for the string; that tells you the family and the first move.
| Error string in log | Family | First thing to try |
|---|
CHOLMOD warning: matrix not positive definite | A โ SPD violation | Newton=1 โ 0. Still fails: free_surface_stab=1 โ 0. Still fails: aniso_factor โ 1. |
CHOLDMOD failed... probably negative! | A โ SPD violation | Same ladder (this MDOODZ error follows the SuiteSparse warning). |
incompressibility constrain was not satisfied to abs. tol. | B โ PH non-convergence | Raise penalty ร10. NOT max_its_PH. Also try lin_solver=2. |
Try modifying the PENALTY factor or check MIN/MAX viscosities / Good luck! | B โ PH non-convergence | The hint is half-right โ penalty matters; min/max viscosity usually does not. |
Fu abs. = nan at iteration 00 of step 1 | C โ upstream NaN | Test cstv=1. Passes โ power-law issue. Still NaN โ particle init or BC. |
Lazy bastard! Fix your particle phase ID! | C โ SetPhase bug | The .c returns a phase ID outside [0, Nb_phases). Open the .c, find the return. |
negative dissipation: you crazy! Wdiss = ... | A/C precursor | Anisotropic rheology produced negative eta โ see Bug 1. Often precedes a crash. |
min. Maxwell = -... (negative) at DefineInitialTimestep | C โ Bug 1 signature | Negative eta_ve from anisotropic Newton overshoot on cold cells โ see Bug 1. |
Killed / Killed: 9 with no error | OOM | Grid too large for memory. Drop resolution. |
Run completes but Nt < requested | Silent stop | Parent shell terminated the process โ use nohup next time. |
The Stokes solve, in one paragraph
MDOODZ discretises incompressible Stokes on a MAC staggered grid โ a coupled saddle-point block system. It does not factor the whole (indefinite) matrix: it Schur-reduces to a velocity-only system Jts = J_uu โ Bแต(1/penalty)B, factors that with CHOLMOD, and recovers pressure via outer Powell-Hestenes (PH) iterations. CHOLMOD requires Jts to be SPD (symmetric positive-definite). Every failure family is a way that contract breaks. (See skill-solvers for the full architecture.)
Family A โ CHOLMOD non-positive-definite (SPD violation)
Symptom: the CHOLMOD warning: matrix not positive definite / CHOLDMOD failed strings. A Cholesky pivot crossed zero โ MDLIB/Solvers.c:1246-1251 catches it and exit(1)s.
Causes, most likely first:
- Newton mode with strong nonlinearity. The Newton tangent adds
โฮท/โฮตฬ cross-terms that are not symmetric (D12 โ D21) โ MDLIB/StokesRoutines.c:304-312. Picard (Newton=0) keeps J_uu SPD by construction. Bisect by switching to Newton=0.
aniso_factor extreme (>~15 in assembled cells). The anisotropic stress projection blows the operator viscosity up; can fail later (step 50+) when the fabric has rotated to align with the stiff axes.
- Free-surface stabilisation sign error. Rare. The guard at
MDLIB/StokesAssemblyDecoupled.c:143-144 is the safety net. If it breaks the moment free_surface=1, isolate with free_surface_stab=0.
free_surface=1 with no air buffer (zmax โค max-z of material phases) โ see "Implicit conventions" below.
Ladder: Newton=0 โ free_surface_stab=0 โ aniso_factor=1 (then ramp slowly) โ log the eta range (LOG_INFO("max(eta_s)=%e min(eta_s)=%e")) at the first call site after rheology and look for a negative or NaN value.
Family B โ Powell-Hestenes non-convergence
Symptom: rel.max.div stagnates at 10โปยฒโ10โปยณ across many PH iterations, then incompressibility constrain was not satisfied. The PH outer iteration should drive max(div u) to ~10โปโน in 5โ10 iterations; if it decays but stalls (1.0 โ 0.1 โ 0.01 โ 0.005 โ stuck), it is stalling.
Causes: (1) penalty too small โ the pressure update is too gentle; (2) Schur preconditioner degenerate โ Jts is technically SPD but near-singular, which large aniso_factor / extreme operator-viscosity contrast produces.
Ladder:
- Raise
penalty first (ร10: 1e2 โ 1e3 โ 1e4), not max_its_PH โ more iterations just stall longer. (The CI-tested calibration recipe uses penalty=1e5.)
- Switch to
lin_solver=2 (augmented PH / "Killer solver").
- Reduce contrast at the operator level:
aniso_factor โ 1 temporarily, confirm PH converges, ramp back while watching rel.max.div track.
Time-dependent variant โ passes smoke, stalls mid-production. With aniso_factor โฅ 8, early steps converge in 3 PH iterations; by step 50โ100 the fabric has rotated and some cells become locally hyper-viscous. Fingerprint: wall-time per step climbs 3โ5ร over ~10 steps before the crash (a flat-then-sudden-break profile is the static stall instead). This was MDLIB Bug 3 โ see below; if your MDLIB has the fix (d4b4784), it is gone at moderate strain rate.
Family C โ first-iteration NaN
Symptom: Fu abs. = nan at iteration 00 of step 1 โ before any matrix is factored. An upstream computation produced a NaN.
Cause: the visco-elastic-plastic rheology iteration uses pow(Tii, n_pwl โ 1); if Tii is negative (sign-convention bug, or anisotropic Newton overshoot โ Bug 1), pow(negative, non-integer) returns NaN. Fingerprint: negative Tii/eta_ve at iter 00 step 1.
Ladder:
- Bisect
cstv=0 vs cstv=1 (cstv=1 pwlv=0 eta0=1e22). Works โ bug is in the power-law iteration. Still NaN โ bug is upstream of rheology (particle init or BCs).
- Check the
SetPhase callback in the .c โ a phase ID outside [0, Nb_phases) triggers Lazy bastard!. Reducing Nb_phases in the .txt without updating the .c is the classic cause.
- Print stress invariants at the first call:
LOG_INFO("Tii=%e eta_ve=%e ...") at the top of LocalIterationViscoElastic in MDLIB/RheologyDensity.c.
The eta-contrast reality
A common wrong intuition: "the problem is the eta contrast, so tune min_eta/max_eta." min_eta/max_eta clamp the marker rheology evaluation, not the assembled operator. A sweep of min_eta across 5 orders of magnitude on CollisionAnisotropy.txt changed nothing โ PH count and residuals identical. The operator viscosity that actually enters the matrix is routinely 10โดโตโ10โตโธ when your caps say 10ยฒโต.
What does drive operator stiffness is aniso_factor. There is a regime change โ a ~21-order-of-magnitude cliff between aniso_factor=2 and aniso_factor=5 in max(eta_s). PH residual quality degrades ~40ร from aniso_factor=1 to 20; per-step wall time doubles. Practical reading: aniso_factor โค ~5 is the easy regime; โฅ 8 is the hard end where you need tighter PH settings or lin_solver=2, and where production runs can partial-complete.
The two anisotropy recipes โ pick one, don't invent a third
Every anisotropy=1 SET in SETS/ follows one of two working recipes. The classic mistake is forking from a non-anisotropy SET and inventing a third combination that exists nowhere.
| Calibration recipe (CI-tested) | Production recipe |
|---|
| Examples | AniFstrainQuartzBlackford, PinchSwellGSEAniso | CollisionAnisotropy, RiftingCheninAniso, Beghein* |
| Free surface | free_surface=0 | free_surface=1, free_surface_stab=1 |
| Rheology | cstv=1 pwlv=0 eta0=1e22 (AniFstrain) or 2-phase (PinchSwell) | cstv=0, pwlv=10/19/40/41 (real flow laws) |
| Nonlinear | per-scenario (Newton=0/1, nit_max=1/10) | Newton=0, nit_max=1 (pure Picard; the time loop carries the nonlinearity) |
penalty | 1e5 (high โ PH converges fast) | 1e2 (modest, calibrated to multi-phase contrast) |
Nb_phases | 1โ2 | 4โ9 |
| Purpose | test the anisotropy plumbing in isolation | real geodynamics |
Both work. Neither is "right" โ they serve different purposes. Fork from the one closest to your target.
The cookbook โ build a new scenario without crashes
- Step 0 โ pick the closest working SET (
AniFstrainQuartzBlackford for a calibration test; CollisionAnisotropy / RiftingCheninAniso for production).
- Step 1 โ drop resolution to 101ร41 (always smoke at the CI resolution first; a 101ร41 smoke is 30โ60 s).
- Step 2 โ run the unmodified SET at
Nt=10. If the base recipe fails here, your build/environment is the problem, not your edits.
- Step 3 โ change one parameter toward your target. Re-smoke
Nt=10.
- Step 4 โ if it breaks, the error + the one thing you just changed is the diagnosis.
- Step 5 โ if it works, stage the next single change. Repeat.
- Step 6 โ only after the full modification set smokes clean at 101ร41, ramp resolution (151ร61 โ 201ร81 โ โฆ), re-smoking at each.
- Step 7 โ if 7+ single changes all pass and it still breaks at higher resolution / longer
Nt, look at the .c file (usually the SetPhase callback), not the .txt.
High-aniso production partial-completion decision (aniso_factor โฅ 5): a run that passes smoke can still partial-complete via the time-dependent PH stall.
- Crash at step
k โฅ 50, HDF5 outputs intact โ accept as a data point (real science to t โ 5 Ma, ฮณ โ 0.25โ0.5; the boundary is solver-bound, not physics-bound).
- Crash at step
k < 30 โ retry with aniso_factor halved (drop the highest aniso point, keep the comparison ladder).
k โ [30, 50] โ case-by-case on whether your diagnostic question is answered.
Chasing the stall with more solver tuning does not work โ the preconditioner is structurally failing, not under-iterating.
Known MDLIB anisotropy bugs
Check whether your MDLIB has the fixes: git log --oneline | grep -E "fix ani routine|fix bugs".
- Bug 1 โ cold-cell anisotropic Newton overshoot. FIXED (
533c48c "fix ani routine"). pwlv=41 (HK03 wet olivine) + anisotropy=1 uniformly over a column with cold-surface nodes (T < ~800 K): ViscosityConciseAniso (MDLIB/AnisotropyRoutines.c) had no bisection safeguard, Newton overshot to negative eta_ve โ negative eta โ Wdiss < 0 โ negative min. Maxwell โ negative ฮt โ NaN at iter 00 step 1. Fix: log-space bisection-then-Newton + an *eta_vep positivity clamp.
- Bug 3 โ missing ceiling clamps in
ViscosityConciseAniso. FIXED (d4b4784 "fix bugs"). The aniso path had the floor clamps (Bug 1 fix) but not the ceiling clamps the non-aniso path has (RheologyDensity.c:1058-1073). eta_ve reached 10โธโฐ with max_eta=1e25 โ operator condition number past CHOLMOD's double-precision tolerance โ PH stall mid-production. Fix: ceiling clamps mirroring the floors, + a KillerSolver noise-floor relaxation in Solvers.c. Caveat: necessary but not sufficient at high strain rate โ at ฮตฬ ~ 10โปยนโด the pathology re-emerges in a different regime; the safe aniso_factor ceiling is strain-rate-dependent (โค 13 at ฮตฬ ~ 10โปยนโต, back toward โค 8 at ฮตฬ ~ 10โปยนโด).
- Bug 4 โ Phase-1-freeze + cold cells CHOLMOD non-PD wall. CANDIDATE, OPEN. A frozen-fabric + cold-cell setup hits a hard CHOLMOD non-PD wall at a fixed step, identical across solver-knob recovery attempts (the local rheology is healthy โ failure is in the assembled operator). Suspected freeze-interface code-symmetry gap, same shape as Bugs 1 & 3. Investigation: instrument
ViscosityConciseAniso + the stress assembly around the freeze branch with printf at the failing step. (Note: the ani_fstrain=3 T-threshold freeze this bug concerns was redefined to a ฮด-relaxation in the ani-fstrain3-delta-relaxation change โ this entry describes the historical freeze.)
Implicit conventions that look like bugs
These produce crashes whose error message points the wrong way:
free_surface=1 needs an air buffer: zmax must exceed the max-z of any material phase. Every production lithospheric aniso SET uses zmax=+10e3 (10 km air). zmax=0 โ surface-row coefficients turn pathological โ CHOLMOD non-PD. The bug is in the error message, not the solver.
Nb_phases / SetPhase consistency: changing Nb_phases in the .txt without updating the .c โ Lazy bastard! abort.
min_eta/max_eta are marker-level, not operator-level โ see "eta-contrast reality" above.
Meta-rules
- Diff against the nearest working SET first. Do not tune. This is rule #1 for a reason.
- Bisection-against-known-working (single-variable changes from a working SET) isolates a root cause fast; arbitrary parameter sweeps produce nothing.
- When static-analysis confidence on a bug is below ~80%, instrument before you patch. Bug 3 had three plausible static-analysis hypotheses โ all three were wrong; one round of
printf in ViscosityConciseAniso found the real cause in <30 min.
- Smoke (
Nt=10) is necessary but not sufficient. High-aniso_factor runs pass smoke and stall in production. Budget for partial completion at aniso_factor โฅ 8.
- Watch for silent scope swaps. An autonomous agent debugging a run can quietly swap physics parameters (
cstv, free_surface) to make runs "go through" and still emit a confident verdict. Review the diff against the known-working baseline before trusting any "investigation complete".
See also
skill-solvers โ the Stokes/PH/CHOLMOD architecture (this skill is its failure-mode companion).
skill-rheology โ the viscosity pipeline and creep mechanisms behind Family C.
skill-anisotropy โ the director / aniso_factor / ani_fstrain machinery behind the eta-contrast cliff.
skill-parameter-validation โ pre-run .txt checks that catch some of these before launch.
~/.claude/reading/cooking-mdoodz-models/primer.md โ the full narrative source, with figures (decision tree, eta-ladder, recipe comparison) and the v1 postmortem.