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>
22 KiB
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,EvaporatorandFloodedEvaporatorbrought to the same coupled ε-NTU level. Secondary fields (secondary_inlet_temp_k,secondary_capacity_rate), builderswith_secondary_stream/set_secondary_stream, helperscoupled_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:
- EXV orifice —
ṁ = Cd·A(θ)·√(2ρΔP), opening as bounded solver var. - Fan head-pressure — air-cooled condenser: secondary air capacity rate scales
with fan speed; fan speed controls condensing pressure.
UA_eff=(1−λ)·UA_nom. - Slide-valve (screw compressor) — capacity modulation of swept volume.
- Liquid-injection EXV — economized screw, controls discharge temperature
(factor-actuator: closed by a
controls[]loop, does NOT grown_equations). - Flooded-condenser level (
cond-level) — head-pressure via liquid level; mirrors the fan actuator, mutually exclusive with it (sharesfan_actuator_idx). Exact flood Jacobian∂r1/∂λ = +UA_nom·(T_cond−T_sec)·exp(−UA_eff/C_sec). - Four-way reversing valve — see §2.6.
- EXV orifice —
- p0a real constraint measurement via fluid backend (removed mocked
superheat / subcooling / capacity in
system.rs). - p0b compressor variable-speed capacity actuator (bounded
speed_hzDoF).
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_TUTORIALsection for these commands. - Full documentation written (per user request).
2.5 Circuit on/off staging — DONE
CircuitConfig.enabled: bool(#[serde(default = "default_true")]) incrates/cli/src/config.rs. A disabled circuit's components / edges / thermal couplings are never added to theSystem→ 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.rsskip-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}instate_machine.rsexists 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.
- r0 = P_out − (P_in − ΔP),
- 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_leakwith 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. Theleak_enthalpyparameter is retained + documented for the future bypass-mass model. - CLI arm
"ReversingValve" | "FourWayValve". Exampleheatpump_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_accuracywas failing (embeddedr134a.jsondensity >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;resolveSecondaryStreamsmaps 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.rsmod testswas missing#[cfg(test)](compiled into normal builds). Migrated deprecatedTEMP_MASS_FLOW_SEED_KG_S→DEFAULT_MASS_FLOW_SEED_KG_Sacross 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 — acontrols[]loop closes them. - Two Component conventions coexist:
- Port-based (e.g.
ExpansionValve<Connected>readsself.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 readsstate[idx]. The emergent CLI cycle uses the index-based convention.IsenthalpicExpansionValveis the template for new inline components (itsget_ports()returns&[]).
- Port-based (e.g.
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_performancesums every component'senergy_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-hocif/tanh— to keep the Newton Jacobian continuous. - CRLF gotcha: example JSONs are written with CRLF on Windows; test string-patches
using
\nfail silently. Split on a distinctive marker (e.g."Circuit B") thenreplacen. edittool 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
# 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 formr_y = K(y_ref−y) − (x − S(x))(wasx + S(x), a droop with steady-state offset that forced fragile high gains → the reported convergence/slowness). (2) Newinverse/override_network.rs: one actuator arbitrated by severalObjective{output,setpoint,gain,combine:min|max}folded through C^∞softMin/softMaxwith exact analytic selector weights (Jacobian- smoothing; literature-backed robustness). (3)SaturatedControllergains opt-inobjectives+alpha;system.rsresidual/Jacobian branch for network mode (∂E/∂col = Σ w_i(−gain_i)∂y_i/∂col). (4) Configcontrols[].objectives[]+alpha(schema-exposed),run.rswiring. Examplechiller_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 byrun.rs: primary-only baseline → full network). The λ-step now uses the same predictor–corrector control asstrategies::homotopy— start 0.5, halve on any failed solve (retry from the last good λ), grow on success, floorMIN_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 wiressolver.max_iterations/toleranceinto 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 asuperheat_regulatedevaporator 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 (compressorf_m), covered by the passing testtest_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-insuperheat_regulatedmode (drops the emergent outlet-closurer2→ superheat emerges from the ε-NTU energy balance); acontrols[]saturated loop withactuator.factor:"opening"drives the opening (wired toCalibIndices.actuator), the EXV orifice residual closes it, DoF stays square (orifice +1, drop r2 −1, saturated +2/+2). Realmeasure_output(Superheat)added. Examplechiller_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 CLIschemacommand already emits the canonical Model IR JSON Schema (emit_schema→ScenarioConfig::json_schema()viaschemars);controls[](saturated loops) parse and co-solve; bounded control variables are expressed inactuator{initial,min,max};parse_component_outputsupports capacity/superheat/subcooling/saturationTemperature/ massflow/pressure/temperature. REMAINING (optional): a dedicated top-level hard-inverseconstraints[]array + a distinctdesign/controlCLI command (todayrunco-solves everything).p0c-ui-align— make the control schema map cleanly to theapps/webnode/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
pyo3incrates/bindings/pythonOR pinning the uv env to 3.13, then verifyuv run maturin develop --release+uv run pytest. The bindings crates may be absent from the current snapshot — confirm before starting.
- ⚠️ Blocked in this env: PyO3 max supported Python is 3.13 but the interpreter
is 3.14. Fix by upgrading
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 (61AQStateMachine). (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 61AQEXVControl_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
enabledtoggle inapps/webconfigBuilder (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-improvefindings (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 --workspacefails for the same reason — test crates individually.- CoolProp is enabled via Cargo features; CLI/solver runs need no
--featuresflag. 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-fluidstabular-accuracy test (regenerated r134a.json). - Not buildable in this env:
bindings/python(PyO3 vs Python 3.14).