Skip to content

Differentiability gradient-audit + hardening arc (registry = release gradient-gate) - #8

Merged
drannarosen merged 55 commits into
mainfrom
feat/differentiability-audit
Jun 14, 2026
Merged

drannarosen merged 55 commits into
mainfrom
feat/differentiability-audit

Conversation

@drannarosen

Copy link
Copy Markdown
Member

Why

progenax's value proposition is that a differentiable forward model turns cluster inference into information geometry: every IC is jax.grad-able, so the Fisher matrix $F = J^\mathsf{T}J$ is computable. That has a sharp failure mode — a silently zeroed or wrong column of $J$ makes a parameter look infinitely well-constrained (a confidently wrong uncertainty) with no error raised. A NaN crashes loudly; a zeroed gradient does not. Gradient integrity is Fisher integrity.

This arc builds a systematic AD-vs-finite-difference audit over every public sampling/physics entry point, fixes the hazards it finds, and makes the audit registry the release gradient-gate.

What's in here

A 66-case AD-vs-FD harness (tests/validation/grad_audit/) over both protected directions (params → IC and params → binned summary), with a computed (never hand-set) status: 62 clean, 4 pinned known-limitations, 0 hazards. Numbers are regenerated by scripts/audit_gradients.py → validation/data/grad_audit_results.json; the living tracker is docs/website/50-validation/differentiability-audit.md.

Two real hazards found and fixed — both in sites previously believed correct-by-design, both gradient-only (forward physics bit-identical):

  1. King tidal radius r_t was a silent zero ($\mathrm{d}r_t/\mathrm{d}W_0 = 0$ vs a true ~10–48). A jnp.maximum(\psi,0) clamp zeroed the crossing-node gradient before the shared _find_tidal_radius. Fixed by feeding unclamped $\psi$ to the interpolation — restored across King / Michie / LIMEPY / multicomponent (5 models).
  2. The mass-segregation surrogate's stop_gradient omitted ~27% of the gradient at the default softmin temperature. Removed; now FD-consistent.

The Fisher path itself audited — binned $\sigma(r)$ / $\beta(r)$ channels are clean with a mutation check proving teeth (stop_gradient(pos,vel) collapses AD to 0). A new public PlummerProfile.enclosed_mass_fraction supplies the model-side $N(r)$ Fisher channel, while the binned data count is pinned as correctly non-differentiable (frozen data is out of scope; the gradient lives in the model).

The registry is now the release gradient-gate — ~24 duplicate AD-vs-FD assertions migrated in, ~15 finite-only isfinite(grad) smoke tests deleted (a silent-zero passes them), coverage preserved by a safety interlock + per-batch full gates (which surfaced 2 real gaps that were then filled). Scattered tests carry pointer comments to the registry.

Verification

  • Full released-core gate: 1302 passed, 1 skipped, 0 failed
  • Grad-audit gate: 67 passed (66 cases + mutation-teeth test)
  • scripts/audit_gradients.py: exit 0, 0 hazards
  • Website: 66 cases, build 0 warnings (with embedded ratio/summary plots)
  • PlummerProfile.enclosed_mass_fraction: 12 unit tests; King c(W₀) vs Table II unchanged (max|Δlog₁₀c|=0.0023)

Completion doc: .claude-work/TASK_differentiability-audit_COMPLETE.md.

🤖 Generated with Claude Code

drannarosen and others added 30 commits June 13, 2026 09:32
Validated design from the Phase-1 brainstorm (HITL with Anna):
- shared core + thin pytest/script layers so the gate and the website table
  cite the same measured AD-vs-FD numbers (single source of truth)
- explicit expect taxonomy (consistent / known_zero / known_blocked); known
  limitations PINNED, not skipped
- per-class tolerance (1e-5 closed-form, 1e-3 ODE) + relative central-FD step
- per-channel physical IC reductions (mean r / mean v / mean m)
- full surface in 4 tiers; T4 consolidates all scattered grad tests into the
  registry (the release gradient-gate; fixes audit T6)
