Entropyk/_bmad-output/implementation-artifacts/3-6-hierarchical-macro-components.md

82 lines
4.2 KiB
Markdown

# Story 3.6: Hierarchical Subsystems (MacroComponents)
Status: ready-for-dev
## Story
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
1. **MacroComponent Trait Implementation** (AC: #1)
- Given a fully defined `System` with internal components and connections
- When I wrap it in a `MacroComponent`
- Then this `MacroComponent` implements the `Component` trait
- And the global solver treats it exactly like a basic Component
2. **External Port Mapping** (AC: #2)
- Given a `MacroComponent` wrapping an internal `System`
- When I want to expose specific internal ports (e.g., Evaporator Water In/Out, Condenser Water In/Out)
- Then I can map these to the `MacroComponent`'s external ports
- And external connections to these mapped ports correctly route fluid states to the internal components
3. **Residual and Jacobian Delegation** (AC: #3)
- Given a system solver calling `compute_residuals` or `jacobian_entries` on a `MacroComponent`
- When the `MacroComponent` executes these methods
- Then it delegates or flattens the computation down to the nested internal `System`
- And all equations are solved simultaneously globally, avoiding nested numerical solver delays
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
## 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.
## Dev Notes
### Epic Context
**Epic 3: System Topology (Graph)** — Enable component assembly via Ports and manage multi-circuits with thermal coupling.
This story adds the capability to wrap topologies into sub-blocks.
**FRs covered:** FR48 (Hierarchical Subsystems).
### Architecture Context
**Technical Stack:**
- Rust, `entropyk-components`, `entropyk-solver`
- Need to ensure that `SystemState` indices stay aligned when a `MacroComponent` is placed into a larger `System`.
**Relevant Architecture Decisions:**
- **Wrapper Pattern:** `MacroComponent` implements `Component`.
- **SystemState Flattening:** The global solver dictates state vector indices. The `MacroComponent` must know how its internal node IDs map to the global `SystemState` indices, or it must reconstruct an internal `SystemState` slice.
- **Zero-allocation:** Port mapping and index offsetting must be pre-calculated during the topology finalization phase.
### 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.
### 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.