---
stepsCompleted:
  - step-01-validate-prerequisites
  - step-02-design-epics
  - step-03-create-stories
inputDocuments:
  - /Users/sepehr/dev/Entropyk/_bmad-output/planning-artifacts/prd.md
  - /Users/sepehr/dev/Entropyk/_bmad-output/planning-artifacts/architecture.md
---

# 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.toml` for mathematical documentation
- `tracing` for structured logging (never println!)
- `thiserror` for error handling with ThermoError enum
- `approx` crate 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 tests
- `cargo bench`: Performance regression (failure if > 5% degradation)
- `valgrind`: Memory leaks (FFI)
- `cargo clippy -- -D warnings`: Zero tolerance style
- `miri 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](../implementation-artifacts/coherence-audit-remediation-plan.md)

---

<!-- ALL EPICS AND STORIES -->

## 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 solution
- [ ] `use_numerical_jacobian: bool` - finite difference Jacobian
- [ ] `jacobian_freezing: Optional[JacobianFreezingConfig]` - reuse Jacobian optimization
- [ ] `convergence_criteria: Optional[ConvergenceCriteria]` - multi-circuit criteria
- [ ] `timeout_config: TimeoutConfig` - detailed timeout behavior
- [ ] `previous_state: Optional[List[float]]` - for HIL ZOH fallback
- [ ] `line_search_armijo_c: float` - Armijo constant
- [ ] `line_search_max_backtracks: int` - max backtracking iterations
- [ ] `divergence_threshold: float` - divergence detection threshold

**And** `SolverStrategy` enum is exposed:
- [ ] `SolverStrategy.newton()` - create Newton strategy
- [ ] `SolverStrategy.picard()` - create Picard strategy
- [ ] `SolverStrategy.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)`
- [ ] `ConvergenceCriteria` with 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:**

1. **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)

2. **Implementation Approach:**
   - Extend `PyNewtonConfig` and `PyPicardConfig` structs
   - Add new `PySolverStrategy` wrapper class
   - Create `PyJacobianFreezingConfig`, `PyTimeoutConfig`, `PyConvergenceCriteria`
   - Update `lib.rs` to register new classes

3. **Testing:**
   - Unit tests for each new configuration field
   - Integration test: solve with `initial_state` from 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](../implementation-artifacts/coherence-audit-remediation-plan.md)

### 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](epic-11-technical-specifications.md)

---

### 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 identifier
- `list_compressor_models()` → available models
- `get_compressor_coefficients(model)` → AHRI 540 coefficients
- `list_bphx_models()` → available BPHX models
- `get_bphx_parameters(model)` → geometry and UA curves
- `compute_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 |
