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>
388 lines
26 KiB
Markdown
388 lines
26 KiB
Markdown
# 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 0–4)** (`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_plates−1`. |
|
||
| `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_out−h_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 B1–B5 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 B–F 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_out−h_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` | Low–Medium |
|
||
| 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_dew−T_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** (B1–B5). These must be unblocked before Phase B–F 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.
|