Entropyk/_bmad-output/implementation-artifacts/3-1-system-graph-structure.md

213 lines
9.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Story 3.1: System Graph Structure
Status: done
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
## 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
1. **Edges as State Indices** (AC: #1)
- [x] Given components with ports, when adding to System graph, edges serve as indices into solver's state vector
- [x] Each flow edge represents P and h unknowns (2 indices per edge)
- [x] State vector layout: `[P_edge0, h_edge0, P_edge1, h_edge1, ...]` or equivalent documented layout
2. **Graph Traversal for Jacobian** (AC: #2)
- [x] Solver traverses graph to assemble Jacobian
- [x] Components receive state slice indices via edge→state mapping
- [x] JacobianBuilder entries use correct (row, col) from graph topology
3. **Cycle Detection and Validation** (AC: #3)
- [x] Cycles are detected (refrigeration circuits form cycles - expected)
- [x] Topology is validated (no dangling nodes, consistent flow direction)
- [x] Clear error when topology is invalid
## Tasks / Subtasks
- [x] Create solver crate (AC: #1)
- [x] Add `crates/solver` to workspace Cargo.toml
- [x] Create Cargo.toml with deps: entropyk-core, entropyk-components, petgraph, thiserror
- [x] Create lib.rs with module structure (system, graph)
- [x] Implement System graph structure (AC: #1)
- [x] Use petgraph `Graph` with nodes = components, edges = flow connections
- [x] Node weight: `Box<dyn Component>` or component handle
- [x] Edge weight: `FlowEdge { state_index_p: usize, state_index_h: usize }` or similar
- [x] Build edge→state index mapping when graph is finalized
- [x] State vector indexing (AC: #1)
- [x] `state_vector_len() -> usize` = 2 * edge_count (P and h per edge)
- [x] `edge_state_indices(edge_id) -> (usize, usize)` for P and h columns
- [x] Document layout in rustdoc
- [x] Graph traversal for Jacobian assembly (AC: #2)
- [x] `traverse_for_jacobian()` or iterator over (component, edge_indices)
- [x] Components receive state and write to JacobianBuilder with correct col indices
- [x] Integrate with existing `Component::jacobian_entries(state, jacobian)`
- [x] Cycle detection and validation (AC: #3)
- [x] Use `petgraph::algo::is_cyclic_directed` for cycle detection
- [x] Validate: refrigeration cycles are expected; document semantics
- [x] Validate: no isolated nodes, all ports connected
- [x] Return `Result<(), TopologyError>` on invalid topology
- [x] Add tests
- [x] Test: simple cycle (4 nodes, 4 edges) builds correctly
- [x] Test: state vector length = 2 * edge_count
- [x] Test: edge indices are contiguous and unique
- [x] Test: cycle detection identifies cyclic graph
- [x] 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. FR9FR13 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 (FR9FR13):**
- 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/solver` is 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):**
```rust
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 vector
- `JacobianBuilder` has `add_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_directed` returns true for valid refrigeration topology
- "Validated" = topology is well-formed (connected, no illegal configs), not "no cycles"
- Port `ConnectionError::CycleDetected` may refer to a different concept (e.g., connection graph cycles during build) - clarify in implementation
**petgraph API:**
- `Graph<N, E, Ty, Ix>` - use `Directed` for flow direction
- `add_node(weight)`, `add_edge(a, b, weight)`
- `petgraph::algo::is_cyclic_directed(&graph) -> bool`
- `NodeIndex`, `EdgeIndex` for stable references
### Library/Framework Requirements
**petgraph:**
- Version: 0.6.x (latest stable, check crates.io)
- Docs: https://docs.rs/petgraph/
- `use petgraph::graph::Graph` and `petgraph::algo::is_cyclic_directed`
**Dependencies (solver Cargo.toml):**
```toml
[dependencies]
entropyk-core = { path = "../core" }
entropyk-components = { path = "../components" }
petgraph = "0.6"
thiserror = "1.0"
```
### File Structure Requirements
**New files:**
- `crates/solver/Cargo.toml`
- `crates/solver/src/lib.rs` - re-exports
- `crates/solver/src/system.rs` - System struct, Graph, state indexing
- `crates/solver/src/graph.rs` (optional) - graph building helpers
- `crates/solver/src/error.rs` (optional) - TopologyError
**Modified files:**
- Root `Cargo.toml` - uncomment `crates/solver` in workspace members
### Testing Requirements
**Required Tests:**
- `test_simple_cycle_builds` - 4 components, 4 edges, graph builds
- `test_state_vector_length` - len = 2 * edge_count
- `test_edge_indices_contiguous` - indices 0..2n for n edges
- `test_cycle_detected` - cyclic graph, is_cyclic_directed true
- `test_dangling_node_error` - node with no edges returns TopologyError
- `test_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.rs` for FR9FR13 - 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/solver` with 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)