9.2 KiB
Story 9.8: SystemState Dedicated Struct
Status: done
Story
As a Rust developer,
I want a dedicated SystemState struct instead of a type alias,
so that I have layout validation, typed access methods, and better semantics for the solver state.
Acceptance Criteria
-
Given
SystemStateis currentlyVec<f64>incrates/components/src/lib.rs:182When the struct is created Thenpressure(edge_idx)returnsPressuretype Andenthalpy(edge_idx)returnsEnthalpytype Andset_pressure()andset_enthalpy()accept typed physical quantities -
Given a
SystemStateinstance When accessing data ThenAsRef<[f64]>andAsMut<[f64]>are implemented for solver compatibility AndFrom<Vec<f64>>andFrom<SystemState> for Vec<f64>enable migration -
Given invalid data (odd length vector) When calling
SystemState::from_vec()Then panic with clear error message -
Given out-of-bounds edge index When calling
pressure()orenthalpy()Then returnsNone(safe, no panic) -
Given all tests passing before change When refactoring is complete Then
cargo test --workspacepasses And public API is unchanged for solver consumers
Tasks / Subtasks
-
Task 1: Create
SystemStatestruct inentropyk_core(AC: 1, 3, 4)- Create
crates/core/src/state.rswithSystemStatestruct - Implement
new(edge_count),from_vec(),edge_count() - Implement
pressure(),enthalpy()returningOption<Pressure/Enthalpy> - Implement
set_pressure(),set_enthalpy()accepting typed values - Implement
as_slice(),as_mut_slice(),into_vec() - Implement
iter_edges()iterator
- Create
-
Task 2: Implement trait compatibility (AC: 2)
- Implement
AsRef<[f64]>for solver compatibility - Implement
AsMut<[f64]>for mutable access - Implement
From<Vec<f64>>andFrom<SystemState> for Vec<f64> - Implement
Defaulttrait
- Implement
-
Task 3: Export from
entropyk_core(AC: 5)- Add
statemodule tocrates/core/src/lib.rs - Export
SystemStatefrom crate root
- Add
-
Task 4: Migrate from type alias (AC: 5)
- Remove
pub type SystemState = Vec<f64>;fromcrates/components/src/lib.rs - Add
use entropyk_core::SystemState;to components crate - Update solver crate imports if needed
- Remove
-
Task 5: Add unit tests (AC: 3, 4)
- Test
new()creates correct size - Test
pressure()/enthalpy()accessors - Test out-of-bounds returns
None - Test
from_vec()with valid and invalid data - Test
iter_edges()iteration - Test
From/Intoconversions
- Test
-
Task 6: Add documentation (AC: 5)
- Add rustdoc for struct and all public methods
- Document layout:
[P_edge0, h_edge0, P_edge1, h_edge1, ...] - Add inline code examples
Dev Notes
Current Implementation
// crates/components/src/lib.rs:182
pub type SystemState = Vec<f64>;
Layout: Each edge in the system graph has 2 variables: [P0, h0, P1, h1, ...]
P: Pressure in Pascalsh: Enthalpy in J/kg
Proposed Implementation
// crates/core/src/state.rs
pub struct SystemState {
data: Vec<f64>,
edge_count: usize,
}
impl SystemState {
pub fn new(edge_count: usize) -> Self;
pub fn from_vec(data: Vec<f64>) -> Self; // panics on odd length
pub fn edge_count(&self) -> usize;
pub fn pressure(&self, edge_idx: usize) -> Option<Pressure>;
pub fn enthalpy(&self, edge_idx: usize) -> Option<Enthalpy>;
pub fn set_pressure(&mut self, edge_idx: usize, p: Pressure);
pub fn set_enthalpy(&mut self, edge_idx: usize, h: Enthalpy);
pub fn as_slice(&self) -> &[f64];
pub fn as_mut_slice(&mut self) -> &mut [f64];
pub fn into_vec(self) -> Vec<f64>;
pub fn iter_edges(&self) -> impl Iterator<Item = (Pressure, Enthalpy)> + '_;
}
Architecture Compliance
- NewType Pattern: Consistent with architecture requirement for type-safe physical quantities
- Zero-Allocation in Hot Path: Internal
Vec<f64>is pre-allocated; no allocation in accessors - Error Handling: Out-of-bounds returns
Option::None, not panic (exceptfrom_vecwith odd length)
Files to Touch
| File | Action |
|---|---|
crates/core/src/state.rs |
Create |
crates/core/src/lib.rs |
Add pub mod state; and re-export |
crates/components/src/lib.rs |
Remove type alias, add import from core |
crates/solver/src/*.rs |
Update imports if needed (may work via re-export) |
Project Structure Notes
SystemStatebelongs inentropyk_core(shared between solver and components)- Physical types (
Pressure,Enthalpy) already exist inentropyk_core - Solver uses
AsRef<[f64]>trait - no breaking change
Testing Standards
- Use
approxcrate for float comparisons - Tolerance: 1e-9 for exact values (matches NFR determinism requirement)
- Run
cargo test --workspaceto verify no regressions
References
- [Source: architecture.md#Core-Architectural-Decisions] - NewType pattern requirement
- [Source: epics.md#Story-9.8] - Story definition
- [Source: crates/components/src/lib.rs:182] - Current type alias location
Dev Agent Record
Agent Model Used
Claude 3.5 Sonnet (via OpenCode)
Debug Log References
N/A
Completion Notes List
-
Created
SystemStatestruct incrates/core/src/state.rswith:- Typed accessor methods (
pressure(),enthalpy()) - Typed setter methods (
set_pressure(),set_enthalpy()) From<Vec<f64>>andFrom<SystemState> for Vec<f64>conversionsAsRef<[f64]>andAsMut<[f64]>implementationsDeref<Target=[f64]>andDerefMutfor seamless slice compatibilityIndex<usize>andIndexMut<usize>for backward compatibilityto_vec()method for cloning data- 25 unit tests covering all functionality
- Typed accessor methods (
-
Updated Component trait to use
&StateSlice(type alias for&[f64]) instead of&SystemState:- This allows both
&Vec<f64>and&SystemStateto work via deref coercion - Updated all component implementations
- Updated all solver code
- This allows both
-
Added
StateSlicetype alias for clarity in method signatures
File List
crates/core/src/state.rs(created)crates/core/src/lib.rs(modified)crates/components/src/lib.rs(modified)crates/components/src/compressor.rs(modified)crates/components/src/expansion_valve.rs(modified)crates/components/src/fan.rs(modified)crates/components/src/pump.rs(modified)crates/components/src/pipe.rs(modified)crates/components/src/node.rs(modified)crates/components/src/flow_junction.rs(modified)crates/components/src/refrigerant_boundary.rs(modified)crates/components/src/python_components.rs(modified)crates/components/src/heat_exchanger/exchanger.rs(modified)crates/components/src/heat_exchanger/evaporator.rs(modified)crates/components/src/heat_exchanger/evaporator_coil.rs(modified)crates/components/src/heat_exchanger/condenser.rs(modified)crates/components/src/heat_exchanger/condenser_coil.rs(modified)crates/components/src/heat_exchanger/economizer.rs(modified)crates/solver/src/system.rs(modified)crates/solver/src/macro_component.rs(modified)crates/solver/src/initializer.rs(modified)crates/solver/src/strategies/mod.rs(modified)crates/solver/src/strategies/sequential_substitution.rs(modified)crates/solver/tests/*.rs(modified - all test files)demo/src/bin/*.rs(modified - all demo binaries)
Senior Developer Review (AI)
Reviewer: Claude 3.5 Sonnet (via OpenCode) Date: 2026-02-22 Outcome: Changes Requested → Fixed
Issues Found
| # | Severity | Issue | Resolution |
|---|---|---|---|
| 1 | HIGH | Clippy manual_is_multiple_of failure (crate has #![deny(warnings)]) |
Fixed: data.len() % 2 == 0 → data.len().is_multiple_of(2) |
| 2 | HIGH | Missing serde support for JSON persistence (Story 7-5 dependency) | Fixed: Added Serialize, Deserialize derives to SystemState and InvalidStateLengthError |
| 3 | MEDIUM | Silent failure on set_pressure/set_enthalpy hides bugs |
Fixed: Added #[track_caller] and debug_assert! for early detection |
| 4 | MEDIUM | No fallible constructor (try_from_vec) |
Fixed: Added try_from_vec() returning Result<Self, InvalidStateLengthError> |
| 5 | MEDIUM | Demo binaries have uncommitted changes | Noted: Unrelated to story scope |
Fixes Applied
- Added
InvalidStateLengthErrortype withstd::error::Errorimpl - Added
try_from_vec()fallible constructor - Added
#[track_caller]anddebug_assert!toset_pressure/set_enthalpy - Added
Serialize, Deserializederives (serde already in dependencies) - Added 7 new tests:
test_try_from_vec_validtest_try_from_vec_odd_lengthtest_try_from_vec_emptytest_invalid_state_length_error_displaytest_serde_roundtriptest_set_pressure_out_of_bounds_panics_in_debugtest_set_enthalpy_out_of_bounds_panics_in_debug
Test Results
entropyk-core: 90 tests passedentropyk-components: 379 tests passedentropyk-solver: 211 tests passed- Clippy: 0 warnings