Files
Entropyk/docs/audits/jacobian-killing-pattern-audit.md
sepehr 88620790d6 Complete Story 0.2 phantom-gradient regularization standard.
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>
2026-07-18 16:33:48 +02:00

149 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.20.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` (~L396445) 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.10.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.20.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.20.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 / condenserevaporator 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.30.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.