Entropyk/_bmad-output/implementation-artifacts/3-2-port-compatibility-validation.md

13 KiB
Raw Permalink Blame History

Story 3.2: Port Compatibility Validation

Status: done

Story

As a system designer, I want port connection validation at build time, so that incompatible connections are caught early.

Acceptance Criteria

  1. Incompatible Fluid Rejection (AC: #1)

    • Given two ports with incompatible fluids (e.g., R134a vs Water)
    • When attempting to connect
    • Then connection fails with clear error (e.g., ConnectionError::IncompatibleFluid or TopologyError)
    • And error message identifies both fluids
  2. Valid Connections Accepted (AC: #2)

    • Given two ports with same fluid, matching pressure and enthalpy within tolerance
    • When attempting to connect
    • Then connection is accepted
    • And edge is added to system graph
  3. Pressure/Enthalpy Continuity Enforced (AC: #3)

    • Given two ports with same fluid but pressure or enthalpy mismatch beyond tolerance
    • When attempting to connect
    • Then connection fails with clear error
    • And tolerance follows existing port.rs constants (PRESSURE_TOLERANCE_FRACTION, ENTHALPY_TOLERANCE_J_KG)

Tasks / Subtasks

  • Integrate port validation into System graph build (AC: #1, #2, #3)
    • Extend add_edge API to specify port indices: add_edge_with_ports(source_node, source_port_idx, target_node, target_port_idx) -> Result<EdgeIndex, ConnectionError>
    • When adding edge: retrieve ports via component.get_ports() for source and target nodes
    • Compare fluid_id() of outlet port (source) vs inlet port (target) — reject if different
    • Compare pressure and enthalpy within tolerance — reject if mismatch
    • Return Result<EdgeIndex, ConnectionError> on failure
  • Error handling and propagation (AC: #1)
    • Reuse ConnectionError from port (re-exported in solver)
    • Add ConnectionError::InvalidPortIndex for out-of-bounds port indices
    • Ensure error messages are clear and actionable
  • Tests
    • Test: connect R134a outlet to R134a inlet — succeeds
    • Test: connect R134a outlet to Water inlet — fails with IncompatibleFluid
    • Test: connect with pressure mismatch — fails with PressureMismatch
    • Test: connect with enthalpy mismatch — fails with EnthalpyMismatch
    • Test: valid connection in 4-node cycle — all edges accepted

Dev Notes

Epic Context

Epic 3: System Topology (Graph) — Enable component assembly via Ports and manage multi-circuits with thermal coupling. FR10 (component connection via Ports) and FR9FR13 map to crates/solver and crates/components.

Story Dependencies:

  • Epic 1 (Component trait, Ports, State machine) — done
  • Story 3.1 (System graph structure) — done
  • port.rs already has Port::connect() with fluid, pressure, enthalpy validation — reuse logic or call from solver

Architecture Context (Step 3.2 — CRITICAL EXTRACTION)

Technical Stack:

  • Rust, petgraph 0.6.x, thiserror, entropyk-core, entropyk-components
  • No new external dependencies required

Code Structure:

  • crates/solver/src/system.rs — primary modification site
  • crates/solver/src/error.rs — extend TopologyError if needed
  • crates/components/src/port.rs — existing ConnectionError, FluidId, Port validation logic

API Patterns:

  • Current: add_edge(source: NodeIndex, target: NodeIndex) -> EdgeIndex (no port validation)
  • Target: Either extend to add_edge(source, source_port, target, target_port) -> Result<EdgeIndex, ConnectionError> or validate in finalize() by traversing edges and checking component ports
  • Convention: Components typically have get_ports() returning [inlet, outlet] for 2-port components; multi-port (economizer) need explicit port indices

Relevant Architecture Sections:

  • Type-State for Connection Safety (architecture line 239250): Ports use Disconnected/Connected; connection validation at connect time
  • Component Trait (architecture line 231237): get_ports() -> &[Port] provides port access
  • Error Handling (architecture line 276308): ThermoError, Result<T, E> throughout; zero-panic policy
  • System Topology (architecture line 617619): FR9FR13 in solver/system.rs

Performance Requirements:

  • Validation at build time only (not in solver hot path)
  • No dynamic allocation in solver loop — validation happens before finalize()

Testing Standards:

  • approx::assert_relative_eq! for float comparisons
  • Tolérance pression: 1e-4 relative ou 1 Pa min (port.rs)
  • Tolérance enthalpie: 100 J/kg (port.rs)

Integration Patterns:

  • Solver depends on components; components expose get_ports() and ConnectionError
  • May need to re-export or map ConnectionError from components in solver crate

Developer Context

Existing Implementation:

  • port.rs::Port::connect() already validates: IncompatibleFluid, PressureMismatch, EnthalpyMismatch
  • port.rs constants: PRESSURE_TOLERANCE_FRACTION=1e-4, ENTHALPY_TOLERANCE_J_KG=100, MIN_PRESSURE_TOLERANCE_PA=1
  • system.rs::add_edge() currently accepts any (source, target) without port validation
  • system.rs::validate_topology() checks isolated nodes only; comment says "Story 3.2" for port validation
  • TopologyError has UnconnectedPorts and InvalidTopology (allow dead_code) — ready for use

Design Decision:

  • Option A: Extend add_edge(source, source_port_idx, target, target_port_idx) — explicit, validates at add time
  • Option B: Add validate_port_compatibility() in finalize() — traverses edges, infers port mapping from graph (e.g., outgoing edge from node = outlet port, incoming = inlet)
  • Option B is simpler if graph structure implies port mapping (one outlet per node for simple components); Option A is more flexible for multi-port components

Port Mapping Convention:

  • For 2-port components: get_ports()[0] = inlet, get_ports()[1] = outlet (verify in compressor, condenser, etc.)
  • For economizer (4 ports): explicit port indices required

Technical Requirements

Validation Rules:

  1. Fluid compatibility: source_port.fluid_id() == target_port.fluid_id()
  2. Pressure continuity: |P_source - P_target| <= max(P * 1e-4, 1 Pa)
  3. Enthalpy continuity: |h_source - h_target| <= 100 J/kg

Error Types:

  • Reuse ConnectionError::IncompatibleFluid from entropyk_components
  • Reuse ConnectionError::PressureMismatch, EnthalpyMismatch or add TopologyError::IncompatiblePorts with nested cause
  • Solver crate depends on components — can use ConnectionError via entropyk_components::ConnectionError

Architecture Compliance

  • NewType pattern: Use Pressure, Enthalpy from core (ports already use them)
  • No bare f64 in public API
  • tracing for validation failures (e.g., tracing::warn!("Port validation failed: {}", err))
  • Result<T, E> — no unwrap/expect in production
  • approx for float assertions in tests

Library/Framework Requirements

  • entropyk_components: ConnectionError, FluidId, Port, ConnectedPort, Component::get_ports
  • petgraph: Graph, NodeIndex, EdgeIndex — no change
  • thiserror: TopologyError extension if needed

File Structure Requirements

Modified files:

  • crates/solver/src/system.rs — add port validation in add_edge or finalize
  • crates/solver/src/error.rs — possibly add TopologyError variants or use ConnectionError
  • crates/solver/Cargo.toml — ensure entropyk_components dependency (already present)

No new files required unless extracting validation to a separate module (e.g., validation.rs).

Testing Requirements

Unit tests (system.rs or validation module):

  • test_valid_connection_same_fluid — R134a to R134a, matching P/h
  • test_incompatible_fluid_rejected — R134a to Water
  • test_pressure_mismatch_rejected — same fluid, P differs > tolerance
  • test_enthalpy_mismatch_rejected — same fluid, h differs > 100 J/kg
  • test_simple_cycle_port_validation — 4 components, 4 edges, all valid

Integration:

  • Use real components (e.g., Compressor, Condenser) with ConnectedPort if available, or mock components with get_ports() returning ports with specific FluidId/P/h

Project Structure Notes

  • Architecture specifies crates/solver/src/system.rs for topology — matches
  • Story 3.1 created System with add_edge, finalize, validate_topology
  • Story 3.2 extends validation to port compatibility
  • Story 3.3 (Multi-Circuit) will add circuit tracking — no conflict

Previous Story Intelligence (3.1)

  • System uses Graph<Box<dyn Component>, FlowEdge, Directed>
  • add_edge(source, target) returns EdgeIndex; finalize() assigns state indices
  • MockComponent in tests has get_ports() -> &[] — need mock with non-empty ports for 3.2 tests
  • TopologyError::IsolatedNode already used; UnconnectedPorts/InvalidTopology reserved
  • traverse_for_jacobian yields (node, component, edge_indices); components get state via edge mapping

References

  • Epic 3 Story 3.2: [Source: planning-artifacts/epics.md#Story 3.2]
  • Architecture FR10: [Source: planning-artifacts/architecture.md — Component connection via Ports]
  • Architecture Type-State: [Source: planning-artifacts/architecture.md — line 239]
  • port.rs ConnectionError: [Source: crates/components/src/port.rs]
  • port.rs validation constants: [Source: crates/components/src/port.rs — PRESSURE_TOLERANCE_FRACTION, etc.]
  • system.rs validate_topology: [Source: crates/solver/src/system.rs — line 114]
  • Story 3.1: [Source: implementation-artifacts/3-1-system-graph-structure.md]

Change Log

  • 2026-02-15: Story 3.2 implementation complete. Added add_edge_with_ports with port validation, validate_port_continuity in port.rs, ConnectionError::InvalidPortIndex. All ACs satisfied, 5 new port validation tests + 2 port.rs tests.
  • 2026-02-17: Code review complete. Fixed File List documentation, enhanced InvalidPortIndex error messages with context, added pressure tolerance boundary test. Status: review → done. All tests passing (43 solver + 297 components).

Dev Agent Record

Agent Model Used

{{agent_model_name_version}}

Debug Log References

Completion Notes List

  • Added validate_port_continuity(outlet, inlet) in port.rs reusing PRESSURE_TOLERANCE_FRACTION, ENTHALPY_TOLERANCE_J_KG
  • Added add_edge_with_ports(source, source_port_idx, target, target_port_idx) -> Result<EdgeIndex, ConnectionError>
  • Convention: port 0 = inlet, port 1 = outlet for 2-port components
  • Components with no ports: add_edge (unvalidated) or add_edge_with_ports with empty ports skips validation
  • ConnectionError re-exported from solver; InvalidPortIndex for invalid node/port indices
  • 5 solver tests + 2 port.rs tests for validate_port_continuity

File List

  • crates/components/src/port.rs (modified: validate_port_continuity, ConnectionError::InvalidPortIndex)
  • crates/components/src/lib.rs (modified: re-export validate_port_continuity)
  • crates/solver/ (new: initial solver crate implementation)
    • src/system.rs: System graph with add_edge_with_ports and port validation
    • src/lib.rs: Public API exports including ConnectionError re-export
    • src/error.rs: TopologyError and AddEdgeError definitions
    • src/coupling.rs: ThermalCoupling for multi-circuit support
    • src/graph.rs: Graph traversal utilities
    • Cargo.toml: Dependencies on entropyk-components, entropyk-core, petgraph, thiserror, tracing
  • crates/solver/tests/multi_circuit.rs (new: integration tests for multi-circuit topology)
  • _bmad-output/implementation-artifacts/sprint-status.yaml (modified: 3-2 in-progress → review)

Senior Developer Review (AI)

Reviewer: Code Review Agent
Date: 2026-02-17
Outcome: APPROVED

Issues Found and Fixed

  1. Documentation Inaccuracy (MEDIUM) - Fixed

    • File List incorrectly stated solver files were "modified"
    • Corrected to reflect crates/solver/ is a new crate
  2. Error Message Context (LOW) - Fixed

    • InvalidPortIndex error lacked context about which index failed
    • Enhanced to: Invalid port index {index}: component has {port_count} ports (valid: 0..{max_index})
  3. Test Coverage Gap (LOW) - Fixed

    • Added test_pressure_tolerance_boundary() to verify exact tolerance boundary behavior
    • Tests both at-tolerance (success) and just-outside-tolerance (failure) cases

Verification Results

  • All 43 solver tests passing
  • All 297 components tests passing
  • All 5 doc-tests passing
  • No compiler warnings (solver crate)

Quality Assessment

  • Architecture Compliance: Follows type-state pattern, NewType pattern
  • Error Handling: Result<T,E> throughout, zero unwrap/expect in production
  • Test Coverage: All ACs covered with unit tests
  • Documentation: Clear docstrings, examples in public API
  • Security: No injection risks, input validation at boundaries