# 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_cond−T_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 ~1–3 % 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 (~1–2 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_ref−y) − (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 predictor–corrector
  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`** — 1–4 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).
