Entropyk/_bmad-output/implementation-artifacts/3-6-hierarchical-macro-components.md
2026-02-21 10:43:55 +01:00

7.2 KiB
Raw Blame History

Story 3.6: Hierarchical Subsystems (MacroComponents)

Status: done

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 (Moved to Future Scope: Serialization not natively supported yet)

Tasks / Subtasks

  • Define MacroComponent struct in crates/solver/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_edge_pos, name, port)
  • 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.
  • Create Action Item: Implement fully recursive serde serialization for MacroComponent topologies.

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

  • Created crates/solver/src/macro_component.rs (placed in solver crate to avoid circular dependency with components crate).
  • No structural adjustments to crates/solver/src/system.rs were required — System already supports complete embedding.

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, ...). An initialization step via set_global_state_offset() allows the parent system to inform the MacroComponent of its global state offsets before solving begins.

Dev Agent Record

Implementation Plan

  • Created MacroComponent struct in crates/solver/src/macro_component.rs
  • MacroComponent wraps a finalized System and implements the Component trait
  • Residual computation delegates to the internal System::compute_residuals() with a state slice extracted via global_state_offset
  • Jacobian entries are collected from System::assemble_jacobian() and column indices are offset by global_state_offset
  • External ports are exposed via expose_port(internal_edge_pos, name, port) API
  • Port mappings stored as ordered Vec<PortMapping>, providing get_ports() for the Component trait

Completion Notes

  • AC #1: MacroComponent implements Component trait — verified via test_macro_component_as_trait_object and test_macro_component_creation
  • AC #2: External port mapping works — verified via test_expose_port, test_expose_multiple_ports, test_expose_port_out_of_range
  • AC #3: Residual and Jacobian delegation works — verified via test_compute_residuals_delegation, test_compute_residuals_with_offset, test_jacobian_entries_delegation, test_jacobian_entries_with_offset
  • Integration: MacroComponent placed inside parent System — verified via test_macro_component_in_parent_system
  • AC #4 (Serialization): Not implemented — requires serde derives on System, which is a separate concern. Story scope focused on runtime behavior.
  • MacroComponent placed in entropyk-solver crate (not entropyk-components) to avoid circular dependency since it depends on System.
  • ⚠️ Code Review Fix (2026-02-21): Corrected System::state_vector_len and System::finalize offset computation bug where multiple MacroComponents were assigned overlapping indices.
  • ⚠️ Code Review Fix (2026-02-21): Added internal_state_len to Component trait.
  • 12 unit tests pass, 130+ existing solver tests pass, 18 doc-tests pass, 0 regressions.

File List

(Note: During implementation, numerous other files outside the strict scope of this Story (e.g. inverse control, new flow components) were brought in or modified; those are tracked by their respective stories, but are present in the current git diff).

  • crates/solver/src/macro_component.rs (NEW)
  • crates/solver/tests/macro_component_integration.rs (NEW)
  • crates/components/src/lib.rs (MODIFIED — added internal_state_len)
  • crates/solver/src/system.rs (MODIFIED — fixed global_state_offset logic)
  • crates/solver/src/lib.rs (MODIFIED)

Change Log

  • 2026-02-21: Code review conducted. Fixed critical state overlap bug in system.rs for multiple MacroComponents. AC #4 deferred to future scope.
  • 2026-02-20: Implemented MacroComponent struct with Component trait, port mapping API, 12 unit tests. All tests pass.