- confirmed-unfixed hazards -> xfail(strict) + hazard-id (self-cleaning ratchet)
- preliminary probes: H4/H6 verify-benign; alpha=1-exactly is a known branch
  point; live suspect surface narrows to H2 (psi=0 masks) + shared r_t kink

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…se 2)

Strict RED->GREEN task-by-task plan, 4 tiers + checkpoints:
- Tier 0: TDD the engine (toy NaN/silent-zero/wrong-by-2x must be caught) +
  reductions + parametrized gate + audit_gradients.py
- Tier 1: headline samplers + packaged summaries + king r_t known_zero pin +
  alpha=1 known_blocked split + H4/H6 boundary probes; mutation-check teeth
- Tier 1.5: living website validation doc, build to 0 warnings
- Tier 2: MultiComponentCluster A/B (H2 psi=0 + r_t-kink probes), binaries, rotation
- Tier 3: vendored frozen-edge binner -> binned sigma/beta/N Fisher path
- Tier 4: inventory + migrate/dedupe all scattered grad tests into the registry
  (release gradient-gate; fixes audit T6); plots; completion doc; ONE final PR
Plan-time refinement: segregation is a consistent (non-zero) pin, not known_zero
(proves stop_gradient didn't block the real gradient); only king r_t is known_zero.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
King positions (W0, r_c), King velocity DF (W0): all clean (FD-consistent,
tol=1e-3); W0=12 edge clean via explicit ODE domain (xi_max=700) so the
traced-W0 pinned-r_t guard does not trip. KingProfile.r_t added as
expect=known_zero -- at theta0=7.0 r_t is grid-snapped so AD~0 AND FD~0
(known-limitation). MEASURED finding: at W0=8.0 the r_t VALUE moves with W0
(FD=62.5, fixed grid) while AD stays 0 -- a genuine silent-zero on the
argmax-protected path, flagged for Anna's triage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ichie/LIMEPY/multicomponent via unclamped-psi interpolation (audit finding; forward r_t now interpolated crossing, Anna-ratified; no compat shim)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rentiable)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fferentiability

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… alpha=1 known_blocked split

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…prox summaries (segregation stop_gradient is a consistent non-zero pin)
…scale — restores FD-consistent gradient at default beta (audit finding; forward value unchanged, Anna-ratified)
…e (Tier 1.5)

One row per (entry point x param x direction) with REAL measured AD/FD ratios from
validation/data/grad_audit_results.json (38 cases, 35 clean / 3 known-limitation).
Documents the two audit-found hazards fixed gradient-only (king r_t silent-zero across
5 models; segregation stop_gradient ~27% bias), the alpha=1 known_blocked branch points,
the expect-class taxonomy, and a changelog. Wired into myst.yml validation TOC + index
intro. Builds 0 warnings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ases (Task 2.1)

Add the params->IC grad-audit cases for the lowered-isothermal multimass
equilibrium: from_imf(...).sample_cluster reduced by mean_radius, audited in the
three traceable equilibrium drivers W0, g, delta (n_comp=3, n_stars=400).
Measured-first frozen gates (AD vs central-FD, tol=1e-3):

  W0    @5.0: AD=4.709932e-1 FD=4.710074e-1  |ratio-1|=3.0e-5
  g     @1.0: AD=1.459179e-1 FD=1.459341e-1  |ratio-1|=1.1e-4
  delta @0.5: AD=8.716899e-1 FD=8.716973e-1  |ratio-1|=8.5e-6

H2 probe (ψ=0 boundary masks @ multicomponent.py:266/272/286 + shared r_t kink)
CLEARED BENIGN via a W0=3[H2-boundary] edge (most extended, r_t=4.26,
|ratio-1|=2.6e-4): (1) the shared tidal radius r_t IS differentiable in W0
through from_imf (ratio 1.000000 at W0=5,7) — the Tier-1 unclamped-ψ fix
(9d3365a) still flows here; (2) no sampled star ever reaches ψ(r)<=0 (the
boundary shell carries ~zero mass; 0/400 hit the max(ψ,0) clamp), so the
jnp.where(psi>0,...) masks are structurally inert for the sampled stars.

Categorical-assignment discreteness (OUT of scope) side-stepped by case design,
not by weakening tol: at h=1e-4 the fixed-key categorical draw is STABLE — 0/400
assignment flips for all of W0, g, delta — so the FD is the pure smooth
position-physics derivative, uncontaminated.

Gate teeth verified RED (tol=1e-8 -> hazard at ratio 1.000258) then GREEN
(tol=1e-3). Full grad-audit gate: 42 passed (was 38), 0 hazards. JSON regenerated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rements (review fixes)

Code-review precision fixes to the H2-probe comments in the Engine A
sample_cluster cases (comments only; no code change):

- Fix wrong file:line citations. The ψ=0 / boundary masks were cited as
  multicomponent.py:266/272/286: :266 is blank, :272 IS the genuine
  jnp.where(psi_grid>0,...) xi-grid support mask (kept), :286 is the
  jnp.where(r_grid<=r_t,...) RADIAL truncation mask (relabeled accurately,
  not a ψ=0 mask). The max(ψ,0) clamp was mis-cited at multicomponent.py:66
  (a docstring line) — the real clamps are sampling.py:64-66 (per-star W_i
  on the sampling path) and limepy.py:275 (model-build grid clamp).

- Replace the W0=5-only "benign" evidence with the W0=3 stress-test numbers
  (most extended cluster, r_t=4.26, boundary most stretched): measured
  min ψ(r_sampled)=0.153 (max sampled radius 3.66 < r_t), 0/400 stars hit
  the max(ψ,0) clamp, 0/400 categorical assignment flips at h=1e-4. The
  boundary is approached more closely at W0=3 but never reached; the
  jnp.where masks carry zero live gradient at the edge (masked-out density
  past r_t is ~0). H2 stays benign; gate green (42 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s cases (Task 2.2)

Add three params->IC grad-audit Cases through the density-defined shared-Psi
Eddington/OM build (Poisson quadrature + Eddington inversion, NO ODE) + per-star
draw, on the proven-realizable Plummer-halo + EFF-core headline mix reused from
test_engine_b_physics._headline_model:

  - halo Plummer r_h -> mean_radius: AD=6.767522e-1 vs FD=6.767957e-1 (|r-1|=6.4e-5)
  - core EFF gamma   -> mean_radius: AD=-1.204971e-1 vs FD=-1.205328e-1 (|r-1|=3.0e-4)
  - halo OM r_a_j[0] -> mean_speed:  AD=1.768007e-3 vs FD=1.768007e-3 (|r-1|=2.7e-9)

All FD probes (theta +- h) stay realizable (f_min_j > 0 measured at every point;
the EFF-core ~1.185e-4 margin is small but strictly positive and flat), so the
central FD never trips the concrete negative-DF ValueError. 0/400 categorical
assignment flips at h for all three params, so FD is discreteness-free. tol=1e-3
(set by the widest band, gamma at 3.0e-4). Reduced resolution n_r=3000, n_e=500
(AD==FD is a graph property; both sides share grids).

Full grad-audit gate: 45 passed, 0 hazards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ary_components / MoeCompanions (Task 2.3)

Add three params->IC binary-layer cases to the grad-audit registry, all
classified consistent/clean by measurement (no weakening, no cherry-picking):

- KeplerElements.to_state (param e): elements -> Cartesian position via the
  fixed-iteration Newton Kepler solve + trig rotation. mean_radius, tol=1e-5.
  AD/FD ratio 1.0000000 at e=0.5 AND at the e=0.999 near-parabolic EDGE (the
  max(1-e²,1e-12) guard is inactive there; 50 Newton iters converge).

- resolve_binary_components (param a): binary->spatial connector, N=50 all
  binaries (sanitization inactive). mean_radius over (2N,3), tol=1e-5,
  ratio 1.0000000.

- MoeCompanions.sample (param m1-scale): a sampler MIXING a smooth grid-CDF
  (P,q,e) draw with a DISCRETE is_binary Heaviside mask + m2 gate. Audited via
  <e> (mask-independent P->e coupling). Classified clean by measurement:
  0/400 is_binary flips at s=1±h (the discrete selection injects no FD noise),
  <e> ratio 1.0000000 (|AD|=1.02e-4 >> eps); even the gated <m2=m1·q> is
  ratio 1.0000000 since 0 flips keeps the where() locally smooth. The grid-CDF
  reparameterization makes the coupling gradient flow; the mask does not block it.

Registry: 45 -> 48 baseline Cases; JSON 49 rows (incl Kepler e=0.999 edge).
Full grad-audit gate: 49 passed, 0 hazards.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ocity-channel + Moe is_circular branch, add mixed-is_binary resolve case (Task 2.3 review)

Issue 1: the resolve_binary_components comment claimed AD/FD=5.872715e-2, but
the committed _resolve_binary_a closure (PRNGKey(11)) measures 8.437447e-2
(ratio 1.0000000008, matches JSON). The 5.87e-2 was a stale draft value from
an earlier PRNGKey(0) variant; corrected the comment to the true numbers and
noted the seed provenance. Case is valid/clean.

Issue 2: re-measured the Kepler to_state VELOCITY channel at e=0.999. With the
engine's central FD it is machine-precision clean (AD=-7.484823e-2 vs
FD=-7.484823e-2, ratio 1.0); it only softens under a coarse one-sided FD
(forward h=1e-3 -> ~0.99993) from the d/de[sqrt(1-e^2)] curvature. Documented
that the velocity gradient is NOT blocked, only FD-noisier, hence position is
the audited (cleaner) channel.

Issue 3: flip-counted the MoeEccentricity is_circular branch (etap1<=1e-6). For
the closure masses, 10/400 stars are already deeply circular (min etap1=-698)
but 0/400 flip at s=1+-h, so the discrete jnp.where(is_circular,0,e) injects
zero FD noise -- consistent with <e> ratio 1.0. Documented.

Issue 4: added resolve_binary_components[mixed] case (25 binaries + 25 singles)
exercising the single-star sanitization path (a_safe/e_safe/m2_safe,
assembly.py:85-87). Confirmed jacfwd is NaN-free with d/da=0 on every single
slot and a live gradient on every binary slot -- the sanitization does not leak
NaN into a Fisher matrix. AD=3.019178e-2 vs FD=3.019178e-2 (ratio 1.0).

Regenerated grad_audit_results.json (50 cases, 0 hazards); full gate 50 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… (Task 2.4)

Add the three rotation kinematic-overlay grad-audit cases (params->IC),
completing Tier 2 of the differentiability gradient-audit arc:

  - apply_solid_body_rotation in omega: EXACTLY-linear overlay, AD matches the
    closed form mean(|axis x r|)=1.1226200071 to <1e-12; AD/FD ratio 1.0 (6.9e-13).
  - apply_differential_rotation in v_peak: linear in v_peak, machine-exact (5.8e-13).
  - apply_differential_rotation in R_peak: genuinely NONLINEAR (R_peak in both the
    (R/R_peak) prefactor and exp(1-R/R_peak)); FD-consistent to 5.5e-8. tol=1e-5.

All three use zero base velocities to isolate the pure rotation overlay over a
fixed Plummer position sample + fixed z-axis. Regenerated
validation/data/grad_audit_results.json (53 cases, 0 hazards).

Website doc (50-validation/differentiability-audit.md): refresh for ALL of Tier 2
(Engine A, Engine B, binaries, rotation) -- updated the case count 38 -> 53, added
a "multi-component clusters, binaries & rotation" status list-table with real
measured ratios from the JSON, and a Tier-2 findings narrative (H2 psi=0 boundary
benign; discrete is_binary/is_circular branches don't block the q/e gradient;
single-star sanitization NaN-safe under jacfwd; Engine A/B r_t differentiable).
Tier 2 added 15 cases, all clean, 0 new hazards -> changelog unchanged. MyST build
clean (0 warnings).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…her-path cases + mutation check (Task 3.1; N(r) framing pending review)

Tier 3 of the differentiability gradient-audit arc: audit the exact Jacobian
J = d(binned kinematic summary)/d(theta) that the Fisher matrix F = JᵀJ is built
from, for the velocity-dispersion sigma_1d(r) and Binney anisotropy beta(r) channels.

Part A: vendor the five frozen-edge binners (_bin_index, _grouped_bin_sums,
binned_sigma1d, binned_sigma_beta + SigmaBetaResult, binned_number_density) VERBATIM
from scripts/_demo_inference.py @ 2090dcd into tests/validation/grad_audit/binners.py
so the audit has a stable binner independent of the (non-package) demo script.

Part B: two params->summary Fisher cases through build_spatial_ic + the frozen-edge
binner. Both are machine-precision FD-consistent (ratio 1.0000000) at h_rel=1e-5:
  - binned_sigma1d[Plummer]      d(sig_hat)/d(r_h):  AD=-3.345969e+00, ratio 1.0
  - binned_sigma_beta[Plummer+OM] d(beta_hat)/d(r_a): AD=-1.000759e+00, ratio 1.0
The FD step is tightened to h_rel=1e-5 (not tol weakened): at the default h_rel=1e-4 a
single boundary star (#821, r=7.99977) straddles the r=8 outer edge and corrupts the
COARSE central FD to ratio 0.86 — a coarse-FD edge-straddle, NOT an autodiff bug
(per-bin ratios all 1.0; ratio stable for h<=1e-5 and across N=2000/4000/8000). At
h_rel=1e-5: 0/2000 bin crossings at +-h.

Part B2: test_binned_sigma_mutation_has_teeth — wrapping (pos,vel) in stop_gradient
before the binner collapses AD to 0.000000e+00 while the live FD stays -3.35e+00,
proving the case differentiates the real params->summary channel (not a constant).

N(r): probed BOTH framings (data-side count vs model-side analytic Plummer p_k) but
did NOT add a Case — the framing decision is pending review (CHECKPOINT 3).

Gate: 55 audit case-rows + 1 mutation test, 0 hazards (was 53).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…leaves bin membership invariant, default h is clean (review fix)
…n (N(r) model-side Fisher channel) + pinned data-count non-diff case + website (Task 3.1 N(r))

Add the public PlummerProfile.enclosed_mass_fraction CDF M(<r)/M = r^3/(r^2+a^2)^(3/2),
the differentiable per-shell occupancy p_k(theta) that a Poisson number-density
likelihood differentiates -- the model-side N(r) Fisher channel. The frozen binned
data count remains correctly non-differentiable (AD=0 by design), pinned as a
known_blocked limitation.

- src: new enclosed_mass_fraction method (pure jnp, differentiable in r and r_h)
- unit tests: M(<r_h)=0.5, M(<a)=1/(2sqrt2), M(<0)=0, M(<inf)=1, monotone,
  vectorized, JIT, grad in r (>0) and r_h (flows through a)
- grad-audit registry: N(r) model case (consistent, ratio 1.0000000, tol=1e-5)
  + pinned data-count case (known_blocked, AD=0 finite, FD=-5e3 bin-crossing)
- website: binned-kinematic Fisher path table (sigma/beta + N(r) split),
  case count 53->57, changelog Tier-3 addition row
- regenerated grad_audit_results.json (57 rows, 0 hazards; gate 58 passed)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nsolidation (Task 4.1; no deletions — awaiting CHECKPOINT 4a sign-off)
…e+7 new cases; keep borderline; fast-per-file/full-per-batch)
drannarosen and others added 25 commits June 13, 2026 16:08
…min, Kepler a/M0/from_state, Michie density) + fix H6 boundary probe + dedupe (Task 4.2a)

