Add analytic derivatives and C1 tests for smooth_max/min/clamp, document epsilon/width guidance, and declare flow_regularization as the HX reference pattern without changing component physics. Co-authored-by: Cursor <cursoragent@cursor.com>
8.5 KiB
8.5 KiB
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_flowalready forcesdp = ΔP.max(0), so ṁ=0 andvalve_mass_flow_dp_upreturns 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_clampis the intended replacement (entropyk_core::smoothingalready exists). - EXV phantom ΔP:
DP_FLOOR(~L396–445) is a partialalready-regularizedmitigation 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— onlyvalve_mass_flow_dp_upexists today (lib.rsre-export). Catalogue gap severityinfo; inventingdp_downis 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:
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)
- Reuse
check_jacobian_healthover per-component physical-domain grids (ΔP≤0, quality 0/1, opening 0/1, dome edges, actuator clamps). - Fail CI on new
zero_gradient_killersor mismatches outside an allow-list that shrinks to empty after 0.2–0.4. - 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
- P0 (fix in 0.3–0.4): valve ΔP zero-gradient; EXV opening clamps; two-phase quality clamps; condenser fan/flood clamps.
- P1: sat-domain pressure clamp; screw eco/slide clamps when unknowns; zivi hard edges.
- P2 / info: correlation floors, config clamps, NFR9 FD-in-production debt.