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

4.2 KiB

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 MacroComponents
    • 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.