Files
Entropyk/docs/audits/jacobian-killing-pattern-audit.md
sepehr 5bd180b5b8
Some checks failed
CI / check (push) Has been cancelled
Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 16:35:31 +02:00

142 lines
8.8 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` — ΔP≤0 / opening clamps / `dp_up` hard zero | `already-regularized` | info | Story **0.3 landed**`smooth_max` ΔP + `smooth_clamp` opening + `valve_mass_flow_dp_down` |
| `crates/components/src/isenthalpic_expansion_valve.rs` — orifice opening clamps + ΔP early-out | `already-regularized` | info | Story **0.3 landed** — shared `smooth_clamp` / `smooth_max` in residual **and** Jacobian |
| `crates/components/src/heat_exchanger/two_phase_dp.rs` — quality in `homogeneous_density` / `friedel_multiplier` | `already-regularized` | info | Story **0.4 landed**`smooth_clamp` quality (`QUALITY_WIDTH=1e-2`) |
| `crates/components/src/heat_exchanger/condenser.rs` — fan φ / flood λ actuators | `already-regularized` | info | Story **0.4 landed**`smooth_clamp` + Jacobian `*_derivative` chain rule |
| `crates/components/src/heat_exchanger/two_phase_dp.rs` — MSH Hermite blend near x→1 | `already-regularized` | info | Story **0.4 verified** — FD-bounded `∂g/∂x` across `X_BLEND=0.90` |
### Evidence notes
- **Valve / EXV (Story 0.3):** `dp_eff = smooth_max(ΔP, 0, k)` with `k = 1e3` Pa; opening via `smooth_clamp(..., width=1e-2)`; `valve_mass_flow_dp_up` / `dp_down` are chain-rule consistent; EXV residual and Jacobian share the same expression (no Jacobian-only `DP_FLOOR` split).
- **HX (Story 0.4):** quality helpers use `smooth_clamp`; condenser fan/flood residual+Jacobian share `smooth_clamp` / `smooth_clamp_derivative`; MSH singularity blend kept and FD-verified. Evaporator has no fan/flood actuator clamps (consumes regularized `two_phase_dp`).
---
## 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` | `smooth_max` ΔP + `smooth_clamp` opening + `dp_up`/`dp_down` | `already-regularized` | info | Story 0.3 landed |
| `isenthalpic_expansion_valve.rs` orifice | shared `smooth_clamp` / `smooth_max` residual+Jacobian | `already-regularized` | info | Story 0.3 landed |
| `two_phase_dp.rs` quality helpers | `smooth_clamp` quality | `already-regularized` | info | Story 0.4 landed |
| `two_phase_dp.rs` `zivi_void_fraction` | hard `x<=0` / `x>=1` branches | `benign` | info | Not on live Newton path (export/tests only) — left as-is in 0.4 |
| `two_phase_dp.rs` `msh_gradient` | Hermite blend near x→1 | `already-regularized` | info | Story 0.4 verified (FD bounded) |
| `condenser.rs` fan / flood | `smooth_clamp` + chain-rule J | `already-regularized` | info | Story 0.4 landed |
| `evaporator.rs` | no fan/flood actuator clamps | `benign` | info | N/A — consumes regularized `two_phase_dp` |
| `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)` | `already-regularized` | info | Tube-ΔP path now uses a C¹ `smooth_clamp` with exposed derivative (`heat_exchanger::tube_dp`, 2026-07-19); the shared helper keeps the hard clamp for its other consumers |
| `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 |
| ~~`condenser.rs` / `evaporator.rs` tube-ΔP momentum partials~~ | **Resolved (2026-07-19):** was whole-ΔP central FD through the backend (~72 CoolProp calls/assembly, straddling clamps/blends); now exact analytic composition in `heat_exchanger::tube_dp` with narrow domain-safe FDs only on individual saturation properties (thin FFI exposes no saturation derivatives). Friedel correlation partials use narrow FD of the pure correlation | info (NFR9) |
---
## 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())?;
// Story 0.5 gate (Epic-0 P0 surfaces):
assert!(report.is_clean());
// Or: assert_jacobian_healthy(&component, &state, cfg, surface, region, &[]);
```
### 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 gate tests: `crates/components/tests/jacobian_health_sweep.rs`.
---
## Story 0.5 CI gate (landed)
1. Reuse `check_jacobian_health` / `assert_jacobian_healthy` over Epic-0 physical-domain grids (ΔP≤0, quality edges, opening 0/1, dome edges, actuator clamps).
2. Fail CI on new `zero_gradient_killers` or mismatches outside an allow-list — Epic-0 P0 allow-list is **empty**.
3. Committed per-surface / per-region report: [`jacobian-health-report.md`](./jacobian-health-report.md) (+ optional JSON twin).
4. Explicit CI step: `cargo test -p entropyk-components --test jacobian_health_sweep` in `.github/workflows/ci.yml`.
---
## 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****landed** |
| Quality / condenserevaporator state clamps + MSH singularity | Story **0.4****landed** |
| Full-envelope CI Jacobian health gate | Story **0.5****landed** (see [`jacobian-health-report.md`](./jacobian-health-report.md)) |
| Replace production FD Jacobian in generic 4-port HX | Separate NFR9 / remediation track |
---
## Severity ranking summary
1. **P0 (fixed):** valve/EXV (0.3); two-phase quality + condenser fan/flood (0.4).
2. **P1:** sat-domain pressure clamp; screw eco/slide clamps when unknowns.
3. **P2 / info:** correlation floors, config clamps, unused `zivi` hard edges, NFR9 FD-in-production debt.