9.2 KiB
Story 3.1: System Graph Structure
Status: done
Story
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
-
Edges as State Indices (AC: #1)
- Given components with ports, when adding to System graph, edges serve as indices into solver's state vector
- Each flow edge represents P and h unknowns (2 indices per edge)
- State vector layout:
[P_edge0, h_edge0, P_edge1, h_edge1, ...]or equivalent documented layout
-
Graph Traversal for Jacobian (AC: #2)
- Solver traverses graph to assemble Jacobian
- Components receive state slice indices via edge→state mapping
- JacobianBuilder entries use correct (row, col) from graph topology
-
Cycle Detection and Validation (AC: #3)
- Cycles are detected (refrigeration circuits form cycles - expected)
- Topology is validated (no dangling nodes, consistent flow direction)
- Clear error when topology is invalid
Tasks / Subtasks
- Create solver crate (AC: #1)
- Add
crates/solverto workspace Cargo.toml - Create Cargo.toml with deps: entropyk-core, entropyk-components, petgraph, thiserror
- Create lib.rs with module structure (system, graph)
- Add
- Implement System graph structure (AC: #1)
- Use petgraph
Graphwith nodes = components, edges = flow connections - Node weight:
Box<dyn Component>or component handle - Edge weight:
FlowEdge { state_index_p: usize, state_index_h: usize }or similar - Build edge→state index mapping when graph is finalized
- Use petgraph
- State vector indexing (AC: #1)
state_vector_len() -> usize= 2 * edge_count (P and h per edge)edge_state_indices(edge_id) -> (usize, usize)for P and h columns- Document layout in rustdoc
- Graph traversal for Jacobian assembly (AC: #2)
traverse_for_jacobian()or iterator over (component, edge_indices)- Components receive state and write to JacobianBuilder with correct col indices
- Integrate with existing
Component::jacobian_entries(state, jacobian)
- Cycle detection and validation (AC: #3)
- Use
petgraph::algo::is_cyclic_directedfor cycle detection - Validate: refrigeration cycles are expected; document semantics
- Validate: no isolated nodes, all ports connected
- Return
Result<(), TopologyError>on invalid topology
- Use
- Add tests
- Test: simple cycle (4 nodes, 4 edges) builds correctly
- Test: state vector length = 2 * edge_count
- Test: edge indices are contiguous and unique
- Test: cycle detection identifies cyclic graph
- Test: invalid topology (dangling node) returns error
Dev Notes
Epic Context
Epic 3: System Topology (Graph) - Enable component assembly via Ports and manage multi-circuits with thermal coupling. FR9–FR13 map to crates/solver/src/system.rs.
Story Dependencies:
- Epic 1 (Component trait, Ports, State machine) - in progress, 1-8 in review
- No dependency on Epic 2 (fluids) for this story - topology only
Architecture Context
System Topology (FR9–FR13):
- Location:
crates/solver/src/system.rs - petgraph for graph topology representation (architecture line 89, 147)
solver → core + fluids + components(components required)- Jacobian entries assembled by solver (architecture line 760)
Workspace:
crates/solveris currently commented out in root Cargo.toml:# "crates/solver", # Will be added in future stories- This story CREATES the solver crate
Component Trait (from components):
pub trait Component {
fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError>;
fn jacobian_entries(&self, state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError>;
fn n_equations(&self) -> usize;
fn get_ports(&self) -> &[ConnectedPort];
}
SystemState = Vec<f64>- state vectorJacobianBuilderhasadd_entry(row, col, value)- col = state index- Components need to know which state indices map to their ports
Technical Requirements
Graph Model:
- Nodes: Components (or component handles). Each node = one
dyn Component. - Edges: Flow connections between component ports. Direction = flow direction (e.g., compressor outlet → condenser inlet).
- Edge weight: Must store
(state_index_p, state_index_h)so solver can map state vector to edges.
State Vector Layout:
- Option A:
[P_0, h_0, P_1, h_1, ...]- 2 per edge, edge order from graph - Option B: Separate P and h vectors - less common for Newton
- Recommended: Option A for simplicity; document in
System::state_layout()
Cycle Semantics:
- Refrigeration circuits ARE cycles (compressor → condenser → valve → evaporator → compressor)
is_cyclic_directedreturns true for valid refrigeration topology- "Validated" = topology is well-formed (connected, no illegal configs), not "no cycles"
- Port
ConnectionError::CycleDetectedmay refer to a different concept (e.g., connection graph cycles during build) - clarify in implementation
petgraph API:
Graph<N, E, Ty, Ix>- useDirectedfor flow directionadd_node(weight),add_edge(a, b, weight)petgraph::algo::is_cyclic_directed(&graph) -> boolNodeIndex,EdgeIndexfor stable references
Library/Framework Requirements
petgraph:
- Version: 0.6.x (latest stable, check crates.io)
- Docs: https://docs.rs/petgraph/
use petgraph::graph::Graphandpetgraph::algo::is_cyclic_directed
Dependencies (solver Cargo.toml):
[dependencies]
entropyk-core = { path = "../core" }
entropyk-components = { path = "../components" }
petgraph = "0.6"
thiserror = "1.0"
File Structure Requirements
New files:
crates/solver/Cargo.tomlcrates/solver/src/lib.rs- re-exportscrates/solver/src/system.rs- System struct, Graph, state indexingcrates/solver/src/graph.rs(optional) - graph building helperscrates/solver/src/error.rs(optional) - TopologyError
Modified files:
- Root
Cargo.toml- uncommentcrates/solverin workspace members
Testing Requirements
Required Tests:
test_simple_cycle_builds- 4 components, 4 edges, graph buildstest_state_vector_length- len = 2 * edge_counttest_edge_indices_contiguous- indices 0..2n for n edgestest_cycle_detected- cyclic graph, is_cyclic_directed truetest_dangling_node_error- node with no edges returns TopologyErrortest_traverse_components- traversal yields all components with correct edge indices
Integration:
- Build a minimal cycle (e.g., 2 components, 2 edges) and verify state layout
Project Structure Notes
Alignment:
- Architecture specifies
crates/solver/src/system.rsfor FR9–FR13 - matches - petgraph from architecture - matches
- Component trait from Epic 1 - get_ports() provides port info for graph building
Note: Story 3.2 (Port Compatibility Validation) will add connection-time checks. This story focuses on graph structure and state indexing once components are added.
References
- Epic 3 Story 3.1: [Source: planning-artifacts/epics.md#Story 3.1]
- Architecture System Topology: [Source: planning-artifacts/architecture.md - FR9-FR13, system.rs]
- Architecture petgraph: [Source: planning-artifacts/architecture.md - line 89, 147]
- Component trait: [Source: crates/components/src/lib.rs]
- Port types: [Source: crates/components/src/port.rs]
- JacobianBuilder: [Source: crates/components/src/lib.rs - JacobianBuilder]
- petgraph is_cyclic_directed: https://docs.rs/petgraph/latest/petgraph/algo/fn.is_cyclic_directed.html
Change Log
- 2026-02-15: Story 3.1 implementation complete. Created crates/solver with System graph (petgraph), FlowEdge, state indexing, traverse_for_jacobian, cycle detection, TopologyError. All ACs satisfied, 7 tests pass.
- 2026-02-15: Code review fixes. Added state_layout(), compute_residuals bounds check, robust test_dangling_node_error, TopologyError #[allow(dead_code)], removed unused deps (entropyk-core, approx), added test_state_layout_integration and test_compute_residuals_bounds_check. 9 tests pass.
Dev Agent Record
Agent Model Used
{{agent_model_name_version}}
Debug Log References
Completion Notes List
- Created
crates/solverwith petgraph-based System graph - FlowEdge stores (state_index_p, state_index_h) per edge
- State vector layout: [P_0, h_0, P_1, h_1, ...] documented in rustdoc and state_layout()
- traverse_for_jacobian() yields (component, edge_indices) for Jacobian assembly
- TopologyError::IsolatedNode for dangling nodes; refrigeration cycles expected (is_cyclic)
- All 9 tests pass (core, components, solver); no fluids dependency
- Code review: state_layout(), bounds check in compute_residuals, robust dangling-node test, integration test
File List
- crates/solver/Cargo.toml (new)
- crates/solver/src/lib.rs (new)
- crates/solver/src/error.rs (new)
- crates/solver/src/graph.rs (new)
- crates/solver/src/system.rs (new)
- Cargo.toml (modified: uncommented crates/solver in workspace)
- _bmad-output/implementation-artifacts/sprint-status.yaml (modified: 3-1 in-progress → review)