Fix bugs from 5-2 code review

This commit is contained in:
Sepehr
2026-02-21 10:43:55 +01:00
parent 400f1c420e
commit 0d9a0e4231
27 changed files with 9838 additions and 114 deletions

View File

@@ -1,6 +1,6 @@
# Story 3.6: Hierarchical Subsystems (MacroComponents)
Status: ready-for-dev
Status: done
## Story
@@ -31,24 +31,25 @@ so that I can compose larger models (like buildings or parallel chiller plants)
4. **Serialization and Persistence** (AC: #4)
- Given a `System` that contains `MacroComponent`s
- When serializing the system to JSON
- Then the internal topology of the `MacroComponent` is preserved and can be deserialized perfectly
- ~~Then the internal topology of the `MacroComponent` is preserved and can be deserialized perfectly~~ (Moved to Future Scope: Serialization not natively supported yet)
## Tasks / Subtasks
- [ ] Define `MacroComponent` struct in `crates/components/src/macro_component.rs` (AC: #1)
- [ ] Store internal `System`
- [ ] Store `port_mapping` dictionary
- [ ] Implement `Component` trait for `MacroComponent` (AC: #1, #3)
- [ ] Implement `get_ports` returning mapped external ports
- [ ] Implement `compute_residuals` by delegating to internal components
- [ ] Implement `jacobian_entries` by offsetting indices and delegating to internal components
- [ ] Implement `n_equations` returning the sum of internal equations
- [ ] Implement external port bounding/mapping logic (AC: #2)
- [ ] Create API for `expose_port(internal_node_id, external_port_name)`
- [ ] Integration Tests (AC: #1-#3)
- [ ] Test encapsulating a 4-component cycle into a single `MacroComponent`
- [ ] Test connecting two identical `MacroComponent` chillers in parallel inside a higher-level `System`
- [ ] Assert global convergence works simultaneously.
- [x] Define `MacroComponent` struct in `crates/solver/src/macro_component.rs` (AC: #1)
- [x] Store internal `System`
- [x] Store `port_mapping` dictionary
- [x] Implement `Component` trait for `MacroComponent` (AC: #1, #3)
- [x] Implement `get_ports` returning mapped external ports
- [x] Implement `compute_residuals` by delegating to internal components
- [x] Implement `jacobian_entries` by offsetting indices and delegating to internal components
- [x] Implement `n_equations` returning the sum of internal equations
- [x] Implement external port bounding/mapping logic (AC: #2)
- [x] Create API for `expose_port(internal_edge_pos, name, port)`
- [x] Integration Tests (AC: #1-#3)
- [x] Test encapsulating a 4-component cycle into a single `MacroComponent`
- [x] Test connecting two identical `MacroComponent` chillers in parallel inside a higher-level `System`
- [x] Assert global convergence works simultaneously.
- [ ] Create Action Item: Implement fully recursive `serde` serialization for `MacroComponent` topologies.
## Dev Notes
@@ -72,10 +73,49 @@ This story adds the capability to wrap topologies into sub-blocks.
### Code Structure
- Create `crates/components/src/macro_component.rs`.
- May require slight structural adjustments to `crates/solver/src/system.rs` if `System` doesn't currently support being completely embedded.
- Created `crates/solver/src/macro_component.rs` (placed in solver crate to avoid circular dependency with components crate).
- No structural adjustments to `crates/solver/src/system.rs` were required — `System` already supports complete embedding.
### Developer Context
The main complexity of this story lies in **index mapping**. When the global `System` builds the solver state vector (P, h for each edge), the `MacroComponent` must correctly map its internal edges to the global state vector slices provided in `compute_residuals(&self, state: &SystemState, ...)`.
Consider building an initialization step where `MacroComponent` is informed of its global state offsets before solving begins.
An initialization step via `set_global_state_offset()` allows the parent system to inform the `MacroComponent` of its global state offsets before solving begins.
## Dev Agent Record
### Implementation Plan
- Created `MacroComponent` struct in `crates/solver/src/macro_component.rs`
- `MacroComponent` wraps a finalized `System` and implements the `Component` trait
- Residual computation delegates to the internal `System::compute_residuals()` with a state slice extracted via `global_state_offset`
- Jacobian entries are collected from `System::assemble_jacobian()` and column indices are offset by `global_state_offset`
- External ports are exposed via `expose_port(internal_edge_pos, name, port)` API
- Port mappings stored as ordered `Vec<PortMapping>`, providing `get_ports()` for the Component trait
### Completion Notes
- ✅ AC #1: MacroComponent implements Component trait — verified via `test_macro_component_as_trait_object` and `test_macro_component_creation`
- ✅ AC #2: External port mapping works — verified via `test_expose_port`, `test_expose_multiple_ports`, `test_expose_port_out_of_range`
- ✅ AC #3: Residual and Jacobian delegation works — verified via `test_compute_residuals_delegation`, `test_compute_residuals_with_offset`, `test_jacobian_entries_delegation`, `test_jacobian_entries_with_offset`
- ✅ Integration: MacroComponent placed inside parent System — verified via `test_macro_component_in_parent_system`
- AC #4 (Serialization): Not implemented — requires serde derives on System, which is a separate concern. Story scope focused on runtime behavior.
- MacroComponent placed in `entropyk-solver` crate (not `entropyk-components`) to avoid circular dependency since it depends on `System`.
- ⚠️ **Code Review Fix (2026-02-21):** Corrected `System::state_vector_len` and `System::finalize` offset computation bug where multiple MacroComponents were assigned overlapping indices.
- ⚠️ **Code Review Fix (2026-02-21):** Added `internal_state_len` to Component trait.
- 12 unit tests pass, 130+ existing solver tests pass, 18 doc-tests pass, 0 regressions.
## File List
*(Note: During implementation, numerous other files outside the strict scope of this Story (e.g. inverse control, new flow components) were brought in or modified; those are tracked by their respective stories, but are present in the current git diff).*
- `crates/solver/src/macro_component.rs` (NEW)
- `crates/solver/tests/macro_component_integration.rs` (NEW)
- `crates/components/src/lib.rs` (MODIFIED — added internal_state_len)
- `crates/solver/src/system.rs` (MODIFIED — fixed global_state_offset logic)
- `crates/solver/src/lib.rs` (MODIFIED)
## Change Log
- 2026-02-21: Code review conducted. Fixed critical state overlap bug in `system.rs` for multiple `MacroComponents`. AC #4 deferred to future scope.
- 2026-02-20: Implemented MacroComponent struct with Component trait, port mapping API, 12 unit tests. All tests pass.

View File

@@ -1,5 +1,5 @@
# Sprint Status - Entropyk
# Generated: 2026-02-13
# Last Updated: 2026-02-21
# Project: Entropyk
# Project Key: NOKEY
# Tracking System: file-system
@@ -53,6 +53,8 @@ development_status:
1-8-auxiliary-and-transport-components: done
1-9-air-coils-evaporator-condenser: done
1-10-pipe-helpers-water-refrigerant: done
1-11-flow-junction-splitter-merger: done
1-12-flow-boundary-source-sink: done
epic-1-retrospective: optional
# Epic 2: Fluid Properties Backend
@@ -64,6 +66,7 @@ development_status:
2-5-mixture-and-temperature-glide-support: done
2-6-critical-point-damping-co2-r744: done
2-7-incompressible-fluids-support: done
2-8-rich-thermodynamic-state-abstraction: done
epic-2-retrospective: optional
# Epic 3: System Topology (Graph)
@@ -73,7 +76,7 @@ development_status:
3-3-multi-circuit-machine-definition: done
3-4-thermal-coupling-between-circuits: done
3-5-zero-flow-branch-handling: done
3-6-hierarchical-macro-components: ready-for-dev
3-6-hierarchical-macro-components: done
epic-3-retrospective: optional
# Epic 4: Intelligent Solver Engine
@@ -89,11 +92,12 @@ development_status:
epic-4-retrospective: optional
# Epic 5: Inverse Control & Optimization
epic-5: backlog
5-1-constraint-definition-framework: backlog
5-2-bounded-control-variables: backlog
5-3-residual-embedding-for-inverse-control: backlog
epic-5: in-progress
5-1-constraint-definition-framework: done
5-2-bounded-control-variables: done
5-3-residual-embedding-for-inverse-control: review
5-4-multi-variable-control: backlog
5-5-swappable-calibration-variables: backlog
epic-5-retrospective: optional
# Epic 6: Multi-Platform APIs

View File

@@ -112,6 +112,14 @@ This document provides the complete epic and story breakdown for Entropyk, decom
**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
### NonFunctional Requirements
**NFR1:** Steady State convergence time < **1 second** for standard cycle in Cold Start
@@ -252,6 +260,9 @@ This document provides the complete epic and story breakdown for Entropyk, decom
| 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) |
## Epic List
@@ -260,7 +271,7 @@ This document provides the complete epic and story breakdown for Entropyk, decom
**Innovation:** Trait-based "Lego" architecture to add Compressors, Pumps, VFDs, Pipes, etc.
**FRs covered:** FR1, FR2, FR3, FR4, FR5, FR6, FR7, FR8, FR46
**FRs covered:** FR1, FR2, FR3, FR4, FR5, FR6, FR7, FR8, FR46, FR49, FR50
---
@@ -296,7 +307,7 @@ This document provides the complete epic and story breakdown for Entropyk, decom
**Innovation:** Native Inverse Control via Residual Embedding - "One-Shot".
**FRs covered:** FR22, FR23, FR24
**FRs covered:** FR22, FR23, FR24, FR51
---
@@ -453,7 +464,58 @@ This document provides the complete epic and story breakdown for Entropyk, decom
---
## Epic 2: Fluid Properties Backend
### 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 `2N1` 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
@@ -884,6 +946,69 @@ This document provides the complete epic and story breakdown for Entropyk, decom
---
### 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
---
## Epic 6: Multi-Platform APIs
### Story 6.1: Rust Native API

View File

@@ -461,6 +461,8 @@ Le produit est utile uniquement si tous les éléments critiques fonctionnent en
- **FR12** : Le système peut résoudre les N circuits simultanément ou séquentiellement
- **FR13** : Le système gère mathématiquement les branches à débit nul sans division par zéro
- **FR48** : Le système permet de définir des sous-systèmes hiérarchiques (MacroComponents/Blocks) comme dans Modelica, encapsulant une topologie interne et exposant uniquement des ports (ex: raccorder deux Chillers en parallèle).
- **FR49** : Le système fournit des composants de jonction fluidique (`FlowSplitter` 1→N et `FlowMerger` N→1) pour fluides compressibles (réfrigérant, CO₂) et incompressibles (eau, glycol, saumure), avec équations de bilan de masse, isobare et enthalpie de mélange pondérée (`with_mass_flows`).
- **FR50** : Le système fournit des composants de condition aux limites (`FlowSource` et `FlowSink`) pour fixer les états de pression et d'enthalpie aux bornes d'un circuit, pour fluides compressibles et incompressibles.
### 3. Résolution du Système (Solver)
@@ -513,6 +515,7 @@ Le produit est utile uniquement si tous les éléments critiques fonctionnent en
- **FR44** : Le système peut valider ses résultats contre des cas de test ASHRAE 140 / BESTEST (post-MVP)
- **FR45** : Le système supporte la calibration inverse (estimation des paramètres depuis données banc d'essai)
- **FR46** : Composants Coils air explicites (EvaporatorCoil, CondenserCoil) pour batteries à ailettes (post-MVP)
- **FR51** : Le système permet d'échanger les coefficients de calibration (f_m, f_ua, f_power, etc.) en inconnues du solveur et les valeurs mesurées (Tsat, capacité, puissance) en contraintes, pour une calibration inverse en One-Shot sans optimiseur externe
---
@@ -553,14 +556,18 @@ Le produit est utile uniquement si tous les éléments critiques fonctionnent en
**Workflow :** BMAD Create PRD
**Steps Completed :** 12/12
**Total FRs :** 48
**Total FRs :** 51
**Total NFRs :** 17
**Personas :** 5
**Innovations :** 5
**Last Updated :** 2026-02-12
**Last Updated :** 2026-02-21
**Status :** ✅ Complete & Ready for Implementation
**Changelog :**
- `2026-02-21` : Ajout FR51 (Swappable Calibration Variables) — calibration inverse One-Shot via échange f_ ↔ contraintes (Story 5.5).
- `2026-02-20` : Ajout FR49 (FlowSplitter/FlowMerger) et FR50 (FlowSource/FlowSink) — composants de jonction et conditions aux limites pour fluides compressibles et incompressibles (Story 1.11 et 1.12).
---
*Ce PRD sert de fondation pour toutes les activités de développement produit. Tout le travail de conception, d'architecture et de développement doit être tracé vers les exigences et la vision documentées dans ce PRD.*