Grad-audit gate 65 passed. All measured-first; H6 boundary now a standalone case
running the real u=1e-12 probe (eps=1e-12 below the live |AD|), duplicate edge removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…it (i=0.3,Omega=0.4,omega=0.5) — runs the general inversion path, value unchanged (review note)
…rad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… AD-FD assertion into grad-audit registry (audit T6 consolidation; registry is SoT)

test_profile_gradients.py: removed plummer r_h, eff gamma, king r_c, king W0
sampler FD tests; KEPT test_eff_grad_a with a TODO (EFFProfile.sample_positions(a)
is not directly owned by the registry — safety interlock against coverage loss).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-FD assertion into grad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sertion into grad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…D-FD assertion into grad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…on into grad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-FD assertion into grad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…D assertions into grad-audit registry (audit T6 consolidation; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion into grad-audit registry (audit T6 consolidation; registry is SoT)

Split test_jit_and_grad_safe -> test_jit_safe: dropped the d|sep|/da grad
smoke (owned by the registry resolve_binary_components Case), kept the JIT
finite assertion.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…FFProfile.sample_positions(a) cases, migrate test_eff_grad_a, fix Michie W0 pointer

Review found the W0 density channel (closed-form density() path, distinct from the
sampler) and EFF positions-in-a were uncovered. Both added (measured-first, clean),
closing the migrate interlock's one skip. Registry now 66 cases.
…t smoke test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oke test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ke test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…moke test(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t(s) — registry FD-covers (audit T6; registry is SoT)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…yGradients + clarify sample/ppf reparam pointer (4.2c review)
…site 66-case reconcile + consolidation narrative, STATUS (Task 4.3)

- scripts/audit_gradients.py: add --plots flag rendering grad_audit_ratio.png
  (per-case |AD/FD-1| residual, log axis, tol bands, known-limitation annotated)
  + grad_audit_summary.png (counts by direction × status), straight from the
  single-source JSON. Guarded matplotlib (viz extra) import.
- website differentiability-audit.md reconciled 57→66 cases: 9 Tier-4 rows added
  with real measured ratios (King/EFF DF scale params, PowerLawIMF.ppf(m_min),
  Kepler to_state(a,M0)/from_state, Michie density observable, H6 standalone),
  new "Tier 4 — registry as the release gradient-gate" section (24 migrated /
  15 deleted T6 smoke / 2 gaps surfaced / H6 fix), both figures embedded,
  changelog row. myst build 161 pages, 0 warnings.
- STATUS: differentiability gradient-audit arc complete (Tiers 0–4); registry is
  the release gradient-gate (66 cases, 62 clean / 4 known-limitation, 0 hazards,
  2 fixed); awaits final PR / CHECKPOINT-4b.

audit_gradients.py exits 0 (66 cases, 0 hazards); test_grad_audit 67 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ion (Michie density = 1 dual-param Case) (4.3 review nit)

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fe8b8a69b2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

f"the anisotropy is too strong (no finite tidal radius). Increase ra / xi_max."
)
return xi_grid, psi_grid, rho_j_grid
return xi_grid, psi_grid, psi_raw, rho_j_grid

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Update validation callers for the new solve return arity

When this solver now returns four values, the DF-table validation script still unpacks three: scripts/validate_df_tables.py uses _solve(...) as xi_q, psi_q, _ = ... at lines 106-107 and directly calls _, psi, _ = solve_multicomponent_limepy(...) at line 146. Running that validation path after this API change raises ValueError: too many values to unpack, so the table-accuracy/gradient validation script can no longer run until those unpack sites are updated or a compatibility wrapper is kept.

Useful? React with 👍 / 👎.

@drannarosen
drannarosen merged commit 2164a7f into main Jun 14, 2026
9 checks passed
@drannarosen
drannarosen deleted the feat/differentiability-audit branch June 14, 2026 01:30
drannarosen added a commit that referenced this pull request Jun 14, 2026
…CHECKPOINT 4b closed)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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