Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
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>
This commit is contained in:
2026-07-19 16:35:31 +02:00
parent 88620790d6
commit 5bd180b5b8
1363 changed files with 101041 additions and 58547 deletions

372
HANDOFF.md Normal file
View File

@@ -0,0 +1,372 @@
# Entropyk — Development Handoff
> **Purpose.** This document hands off the current state of Entropyk to a new
> coding tool / session. It summarizes **what was done**, **why it was done that
> way** (key technical decisions), and **what remains to do**, so work can resume
> without loss of context.
>
> **Conventions (from `AGENTS.md`).** Communicate with the user (**Sepehr**) in
> **French**; write all code / docs / commit messages in **English**. Rust package
> manager = **cargo**; Python = **uv** (venv at repo-root `.venv`).
>
> **Last updated:** 2026-07-07
---
## 1. Project overview
**Entropyk** is a Rust steady-state thermodynamic-cycle simulation library for
refrigeration, heat pumps and HVAC. The goal (Sepehr's) is to make it a
best-in-class HVAC **design + control** simulator — **very accurate and very
fast** — to replace an internal tool. Hard rule from the user: **no "bricolage"**
(no hacks such as auto-injecting design-point temperatures). Every model must be
**genuinely physically coupled** and must **respect the First Law**.
### Workspace layout
```
entropyk/
├── crates/
│ ├── core/ # Strong types (Pressure, Temperature, Enthalpy…), smoothing primitives
│ ├── components/ # Thermodynamic components (Compressor, HX, valves, ReversingValve…)
│ ├── fluids/ # Fluid backends (CoolProp, tabular, incompressible)
│ ├── solver/ # Newton-Raphson / Picard / Homotopy, System topology, DoF, controls
│ ├── entropyk/ # Main library facade (System, SystemBuilder, rating)
│ ├── cli/ # CLI: run / qualify / rate / scop / seer commands
│ ├── vendors/ # Vendor data parsers
│ └── bindings/ # Python (PyO3), C (cbindgen), WASM — DO NOT build in this env
├── apps/web/ # Dymola-style web UI (SvelteKit + Vitest)
├── _bmad/ , _bmad-output/ # BMAD methodology workflows + artifacts
└── AGENTS.md # Project instructions (READ FIRST)
```
---
## 2. What has been DONE (verified)
### 2.1 Genuine secondary-side coupling of heat exchangers (ε-NTU)
The core goal: HX respond to **secondary** (water / brine / air) temperature & flow.
- `Condenser`, `Evaporator` and `FloodedEvaporator` brought to the same coupled
ε-NTU level. Secondary fields (`secondary_inlet_temp_k`, `secondary_capacity_rate`),
builders `with_secondary_stream` / `set_secondary_stream`, helpers `coupled_ready`,
`coupled_duty`, `effectiveness`, `cond_temperature` / `evap_temperature`.
- Coupled residual path active only when a secondary stream is set AND indices
resolved AND P_in > 10 kPa (otherwise legacy path — **backward compatible, DoF
unchanged**). `Q = ε·C_sec·ΔT`, `ε = 1 exp(UA/C_sec)` (C_min = C_sec for a
phase-changing refrigerant). Analytical Jacobian (dT/dP via central FD).
- Files: `crates/components/src/heat_exchanger/{condenser,evaporator}.rs`.
### 2.2 Emergent pressures (end-to-end coupling) — the "vrais modèles échangeurs" epic
Condensing / evaporating **pressures are now emergent** from the HX↔secondary
balance instead of fixed by compressor design points. Opt-in `emergent_pressure`
mode on each component:
- **Compressor** (`comp-emergent`): mass-flow closure `ṁ = ρ_suc·V_s·N·η_vol`;
r0 = ṁ_state ṁ_calc.
- **Condenser** (`cond-emergent`) / **Evaporator** (`evap-emergent`): 3 thermo eqs
(ΔP, energy balance, outlet closure), `n_equations` +1.
- **EXV** (`exv-emergent`): drops the P_evap fix, keeps only isenthalpic h3=h2,
`n_equations` 1.
- Integration test: 4-component emergent loop converges; varying secondary T/flow
changes COP/capacity; **DoF balanced (9=9)**.
### 2.3 Modular control architecture (arch-1 … arch-6) — ALL DONE
- **arch-1** Saturated-PI controller wired into System DoF (2 residuals + 2 unknowns
per loop; actuator u → CalibIndices, measurement y ← measure_output; co-solved).
- **arch-2** Typed ports `PortKind{Refrigerant, Secondary, Mechanical, Signal}` +
edge validation by kind; Signal ports for control wiring.
- **arch-3** `controls[]` schema + control-block library (Min/Max/Setpoint/Limiter),
CLI design/control command co-solving design + control.
- **arch-4** subsystems/instances hierarchical templates (reusable parameterized
assemblies flattened to graph; multi-circuit A/B).
- **arch-5** unified model IR across CLI / Python / WASM / UI (schema v2 loader,
v1-compatible).
- **arch-6** **Physical actuators — all 6 done**, each with exact analytic Jacobian
(FD only for property derivatives) and CLI wiring + example + integration test:
1. **EXV orifice**`ṁ = Cd·A(θ)·√(2ρΔP)`, opening as bounded solver var.
2. **Fan head-pressure** — air-cooled condenser: secondary air capacity rate scales
with fan speed; fan speed controls condensing pressure. `UA_eff=(1λ)·UA_nom`.
3. **Slide-valve** (screw compressor) — capacity modulation of swept volume.
4. **Liquid-injection** EXV — economized screw, controls discharge temperature
(factor-actuator: closed by a `controls[]` loop, does NOT grow `n_equations`).
5. **Flooded-condenser level** (`cond-level`) — head-pressure via liquid level;
mirrors the fan actuator, mutually exclusive with it (shares `fan_actuator_idx`).
Exact flood Jacobian `∂r1/∂λ = +UA_nom·(T_condT_sec)·exp(UA_eff/C_sec)`.
6. **Four-way reversing valve** — see §2.6.
- **p0a** real constraint measurement via fluid backend (removed mocked
superheat / subcooling / capacity in `system.rs`).
- **p0b** compressor variable-speed capacity actuator (bounded `speed_hz` DoF).
### 2.4 Modular seasonal rating (SEER / SCOP / IPLV / NPLV / ESEER) — DONE
Per Sepehr's request that **the standards be swappable when norms change**:
- `rate.rs` — AHRI 550/590 IPLV/NPLV/ESEER: re-solve base scenario at each part-load
point overriding secondary temps + compressor speed; rayon-parallel points.
- `seasonal.rs` — EN 14825 bin method (SCOP/SEER): per-bin coupled re-solve driven by
outdoor temp, linear building load line, Cd cycling degradation, backup heater.
**Modular climate selection** (`BinClimateStandard`) so climates/standards are
pluggable.
- CLI subcommands `rate`, `scop`, `seer` + example configs + unit & integration tests.
- French `CLI_TUTORIAL` section for these commands.
- **Full documentation written** (per user request).
### 2.5 Circuit on/off staging — DONE
- `CircuitConfig.enabled: bool` (`#[serde(default = "default_true")]`) in
`crates/cli/src/config.rs`. A disabled circuit's components / edges / thermal
couplings are **never added to the `System`** → excluded from the solve entirely
(zero mass flow / duty). Physically-correct steady-state for an unenergized
circuit, **not** a hack.
- 5 circuit-iteration loops in `run.rs` skip-guarded; all-disabled → early rejection.
- Example `chiller_r134a_dual_circuit_staging.json`. Verified e2e: both on = 15.139 kW
/ COP 4.379 (8 edges); circuit B off = 7.569 kW (4 edges) = exactly half.
- 2 CLI integration tests pass.
- NOTE: the component-level `OperationalState{On,Off,Bypass}` in
`state_machine.rs` exists but is **dead code w.r.t. the solver** — staging is done
at CLI/config level, not via that enum.
### 2.6 Four-way reversing valve (arch-6 #6) — DONE
- `crates/components/src/reversing_valve.rs`: `ReversingValve` + `ReversingMode{Cooling,
Heating}`. Deterministic inline 2-port pass-through:
- r0 = P_out (P_in ΔP), `ΔP = ΔP_base + k·ṁ·|ṁ|`
- r1 = h_out (h_in + Δh_leak)
- r2 = ṁ_out ṁ_in (dropped when the inlet/outlet share a mass-flow index)
- `n_equations` = 2 or 3. Exact analytic Jacobian (`∂ΔP/∂ṁ = k·2|ṁ|`).
- **DoF-neutral**: emits a pressure *relation* (not a fix), so it works unchanged
in both fixed-pressure and emergent-pressure cycles.
- **Key modeling decision (honest, no bricolage):** the validated cycle uses
`Δh_leak = 0` (**isenthalpic ΔP-only**). A non-zero single-stream leak enthalpy
injects `ṁ·Δh_leak` with **no matching source** → it broke machine-level First Law
(measured 0.077 kW imbalance, wrong sign on the discharge line). The rigorous
discharge→suction leak needs a **bypass-mass split (two streams)** — deferred to a
future topology layer. The ΔP alone (~0.25 bar valve-body drop) already reproduces
the cited ~13 % COP/capacity penalty **and closes the First Law exactly**. The
`leak_enthalpy` parameter is retained + documented for the future bypass-mass model.
- CLI arm `"ReversingValve" | "FourWayValve"`. Example
`heatpump_r410a_reversing_valve.json` (reversible R410A air→water HP, heating mode,
RV on discharge line, all emergent). **Verified e2e:** converges 5 iters, ΔP
39.068 → 38.788 bar, Q_cool 9.918 kW / Q_heat 15.486 kW / Power 5.491 kW, EER 1.806,
First Law closes. 5 unit tests + 1 CLI integration test
(`test_reversing_valve_imposes_pressure_drop_penalty`).
### 2.7 Fluids accuracy fix — DONE
- `entropyk-fluids :: tabular_backend::tests::test_tabular_vs_coolprop_accuracy` was
failing (embedded `r134a.json` density >1 % off vs CoolProp). Fixed **properly** by
regenerating the table from CoolProp (not by widening epsilon).
### 2.8 Web UI (apps/web) — DONE (earlier turns)
- Secondary ports (`secondary_inlet` / `secondary_outlet`, labeled htf·in / htf·out)
on all HX; `resolveSecondaryStreams` maps a connected boundary source into HX
secondary params and hides secondary edges from the solver graph.
- Dymola-style light UI, grid snap, node rotation. Vitest suite green (61 tests).
### 2.9 Workspace warning cleanup — 0 warnings
- `cargo build --workspace --all-targets` (bindings excluded) emits **0 warnings**.
- Notable real fix: `crates/components/src/brine_boundary.rs` `mod tests` was missing
`#[cfg(test)]` (compiled into normal builds). Migrated deprecated
`TEMP_MASS_FLOW_SEED_KG_S` → `DEFAULT_MASS_FLOW_SEED_KG_S` across 8 solver test files.
---
## 3. Key technical patterns & conventions (carry these forward)
- **Free-actuator DoF pattern**: +1 unknown appended at the END of the state vector.
*Setpoint* actuators (slide / fan / cond-level) grow `n_equations()` by 1; *factor*
actuators (liquid-injection) do NOT — a `controls[]` loop closes them.
- **Two Component conventions coexist**:
- *Port-based* (e.g. `ExpansionValve<Connected>` reads `self.port_inlet`).
- *Index-based* (`IsenthalpicExpansionValve`, `Condenser`, `ReversingValve`):
`set_system_context(offset, &[(m_idx,p_idx,h_idx); edges])`, edge[0]=inlet,
edge[1]=outlet, then reads `state[idx]`. **The emergent CLI cycle uses the
index-based convention.** `IsenthalpicExpansionValve` is the template for new
inline components (its `get_ports()` returns `&[]`).
- **`same_branch_m`**: true when inlet_m_idx == outlet_m_idx (series-branch collapse,
CM1.4) → drop the mass-conservation residual.
- **Jacobian policy**: exact analytic required. FD allowed ONLY for property
derivatives (dT/dP, dρ/dP). Never numerical-diff a whole residual.
- **Emergent vs fixed pressure**: emit pressure *relations* (ΔP), not absolute
pressure *fixes*, to stay DoF-neutral across both topologies.
- **First Law is a hard gate**: `System::cycle_performance` sums every component's
`energy_transfers`; any unsourced enthalpy injection breaks closure. Do not add
energy without a physical source (this is what killed the leak-enthalpy model).
- **Smoothing**: use `entropyk_core::smoothing` (smoothstep, smootherstep,
cubic/quintic_blend, smooth_max/min/abs/clamp) for C¹/C² correlation transitions —
never ad-hoc `if`/`tanh` — to keep the Newton Jacobian continuous.
- **CRLF gotcha**: example JSONs are written with CRLF on Windows; test string-patches
using `\n` fail silently. Split on a distinctive marker (e.g. `"Circuit B"`) then
`replacen`.
- **`edit` tool gotcha**: verify match-arm headers (`"Pipe" => {`) survive edits;
re-view files after risky edits.
- **AGENTS.md integration protocol**: a new component must be integrated across Rust
core+solver, CLI, Python (PyO3), and WASM. Several new components currently have
Rust core + CLI only (bindings pending — see §5).
---
## 4. Build / test / run
```bash
# Build & test core crates (bindings excluded — they DON'T build in this env)
cargo build --workspace --all-targets --exclude entropyk-python --exclude entropyk-c --exclude entropyk-wasm
cargo test -p entropyk-core -p entropyk-components -p entropyk-solver -p entropyk-cli -p entropyk
# Targeted (fast) examples
cargo test -p entropyk-components reversing_valve
cargo test -p entropyk-cli --test single_run test_reversing_valve_imposes_pressure_drop_penalty
cargo run -q -p entropyk-cli -- run --config crates/cli/examples/heatpump_r410a_reversing_valve.json
# Frontend
cd apps/web && npm run test # Vitest
cd apps/web && npm run dev # http://localhost:3000
cargo run --package entropyk-demo --bin ui-server # backend http://localhost:3030
# Python bindings (uv only — never pip)
uv run maturin develop --release
uv run pytest crates/bindings/python/tests/
```
**Guardrails:** NEVER `cargo test --workspace` (pyo3 fails on Python 3.14). CLI
integration tests are slow (~12 min each, real CoolProp solve) and self-skip when
CoolProp is unavailable (`if status != Converged { return; }`). `cargo test` accepts
only ONE positional TESTNAME filter per invocation.
---
## 5. What REMAINS to do (prioritized)
### 5.1 In progress / immediate
- **`adv-override-control`** *(DONE 2026-07-07)* — Modelica-style **override/selector
control** + **offset-free** saturated-PI. (1) Fixed the control law to the
canonical offset-free form `r_y = K(y_refy) (x S(x))` (was `x + S(x)`, a
droop with steady-state offset that forced fragile high gains → the reported
convergence/slowness). (2) New `inverse/override_network.rs`: one actuator
arbitrated by several `Objective{output,setpoint,gain,combine:min|max}` folded
through C^∞ `softMin/softMax` with **exact analytic selector weights** (Jacobian-
smoothing; literature-backed robustness). (3) `SaturatedController` gains opt-in
`objectives`+`alpha`; `system.rs` residual/Jacobian branch for network mode
(`∂E/∂col = Σ w_i(gain_i)∂y_i/∂col`). (4) Config `controls[].objectives[]`+`alpha`
(schema-exposed), `run.rs` wiring. Example `chiller_r134a_exv_override.json`;
unit + FD-Jacobian + CLI integration tests (286 solver tests pass). Spec:
`_bmad-output/implementation-artifacts/spec-advanced-override-control.md`.
ROBUSTNESS (done): warm-started **ADAPTIVE activation continuation**
(`SaturatedController::activation` λ walked 0→1 by `run.rs`: primary-only
baseline → full network). The λ-step now uses the **same predictorcorrector
control as `strategies::homotopy`** — start 0.5, **halve on any failed solve**
(retry from the last good λ), grow on success, floor `MIN_LAMBDA_STEP=5e-3` —
so the walk auto-refines through the hard switching region and takes big steps
elsewhere; each step is seeded from the previous solution. CLI also wires
`solver.max_iterations`/`tolerance` into every solver stage (was capped at 100).
Measured effect: markedly wider convergence range vs the old fixed `[0,0.5,1]`
schedule (aggressive EXV overrides now converge through many more intermediate
λ). Known **physical** limit (not a solver defect): with a `superheat_regulated`
evaporator and the EXV opening as the *only* actuator, forcing capacity well
below the natural duty drives `∂capacity/∂opening → 0` → the plant Jacobian goes
singular (loss of controllability). The robust demonstrated path uses a
well-conditioned actuator (compressor `f_m`), covered by the passing test
`test_override_capacity_limit_takes_authority_via_continuation`.
NEXT (optional): a second actuator (e.g. compressor speed) to restore
controllability for deep EXV duty cuts; optional setpoint **scheduling**
(`Table1D/2D`/`Poly`) per the PPTX/CombiTable patterns.
- **`p0b-exv-opening`** *(DONE 2026-07-07)* — controllable EXV opening regulates
evaporator **superheat**. Evaporator gains an opt-in `superheat_regulated` mode
(drops the emergent outlet-closure `r2` → superheat emerges from the ε-NTU energy
balance); a `controls[]` saturated loop with `actuator.factor:"opening"` drives the
opening (wired to `CalibIndices.actuator`), the EXV orifice residual closes it, DoF
stays square (orifice +1, drop r2 1, saturated +2/+2). Real `measure_output(Superheat)`
added. Example `chiller_r134a_superheat_control.json`, integration + unit tests, +
fail-fast config guards. Spec: `_bmad-output/implementation-artifacts/spec-p0b-exv-opening.md`.
- **`p0c-json-schema`** *(LARGELY DONE — audit before extending)* — the CLI `schema`
command already emits the canonical Model IR JSON Schema (`emit_schema` →
`ScenarioConfig::json_schema()` via `schemars`); `controls[]` (saturated loops) parse
and co-solve; bounded control variables are expressed in `actuator{initial,min,max}`;
`parse_component_output` supports capacity/superheat/subcooling/saturationTemperature/
massflow/pressure/temperature. REMAINING (optional): a dedicated top-level hard-inverse
`constraints[]` array + a distinct `design`/`control` CLI command (today `run` co-solves
everything).
- **`p0c-ui-align`** — make the control schema map cleanly to the `apps/web`
node/port model so the UI can generate/consume it. **(User note: a UI is being
developed in parallel — keep schema UI-friendly.)**
- **`p0-tests-docs`** — unit + integration tests proving control actually regulates
(varying target changes state physically); update docs.
### 5.2 Bindings (AGENTS.md protocol — currently Rust+CLI only)
- **Python (PyO3) + WASM** wrappers for the new components/actuators, notably
`ReversingValve`, flooded-level, slide-valve, liquid-injection.
- ⚠️ **Blocked in this env**: PyO3 max supported Python is 3.13 but the interpreter
is 3.14. Fix by upgrading `pyo3` in `crates/bindings/python` OR pinning the uv env
to 3.13, then verify `uv run maturin develop --release` + `uv run pytest`. The
bindings crates may be absent from the current snapshot — confirm before starting.
### 5.3 Water-cooled machines (workspace `61XW_VFD_EMEA`, `30XF`, `61AQ` references)
- **`wc-single-circuit-e2e`** — compressor + condenser + EXV + flooded-evap with water
loops both sides; validate convergence + secondary sensitivity. *Easiest first-class
target (no air/fan/defrost).*
- **`wc-multicircuit`** — parallel dual-circuit (A/B) compressors/HX with a shared
secondary manifold (`System_2C`).
- **`wc-pass-count`** — 1P/2P/3P evaporator & condenser pass configuration.
- **`wc-slide-valve`** — second actuator alongside speed (`CompressorControl_double`).
*(A screw slide-valve actuator already exists — this is the water-cooled twin-actuator
variant.)*
- **`exv-sst-actuator`** — make EXV opening physically move suction saturated temp /
mass flow so a SaturatedController can regulate SST.
### 5.4 Real-machine feature blocks (30XF / 61AQ parity)
- **`rm-staging`** — 14 module staging state machine (61AQ `StateMachine`). *(Circuit
on/off staging at config level is done; this is the multi-module controller.)*
- **`rm-fan-headp`** — air-cooled condenser SDT limit → fan speed via SaturatedController.
*(A fan head-pressure actuator exists — this is the closed-loop controller variant.)*
- **`rm-exv-orifice`** — `ṁ=Cd·A(θ)·√(2ρΔP)` opening sets emergent P_evap=SST (matches
61AQ `EXVControl_cooling`). *(Orifice actuator exists — this is the SST loop.)*
- **`rm-freecool`** — free-cooling exchanger + glycol routing + OAT changeover (30XF).
- **`rm-defrost`** — defrost seasonal correction block (`Auxiliary.Defrost.Defrost_factor`).
- **`rm-en14511`** — EN 14511 net-from-gross corrections (pump/fan penalties on net
capacity/power).
- **`rm-moistair`** — moist-air (RH / wet-bulb) psychrometric properties for air-side HX.
- **`rm-surrogate`** — MCHX/compressor surrogate-map (performance-map) backend for HX
and compressor (speed/accuracy trade-off).
### 5.5 Web UI
- Circuit **`enabled` toggle** in `apps/web` configBuilder (emit `"enabled": false`).
- Keep the control JSON schema aligned with the UI node/port model (see `p0c-ui-align`).
### 5.6 Bigger / architectural (research-backed)
- Consider a **modular Modelica-style** model/controller composition system (user's
explicit interest) — arch-1…6 laid the groundwork (typed ports, subsystems, unified
IR, controls[]); next is a first-class reusable controller/model library and
possibly acausal connectors.
- Performance: exploit `research-improve` findings (surrogate maps, warm-starting,
Anderson acceleration already present, parallel rating points already via rayon).
---
## 6. Known environment limitations
- **Python bindings do not build here**: PyO3 max 3.13 vs interpreter 3.14.
- **`cargo test --workspace` fails** for the same reason — test crates individually.
- CoolProp is enabled via Cargo features; CLI/solver runs need **no** `--features`
flag. CLI integration tests self-skip without CoolProp.
---
## 7. Key files map
| Area | Path |
| --- | --- |
| Project instructions | `AGENTS.md` |
| Coupled HX | `crates/components/src/heat_exchanger/{condenser,evaporator,flooded_evaporator}.rs` |
| Reversing valve | `crates/components/src/reversing_valve.rs` |
| Inline-component template | `crates/components/src/isenthalpic_expansion_valve.rs` |
| Compressor + actuators | `crates/components/src/isentropic_compressor.rs` |
| System / DoF / performance | `crates/solver/src/system.rs` |
| Controls / saturated PI | `crates/solver/src/inverse/saturated_control.rs` |
| Smoothing primitives | `crates/core/src/smoothing.rs` |
| CLI wiring hub | `crates/cli/src/run.rs` |
| CLI config schema | `crates/cli/src/config.rs` |
| Rating / seasonal | `crates/cli/src/{rate,seasonal,qualify}.rs` |
| CLI integration tests | `crates/cli/tests/single_run.rs` |
| Examples | `crates/cli/examples/*.json` |
| Web UI | `apps/web/src/lib/{componentMeta,configBuilder}.ts` |
| Session artifacts | `_bmad-output/implementation-artifacts/sprint-status.yaml` |
---
## 8. Test status snapshot (at handoff)
- **PASS**: entropyk-core, entropyk-components (792), entropyk-solver (+integration),
entropyk, entropyk-cli, web vitest (61). **0 warnings** on core crates.
- **Fixed**: `entropyk-fluids` tabular-accuracy test (regenerated r134a.json).
- **Not buildable in this env**: `bindings/python` (PyO3 vs Python 3.14).