# 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` 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).