Some checks failed
CI / check (push) Has been cancelled
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>
142 lines
8.8 KiB
Markdown
142 lines
8.8 KiB
Markdown
# 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` — Δ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.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 |
|
||
| ~~`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 / condenser–evaporator 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.
|