68 KiB
| stepsCompleted | inputDocuments | |||||
|---|---|---|---|---|---|---|
|
|
Entropyk - Epic Breakdown
Overview
This document provides the complete epic and story breakdown for Entropyk, decomposing the requirements from the PRD and Architecture requirements into implementable stories.
Requirements Inventory
Functional Requirements
FR1: The system can model a Compressor according to AHRI 540 standard with manufacturer coefficients
FR2: The system can model a Condenser with heat transfer calculation
FR3: The system can model an Expansion Valve (isenthalpic expansion)
FR4: The system can model an Evaporator with phase change
FR5: The system can model an Economizer (internal heat exchanger) with Bypass mode
FR6: Each component can be in OperationalState (On, Off, Bypass)
FR7: In Off mode, an active component contributes zero mass flow to the system
FR8: In Bypass mode, a component behaves as an adiabatic pipe (P_in = P_out, h_in = h_out)
FR9: User can define a Machine containing N independent fluid circuits
FR10: User can connect components via Ports (inlet/outlet)
FR11: System supports connections between circuits (thermal coupling)
FR12: System can solve N circuits simultaneously or sequentially
FR13: System mathematically handles zero-flow branches without division by zero
FR14: System can solve equations using Newton-Raphson method
FR15: System can solve equations using Sequential Substitution (Picard) method
FR16: Solver automatically switches to Sequential Substitution if Newton-Raphson diverges
FR17: Solver respects configurable time budget (timeout)
FR18: On timeout, solver returns best known state with NonConverged status
FR19: Solver can freeze Jacobian calculation to accelerate (Jacobian Freezing)
FR20: Convergence criterion checks Delta Pressure < 1 Pa (1e-5 bar)
FR21: For multi-circuits, global convergence is achieved when ALL circuits converge
FR22: User can define output constraints (e.g., Superheat = 5K)
FR23: System calculates necessary inputs (e.g., valve opening) respecting Bounded Constraints (0.0 ≤ Valve ≤ 1.0). If solution is out of bounds, solver returns "Saturation" or "ControlLimitReached" error
FR24: Inverse Control is solved simultaneously with cycle equations (One-Shot)
FR25: System can load fluid properties via CoolProp
FR26: System supports Tabular Interpolation tables for 100x performance
FR27: System handles pure fluids and mixtures (R32/R125, R454B)
FR28: System handles Temperature Glide for zeotropic mixtures
FR29: System uses automatic damping near critical point (CO2 R744)
FR30: Native Rust API with types and ownership safety
FR31: Python bindings via PyO3 with tespy-compatible API
FR32: C FFI for integration with external systems (PLC, LabView)
FR33: WebAssembly compilation support for web interfaces
FR34: CLI for batch simulation execution
FR35: System automatically checks mass balance (Σ ṁ_in - Σ ṁ_out < 1e-9 kg/s)
FR36: System automatically checks energy balance (Σ Q̇ + Ẇ - Σ (ṁ · h) < 1e-6 kW)
FR37: Each result contains traceability metadata (solver version, fluid version, SHA-256 input hash)
FR38: Debug Verbose mode to display residuals and convergence history
FR39: Error handling via Result<T, ThermoError> (Zero-Panic Policy)
FR40: System handles Incompressible Fluids (Water, Glycol, Humid Air simplified) via lightweight models (constant Cp or polynomial) for heat sources and sinks
FR41: Complete graph (Topology + Parameters + Fluid State) is serializable/deserializable to JSON (or TOML)
FR42: System includes Automatic Initialization Heuristic (Smart Guesser) proposing coherent initial pressure values based on source/sink temperatures
FR43: Components support calibration parameters (Calib: f_m, f_dp, f_ua, f_power, f_etav) to match simulation to real machine test data
FR44: System can validate results against ASHRAE 140 / BESTEST test cases (post-MVP)
FR45: System supports inverse calibration (parameter estimation from test bench data)
FR46: Explicit Air Coil components (EvaporatorCoil, CondenserCoil) for finned air heat exchangers (post-MVP)
FR47: Each refrigeration component natively exposes a complete thermodynamic state (Pressure, Temperature, T_sat, Quality, Superheat, Subcooling, Mass flow, Reynolds, Enthalpy, Entropy) easily accessible without complex recalculations.
FR48: Hierarchical Subsystems (MacroComponents) - encapsulate complete systems into reusable blocks
FR49: Flow Junctions (FlowSplitter 1→N, FlowMerger N→1) for compressible & incompressible fluids
FR50: Boundary Conditions (FlowSource, FlowSink) for compressible & incompressible fluids
FR51: Swappable Calibration Variables - swap calibration factors (f_m, f_ua, f_power, etc.) into solver unknowns and measured values (Tsat, capacity, power) into constraints for one-shot inverse calibration
FR52: Bounded Variable Step Clipping - during Newton-Raphson iterations, bounded control variables are clipped to [min, max] at EVERY iteration, preventing physically impossible values (e.g., valve > 100%) and improving convergence stability
FR53: Node - Passive probe component (0 equations) for extracting P, h, T, quality, superheat, subcooling at any point in the circuit without affecting the solver
FR54: FloodedEvaporator - Flooded evaporator where liquid refrigerant completely floods the tubes via a low-pressure receiver, producing a two-phase mixture (50-80% vapor) at the outlet
FR55: FloodedCondenser - Accumulation condenser where condensed refrigerant forms a liquid bath around the cooling tubes to regulate condensing pressure
FR56: Drum - Recirculation drum for evaporator recirculation cycles (2 inlets → 2 outlets: saturated liquid + saturated vapor)
FR57: BphxExchanger - Brazed Plate Heat Exchanger configurable as DX evaporator, flooded evaporator, or condenser with plate-specific correlations
FR58: MovingBoundaryHX - Zone-discretized heat exchanger with phase zones (superheated/two-phase/subcooled) and configurable correlation (Longo, Shah, Kandlikar, etc.)
FR59: VendorBackend - API for vendor data (Copeland, Danfoss, SWEP, Bitzer) with compressor coefficients and heat exchanger parameters loaded from JSON/CSV
FR60: CorrelationSelector - Heat transfer correlation selection with Longo (2004) as default for BPHX, supporting Shah (1979, 2021), Kandlikar (1990), Gungor-Winterton (1986), Gnielinski (1976), and others
NonFunctional Requirements
NFR1: Steady State convergence time < 1 second for standard cycle in Cold Start
NFR2: Simple cycle (Single-stage) solved in < 100 ms
NFR3: Complex cycle (Multi-circuits) solved in < 1 second
NFR4: No dynamic allocation in solver loop (pre-calculated allocation only)
NFR5: Guaranteed determinism: same inputs → same results within 1e-9 precision on all platforms (x86, ARM, WASM)
NFR6: HIL latency < 20 ms for real-time integration with PLC
NFR7: Zero-Panic Policy: no crash even with invalid inputs or impossible states
NFR8: Memory Safety guaranteed by Rust ownership system (no memory leaks, no use-after-free)
NFR9: Capability to run 48h+ without interruption or degradation (HIL/Embedded)
NFR10: Graceful error handling: timeout, non-convergence, saturation return explicit Result<T, Error>
NFR11: CoolProp 6.4+ compatibility for fluid properties
NFR12: Stable C FFI: auto-generated .h headers via cbindgen, C99/C++ compatible
NFR13: Deterministic WebAssembly: same behavior as native, no non-determinism sources
NFR14: Python bindings PyO3: tespy-compatible API for facilitated migration
NFR15: 100% documentation of public APIs (rustdoc)
NFR16: Automated CI/CD: tests, benchmarks, memory checks (Valgrind + Miri)
NFR17: Zero tolerance warnings: cargo clippy -- -D warnings
Additional Requirements
Architecture Requirements:
- Workspace-based multi-crate architecture with 4 crates (core, solver, components, fluids) and 3 bindings (python, c, wasm)
- Trait-based static polymorphism with enum dispatch for zero-cost abstraction
- NewType pattern for all physical quantities (Pressure, Temperature, Enthalpy, MassFlow) - never bare f64 in public APIs
- Type-State pattern for connection safety at compile-time
- Pre-allocated buffers only - no heap allocation in hot paths
#![deny(warnings)]in lib.rs for all crates- KaTeX configuration in
.cargo/config.tomlfor mathematical documentation tracingfor structured logging (never println!)thiserrorfor error handling with ThermoError enumapproxcrate for floating-point assertions with explicit tolerances- Sys-crate pattern for CoolProp C++ integration
- cbindgen for C header generation
- Miri validation in CI for undefined behavior detection
- Mass balance tolerance: 1e-9 kg/s
- Energy balance tolerance: 1e-6 kW
- Convergence pressure tolerance: 1 Pa
Standards Compliance:
- AHRI 540 standard implementation for compressor modeling with manufacturer coefficients (Bitzer, Copeland, Danfoss)
- NIST REFPROP as gold standard for fluid properties
- CoolProp as open-source reference
- Tabular interpolation achieving < 0.01% deviation from NIST while being 100x faster than direct EOS calls
Domain-Specific Requirements:
- Critical Point handling (CO2 R744) with automatic damping to prevent NaN in partial derivatives
- Temperature Glide handling for zeotropic mixtures (R454B, R32/R125) integrating Bubble Point → Dew Point without approximation
- Traçability: Each SimulationResult contains solver_version, fluid_backend version, input_hash (SHA-256)
- Smart initialization heuristic for initial pressure guesses based on source/sink temperatures
CI/CD Requirements:
cargo test: Unit testscargo bench: Performance regression (failure if > 5% degradation)valgrind: Memory leaks (FFI)cargo clippy -- -D warnings: Zero tolerance stylemiri test: Undefined behavior detection- 100% public documentation coverage
- Compiled and tested examples in CI
Packaging & Distribution:
- Rust: crates.io
- Python: pip install rust-thermo-cycle (Wheels PyPI, manylinux)
- C/C++: Headers + dynamic libraries
- Docker: Image rust-thermo-lab (JupyterLab + Rust kernel + CoolProp)
FR Coverage Map
| FR | Epic | Description |
|---|---|---|
| FR1 | Epic 1 | Compressor AHRI 540 modeling |
| FR2 | Epic 1 | Condenser heat transfer |
| FR3 | Epic 1 | Expansion valve isenthalpic |
| FR4 | Epic 1 | Evaporator phase change |
| FR5 | Epic 1 | Economizer with bypass |
| FR6 | Epic 1 | Component states ON/OFF/BYPASS |
| FR7 | Epic 1 | Zero mass flow in OFF mode |
| FR8 | Epic 1 | Adiabatic pipe in BYPASS mode |
| FR9 | Epic 3 | Multi-circuit machine definition |
| FR10 | Epic 3 | Component connection via Ports |
| FR11 | Epic 3 | Thermal coupling between circuits |
| FR12 | Epic 3 | Simultaneous/sequential circuit solving |
| FR13 | Epic 3 | Zero-flow branch handling |
| FR14 | Epic 4 | Newton-Raphson solver |
| FR15 | Epic 4 | Sequential Substitution solver |
| FR16 | Epic 4 | Auto-fallback solver switching |
| FR17 | Epic 4 | Configurable timeout |
| FR18 | Epic 4 | Best state on timeout |
| FR19 | Epic 4 | Jacobian freezing |
| FR20 | Epic 4 | Delta Pressure < 1 Pa convergence |
| FR21 | Epic 4 | Global multi-circuit convergence |
| FR22 | Epic 5 | Output constraints definition |
| FR23 | Epic 5 | Bounded input calculation |
| FR24 | Epic 5 | One-shot inverse control |
| FR25 | Epic 2 | CoolProp integration |
| FR26 | Epic 2 | Tabular interpolation tables |
| FR27 | Epic 2 | Pure fluids and mixtures support |
| FR28 | Epic 2 | Temperature glide handling |
| FR29 | Epic 2 | Critical point damping |
| FR30 | Epic 6 | Native Rust API |
| FR31 | Epic 6 | Python PyO3 bindings |
| FR32 | Epic 6 | C FFI bindings |
| FR33 | Epic 6 | WebAssembly support |
| FR34 | Epic 6 | CLI for batch execution |
| FR35 | Epic 7 | Mass balance validation |
| FR36 | Epic 7 | Energy balance validation |
| FR37 | Epic 7 | Traceability metadata |
| FR38 | Epic 7 | Debug verbose mode |
| FR39 | Epic 7 | Zero-panic error handling |
| FR40 | Epic 2 | Incompressible fluids support |
| FR41 | Epic 7 | JSON serialization |
| FR42 | Epic 4 | Smart initialization heuristic |
| FR43 | Epic 7 | Component calibration parameters (Calib) |
| FR44 | Epic 7 | ASHRAE 140 / BESTEST validation |
| FR45 | Epic 7 | Inverse calibration (parameter estimation) |
| FR46 | Epic 1 | Air Coils (EvaporatorCoil, CondenserCoil) |
| FR47 | Epic 2 | Rich Thermodynamic State Abstraction |
| FR48 | Epic 3 | Hierarchical Subsystems (MacroComponents) |
| FR49 | Epic 1 | Flow Junctions (FlowSplitter 1→N, FlowMerger N→1) for compressible & incompressible fluids |
| FR50 | Epic 1 | Boundary Conditions (FlowSource, FlowSink) for compressible & incompressible fluids |
| FR51 | Epic 5 | Swappable Calibration Variables (inverse calibration one-shot) |
| FR52 | Epic 6 | Python Solver Configuration Parity - expose all Rust solver options in Python bindings |
| FR53 | Epic 11 | Node passive probe for state extraction |
| FR54 | Epic 11 | FloodedEvaporator |
| FR55 | Epic 11 | FloodedCondenser |
| FR56 | Epic 11 | Drum recirculation |
| FR57 | Epic 11 | BphxExchanger |
| FR58 | Epic 11 | MovingBoundaryHX |
| FR59 | Epic 11 | VendorBackend API |
| FR60 | Epic 11 | CorrelationSelector |
Epic List
Epic 1: Extensible Component Framework
Goal: Create the foundation that allows adding any component (VFD, Battery, Pump, Pipe) by simply implementing a Rust Trait, without touching the calculation engine.
Innovation: Trait-based "Lego" architecture to add Compressors, Pumps, VFDs, Pipes, etc.
FRs covered: FR1, FR2, FR3, FR4, FR5, FR6, FR7, FR8, FR46, FR49, FR50
Epic 2: Fluid Properties Backend
Goal: Provide precise thermodynamic properties via CoolProp, tabular tables, mixture handling, and critical point management.
Innovation: 100x performance with tabular tables, automatic CO2 damping.
FRs covered: FR25, FR26, FR27, FR28, FR29, FR40, FR47
Epic 3: System Topology (Graph)
Goal: Enable component assembly via Ports and manage multi-circuits with thermal coupling, and support hierarchical subsystems.
Innovation: Multi-fluid directed graph in a single model, with natively supported hierarchical sub-blocks.
FRs covered: FR9, FR10, FR11, FR12, FR13, FR48
Epic 4: Intelligent Solver Engine
Goal: Solve any system with < 1s guarantee, Newton-Raphson ↔ Sequential Substitution fallback.
Innovation: Solver-agnostic with intelligent fallback - guaranteed convergence.
FRs covered: FR14, FR15, FR16, FR17, FR18, FR19, FR20, FR21, FR42
Epic 5: Inverse Control & Optimization
Goal: Transform component parameters (VFD speed, Valve position) into simultaneously solved unknowns.
Innovation: Native Inverse Control via Residual Embedding - "One-Shot".
FRs covered: FR22, FR23, FR24, FR51
Epic 6: Multi-Platform APIs
Goal: Distribute the library via Python (PyO3), WebAssembly, C FFI, and CLI.
Innovation: Multi-target without code duplication (Rust → Python/WASM/C).
FRs covered: FR30, FR31, FR32, FR33, FR34
Epic 7: Validation & Persistence
Goal: Guarantee trust via mass/energy balances, SHA-256 traceability, and JSON persistence.
Innovation: Complete traceability and scientific reproducibility.
FRs covered: FR35, FR36, FR37, FR38, FR39, FR41, FR43, FR44, FR45
Epic 8: Component-Fluid Integration
Goal: Integrate real thermodynamic fluid properties directly into component Residual calculation models.
Innovation: True physical interaction between solver mathematics and state equations.
FRs covered: FR47
Epic 9: Coherence Corrections (Post-Audit)
Goal: Fix coherence issues identified during the post-development audit to ensure complete and correct thermodynamic validation.
Innovation: Systematic remediation of type duplications and incomplete implementations.
FRs covered: FR35, FR36 (completion of Epic 7 validation)
Status: 🔄 In Progress (2026-02-22)
Source: Coherence Audit Report
Epic 1: Extensible Component Framework
Story 1.1: Component Trait Definition
As a library developer, I want to define a generic Component trait with methods for residuals, ports, and state management, So that future components can be implemented by simply implementing this trait.
Acceptance Criteria:
Given a new component struct When it implements the Component trait Then it must provide compute_residuals(), get_ports(), and get_state() methods And the trait must be object-safe for dynamic dispatch
Story 1.2: Physical Types (NewType Pattern)
As a simulation user, I want type-safe physical quantities (Pressure, Temperature, Enthalpy, MassFlow), So that I cannot accidentally mix units.
Acceptance Criteria:
Given a function accepting Pressure and Temperature When I try to pass Temperature where Pressure is expected Then the code must not compile (compile-time safety) And conversions must be explicit (e.g., pressure_bar.to_pascals())
Story 1.3: Port and Connection System
As a system modeler, I want to define inlet/outlet ports for components and connect them bidirectionally, So that I can build fluid circuit topologies.
Acceptance Criteria:
Given two components with compatible ports When I call connect(port_a, port_b) Then the connection is established with state validation And attempting to connect incompatible ports fails at compile-time
Story 1.4: Compressor Component (AHRI 540)
As a thermodynamic engineer, I want to model a compressor using AHRI 540 standard coefficients, So that I can simulate real compressor behavior with manufacturer data.
Acceptance Criteria:
Given a compressor with 10 AHRI 540 coefficients When I compute residuals for a given operating point Then the power consumption and mass flow are calculated per AHRI 540 equations And the result matches certified AHRI test data within 1%
Story 1.5: Generic Heat Exchanger Framework
As a thermal systems engineer, I want a pluggable heat exchanger framework supporting multiple calculation models (Pinch point, LMTD, ε-NTU), So that I can add new exchanger types without modifying the solver.
Acceptance Criteria:
Given a heat exchanger component When I plug in a calculation model (Pinch, LMTD, or ε-NTU) Then the residuals are computed using the selected model And new models can be added by implementing a HeatTransferModel trait And Condenser, Evaporator, and Economizer are implemented as specific configurations
Story 1.6: Expansion Valve Component
As a control engineer, I want to model an expansion valve with isenthalpic expansion, So that I can simulate pressure reduction in the refrigeration cycle.
Acceptance Criteria:
Given an expansion valve with inlet subcooled liquid at high pressure When I compute residuals Then the outlet is two-phase or saturated liquid at lower pressure And enthalpy is conserved (h_in = h_out)
Story 1.7: Component State Machine
As a simulation user, I want components to support ON, OFF, and BYPASS states, So that I can simulate system configurations and failures.
Acceptance Criteria:
Given a component in ON state When I switch it to OFF Then it contributes zero mass flow to the system And when in BYPASS, it behaves as an adiabatic pipe (P_in = P_out, h_in = h_out)
Story 1.8: Auxiliary & Transport Components
As a system integrator, I want to model Pumps, VFDs, and Pipes, So that I can simulate complete HVAC systems.
Acceptance Criteria:
Given a Pump with specified hydraulic efficiency When I compute residuals Then power consumption and pressure rise match the pump curve And a VFD can modify Pump or Compressor speed via affinity laws And a Pipe calculates pressure drop via Darcy-Weisbach
Story 1.11: Flow Junctions — FlowSplitter & FlowMerger
As a system modeler,
I want FlowSplitter (1 inlet → N outlets) and FlowMerger (N inlets → 1 outlet) components,
So that I can build parallel branches in hydraulic and refrigerant circuits without manually writing junction equations.
Status: ✅ Done (2026-02-20)
FRs covered: FR49
Acceptance Criteria:
Given a refrigerant or water circuit with parallel branches
When I instantiate FlowSplitter::compressible("R410A", inlet, vec![out_a, out_b])
Then the splitter contributes 2N−1 equations (isobaric + isenthalpic constraints)
And validation rejects incompatible fluid types (::incompressible rejects refrigerants)
And FlowMerger contributes N+1 equations with weighted enthalpy mixing via with_mass_flows
And both implement Box<dyn Component> (object-safe)
And type aliases Incompressible/CompressibleSplitter and Incompressible/CompressibleMerger are available
Implementation:
crates/components/src/flow_junction.rs—FlowSplitter,FlowMerger,FluidKind- 16 unit tests passing
Story 1.12: Boundary Conditions — FlowSource & FlowSink
As a simulation user,
I want FlowSource and FlowSink boundary condition components,
So that I can define the entry and exit points of a fluid circuit without manually managing pressure and enthalpy constraints.
Status: ✅ Done (2026-02-20)
FRs covered: FR50
Acceptance Criteria:
Given a fluid circuit with an entry point
When I instantiate FlowSource::incompressible("Water", 3.0e5, 63_000.0, port)
Then the source imposes P_edge − P_set = 0 and h_edge − h_set = 0 (2 equations)
And FlowSink::incompressible("Water", 1.5e5, None, port) imposes a back-pressure (1 equation)
And FlowSink with Some(h_back) adds a second enthalpy constraint (2 equations)
And set_return_enthalpy / clear_return_enthalpy toggle the second equation dynamically
And validation rejects incompatible fluid + constructor combinations
And type aliases Incompressible/CompressibleSource and Incompressible/CompressibleSink are available
Implementation:
crates/components/src/flow_boundary.rs—FlowSource,FlowSink- 17 unit tests passing
Story 2.1: Fluid Backend Trait Abstraction
As a library developer, I want to define a FluidBackend trait, So that the solver can switch between CoolProp, tabular, and mock backends.
Acceptance Criteria:
Given a fluid property query When I call backend.property(fluid, property, state) Then it returns correct values regardless of backend implementation And CoolPropBackend, TabularBackend, and TestBackend all implement the trait
Story 2.2: CoolProp Integration (sys-crate)
As a simulation user, I want CoolProp as the primary backend, So that I get NIST-quality thermodynamic data.
Acceptance Criteria:
Given a refrigerant and thermodynamic state When I query via CoolPropBackend Then results match CoolProp 6.4+ within machine precision And C++ CoolProp is statically linked via sys-crate
Story 2.3: Tabular Interpolation Backend
As a performance-critical user, I want pre-computed NIST tables with fast interpolation, So that queries are 100x faster than direct EOS.
Acceptance Criteria:
Given a tabular data file for a fluid When I query via TabularBackend Then results deviate < 0.01% from NIST REFPROP And query time is < 1μs
Story 2.4: LRU Cache for Fluid Properties
As a solver developer, I want lock-free or thread-local caching, So that redundant calculations are avoided without mutex contention.
Acceptance Criteria:
Given repeated property queries When cache is enabled Then Thread-Local or lock-free (dashmap) caching is used And no mutex contention under high parallelism (Rayon) And cache invalidates on state changes
Story 2.5: Mixture and Temperature Glide Support
As a refrigeration engineer, I want robust (P, h) and (P, x) inputs for zeotropic mixtures, So that the solver handles temperature glide reliably.
Acceptance Criteria:
Given a zeotropic mixture at constant pressure When calculating evaporation Then temperature glide from bubble to dew point is correct And backend supports (P, h) and (P, x) inputs robustly And (P, h) is preferred over (P, T) for two-phase mixtures
Story 2.6: Critical Point Damping (CO2 R744)
As a CO2 systems designer, I want smooth, differentiable damping near critical point, So that Newton-Raphson converges without discontinuities.
Acceptance Criteria:
Given CO2 near critical point (Tc=304.13K, Pc=7.3773 MPa) When querying within 5% of critical point Then damping is applied to partial derivatives And no NaN values And damping function is C1-continuous (sigmoid transition)
Story 2.7: Incompressible Fluids Support
As a HVAC engineer, I want water, glycol, and moist air as incompressible fluids, So that heat sources/sinks are fast to compute.
Acceptance Criteria:
Given an incompressible fluid When querying density, Cp, or enthalpy Then lightweight polynomial models are used And results match references within 0.1% And computation is 1000x faster than compressible EOS
Story 2.8: Rich Thermodynamic State Abstraction
As a system engineer,
I want components to expose a comprehensive ThermoState structure (P, T, T_sat, Quality, tsh, Reynolds, Enthalpy, Entropy, etc.),
So that I don't have to manually calculate these from raw state arrays after solver convergence.
Acceptance Criteria:
Given a converged component (e.g., Compressor or Condenser)
When I call component.outlet_thermo_state()
Then it returns a ThermoState object
And the object contains dynamically resolved saturated temperature, vapor quality, superheat, and phase
And FluidBackend natively supports resolving this full snapshot in one trait call full_state(p, h)
Epic 3: System Topology (Graph)
Story 3.1: System Graph Structure
As a system modeler, I want edges to index the solver's state vector, So that P and h unknowns assemble into the Jacobian.
Acceptance Criteria:
Given components with ports When adding to System graph Then edges serve as indices into solver's state vector And each flow edge represents P and h unknowns And solver traverses graph to assemble Jacobian And cycles are detected and validated
Story 3.2: Port Compatibility Validation
As a system designer, I want port connection validation at build time, So that incompatible connections are caught early.
Acceptance Criteria:
Given two ports with incompatible fluids When attempting to connect Then connection fails with clear error And valid connections are accepted And pressure/enthalpy continuity is enforced
Story 3.3: Multi-Circuit Machine Definition
As a R&D engineer (Marie), I want machines with N independent circuits, So that I simulate complex heat pumps.
Acceptance Criteria:
Given a machine with 2+ circuits When defining topology Then each circuit is tracked independently And circuits can be solved simultaneously or sequentially And supports up to N=5 circuits
Story 3.4: Thermal Coupling Between Circuits
As a systems engineer, I want thermal coupling with circular dependency detection, So that solver knows to solve simultaneously vs sequentially.
Acceptance Criteria:
Given two circuits with a heat exchanger When defining thermal coupling Then heat transfer equations link circuits And energy is conserved (Q_hot = -Q_cold) And circular dependencies force simultaneous solving And coupling represented as additional residuals
Story 3.5: Zero-Flow Branch Handling
As a simulation user, I want zero-flow handling with regularization, So that OFF components don't cause numerical instabilities.
Acceptance Criteria:
Given a branch with mass flow = 0 When computing residuals Then no division-by-zero And regularization applied (ε minimum in divisions) And auto-switch to pressure continuity when OFF And Jacobian remains well-conditioned
Story 3.6: Hierarchical Subsystems (MacroComponents)
As a system designer, I want to encapsulate a complete system (e.g., a Chiller with compressor, condenser, valve, evaporator) into a single reusable block, So that I can compose larger models (like buildings or parallel chiller plants) using these blocks, just like in Modelica.
Acceptance Criteria:
Given a fully defined System with internal components and connections
When I wrap it in a MacroComponent
Then I can expose specific internal ports (e.g., Evaporator Water In/Out, Condenser Water In/Out) as the MacroComponent's external ports
And this MacroComponent implements the Component trait
And I can add it to a higher-level System just like any regular component
And the global solver correctly flattens or delegates the residual and jacobian computations down to the nested components.
Epic 4: Intelligent Solver Engine
Story 4.1: Solver Trait Abstraction
As a numerical developer, I want a generic Solver trait, So that strategies are interchangeable.
Acceptance Criteria:
Given a system of equations When instantiating solver strategies Then all implement common Solver trait And provides solve() and with_timeout() methods And zero-cost abstraction via enum dispatch
Story 4.2: Newton-Raphson Implementation
As a simulation engineer, I want Newton-Raphson with analytical Jacobian support, So that HIL performance is optimized.
Acceptance Criteria:
Given a system with residuals When running Newton-Raphson Then quadratic convergence near solution And line search prevents overshooting And supports both numerical and analytical Jacobian And components provide analytical Jacobian entries
Story 4.3: Sequential Substitution (Picard) Implementation
As a fallback solver user, I want Sequential Substitution for robust convergence, So that when Newton diverges, I have a stable alternative.
Acceptance Criteria:
Given a system where Newton diverges When using Sequential Substitution Then it converges reliably And variables updated sequentially And relaxation factors configurable
Story 4.4: Intelligent Fallback Strategy
As a simulation user, I want automatic fallback with smart return conditions, So that convergence is guaranteed without solver oscillation.
Acceptance Criteria:
Given Newton-Raphson diverging When divergence detected (> 3 increasing residuals) Then auto-switch to Sequential Substitution And return to Newton ONLY when convergence radius reached And prevent oscillation by requiring stable Picard first
Story 4.5: Time-Budgeted Solving
As a HIL engineer (Sarah), I want strict timeout with graceful degradation, So that real-time constraints are never violated.
Acceptance Criteria:
Given solver with timeout = 1000ms When time budget exceeded Then solver stops immediately And returns best state with ComputationStatus::Timeout And for HIL, returns previous state with ZOH
Story 4.6: Smart Initialization Heuristic
As a R&D engineer (Marie), I want automatic initial guesses from temperatures, So that cold start convergence is fast.
Acceptance Criteria:
Given source and sink temperatures When initializing system Then pressures estimated via Antoine equation And evaporator pressure < P_critical And condenser pressure from T_sink + ΔT_approach
Story 4.7: Convergence Criteria & Validation
As a simulation user, I want strict criteria with sparse Jacobian for multi-circuit, So that large systems remain tractable.
Acceptance Criteria:
Given system approaching solution When checking convergence Then max |ΔP| < 1 Pa And mass error < 1e-9 kg/s, energy < 1e-6 kW And ALL circuits converge for global convergence And Jacobian uses sparse/block structure And uncoupled circuits give block-diagonal
Story 4.8: Jacobian-Freezing Optimization
As a performance-critical user, I want to freeze Jacobian updates, So that CPU time is reduced.
Acceptance Criteria:
Given nearly-converged system When Jacobian freezing enabled Then Jacobian computed once, reused And speed increases ~80% And auto-disabled if residuals increase
Epic 5: Inverse Control & Optimization
Story 5.1: Constraint Definition Framework
As a control engineer, I want to define output constraints, So that I specify target operating conditions.
Acceptance Criteria:
Given measurable outputs When defining constraint (output - target = 0) Then constraint added to residuals And can reference any component output And multiple constraints supported
Story 5.2: Bounded Control Variables
As a control engineer, I want Box Constraints or Step Clipping, So that Newton steps stay physically possible.
Acceptance Criteria:
Given control variable with bounds [min, max] When computing Newton step (Δx) Then step scaled/clipped if exceeding bounds And variable never outside bounds during iterations And ControlSaturation error if converged solution exceeds bounds
Story 5.3: Residual Embedding for Inverse Control
As a systems engineer, I want constraints embedded with DoF validation, So that system is well-posed.
Acceptance Criteria:
Given constraint and control variable When solving system Then residual added to residual vector And control variable added to unknowns And solved simultaneously (one-shot) And Jacobian includes ∂constraint/∂control And DoF validated (equations = unknowns) And OverConstrainedSystem error if mismatch
Story 5.4: Multi-Variable Control
As a control engineer, I want to control multiple outputs simultaneously, So that I optimize complete operation.
Acceptance Criteria:
Given multiple constraints When defining control problem Then each constraint maps to one control variable And all solved simultaneously And all constraints satisfied within tolerance
Story 5.5: Swappable Calibration Variables (Inverse Calibration One-Shot)
As a R&D engineer calibrating a machine model against test bench data, I want to swap calibration coefficients (f_m, f_ua, f_power, etc.) into unknowns and measured values (Tsat, capacity, power) into constraints, So that the solver directly computes the calibration coefficients in one shot without external optimizer.
Context: Each component has specific calibration factors. In normal simulation, f_ is fixed and outputs are computed. In calibration mode, measured values become constraints and f_ become unknowns.
Component → Calibration Factor Mapping:
| Component | f_ Factors | Measurable Values (can swap) |
|---|---|---|
| Condenser | f_ua, f_dp | Tsat_cond, Q_cond (capacity), ΔP_cond |
| Evaporator | f_ua, f_dp | Tsat_evap, Q_evap (capacity), ΔP_evap |
| Compressor | f_m, f_power, f_etav | ṁ, Power, η_v |
| Expansion Valve | f_m | ṁ |
| Pipe | f_dp | ΔP |
Acceptance Criteria:
Given a Condenser with f_ua as calibration factor When I enable calibration mode and fix Tsat_cond to measured value Then f_ua becomes an unknown in solver state vector And residual added: Tsat_cond_computed - Tsat_cond_measured = 0 And solver computes f_ua directly
Given an Evaporator with f_ua as calibration factor When I enable calibration mode and fix Tsat_evap to measured value Then same swap mechanism: f_ua → unknown, Tsat_evap → constraint
Given a Compressor with f_power as calibration factor When I enable calibration mode and fix Power to measured value Then f_power becomes unknown And residual: Power_computed - Power_measured = 0
Given a Compressor with f_m as calibration factor When I enable calibration mode and fix mass flow ṁ to measured value Then f_m becomes unknown And residual: ṁ_computed - ṁ_measured = 0
Given a machine in cooling mode calibration When I impose evaporator cooling capacity Q_evap_measured Then Q_evap becomes constraint (Q_evap_computed - Q_evap_measured = 0) And corresponding f_ (typically f_ua on evaporator) becomes unknown
Given a machine in heating mode calibration When I impose condenser heating capacity Q_cond_measured Then Q_cond becomes constraint And corresponding f_ (typically f_ua on condenser) becomes unknown
Given multiple calibration swaps on same system When solver runs Then all f_ unknowns solved simultaneously with cycle equations (One-Shot) And Jacobian includes ∂constraint/∂f_ partial derivatives And DoF validated: (equations + calibration_constraints) = (unknowns + swapped_f_factors)
Given a calibration swap configuration When serializing system to JSON Then swap state persisted (which f_ are unknowns, which values are fixed) And deserialization restores exact calibration mode
Story 5.6: Control Variable Step Clipping in Solver
As a control engineer, I want bounded control variables to be clipped at each Newton iteration, So that the solver never proposes physically impossible values (e.g., valve > 100%, frequency < min).
Context: Story 5.2 implemented BoundedVariable and clip_step(), but the Newton-Raphson solver in Story 4.2 doesn't use them. The solver applies x += delta without checking bounds, allowing impossible values.
Acceptance Criteria:
Given a bounded variable with bounds [min, max]
When the solver computes a Newton step Δx
Then the new value is clipped: x_new = clamp(x + Δx, min, max)
And the variable never goes outside bounds during ANY iteration
Given control variables in the state vector at indices [2*edge_count, ...] When the solver updates the state vector Then bounded variables are clipped And regular edge states (P, h) are NOT clipped
Given a converged solution with one or more bounded variables at bounds
When checking convergence status
Then ConvergenceStatus::ControlSaturation is returned
And saturated_variables() returns the list of saturated variables
Given existing code that doesn't use bounded variables When solving Then behavior is unchanged (no clipping applied)
Epic 6: Multi-Platform APIs
Story 6.1: Rust Native API
As a Rust developer, I want clean, idiomatic Rust API, So that I integrate into Rust applications.
Acceptance Criteria:
Given a Rust application When using entropyk crate Then builder pattern for systems And all functions return Result<T, ThermoError> And follows Rust conventions And KaTeX documentation
Story 6.2: Python Bindings (PyO3)
As a Python data scientist (Alice), I want zero-copy NumPy support, So that 100x speedup isn't wasted on conversion.
Acceptance Criteria:
Given Python script with rust_thermo_cycle When creating components Then API similar to tespy And errors mapped to Python exceptions And wheels on PyPI (manylinux) And Buffer Protocol and NumPy arrays supported And zero-copy for 10k+ element vectors And 100x faster than tespy
Story 6.3: C FFI Bindings (cbindgen)
As a HIL engineer (Sarah), I want C headers with explicit memory management, So that PLC integration has no memory leaks.
Acceptance Criteria:
Given C/C++ application When including headers Then extern "C" interface available And headers auto-generated via cbindgen And error codes as enum values And opaque pointers for complex types And every allocation has free function (e.g., entropyk_free_system) And latency < 20ms
Story 6.4: WebAssembly Compilation
As a web developer (Charlie), I want WASM with TabularBackend default, So that it works without CoolProp in browser.
Acceptance Criteria:
Given web application When importing WASM module Then can create and solve in browser And results JSON-serializable And deterministic performance And package on npm And defaults to TabularBackend (CoolProp unavailable in WASM) And works out-of-box in Chrome/Edge And cycle time < 100ms
Story 6.5: CLI for Batch Execution
As a data engineer (David), I want CLI for batch simulations, So that I process millions of scenarios.
Acceptance Criteria:
Given JSON config file When running entropyk-cli run config.json Then simulation executes And outputs results to JSON And batch mode supports parallel execution And progress reported And exit codes indicate success/failure
Story 6.6: Python Solver Configuration Parity
As a Python data scientist (Alice), I want full access to all solver configuration options from Python, So that I can optimize convergence for complex thermodynamic systems.
Problem Statement:
The current Python bindings expose only a subset of the Rust solver configuration options. This prevents Python users from:
- Setting initial states for cold-start solving (critical for convergence)
- Configuring Jacobian freezing for performance optimization
- Using advanced convergence criteria for multi-circuit systems
- Accessing timeout behavior configuration (ZOH fallback for HIL)
Gap Analysis:
| Rust Field | Python Exposed | Impact |
|---|---|---|
initial_state |
❌ | Cannot warm-start solver |
use_numerical_jacobian |
❌ | Cannot debug Jacobian issues |
jacobian_freezing |
❌ | Missing 80% performance optimization |
convergence_criteria |
❌ | Cannot configure multi-circuit convergence |
timeout_config |
❌ | Cannot configure ZOH fallback |
previous_state |
❌ | Cannot use HIL zero-order hold |
line_search_armijo_c |
❌ | Cannot tune line search |
divergence_threshold |
❌ | Cannot adjust divergence detection |
Acceptance Criteria:
Given Python script using entropyk When configuring NewtonConfig or PicardConfig Then all Rust configuration fields are accessible:
initial_state: Optional[List[float]]- warm-start from previous solutionuse_numerical_jacobian: bool- finite difference Jacobianjacobian_freezing: Optional[JacobianFreezingConfig]- reuse Jacobian optimizationconvergence_criteria: Optional[ConvergenceCriteria]- multi-circuit criteriatimeout_config: TimeoutConfig- detailed timeout behaviorprevious_state: Optional[List[float]]- for HIL ZOH fallbackline_search_armijo_c: float- Armijo constantline_search_max_backtracks: int- max backtracking iterationsdivergence_threshold: float- divergence detection threshold
And SolverStrategy enum is exposed:
SolverStrategy.newton()- create Newton strategySolverStrategy.picard()- create Picard strategySolverStrategy.default()- default strategy (Newton)strategy.solve(system)- uniform solve interface
And supporting types are exposed:
JacobianFreezingConfig(max_frozen_iters, threshold)TimeoutConfig(return_best_state_on_timeout, zoh_fallback)ConvergenceCriteriawith per-circuit tolerances
And backward compatibility is maintained:
- Existing Python code continues to work
- New fields have sensible defaults
- Deprecation warnings for removed fields
Technical Notes:
-
Priority Order:
- P0:
initial_state(critical for convergence) - P0:
SolverStrategy(architectural consistency) - P1:
jacobian_freezing(performance) - P1:
convergence_criteria(multi-circuit) - P2: Other fields (advanced tuning)
- P0:
-
Implementation Approach:
- Extend
PyNewtonConfigandPyPicardConfigstructs - Add new
PySolverStrategywrapper class - Create
PyJacobianFreezingConfig,PyTimeoutConfig,PyConvergenceCriteria - Update
lib.rsto register new classes
- Extend
-
Testing:
- Unit tests for each new configuration field
- Integration test: solve with
initial_statefrom previous solution - Performance test: verify Jacobian freezing speedup
References:
- FR52: Python Solver Configuration Parity
- Story 6.2: Python Bindings (PyO3) - foundation
- Epic 4: Intelligent Solver Engine - Rust solver implementation
Epic 7: Validation & Persistence
Story 7.1: Mass Balance Validation
As a simulation engineer, I want automatic mass conservation verification, So that I trust physical correctness.
Acceptance Criteria:
Given converged solution When computing mass balance Then error Σ ṁ_in - Σ ṁ_out < 1e-9 kg/s And violations trigger validation error And check performed after every solve
Story 7.2: Energy Balance Validation
Status: ✅ Done (2026-02-22)
As a simulation engineer, I want First AND Second Law verification, So that thermodynamic consistency is guaranteed.
Acceptance Criteria:
Given converged solution When computing balances Then energy error < 1e-6 kW And violations trigger error with breakdown And Second Law check: entropy generation ≥ 0 And warning if negative entropy destruction And violations flagged
Story 7.3: Traceability Metadata
As a researcher (Robert), I want complete traceability metadata, So that simulations are reproducible.
Acceptance Criteria:
Given simulation result When accessing metadata Then includes solver_version, fluid_backend_version, input_hash (SHA-256) And SHA-256 uniquely identifies input And metadata in structured JSON
Story 7.4: Debug Verbose Mode
As a researcher (Robert), I want detailed convergence diagnostics, So that I debug non-converging systems.
Acceptance Criteria:
Given non-converging system When verbose mode enabled Then residuals logged each iteration And Jacobian condition number reported And solver switches logged And final state dumped
Story 7.5: JSON Serialization & Deserialization
As a system designer, I want complete system serialization with backend definition, So that models are reproducible across machines.
Acceptance Criteria:
Given system with components, connections, parameters When serializing to JSON Then complete state saved (Topology + Parameters + Fluid State) And fluid backend fully included (version, hash) And deserialization reconstructs identical system And round-trip produces identical results And human-readable, versioned format And explicit error if backend missing on load And error specifies required backend version
Story 7.6: Component Calibration Parameters (Calib)
As a R&D engineer matching simulation to real machine test data, I want calibration factors (Calib: f_m, f_dp, f_ua, f_power, f_etav) on components, So that simulation results align with manufacturer test data and field measurements.
Acceptance Criteria:
Given a component with nominal model parameters When Calib (calibration factors) are set (default 1.0 = no correction) Then f_m scales mass flow: ṁ_eff = f_m × ṁ_nominal (Compressor, Expansion Valve) And f_dp scales pressure drop: ΔP_eff = f_dp × ΔP_nominal (Pipe, Heat Exchanger) And f_ua scales thermal conductance: UA_eff = f_ua × UA_nominal (Evaporator, Condenser) And f_power scales compressor power: Ẇ_eff = f_power × Ẇ_nominal (Compressor) And f_etav scales volumetric efficiency: η_v,eff = f_etav × η_v,nominal (Compressor, displacement models)
Given calibration factors from test data optimization When running simulation with calibrated components Then results match test data within configurable tolerance (e.g., capacity ±2%, power ±3%) And Calib values are serializable in JSON (persisted with system definition) And calibration workflow order documented: f_m → f_dp → f_ua, then f_power (prevents parameter fighting)
Given a calibrated system When loading from JSON Then Calib parameters are restored And traceability metadata includes calibration source (test data hash or identifier)
Story 7.7: ASHRAE 140 / BESTEST Validation (post-MVP)
As a simulation engineer seeking industrial credibility, I want to validate Entropyk against ASHRAE Standard 140 and BESTEST test cases, So that results are comparable to EnergyPlus, TRNSYS, and Modelica.
Acceptance Criteria:
Given ASHRAE 140 / Airside HVAC BESTEST (AE101–AE445) test case definitions When running Entropyk on equivalent cycle configurations Then results fall within documented tolerance bands vs reference And discrepancies are documented (algorithmic, modeling assumptions) And CI includes regression tests for selected cases
Given a new Entropyk release When running validation suite Then no regression beyond tolerance And validation report generated (JSON or markdown)
Story 7.8: Inverse Calibration (Parameter Estimation)
As a R&D engineer with test bench data, I want to estimate Calib (or component) parameters from measured data, So that the model matches my machine without manual tuning.
Acceptance Criteria:
Given test data (P, T, ṁ, Ẇ, Q at multiple operating points) When running inverse calibration Then optimizer minimizes error (e.g., MAPE) between model and data And estimated Calib (or coefficients) are returned And supports constraints (e.g., 0.8 ≤ f_ua ≤ 1.2) And calibration order respected (f_m → f_dp → f_ua → f_power)
Given calibrated parameters When saving system Then Calib values and calibration_source (data hash) persisted in JSON
Epic 8: Component-Fluid Integration
Story 8.1: Fluid Backend Component Integration
As a systems engineer,
I want the thermodynamic components (Compressor, Condenser, etc.) to use the real FluidBackend,
So that the residuals sent to the solver reflect accurate physical states.
Acceptance Criteria:
Given a component needing physical properties
When the solver computes its residuals
Then it natively references the configured FluidBackend
And returns real values vs placeholders
And handles missing backends gracefully with fallbacks
Story 1.9: Air Coils (EvaporatorCoil, CondenserCoil) (post-MVP)
As a HVAC engineer modeling split systems or air-source heat pumps, I want explicit EvaporatorCoil and CondenserCoil components, So that air-side heat exchangers (finned) are clearly distinguished from water-cooled.
Acceptance Criteria:
Given an EvaporatorCoil (refrigerant + air) When defining the component Then 4 ports: refrigerant in/out, air in/out And UA or geometry (fins, tubes) configurable And integrates with Fan for air flow And Calib (f_ua, f_dp) applicable
Given a CondenserCoil Then same structure as EvaporatorCoil And refrigerant condenses on hot side, air on cold side
Story 1.10: Pipe Helpers for Water and Refrigerant
As a HVAC engineer modeling refrigerant and incompressible fluid circuits (water, seawater, glycol),
I want convenient constructors Pipe::for_incompressible() and Pipe::for_refrigerant() with explicit ρ/μ from a fluid backend,
So that I can create pipes without hardcoding fluid properties in the component.
Acceptance Criteria:
Given an incompressible fluid circuit (water, seawater, glycol)
When calling Pipe::for_incompressible(geometry, port_inlet, port_outlet, density, viscosity)
Then accepts explicit ρ, μ obtained from IncompressibleBackend (Story 2.7)
And no hardcoded fluid properties in components crate
And doc examples show water and glycol usage
Given a refrigerant circuit (R134a, R410A, CO2, etc.)
When calling Pipe::for_refrigerant(geometry, port_inlet, port_outlet, density, viscosity)
Then accepts explicit ρ, μ (from CoolProp/tabular at design point)
And doc examples show refrigerant circuit usage
And doc states that ρ, μ vary with P,T — design-point values are typical
Given the Pipe module documentation
When reading the crate-level and Pipe docs
Then explicitly states that Pipe serves for both refrigerant and incompressible fluids
And includes a "Fluid Support" section: refrigerant (ρ/μ from backend) vs incompressible (ρ/μ from IncompressibleBackend)
Epic 9: Coherence Corrections (Post-Audit)
Goal: Fix coherence issues identified during the post-development audit to ensure complete and correct thermodynamic validation.
Innovation: Systematic remediation of type duplications and incomplete implementations.
FRs covered: FR35, FR36 (completion of Epic 7 validation)
Status: 🔄 In Progress (2026-02-22)
Source: Coherence Audit Report
Story 9.1: CircuitId Type Unification
As a Rust developer,
I want a single, coherent CircuitId type,
So that I avoid compilation errors when using solver and components modules together.
Acceptance Criteria:
Given the codebase currently has two CircuitId definitions
When the unification is complete
Then only one CircuitId exists in entropyk_core
And From<u8> and From<&str> conversions are available
And all modules re-export from entropyk_core
Story 9.2: FluidId Type Unification
As a Rust developer,
I want a single FluidId type with a consistent API,
So that I avoid confusion between fluids::FluidId and components::port::FluidId.
Acceptance Criteria:
Given the codebase currently has two FluidId definitions
When the unification is complete
Then only one FluidId exists in entropyk_fluids
And both as_str() method and public field access work
And all modules re-export from entropyk_fluids
Story 9.3: ExpansionValve Energy Methods
As a thermodynamic simulation engine,
I want ExpansionValve to implement energy_transfers() and port_enthalpies(),
So that the energy balance is correctly validated for refrigeration cycles.
Acceptance Criteria:
Given an ExpansionValve in a refrigeration cycle
When check_energy_balance() is called
Then the valve is included in the validation (not skipped)
And energy_transfers() returns (Power(0), Power(0)) (isenthalpic)
And port_enthalpies() returns [h_in, h_out]
Story 9.4: FlowSource/FlowSink Energy Methods
As a thermodynamic simulation engine,
I want FlowSource and FlowSink to implement energy_transfers() and port_enthalpies(),
So that boundary conditions are correctly accounted for in the energy balance.
Acceptance Criteria:
Given FlowSource or FlowSink in a system
When check_energy_balance() is called
Then the component is included in the validation
And energy_transfers() returns (Power(0), Power(0))
And port_enthalpies() returns the port enthalpy
Story 9.5: FlowSplitter/FlowMerger Energy Methods
As a thermodynamic simulation engine,
I want FlowSplitter and FlowMerger to implement energy_transfers() and port_enthalpies(),
So that junctions are correctly accounted for in the energy balance.
Acceptance Criteria:
Given FlowSplitter or FlowMerger in a system
When check_energy_balance() is called
Then the component is included in the validation
And energy_transfers() returns (Power(0), Power(0)) (adiabatic)
And port_enthalpies() returns all port enthalpies in order
Story 9.6: Energy Validation Logging Improvement
As a developer debugging a simulation, I want an explicit warning when components are skipped in energy validation, So that I can quickly identify missing implementations.
Acceptance Criteria:
Given a component without energy_transfers() or port_enthalpies()
When check_energy_balance() is called
Then a WARN-level log is emitted with component name and type
And a summary lists all skipped components
Story 9.7: Solver Refactoring - Split Files
As a developer maintaining the code, I want solver strategies to be in separate files, So that code maintainability is improved.
Acceptance Criteria:
Given the current solver.rs is ~2800 lines
When refactoring is complete
Then each file is < 500 lines
And public API is unchanged
And cargo test --workspace passes
Story 9.8: SystemState Dedicated Struct
As a Rust developer,
I want a dedicated SystemState struct instead of a type alias,
So that I have layout validation and typed access methods.
Acceptance Criteria:
Given SystemState is currently Vec<f64>
When the struct is created
Then pressure(edge_idx) returns Pressure
And enthalpy(edge_idx) returns Enthalpy
And compatibility with AsRef<[f64]> is maintained
Epic 11: Advanced HVAC Components
Goal: Implement components for industrial chillers and heat pumps (flooded, BPHX, MovingBoundary, vendor data integration)
Innovation: Native integration of vendor data and advanced heat transfer correlations
FRs covered: FR53, FR54, FR55, FR56, FR57, FR58, FR59, FR60
Status: 📋 Backlog (2026-02-22)
Source: Technical Specifications
Story 11.1: Node - Passive Probe
As a system modeler, I want a passive Node component (0 equations), So that I can extract P, h, T, quality, superheat, subcooling at any point in the circuit.
Status: 📋 Backlog
FRs covered: FR53
Acceptance Criteria:
Given a Node with 1 inlet and 1 outlet When the system converges Then the Node contributes 0 equations (passive) And I can read pressure, temperature, enthalpy, quality And I can read superheat (if superheated) or subcooling (if subcooled) And the Node can be used as a junction in the graph topology And NodeMeasurements struct provides all extracted values
Story 11.2: Drum - Recirculation Drum
As a chiller engineer, I want a Drum component for evaporator recirculation, So that I can simulate flooded evaporator cycles.
Status: 📋 Backlog
FRs covered: FR56
Acceptance Criteria:
Given a Drum with 2 inlets (feed + evaporator return) and 2 outlets (liquid + vapor) When the system converges Then liquid outlet is saturated (x=0) And vapor outlet is saturated (x=1) And mass and energy balances are satisfied And pressure equality across all ports And n_equations() returns 8
Story 11.3: FloodedEvaporator
As a chiller engineer, I want a FloodedEvaporator component, So that I can simulate chillers with flooded evaporators.
Status: 📋 Backlog
FRs covered: FR54
Acceptance Criteria:
Given a FloodedEvaporator with refrigerant side (flooded) and fluid side (water/glycol) When computing heat transfer Then refrigerant outlet is two-phase (50-80% vapor) And UA uses flooded-specific correlations (Longo default for BPHX) And Calib factors (f_ua, f_dp) are applicable And outlet_quality() method returns vapor quality And supports both LMTD and ε-NTU models
Story 11.4: FloodedCondenser
As a chiller engineer, I want a FloodedCondenser component, So that I can simulate chillers with accumulation condensers.
Status: 📋 Backlog
FRs covered: FR55
Acceptance Criteria:
Given a FloodedCondenser with refrigerant side (flooded) and fluid side (water/glycol) When computing heat transfer Then liquid bath regulates condensing pressure And outlet is subcooled liquid And UA uses flooded-specific correlations And subcooling is calculated and accessible
Story 11.5: BphxExchanger Base
As a thermal engineer, I want a base BphxExchanger component, So that I can configure brazed plate heat exchangers for various applications.
Status: 📋 Backlog
FRs covered: FR57
Acceptance Criteria:
Given a BphxExchanger with geometry parameters (plates, chevron angle, area) When computing heat transfer Then Longo (2004) correlation is used by default And alternative correlations can be selected via CorrelationSelector And both single-phase and two-phase zones are handled And HeatExchangerGeometry struct defines dh, area, chevron_angle, exchanger_type
Story 11.6: BphxEvaporator
As a heat pump engineer, I want a BphxEvaporator configured for DX or flooded operation, So that I can simulate plate evaporators accurately.
Status: 📋 Backlog
FRs covered: FR57
Acceptance Criteria:
Given a BphxEvaporator in DX mode When computing heat transfer Then superheat is calculated And outlet quality ≥ 1
Given a BphxEvaporator in flooded mode When computing heat transfer Then outlet is two-phase And works with Drum for recirculation
Story 11.7: BphxCondenser
As a heat pump engineer, I want a BphxCondenser for refrigerant condensation, So that I can simulate plate condensers accurately.
Status: 📋 Backlog
FRs covered: FR57
Acceptance Criteria:
Given a BphxCondenser When computing heat transfer Then outlet is subcooled liquid And subcooling is calculated And Longo condensation correlation is used by default
Story 11.8: CorrelationSelector
As a simulation engineer, I want to select from multiple heat transfer correlations, So that I can compare different models or use the most appropriate one.
Status: 📋 Backlog
FRs covered: FR60
Acceptance Criteria:
Given a heat exchanger with CorrelationSelector When selecting a correlation Then available correlations include:
- Longo (2004) - Default for BPHX evaporation/condensation
- Shah (1979) - Tubes condensation
- Shah (2021) - Plates condensation, recent
- Kandlikar (1990) - Tubes evaporation
- Gungor-Winterton (1986) - Tubes evaporation
- Ko (2021) - Low-GWP plates
- Dittus-Boelter (1930) - Single-phase turbulent
- Gnielinski (1976) - Single-phase turbulent (more accurate) And each correlation implements HeatTransferCorrelation trait And each correlation documents its validity range (Re, quality, mass flux) And CorrelationResult includes h, Re, Pr, Nu, and validity status
Story 11.9: MovingBoundaryHX - Zone Discretization
As a precision engineer, I want a MovingBoundaryHX with phase zone discretization, So that I can model heat exchangers with accurate zone-by-zone calculations.
Status: 📋 Backlog
FRs covered: FR58
Acceptance Criteria:
Given a MovingBoundaryHX When computing heat transfer Then zones are identified: superheated / two-phase / subcooled (refrigerant side) And each zone has its own UA calculated And total UA = Σ UA_zone And pinch temperature is calculated at zone boundaries And zone_boundaries vector contains relative positions [0.0, ..., 1.0] And supports configurable number of discretization points (default 51)
Story 11.10: MovingBoundaryHX - Cache Optimization
As a performance-critical user, I want the MovingBoundaryHX to cache zone calculations, So that iterations 2+ are much faster.
Status: 📋 Backlog
FRs covered: FR58
Acceptance Criteria:
Given a MovingBoundaryHX with cache enabled When running iteration 1 Then full zone calculation is performed (~50ms) And zone boundaries and UA are cached in MovingBoundaryCache
When running iterations 2+ Then cache is used if ΔP < 5% and Δm < 10% And computation is ~2ms (25x faster) And cache is invalidated on significant condition changes And cache includes: zone_boundaries, ua_per_zone, h_sat values, p_ref, m_ref
Story 11.11: VendorBackend Trait
As a library developer, I want a VendorBackend trait, So that vendor data can be loaded from multiple sources.
Status: 📋 Backlog
FRs covered: FR59
Acceptance Criteria:
Given the VendorBackend trait in new entropyk-vendors crate
When implementing a vendor
Then methods include:
vendor_name()→ vendor identifierlist_compressor_models()→ available modelsget_compressor_coefficients(model)→ AHRI 540 coefficientslist_bphx_models()→ available BPHX modelsget_bphx_parameters(model)→ geometry and UA curvescompute_ua(model, params)→ vendor-specific UA calculation (optional) And data is loaded from JSON/CSV files in data/ directory And errors are handled via VendorError enum
Story 11.12: Copeland Parser
As a compressor engineer, I want Copeland compressor data integration, So that I can use Copeland coefficients in simulations.
Status: 📋 Backlog
FRs covered: FR59
Acceptance Criteria:
Given Copeland compressor data files (JSON format) When loading via CopelandBackend Then AHRI 540 coefficients (10 capacity + 10 power) are extracted And valid models are discoverable via list_compressor_models() And errors for missing/invalid models are clear And data files follow format: data/copeland/compressors/{model}.json And index.json lists all available models
Story 11.13: SWEP Parser
As a heat exchanger engineer, I want SWEP BPHX data integration, So that I can use SWEP parameters in simulations.
Status: 📋 Backlog
FRs covered: FR59
Acceptance Criteria:
Given SWEP BPHX data files (JSON + CSV) When loading via SwepBackend Then geometry (plates, area, dh, chevron_angle) is extracted And UA nominal value is available And UA curves (mass_flow_ratio vs ua_ratio) are loaded if available And model selection is supported via list_bphx_models() And data files follow format: data/swep/bphx/{model}.json
Story 11.14: Danfoss Parser
As a refrigeration engineer, I want Danfoss compressor data integration, So that I can use Danfoss coefficients in simulations.
Status: 📋 Backlog
FRs covered: FR59
Acceptance Criteria:
Given Danfoss compressor data files When loading via DanfossBackend Then coefficients are extracted (AHRI 540 or Coolselector2 format) And compatible with standard compressor models And list_compressor_models() returns available models
Story 11.15: Bitzer Parser
As a refrigeration engineer, I want Bitzer compressor data integration, So that I can use Bitzer coefficients in simulations.
Status: 📋 Backlog
FRs covered: FR59
Acceptance Criteria:
Given Bitzer compressor data files (CSV format) When loading via BitzerBackend Then coefficients are extracted And compatible with standard compressor models And supports Bitzer polynomial format And list_compressor_models() returns available models
Future Epics (Vision – littérature HVAC)
Non planifiés – alignement avec EnergyPlus, Modelica, TRNSYS :
| Epic | Thème | Référence littérature |
|---|---|---|
| Transient | Simulation dynamique, start-up/shutdown, ODEs | Reddy, Purdue IIAR |
| Part-load | Courbes PLF, pertes de cyclage | EnergyPlus PLF |
| Frost/Defrost | Givre, dégivrage, enthalpy method | arXiv 2412.00017 |
| Moving Boundary | Échangeurs discretisés, zones phase | Modelica Buildings, TIL Suite |
| Export ML | Données synthétiques pour surrogates | arXiv 2505.15041 |