Entropyk/_bmad-output/implementation-artifacts/3-4-thermal-coupling-between-circuits.md

14 KiB
Raw Permalink Blame History

Story 3.4: Thermal Coupling Between Circuits

Status: done

Story

As a systems engineer, I want thermal coupling with circular dependency detection, so that the solver knows whether to solve simultaneously or sequentially.

Acceptance Criteria

  1. Heat Transfer Linking (AC: #1)

    • Given two circuits with a heat exchanger coupling them
    • When defining thermal coupling
    • Then heat transfer equations link the circuits
    • And coupling is represented as additional residuals
  2. Energy Conservation (AC: #2)

    • Given a thermal coupling between two circuits
    • When computing heat transfer
    • Then Q_hot = -Q_cold (energy conserved)
    • And sign convention is documented (positive = heat INTO circuit)
  3. Circular Dependency Detection (AC: #3)

    • Given circuits with mutual thermal coupling (A heats B, B heats A)
    • When analyzing coupling topology
    • Then circular dependencies are detected
    • And solver is informed to solve simultaneously (not sequentially)
  4. Coupling Residuals (AC: #4)

    • Given a defined thermal coupling
    • When solver assembles residuals
    • Then coupling contributes additional residual equations
    • And Jacobian includes coupling derivatives

Tasks / Subtasks

  • Define ThermalCoupling struct (AC: #1, #2)
    • Create ThermalCoupling with: hot_circuit_id, cold_circuit_id, ua (thermal conductance)
    • Add optional efficiency factor (default 1.0)
    • Document sign convention: Q > 0 means heat INTO cold_circuit
  • Add coupling storage to System (AC: #1)
    • Add thermal_couplings: Vec<ThermalCoupling> to System
    • Add add_thermal_coupling(coupling: ThermalCoupling) -> Result<usize, TopologyError>
    • Validate circuit_ids exist before adding
  • Implement energy conservation (AC: #2)
    • Method compute_coupling_heat(coupling, hot_state, cold_state) -> HeatTransfer
    • Formula: Q = UA * (T_hot - T_cold) where T from respective circuit states
    • Returns positive Q for heat into cold circuit
  • Implement circular dependency detection (AC: #3)
    • Build coupling graph (nodes = circuits, edges = couplings)
    • Detect cycles using petgraph::algo::is_cyclic
    • Add has_circular_dependencies() -> bool
    • Add coupling_groups() -> Vec<Vec<CircuitId>> returning groups that must solve simultaneously
  • Expose coupling residuals for solver (AC: #4)
    • Add coupling_residuals(state: &SystemState) -> Vec<f64>
    • Residual: r = Q_actual - Q_expected (heat balance at coupling point)
    • Add coupling_jacobian_entries() returning (row, col, partial_derivative) tuples
  • Tests
    • Test: add_thermal_coupling valid, retrieves correctly
    • Test: add_thermal_coupling with invalid circuit_id fails
    • Test: compute_coupling_heat positive when T_hot > T_cold
    • Test: circular dependency detection (A→B→A)
    • Test: no circular dependency (A→B, B→C)
    • Test: coupling_groups returns correct groupings
    • Test: energy conservation Q_hot = -Q_cold

Dev Notes

Epic Context

Epic 3: System Topology (Graph) — Enable component assembly via Ports and manage multi-circuits with thermal coupling. FR11 (thermal coupling between circuits) maps to crates/solver.

Story Dependencies:

  • Story 3.1 (System graph structure) — done
  • Story 3.2 (Port compatibility validation) — done
  • Story 3.3 (Multi-circuit machine definition) — done; provides CircuitId, node_to_circuit, circuit accessors
  • Story 3.5 (Zero-flow) — independent

Architecture Context

Technical Stack:

  • Rust, petgraph 0.6.x (already has is_cyclic, cycle detection), thiserror, entropyk-core, entropyk-components
  • No new external dependencies

Code Structure:

  • crates/solver/src/system.rs — primary modification site (add thermal_couplings, add_thermal_coupling)
  • crates/solver/src/coupling.rs — NEW file for ThermalCoupling, coupling graph, dependency detection
  • crates/solver/src/error.rs — add TopologyError::InvalidCircuitForCoupling

Relevant Architecture Sections:

  • System Topology (architecture line 797): FR9FR13 in solver/system.rs
  • FR11: System supports connections between circuits (thermal coupling)
  • Pre-allocation (architecture line 239): Coupling metadata built at finalize time, not in solver hot path

API Patterns:

  • add_thermal_coupling(coupling: ThermalCoupling) -> Result<usize, TopologyError> — returns coupling index
  • Coupling does NOT create flow edges (cross-circuit flow prohibited per Story 3.3)
  • Coupling represents heat transfer ONLY — separate from fluid flow

Performance Requirements:

  • Coupling graph built once at finalize/build time
  • Circular dependency detection at finalize time
  • coupling_residuals() called in solver loop — must be fast (no allocation)

Developer Context

Existing Implementation (from Story 3.3):

  • CircuitId(pub u8) with valid range 0..=4
  • node_to_circuit: HashMap<NodeIndex, CircuitId>
  • circuit_count(), circuit_nodes(), circuit_edges()
  • Cross-circuit flow edges rejected (TopologyError::CrossCircuitConnection)
  • Thermal coupling is the ONLY way to connect circuits

Design Decisions:

  1. Coupling Representation:

    • ThermalCoupling stored in System, separate from graph edges
    • Coupling does NOT create petgraph edges (would confuse flow traversal)
    • Coupling indexed by usize for O(1) access
  2. Circular Dependency Graph:

    • Build temporary petgraph::Graph<CircuitId, ()> for cycle detection
    • Nodes = CircuitIds present in any coupling
    • Directed edge from hot_circuit → cold_circuit (heat flows hot to cold)
    • Use petgraph::algo::is_cyclic::is_cyclic_directed()
  3. Coupling Groups:

    • Strongly connected components (SCC) in coupling graph
    • Circuits in same SCC must solve simultaneously
    • Circuits in different SCCs can solve sequentially (topological order)
  4. Residual Convention:

    • Coupling residual: r = Q_model - Q_coupling where Q_model is from circuit state
    • Jacobian includes ∂r/∂T_hot and ∂r/∂T_cold
    • Solver treats coupling residuals like component residuals

Technical Requirements

ThermalCoupling Struct:

/// Thermal coupling between two circuits via heat exchanger.
/// Heat flows from hot_circuit to cold_circuit.
#[derive(Debug, Clone)]
pub struct ThermalCoupling {
    pub hot_circuit: CircuitId,
    pub cold_circuit: CircuitId,
    pub ua: ThermalConductance, // W/K
}

/// Sign convention: Q > 0 means heat INTO cold_circuit (out of hot_circuit).
pub fn compute_coupling_heat(
    coupling: &ThermalCoupling,
    t_hot: Temperature,
    t_cold: Temperature,
) -> HeatTransfer {
    HeatTransfer(coupling.ua.0 * (t_hot.0 - t_cold.0))
}

Error Types:

  • TopologyError::InvalidCircuitForCoupling { circuit_id: CircuitId } — circuit doesn't exist

Coupling Graph (internal):

fn build_coupling_graph(couplings: &[ThermalCoupling]) -> petgraph::Graph<CircuitId, ()> {
    let mut graph = petgraph::Graph::new();
    // Add nodes for each unique circuit in couplings
    // Add directed edge: hot_circuit -> cold_circuit
    graph
}

Architecture Compliance

  • NewType pattern: Use ThermalConductance(pub f64), HeatTransfer(pub f64) for clarity
  • tracing for circular dependency warnings: tracing::warn!("Circular thermal coupling detected, simultaneous solving required")
  • Result<T, E> — no unwrap/expect in production
  • #![deny(warnings)] — all crates

Library/Framework Requirements

  • petgraph: Already in dependencies; use is_cyclic_directed, kosaraju_scc
  • entropyk_core: Temperature, HeatTransfer (add ThermalConductance if needed)
  • thiserror: TopologyError extension

File Structure Requirements

Modified files:

  • crates/solver/src/system.rs — add thermal_couplings, add_thermal_coupling, finalize validation
  • crates/solver/src/error.rs — add TopologyError::InvalidCircuitForCoupling
  • crates/solver/src/lib.rs — re-export ThermalCoupling

New files:

  • crates/solver/src/coupling.rs — ThermalCoupling, compute_coupling_heat, coupling graph utilities

Tests:

  • Add to crates/solver/src/coupling.rs (inline #[cfg(test)] module)
  • Extend tests/multi_circuit.rs with thermal coupling integration test

Testing Requirements

Unit tests (coupling.rs):

  • test_thermal_coupling_creation — valid coupling, correct fields
  • test_compute_coupling_heat_positive — T_hot > T_cold → Q > 0
  • test_compute_coupling_heat_zero — T_hot == T_cold → Q = 0
  • test_compute_coupling_heat_negative — T_hot < T_cold → Q < 0 (reverse flow)
  • test_circular_dependency_detection — A→B, B→A → cyclic
  • test_no_circular_dependency — A→B, B→C → not cyclic
  • test_coupling_groups_scc — A↔B, C→D → groups A,B], [C], [D or similar

Integration tests (system.rs or multi_circuit.rs):

  • test_add_thermal_coupling_valid — 2 circuits, add coupling, verify stored
  • test_add_thermal_coupling_invalid_circuit — coupling with CircuitId(99) → error
  • test_coupling_residuals_basic — 2 circuits with coupling, verify residual equation

Project Structure Notes

  • Architecture specifies crates/solver/src/system.rs for FR9FR13 — matches
  • Story 3.3 provides circuit foundation; 3.4 adds thermal coupling layer
  • Story 4.x (Solver) will consume coupling_residuals() and coupling_groups()
  • Coupling does NOT modify graph edges — flow edges remain same-circuit only

Previous Story Intelligence (3.3)

  • CircuitId range 0..=4 validated at construction
  • circuit_count() returns number of distinct circuits with components
  • add_component_to_circuit assigns component to circuit
  • Cross-circuit flow edges rejected with TopologyError::CrossCircuitConnection
  • 3.3 explicitly documented: "Story 3.4 will add thermal coupling (cross-circuit heat transfer)"

Previous Story Intelligence (3.1)

  • System uses Graph<Box<dyn Component>, FlowEdge, Directed>
  • finalize() builds edge→state mapping, validates topology
  • traverse_for_jacobian yields (node, component, edge_indices)
  • Solver consumes residuals from components; coupling residuals follow same pattern

References

Dev Agent Record

Agent Model Used

claude-sonnet-4-20250514

Debug Log References

N/A

Completion Notes List

  • Created ThermalConductance NewType in crates/core/src/types.rs for type-safe UA values
  • Created crates/solver/src/coupling.rs with ThermalCoupling struct, compute_coupling_heat(), has_circular_dependencies(), and coupling_groups()
  • Added InvalidCircuitForCoupling error variant to TopologyError in error.rs
  • Extended System struct with thermal_couplings: Vec<ThermalCoupling> and add_thermal_coupling() method
  • Circuit validation in add_thermal_coupling() ensures both hot and cold circuits exist
  • Circular dependency detection uses petgraph's is_cyclic_directed() and SCC grouping via kosaraju_scc()
  • Sign convention: Q > 0 means heat INTO cold_circuit (documented in code)
  • 16 unit tests in coupling.rs + 5 integration tests in system.rs (42 solver tests total)
  • Fixed pre-existing clippy issues in calib.rs, compressor.rs, and expansion_valve.rs
  • All 387 tests pass (297 components + 46 core + 42 solver + 2 integration)
  • Code review (AI) 2026-02-17: Implemented missing AC#4 APIs: coupling_residual_count(), coupling_residuals(temperatures, out), coupling_jacobian_entries(row_offset, t_hot_cols, t_cold_cols). Added tracing::warn! in finalize() when circular thermal dependencies detected. Added integration test test_coupling_residuals_basic in crates/solver/tests/multi_circuit.rs. All solver tests pass (43 unit + 4 integration).

File List

  • crates/core/src/types.rs (modified: added ThermalConductance NewType with Display, From, conversions)
  • crates/core/src/lib.rs (modified: re-export ThermalConductance)
  • crates/solver/src/coupling.rs (new: ThermalCoupling, compute_coupling_heat, has_circular_dependencies, coupling_groups, build_coupling_graph)
  • crates/solver/src/system.rs (modified: thermal_couplings field, add_thermal_coupling, thermal_coupling_count, thermal_couplings, get_thermal_coupling, circuit_exists)
  • crates/solver/src/error.rs (modified: TopologyError::InvalidCircuitForCoupling)
  • crates/solver/src/lib.rs (modified: re-export coupling module and types)
  • crates/core/src/calib.rs (fixed: clippy manual_range_contains)
  • crates/components/src/compressor.rs (fixed: clippy too_many_arguments, unused variable)
  • crates/components/src/expansion_valve.rs (fixed: clippy unnecessary_map_or)
  • crates/solver/tests/multi_circuit.rs (modified: added test_coupling_residuals_basic)
  • _bmad-output/implementation-artifacts/3-4-code-review-findings.md (new: code review report)

Change Log

  • 2026-02-17: Story 3.4 implementation complete. ThermalCoupling struct with hot/cold circuit, UA, efficiency. compute_coupling_heat with proper sign convention. Circular dependency detection via petgraph is_cyclic_directed. Coupling groups via kosaraju_scc for SCC analysis. All ACs satisfied, 42 solver tests pass.
  • 2026-02-17: Code review (AI): Fixed C1/H1 (coupling_residuals, coupling_jacobian_entries), M1 (tracing::warn in finalize), M3 (test_coupling_residuals_basic), M4 (finalize circular check). Story status → done.