Files
Entropyk/docs/audits/heat_exchanger_architecture_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

388 lines
26 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.
# Heat-Exchanger Architecture Audit (Phase A)
**Date:** 2026-07-16
**Scope:** Full Entropyk workspace, focused on the heat-exchanger family and the
`FloodedEvaporator` upgrade target.
**Method:** Code reading + live reproduction of `cargo fmt/check/clippy/test` on
`C:\Users\serameza\impact\dev\Entropyk-main\entropyk`. All claims cite `file:line`.
No code was modified during this audit.
Legend for evidence: **[code]** = confirmed by repository source; **[build]** = confirmed
by running a command this session; **[carrier]** = confirmed by authorized Carrier source
(none used in this audit — no Carrier data was provided); **[assumption]** = engineering
assumption requiring validation.
---
## 1. Workspace and architecture inventory [code]
### 1.1 Workspace members (`Cargo.toml`)
12 workspace members: `crates/{core, components, entropyk, fluids, solver, cli, vendors}`,
`demo`, `bindings/{python, c, wasm}`, `tests/fluids`. Version `0.1.0`, edition 2021,
workspace deps `thiserror`, `serde`, `serde_json`.
### 1.2 Crate roles and dependencies (relevant subset)
| Crate | Role | CoolProp coupling |
|-------|------|-------------------|
| `entropyk-core` | Physical types, smoothing, calibration. **Does NOT own the `Component` trait** (see 1.4). | none |
| `entropyk-fluids` | `FluidBackend` trait + backends (CoolProp, tabular, incompressible, damped, cached, dll, test). | optional features `coolprop`, `dll`; `default = []` |
| `entropyk-components` | `Component` trait + all thermodynamic components + HX family. | depends on `entropyk-fluids` (default, no coolprop) |
| `entropyk-solver` | `System` (petgraph topology, stride-3 state `[m,P,h]` per edge), Newton/Picard/Fallback, `ThermalCoupling`. | `coolprop` feature (default off) |
| `entropyk` (facade) | Re-export library. | **forces `entropyk-fluids/coolprop` unconditionally** (`crates/entropyk/Cargo.toml:16`) |
| `entropyk-cli` | CLI factory `create_component`, config IR v1/v2, subsystem templates. | **forces `entropyk-fluids/coolprop` unconditionally** (`crates/cli/Cargo.toml:23`) |
| `entropyk-python` | PyO3 bindings. | optional; **broken on Python 3.14** (see §4.4-B1) |
| `entropyk-vendors` | Compressor data (Bitzer/Copeland/Danfoss) + SWEP BPHX data. | none |
### 1.3 State vector and topology [code]
- `System` holds `petgraph::Graph<Box<dyn Component>, FlowEdge, Directed>`
(`crates/solver/src/system.rs:198`); nodes = components, edges = flow connections.
- State vector is **stride-3**: `[m₀,P₀,h₀, m₁,P₁,h₁, …]` per edge
(`system.rs:192`). `FlowEdge` carries `state_index_{m,p,h}`, `source_port`,
`target_port`, `mass_flow_branch_id` (`system.rs:134`).
- Series branches share one `state_index_m` via `presolve_mass_flow_topology`
using `Component::flow_paths()` (default empty).
- `MAX_CIRCUIT_ID = 4`**at most 5 refrigerant circuits (IDs 04)** (`system.rs:30`).
- Cross-circuit **edges are hard-rejected** (`TopologyError::CrossCircuitConnection`)
— verified by test (`crates/solver/tests/multi_circuit.rs:111`).
### 1.4 `Component` trait — lives in `components`, not `core` [code]
Defined at `crates/components/src/lib.rs:478`. Required methods:
```rust
fn compute_residuals(&self, state: &StateSlice, residuals: &mut ResidualVector) -> Result<(), ComponentError>;
fn jacobian_entries(&self, state: &StateSlice, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError>;
fn n_equations(&self) -> usize;
fn get_ports(&self) -> &[ConnectedPort];
```
Supporting types: `type StateSlice = [f64];` (`lib.rs:221`),
`type ResidualVector = Vec<f64>;` (`lib.rs:227`),
`JacobianBuilder` = **flat COO triplet accumulator** `add_entry(row,col,value)`
(`lib.rs:235,271`) — not a blocked/sparse matrix. Relevant provided methods:
`flow_paths()`, `set_port_context(&mut self, &[(m,p,h)])`, `set_system_context`,
`energy_transfers`, `measure_output`, `signature`.
### 1.5 Strong physical types available [code]
In `crates/core/src/types.rs` (re-exported `lib.rs:61`): `Pressure`, `Temperature`,
`Enthalpy` (**specific, J/kg — no separate `SpecificEnthalpy`**), `MassFlow`
(with `regularized()` clamp at `1e-12`, `types.rs:334,382`), `Power`, `Concentration`,
`VolumeFlow`, `RelativeHumidity`, `VaporQuality`, `Entropy`, `ThermalConductance` (W/K),
`CircuitId(pub u16)` (hashable).
**Missing types needed by the mission (§6.1):** `Length`, `Mass`, `Area`,
`TemperatureDifference`, `ThermalConductivity`, `Volume`. Geometry is currently raw `f64`
inside BPHX/FinCoil components. (`TemperatureDelta` exists only in
`crates/fluids/src/types.rs:13`, not in core.)
### 1.6 Smoothing / regularization toolkit already present [code]
`crates/core/src/smoothing.rs`: `smoothstep`/deriv (C¹), `smootherstep` (C²),
`cubic_blend`/`quintic_blend`, `smooth_max`/`smooth_min` (C^∞), `smooth_abs`/deriv,
`smooth_clamp`. **All carry verified analytic derivatives.** This is the foundation the
zero-flow regularization (mission §7.2) should reuse, not reimplement.
---
## 2. Heat-exchanger inventory [code]
`crates/components/src/heat_exchanger/` — 23 files. Grouped below; line counts in
parentheses. Detailed per-file fields (purpose/API/ports/equations/jacobian/geometry/
zero-flow/multi-circuit/dp/tests/defects) were captured and are summarized; the most
decision-relevant items are inlined.
### 2.1 Framework / strategy layer
| File (lines) | Role | Key facts |
|--------------|------|-----------|
| `model.rs` (206) | Object-safe `HeatTransferModel` trait + `FluidState`. | `n_equations()` per impl; UA is a single `f64`. Jacobian is the wrapper's job. |
| `eps_ntu.rs` (349) | ε-NTU model. | **`ExchangerType::ShellAndTube{passes}`: `passes` is bound to `_` and unused** (`eps_ntu.rs:168-174`). The closed form used is actually the *cross-flow both-unmixed* formula, mislabelled. Zero-capacity guard: `c_min<1e-10 → Q=0` (`:204`). |
| `lmtd.rs` (403) | LMTD model. | `ShellAndTube1_2` correction factor hard-coded `0.9` (`:53`). |
| `exchanger.rs` (1431) | Generic 4-port `HeatExchanger<M>`. | **Jacobian is finite-difference** by default (`:816-865`), re-evaluates backend property calls per column. Doc says 3 eqns, returns 2 (`:73-79` vs `:867`). `flow_paths=[(0,1),(2,3)]` only. |
| `mod.rs` (108) | Barrel re-export. | — |
### 2.2 Refrigerant-side UA/ε-NTU components
| File (lines) | Geometry | Jacobian | DP | Zero-flow | Multi-circuit | Notable defects |
|--------------|----------|----------|----|-----------|---------------|-----------------|
| `condenser.rs` (2169) | UA only | analytic (+ FD for `dT_cond/dP`, `:1265`) | optional lumped `quadratic_drop` | `c_sec<=1e-10→ε=0` | same-branch only | `p_in>10_000` switch (×4); **`unwrap_or(inlet_p_idx)` mass→pressure column fallback** (`:1057,1231`); `assert!` in `with_flooded_head_pressure` (`:572`) |
| `evaporator.rs` (1897) | UA only | analytic (+ FD `dT_evap/dP`) | optional `quadratic_drop` | `c_sec` guard | same-branch only | same `p_in` switch + `unwrap_or(inlet_p_idx)` (`:851,1030`); heavy duplication with condenser (`SecondaryJacCtx`) |
| `flooded_evaporator.rs` (939) | **UA only** | analytic + **FD `dT_sat/dP`** (`:568-572`) | **none** | `c_sec<=1e-10→ε=0` | **none** | see §3 (target component) |
| `flooded_condenser.rs` (704) | UA only | delegates to inner (FD) | none | inner | none | **`n_equations=3` but only 2 non-trivial residuals** (`:247-266`); **interior mutability via `&self`** (`Cell`, `:45-46,269,280,283`); no `set_system_context` override; ~20 undocumented public items |
### 2.3 Air-side / coil components
| File (lines) | Geometry | Notes |
|--------------|----------|-------|
| `condenser_coil.rs` (278) | inherits | Thin wrapper enforcing Air cold side; `name()` hard-coded. |
| `evaporator_coil.rs` (292) | inherits | Mirror of above (Air hot side). |
| `air_cooled_condenser.rs` (302) | approach-based | `T_cond = OAT + approach` (`:78`); UA unused in solver. |
| `fin_coil_condenser.rs` (886) | **`CoilGeometry` (11 fields)** | **Richest geometry model** — Chang & Wang louvered-fin, fin efficiency, UA from geometry (`:517-597`); UA clamped `[1e2,1e6]` (`:596`); refrigerant resistance lumped `×0.92`. |
| `mchx_condenser_coil.rs` (498) | variable UA | `UA_eff=UA_nom·(ρ/ρ_ref)·fan_speed_ratio^n` (`:234`); `coil_index` metadata only (no bank partition). |
### 2.4 Brazed-plate (BPHX) family — **the pattern to reuse**
| File (lines) | Role |
|--------------|------|
| `bphx_geometry.rs` (504) | **Typed geometry**: `BphxGeometry` (plates, dimensions, chevron, dh, area), `mass_flux(m)`, `n_channels=n_plates1`. |
| `correlation_registry.rs` (1290) | **Evidence-aware registry** (no formulas): `CorrelationId`, `CorrelationMetadata{domain,evidence}`, `SelectionContext`, `assess_candidate``CandidateAssessment{InDomain/NearBoundary/Extrapolated}`, `select_correlation`. Every correlation is `EvidenceIncomplete` (enforced by test `:1267`). |
| `bphx_correlation.rs` (1597) | Formula enum mirroring registry ids; `HeatTransferCorrelation` trait; `CorrelationSelector::select_and_compute` checked evaluation; Gnielinski with C¹ laminar→turbulent blend using `entropyk_core::smoothing`. |
| `bphx_exchanger.rs` (846) | UA/DP from geometry+correlation; **interior mutability via `&self`** (`Cell` fields `:61-64,323-328`); DP helper **not wired into residual**. |
| `bphx_evaporator.rs` (865) / `bphx_condenser.rs` (912) | DX variants; read outlet in `compute_residuals(&self)` writing `Cell` state; superheat/subcooling **not enforced as residual equations**. |
### 2.5 Other
| File (lines) | Role |
|--------------|------|
| `moving_boundary_hx.rs` (851) | Zone discretization (SH/TP/SC). **Per-zone HTC hard-coded** (5000/500/1500, `:436-441`); `correlation_selector` stored but **unused** (`:97`); **Jacobian delegates to inner ignoring the UA scale it just computed** (`:219-225`) — real correctness gap. |
| `two_phase_dp.rs` (380) | Friedel DP + lumped `quadratic_drop` (antisymmetric C¹). **Fully analytic, well-tested.** Rich Friedel model **never wired into any residual** — only `quadratic_drop` is. |
| `economizer.rs` (280) | Suction/liquid IHX, delegates to `HeatExchanger<LmtdModel>`, ON/OFF/BYPASS. |
---
## 3. `FloodedEvaporator` — current-state verification (every claim) [code]
Target file: `crates/components/src/heat_exchanger/flooded_evaporator.rs`.
| Mission claim (§2) | Verdict | Evidence |
|---|---|---|
| Wraps `HeatExchanger<EpsNtuModel>` | ✅ | inner field; UA from `EpsNtuModel` fixed at `new(ua)` |
| Nominal constant UA | ✅ | `self.ua()` returns fixed value (`:106,168-170,275`) |
| `Q = ε·C_sec·(T_sec,in T_sat)` | ✅ | `coupled_duty` (`:285-291`); `T_evap` from `evap_temperature(P)` at quality 0.5 (`:267`) |
| `m_ref·(h_outh_in) Q = 0` | ✅ | `residuals[1]` (`:488`) |
| No refrigerant pressure drop | ✅ | `residuals[0] = P_out P_in` (`:486`); no `pressure_drop_coeff` field |
| Optional outlet quality target | ✅ | `quality_control_enabled` adds `r2 = q_actual q_target` (`:445-449,490-496`) |
| Secondary stored as boundary values, not live ports | ✅ | `secondary_inlet_temp_k`, `secondary_capacity_rate` are `Option<f64>` (`:77-78`); never edges |
| Zero secondary capacity → zero effectiveness | ✅ | `effectiveness()` returns 0 if `c_sec<=1e-10 \|\| ua<=0` (`:276-280`) |
| Finite differences for `dT_sat/dP` | ✅ | central FD `dp=p_in·1e-4+100` (`:568-572`) |
| No shell-and-tube geometry / tube count / passes / circuit partition | ✅ | struct carries only UA, fluid ids, indices, secondary scalars (`:57-79`) |
### 3.1 Defects in the current `FloodedEvaporator`
1. **`target_quality = 0.7` hard-coded default** (`:112`, doc `:148`) — the smell the mission §3 flags.
2. **Quality clamped to `[0,1]`** in `compute_quality` (`:375`) and setters (`:150,189`) — hides subcooled/superheated extrapolation from the solver.
3. **`p_in > 10_000.0` magic switch** (`:471,541`) — duplicated across condenser/evaporator/flooded.
4. **`unwrap_or(inlet_p_idx)`** mass-flow → pressure column fallback (`:478,548`) — if no mass index resolves, the energy residual is written against the *pressure* column (mission §16.3).
5. **Quality-control silent degradation**: `residuals[2] = -target_quality` when backend cannot compute quality (`:494,520,526`) — hides config error.
6. **`last_heat_transfer_w`/`last_outlet_quality` are dead state** — declared but never written on the coupled path; `heat_transfer()` always returns the initial `0.0`.
7. **`assert!(ua >= MIN_UA)` in `new`** (`:105`) — zero-panic policy violation (FloodedCondenser offers `try_new`; this one does not).
---
## 4. Current-state verification (reproduction) [build]
Environment: Rust 1.95.0, cargo 1.95.0, Windows MSVC (VS 2022 BuildTools), CMake present,
**Python 3.14** on PATH. Commands run in the working repo on 2026-07-16.
### 4.1 Commands and exact results
| Command | Result | Exit |
|---------|--------|------|
| `cargo fmt --all -- --check` | **PASS** — no diffs | 0 |
| `cargo check --workspace --all-targets --all-features` | **FAIL** | 101 |
| `cargo check --workspace --all-targets --exclude entropyk-python` (default feats) | **FAIL** | 101 |
| `cargo clippy --workspace --all-targets --all-features -- -D warnings` | **NOT RUN to completion** — clippy was not installed (installed during audit); after install, fails on fluids clippy errors + coolprop/pyo3 blockers | — |
| `cargo test --workspace --all-features` | **NOT RUN** — blocked by build failures above | — |
| `cargo test --doc --workspace` | **NOT RUN** — blocked by build failures above | — |
**Per-crate reproducible baseline (crates that do not force CoolProp):**
| Crate | `cargo test -p <crate>` result |
|-------|--------------------------------|
| `entropyk-core` | **PASS** 160 + 38 = 198 tests |
| `entropyk-fluids` (default, no coolprop) | **PASS** 102 + 4 = 106 tests |
| `entropyk-components` | **PASS** 856 + 70 = 926 tests (18 ignored) |
| `entropyk-solver` | **FAIL**`convergence_criteria`: `test_newton_with_criteria_single_circuit` panics at `.expect("Solver should converge")` (`tests/convergence_criteria.rs:292`); other suites pass |
| `entropyk-vendors` | **PASS** 66 + 4 = 70 tests |
### 4.2 Root causes of the workspace-wide failures (all pre-existing)
**B1 — Python bindings (env).** `entropyk-python` pins `pyo3 = "0.23"` (`bindings/python/Cargo.toml:24`), which resolves to 0.23.5 (max Python 3.13). The installed interpreter is **Python 3.14**, so `pyo3-ffi` build-script exits 1: `configured Python interpreter version (3.14) is newer than PyO3's maximum supported version (3.13)`.
Fixes: install Python 3.13; or `PYO3_USE_ABI3_FORWARD_COMPATIBILITY=1`; or upgrade PyO3.
**B2 — CoolProp from-source build (build).** `crates/entropyk` and `crates/cli` force
`entropyk-fluids/coolprop` *unconditionally* (`crates/entropyk/Cargo.toml:16`,
`crates/cli/Cargo.toml:23`). `entropyk-coolprop-sys/build.rs` **always recompiles CoolProp
from source via CMake** because `vendor/coolprop/CMakeLists.txt` exists (`build.rs:32`),
and **ignores the prebuilt static lib** at
`vendor/coolprop/install_root/static_library/Windows/64bit_MSVC_19.44.35227.0/CoolProp.lib`.
The CMake `generate_headers` custom step fails: `error MSB8066: la build personnalisée de
'…generate_headers.rule' s'est arrêtée. Code 1` (MSBuild). The build **appeared to work
once** this session (`cargo check -p entropyk-fluids --features coolprop` finished in
30.5 s and 115 tests passed) only because cached artifacts from a prior session were
present; once that cache was invalidated the from-source header regeneration fails.
**Consequence:** a clean checkout (`cargo clean && cargo build`) does NOT build; the
facade, CLI, and any CoolProp code path are unbuildable once the cache is gone.
**B3 — Clippy (lint).** Two deny-by-default clippy errors in
`crates/fluids/src/tabular/interpolate.rs:124` and `:131`
(`unnecessary '>= y + 1'`) block clippy on every fluids-dependent crate.
**B4 — Components `http` feature + integration test crate (code).** Under `--all-features`:
`entropyk-components` `http` feature → E0308 at `external_model.rs:675`; `tests/fluids`
(`fluids-integration-tests`) → 89 errors (E0433) under its `coolprop` feature.
**B5 — One red solver test (code).** `test_newton_with_criteria_single_circuit` fails
(convergence expectation), independent of CoolProp.
> None of B1B5 was introduced by this audit. They confirm the warning in mission §4.4
> that the "historical coherence report is stale". **They must be resolved before the
> Phase BF implementation can be validated workspace-wide.**
---
## 5. Equation / unknown audit (per operating mode) [code]
### 5.1 `FloodedEvaporator` (single circuit, current)
Unknowns on its 2 refrigerant edges: inlet `(m,P,h)` + outlet `(P,h)` — outlet mass flow is
the same branch as inlet (series) so `m_out = m_in`. Effectively unknown: **m_in, P_in, h_in,
P_out, h_out** (5), with secondary `T_sec,in` and `C_sec` imposed as boundary scalars.
Equations: `n_equations = 2 (+1 if quality_control)` (`:443-450`):
- r0: `P_out P_in` (no DP)
- r1: `m_ref·(h_out h_in) Q`, `Q = ε·C_sec·(T_sec,in T_sat(P_in))`
- r2 (optional): `q_actual q_target`
### 5.2 Mode-by-mode DoF assessment for the *current* model
| Mode | Behavior | Under/over-determined? |
|------|----------|------------------------|
| Both sides flowing | 2 eqns (or 3), 5 unknowns → completed by upstream/downstream component equations + boundary `C_sec,T_sec` | Determined within a closed cycle |
| `C_sec = 0` (secondary zero) | `effectiveness→0`, `Q→0`; r1 becomes `m·(h_outh_in)=0`. **No equation constrains `h_out` independently** — outlet enthalpy floats unless an upstream/downstream component closes it | **At risk of underdetermination** if nothing else fixes `h_out` |
| Quality control ON | r2 adds an `h_out` constraint via `q_actual(h_out) = q_target` | Restores DoF |
| Component OFF / BYPASS | not implemented for FloodedEvaporator (no `OperationalState` handling visible) | Gap |
| Flow reversal during Newton | `m` may go negative; model uses `m` directly in r1 (sign carried) but `T_evap`/`effectiveness` are flow-magnitude independent → finite, but **no antisymmetric DP** since DP is absent | Finite but physically thin |
### 5.3 Multi-circuit modes — **not representable today**
The current `FloodedEvaporator` has exactly one inlet edge and one outlet edge. The solver
**rejects cross-circuit edges**, and the only inter-circuit heat mechanism is
`ThermalCoupling` (`crates/solver/src/coupling.rs:52`) which transfers **duty via an external
Q unknown**, never a shared fluid volume. Therefore none of these modes are representable:
- shared-vessel two refrigerant circuits + one water circuit (mission §5.1);
- one circuit ON while the other is OFF in the same vessel;
- independent refrigerant mass/energy per circuit with a common water energy balance.
The existing `Drum` (`components/src/drum.rs`) is a 4-port **single-circuit** separator
(`n_equations` 8 legacy / 4 edge-coupled, `:333-338`); it does **not** override
`flow_paths`/`set_port_context` and cannot model a shared multi-circuit vessel.
### 5.4 `tests/multi_circuit.rs` does NOT validate shared-vessel thermodynamics [code]
Read in full (233 lines). It uses a `RefrigerantMock` whose `compute_residuals` **always
returns 0.0** (`:23-26`). Its 4 tests assert only: circuit counting, cross-circuit edge
rejection, max-5-circuits rejection, and one `ThermalCoupling` UA·ΔT arithmetic check
(`coupling_residuals([(350,300)]) ≈ 50000 W`). **No real dual-circuit HX residuals, no shared
vessel, no converged coupled solve.** Any claim of multi-circuit HX coverage from this file
is false (mission §16.13).
---
## 6. Cross-cutting defect catalogue [code]
| Pattern | Locations | Severity |
|---|---|---|
| `assert!` in non-test constructors (zero-panic violation) | `lmtd.rs:101`, `eps_ntu.rs:88`, `flooded_evaporator.rs:105`, `flooded_condenser.rs:70`, `condenser.rs:572`, `bphx_condenser.rs:141`, `mchx_condenser_coil.rs:116-117` | Medium |
| Hard-coded `target_quality=0.7` | `flooded_evaporator.rs:112` | Low |
| `p_in > 10_000.0` magic mode switch | `condenser.rs:1050,1164,1224,1401`; `evaporator.rs:844,947,1023,1149`; `flooded_evaporator.rs:471,541` | Medium |
| `m_idx = … .unwrap_or(inlet_p_idx)` (mass→pressure column) | `condenser.rs:1057,1231`; `evaporator.rs:851,1030`; `flooded_evaporator.rs:478,548` | **High** |
| Quality clamped to `[0,1]` | `flooded_evaporator.rs:150,189,375`; `moving_boundary_hx.rs:474`; `two_phase_dp.rs:108,132,151` | LowMedium |
| Interior mutability via `&self` in residual methods | `flooded_condenser.rs:45-46,269,280,283`; `bphx_exchanger.rs:61-64,323-328`; `bphx_evaporator.rs:94,348`; `bphx_condenser.rs:98-99` | Medium |
| `n_equations` vs actual residual mismatch | `flooded_condenser.rs:247-266`; `moving_boundary_hx.rs:176`; `exchanger.rs:73-79` vs `:867` | Medium |
| Jacobian ignores model state-dependence | `moving_boundary_hx.rs:219-225` | High (that component) |
| `passes` unused in `ShellAndTube` ε-NTU | `eps_ntu.rs:168-174` | Medium |
| Rich Friedel DP never wired into residuals | `two_phase_dp.rs` vs `condenser.rs:668-676` | Medium |
| Duplicated `SecondaryJacCtx`/helpers | `condenser.rs``evaporator.rs`; `compute_quality` ×3 | Medium |
| `ShellAndTube1_2` F=0.9 hard-coded | `lmtd.rs:53` | Low |
| `UA` clamping `[1e2,1e6]` | `fin_coil_condenser.rs:596` | Low |
| `http` feature E0308 | `external_model.rs:675` | Medium (gatekeeps `--all-features`) |
---
## 7. Fluid backend — derivative capability (mission §9.2) [code]
The `FluidBackend` trait (`crates/fluids/src/backend.rs:41`) exposes **point queries only**:
`property`, `full_state`, `bubble_point`, `dew_point`, `temperature_glide` (= `T_dewT_bubble`,
two point queries), `saturation_pressure_t`, `saturation_enthalpy_t`.
**`dT_sat/dP` is NOT exposed by any backend.** The `CoolPropBackend`
(`crates/fluids/src/coolprop.rs:28`) uses only the single-output `PropsSI` FFI shims
(`props_si_pt/ph/ps/px/tq`); it **does not call CoolProp's `AbstractState` derivative API**
(no `first_partial_deriv`). `DampedBackend` smooths property *values* near the critical
point but adds no derivative method.
**Consequence:** every analytic Jacobian needing `dT_sat/dP` must use a central
finite-difference fallback, exactly as `FloodedEvaporator` already does
(`:568-572`, 3 CoolProp calls per Jacobian eval). Phase B should: (a) add a thin, documented
backend method `saturation_temperature_derivative_dp` implemented as central FD over the
backend (single source of truth, cacheable), and (b) optionally wire CoolProp's analytic
derivative later. This satisfies mission §9.3 (explicit, documented fallback).
---
## 8. Bindings & examples coverage [code]
- **Python:** `FloodedEvaporator` is **not exposed**; pyo3 0.23 broken on Python 3.14 (B1).
- **C:** `FloodedEvaporator` **not exposed**; C bindings are largely `SimpleAdapter` stubs
(e.g. `entropyk_compressor_create` ignores AHRI coeffs, `bindings/c/src/components.rs:107-115`).
- **WASM:** `FloodedEvaporator` **not exposed**.
- **CLI factory** (`crates/cli/src/run.rs:2432`): `FloodedEvaporator` arm exists (`:2590`)
but reads only `ua`, `target_quality`, `refrigerant`, `secondary_fluid`, secondary stream —
**no geometry, no circuit count, no shared-vessel config**. Not wired into the factory at
all: `FloodedCondenser`, `Economizer`, `MchxCondenserCoil`, `ReversingValve`,
`PumpController`.
- **Examples (18):** `FloodedEvaporator` is used in **none**. `chiller_r134a_flooded_headpressure.json`
is misnamed (uses DX `Evaporator` + a condenser parameter). `chiller_r134a_dual_circuit_staging.json`
is two **independent** circuits with separate water loops — **no shared vessel**.
- **Vendor data:** Copeland/Danfoss/Bitzer compressors + SWEP BPHX only. **No Carrier data,
no shell-and-tube / flooded-HX selection data.**
---
## 9. Findings directly driving the implementation phases
1. **No shared-vessel / multi-circuit HX primitive exists** — must be built (mission §6.4, Phase D).
Cross-circuit edges are rejected; only duty-transfer `ThermalCoupling` exists.
2. **Geometry domain types are missing from core** (`Length`, `Mass`, `Area`,
`TemperatureDifference`, `ThermalConductivity`, `Volume`) — Phase B prerequisite.
3. **The BPHX registry/geometry/correlation pattern is the reuse target**, not a rewrite:
`correlation_registry.rs` + `bphx_correlation.rs` + `bphx_geometry.rs` + `two_phase_dp.rs`
form a clean, evidence-aware, single-source-of-truth pattern to extend to shell-and-tube
flooded boiling/condensation (mission §6.2).
4. **Zero-flow regularization should build on `entropyk_core::smoothing`** (`smooth_abs`,
`cubic_blend`, `smooth_max`) rather than hard `if m<ε` branches (mission §7.2).
5. **Backend `dT_sat/dP` is absent** — add a documented central-FD method in Phase B (§7).
6. **Baseline is red workspace-wide** (B1B5). These must be unblocked before Phase BF can
be validated. Recommended pre-Phase-B fixes:
- B2 (highest leverage): make `coolprop-sys` prefer the prebuilt `install_root` lib, or
fix the `generate_headers` step, so the facade/CLI build reproducibly.
- B3: fix the two clippy lints in `tabular/interpolate.rs`.
- B5: fix or `#[ignore]` the `convergence_criteria` red test with a tracked issue.
- B1: standardize on Python 3.13 for the bindings (Phase F, but unblock early).
7. **`ShellAndTube` ε-NTU `passes` is decorative** — Phase B/C must implement a real
P-NTU/multipass effectiveness or prevent its use for unsupported arrangements (mission §8.4).
8. **Flooded evaporator outlet/control semantics** (mission §3) are wrong by default
(`target_quality=0.7`) — Phase C replaces with an explicit `FloodedEvaporatorControl` enum.
---
## 10. Open questions requiring Carrier/product input (not answered here)
No Carrier source documents were provided for this audit, so the following remain
**Unknown** and must NOT be guessed (mission §1.4, §13):
- 61XWHVZE flooded-evaporator geometry (shell ID, tube count per pass, active length per
refrigerant circuit, tube type/enhancement, water-box layout, refrigerants).
- 30XF-Z module/pass arrangement details.
- Actual water-path layout through the shared-vessel refrigerant partitions
(mission §6.4 `SharedSecondaryLayout`) — configurable until evidenced.
- Validation datasets for any product.
These are captured in the companion `montluel_machine_coverage.md` with `Unknown` status.