# Jacobian-Killing Pattern Audit **Date:** 2026-07-18 **Story:** `0-1-jacobian-killing-pattern-audit-fd-test-harness` (Epic 0) **Scope:** Inventory of `clamp` / `max` / `min` (and related hard gates) on Newton residual/Jacobian paths in `entropyk-components`. Physics fixes are owned by Stories 0.2–0.4. ## Failure-mode legend | Mode | Meaning | |------|---------| | `zero-gradient` | Analytic ∂r/∂x (or helper derivative) hard-zeros in a region where physics should still inform Newton | | `discontinuity` | Hard clamp/step on a Newton unknown → C⁰ or worse residual; FD and analytic disagree near the edge | | `benign` | Floor/guard for division-by-zero, config validation, or non-Newton parameter — not a solver killer | | `already-regularized` | C¹ / phantom pattern already present (may still need unification) | | Severity | Meaning | |----------|---------| | `P0` | Confirmed FR19 killer; blocks convergence at domain edges | | `P1` | Likely Newton-relevant clamp on live unknowns / quality | | `P2` | Secondary / correlation-local; watch when that path is active | | `info` | Documented debt or missing API; not a clamp site | --- ## FR19 known-bad sites (confirmed 2026-07-18) | Site | Failure mode | Severity | Fix owner | |------|--------------|----------|-----------| | `crates/components/src/valve_flow.rs:105-107` — `valve_mass_flow_dp_up` returns `Ok(0.0)` when `dp <= 0 \|\| m <= 0` | `zero-gradient` | **P0** | Story 0.3 | | `crates/components/src/isenthalpic_expansion_valve.rs:233` — `state[i].clamp(0.0, 1.0)` on orifice opening (residual path) | `discontinuity` | **P0** | Story 0.3 | | `crates/components/src/isenthalpic_expansion_valve.rs:449` — `_state[i].clamp(0.0, 1.0)` on orifice opening (Jacobian path) | `discontinuity` | **P0** | Story 0.3 | | `crates/components/src/heat_exchanger/two_phase_dp.rs:107` — `quality.clamp(0.0, 1.0)` in `homogeneous_density` | `discontinuity` | **P0** | Story 0.4 | | `crates/components/src/heat_exchanger/two_phase_dp.rs:150` — `quality.clamp(0.0, 1.0)` in `friedel_multiplier` | `discontinuity` | **P0** | Story 0.4 | | `crates/components/src/heat_exchanger/condenser.rs:492` — `state[idx].clamp(0.0, 1.5)` fan actuator φ | `discontinuity` | **P0** | Story 0.4 | | `crates/components/src/heat_exchanger/condenser.rs:752` — `state[idx].clamp(0.0, 0.98)` flooded level λ | `discontinuity` | **P0** | Story 0.4 | ### Evidence notes - **Valve ΔP≤0:** `valve_mass_flow` already forces `dp = ΔP.max(0)`, so ṁ=0 and `valve_mass_flow_dp_up` returns a hard zero — Newton gets no direction to restore positive ΔP. - **EXV opening clamps:** same hard clamp appears in both residual helper and Jacobian branch; Story 0.2 `smooth_clamp` is the intended replacement (`entropyk_core::smoothing` already exists). - **EXV phantom ΔP:** `DP_FLOOR` (~L396–445) is a **partial** `already-regularized` mitigation for ΔP≤0 on the Jacobian only; residual still early-outs. Opening clamps remain P0. - **Missing API:** Epic Story 0.3 text mentions `valve_mass_flow_dp_down` — **only `valve_mass_flow_dp_up` exists** today (`lib.rs` re-export). Catalogue gap severity `info`; inventing `dp_down` is out of scope for Story 0.1. --- ## Broader catalogue (residual / Jacobian call graph) Classified from a ripgrep sweep of `.clamp(` / `.max(` / `.min(` under `crates/components/src`. Not every hit is listed — config setters, test helpers, and pure numerical floors are collapsed into summary rows. ### P0 / P1 — Newton-relevant | Location | Pattern | Mode | Sev | Notes / owner | |----------|---------|------|-----|---------------| | `valve_flow.rs:71,75,78,99` | `dp.max(0)`, `opening.clamp`, `m.max(0)` | `zero-gradient` / `discontinuity` | P0 | Feeds EXV/orifice; Story 0.3 | | `valve_flow.rs:105-107` | hard `Ok(0.0)` on ∂ṁ/∂P_up | `zero-gradient` | P0 | FR19 | | `isenthalpic_expansion_valve.rs:233,449` | opening clamp on state | `discontinuity` | P0 | FR19 → 0.3 | | `isenthalpic_expansion_valve.rs:396-445` | `DP_FLOOR` phantom | `already-regularized` | info | Keep; unify with toolkit in 0.2/0.3 | | `two_phase_dp.rs:107,150` | quality clamp | `discontinuity` | P0 | FR19 → 0.4 | | `two_phase_dp.rs:118-131` | hard `x<=0` / `x>=1` branches in `zivi_void_fraction` | `discontinuity` | P1 | Story 0.4 (MSH/dome) | | `two_phase_dp.rs:255-294` | Hermite blend near x→1 (`msh_gradient`) | `already-regularized` | info | Partial MSH fix; 0.4 completes | | `condenser.rs:492,752` | fan / flood actuator clamps | `discontinuity` | P0 | FR19 → 0.4 | | `evaporator.rs` | analogous secondary / property floors | `benign` / P2 | P2 | Re-check when evaporator actuators mirror condenser | | `heat_exchanger/flow_regularization.rs` | smooth mass / activity | `already-regularized` | info | Reference for Story 0.2 unification | | `heat_exchanger/sat_domain.rs:81` | `p_pa.clamp(p_min, p_max)` | `discontinuity` | P1 | Intermediate iterate domain; Story 0.4 / sat path | | `screw_economizer_compressor.rs:486` | eco fraction `.clamp(0.0, 0.4)` | `discontinuity` | P1 | If eco fraction is a Newton unknown | | `screw_economizer_compressor.rs:291,343` | slide valve clamp | `discontinuity` | P1 | Config vs state — verify call site | | `bphx_correlation.rs:588+` | quality / p_reduced clamps | `discontinuity` | P2 | Correlation internals on quality | ### Benign / info (not Epic-0 P0) | Pattern class | Examples | Mode | Sev | |---------------|----------|------|-----| | Divisor floors | `.max(1e-9)`, `.max(1e-12)` on ρ, μ, D | `benign` | info | | Builder / config clamps | `with_orifice_fixed` opening clamp, UA scale `.max(0)` | `benign` | info | | Registry JSON parsing | `registry.rs` quality/RH clamps | `benign` | info | | Air humidity RH clamp | `air_boundary.rs` | `benign` | info | | FD step sizing in tests | `(x.abs()*1e-6).max(1e-3)` | `benign` | info | ### Related NFR9 debt (not clamp killers — do not fix in 0.1–0.4 regularization stories) | Item | Issue | Sev | |------|-------|-----| | `heat_exchanger/exchanger.rs:854-897` | 4-port Jacobian is **100% central FD** in production | info (NFR9) | | `screw_economizer_compressor.rs` | Incomplete analytic Jacobian rows | info | | `python_components.rs` | Empty `jacobian_entries` stubs | info | | Category-B solver tests (`jacobian_freezing`, inverse calibration) | Deferred per `plans/audit-2026-07-remediation-plan.md` | info | --- ## Harness usage (`entropyk_components::jacobian_fd`) Central FD vs analytic at one state point: ```rust use entropyk_components::jacobian_fd::{ check_jacobian_health, JacobianFdConfig, }; let report = check_jacobian_health(&component, &state, JacobianFdConfig::default())?; assert!( report.has_zero_gradient_killers(), "expected clamp/zero-gradient signature, got clean report" ); // After Stories 0.2–0.4 fixes, Story 0.5 flips the gate to: // assert!(report.is_clean()); ``` ### Defaults (aligned with condenser FD tests) | Constant | Default | |----------|---------| | `rel_epsilon` | `1e-6` | | `h_floor` | `1e-3` | | `rel_tol` | `1e-4` | | `analytic_atol` | `1e-12` | | `fd_informative` | `1e-8` | **Do not** call `JacobianMatrix::numerical` (solver) from this harness — that API is **forward** difference for Newton fallback. Integration smoke tests: `crates/components/tests/jacobian_health_sweep.rs`. --- ## Story 0.5 CI gate (future) 1. Reuse `check_jacobian_health` over per-component physical-domain grids (ΔP≤0, quality 0/1, opening 0/1, dome edges, actuator clamps). 2. Fail CI on new `zero_gradient_killers` or mismatches outside an allow-list that shrinks to empty after 0.2–0.4. 3. Commit a per-component / per-region health report artifact (extend this file or generate JSON beside it). --- ## Deferred work (explicit) | Work | Owner | |------|-------| | Standardize / document `smooth_*` ε guidance; declare `flow_regularization` as HX reference | Story **0.2** — **landed** (`docs/components/phantom-gradient-regularization.md`; derivatives in `crates/core/src/smoothing.rs`) | | Valve ΔP≤0 phantom + EXV opening `smooth_clamp` | Story **0.3** | | Quality / condenser–evaporator state clamps + MSH singularity | Story **0.4** | | Full-envelope CI Jacobian health gate | Story **0.5** | | Replace production FD Jacobian in generic 4-port HX | Separate NFR9 / remediation track | --- ## Severity ranking summary 1. **P0 (fix in 0.3–0.4):** valve ΔP zero-gradient; EXV opening clamps; two-phase quality clamps; condenser fan/flood clamps. 2. **P1:** sat-domain pressure clamp; screw eco/slide clamps when unknowns; zivi hard edges. 3. **P2 / info:** correlation floors, config clamps, NFR9 FD-in-production debt.