# Story 1.8: Auxiliary & Transport Components Status: ready-for-dev ## Story As a system integrator, I want to model Pumps, VFDs, and Pipes with complete HVAC transport components, So that I can simulate complete HVAC systems with water, glycol, and air circuits. ## Acceptance Criteria 1. **Pump Component** (AC: #1) ✅ IMPLEMENTED - [x] Pump with polynomial performance curves (head, efficiency) - [x] Affinity laws for variable speed operation (Q ∝ N, H ∝ N², P ∝ N³) - [x] Component trait implementation with compute_residuals, jacobian_entries - [x] StateManageable trait (ON/OFF/BYPASS states) - [x] Hydraulic power calculation: P = Q × ΔP / η 2. **Pipe Component** (AC: #2) ✅ IMPLEMENTED - [x] Darcy-Weisbach pressure drop calculation - [x] Haaland friction factor (laminar + turbulent) - [x] `for_incompressible()` and `for_refrigerant()` constructors - [x] PipeGeometry with roughness constants - [x] Calib (f_dp) for calibration - [x] Component and StateManageable traits 3. **Fan Component** (AC: #3) ✅ IMPLEMENTED - [x] Fan with polynomial performance curves (static pressure, efficiency) - [x] Affinity laws for variable speed operation - [x] Static pressure and total pressure (with velocity pressure) - [x] Component and StateManageable traits 4. **VFD Abstraction** (AC: #4) 🔴 NOT IMPLEMENTED - [ ] Create Vfd component that wraps Pump/Fan/Compressor - [ ] Vfd exposes speed_ratio as controllable parameter - [ ] Vfd implements Component trait (delegates to wrapped component) - [ ] Vfd speed is bounded [0.0, 1.0] for inverse control 5. **Integration Tests** (AC: #5) ⚠️ PARTIAL - [x] Pump unit tests (20+ tests in pump.rs) - [x] Pipe unit tests (25+ tests in pipe.rs) - [x] Fan unit tests (15+ tests in fan.rs) - [ ] VFD integration tests - [ ] Full circuit simulation with Pump + Pipe + VFD ## Tasks / Subtasks - [x] Pump Implementation (AC: #1) - [x] Create PumpCurves struct with quadratic/cubic curves - [x] Implement AffinityLaws scaling in pump.rs - [x] Add Component trait for Pump - [x] Add StateManageable trait for Pump - [x] Implement compute_residuals with ON/OFF/BYPASS handling - [x] Write 20+ unit tests - [x] Pipe Implementation (AC: #2) - [x] Create PipeGeometry struct with roughness constants - [x] Implement Darcy-Weisbach equation - [x] Implement Haaland friction factor - [x] Add `for_incompressible()` and `for_refrigerant()` constructors - [x] Add Component and StateManageable traits - [x] Write 25+ unit tests - [x] Fan Implementation (AC: #3) - [x] Create FanCurves struct with polynomial curves - [x] Implement static_pressure_rise and total_pressure_rise - [x] Add Component and StateManageable traits - [x] Write 15+ unit tests - [ ] VFD Abstraction (AC: #4) - [ ] Create Vfd generic wrapper in new file vfd.rs - [ ] Implement Vfd::new(wrapped_component, initial_speed) - [ ] Implement speed() and set_speed() methods - [ ] Implement Component trait (delegates to wrapped) - [ ] Implement BoundedVariable for speed [0.0, 1.0] - [ ] Write unit tests for Vfd - [ ] Integration Tests (AC: #5) - [ ] Test Pump + Pipe circuit - [ ] Test Fan in air handling system - [ ] Test Vfd controlling Pump speed ## Dev Notes ### 🔥 CRITICAL: Most Components Already Implemented! **This story is mostly COMPLETE.** The following components already exist: | Component | File | Lines | Tests | Status | |-----------|------|-------|-------|--------| | Pump | `pump.rs` | 780 | 20+ | ✅ Done | | Pipe | `pipe.rs` | 1010 | 25+ | ✅ Done | | Fan | `fan.rs` | 636 | 15+ | ✅ Done | | Polynomials | `polynomials.rs` | 702 | 15+ | ✅ Done | | **VFD** | - | - | - | 🔴 **Not Implemented** | ### Remaining Work: VFD Abstraction The only missing piece is the **VFD (Variable Frequency Drive)** abstraction. Currently: - Pump, Fan have `speed_ratio` field (0.0 to 1.0) - `set_speed_ratio()` method exists - But no Vfd component that wraps these for inverse control ### VFD Implementation Approach **Option A: Wrapper Component (Recommended)** ```rust pub struct Vfd { wrapped: T, speed: BoundedVariable, // [0.0, 1.0] } ``` - Wraps Pump, Fan, or Compressor - Delegates Component trait methods - Exposes speed as BoundedVariable for inverse control **Option B: Trait-based Approach** ```rust pub trait SpeedControllable: Component { fn speed_ratio(&self) -> f64; fn set_speed_ratio(&mut self, ratio: f64) -> Result<(), ComponentError>; } ``` - Pump, Fan, Compressor implement this trait - Vfd uses dynamic dispatch ### Architecture Context **FR Coverage:** - FR6-FR8: Component states ON/OFF/BYPASS ✅ - FR40: Incompressible fluids support (via `for_incompressible`) ✅ **Component Trait Pattern:** All components follow the same pattern: ```rust impl Component for Xxx { fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError>; fn jacobian_entries(&self, state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError>; fn n_equations(&self) -> usize; fn get_ports(&self) -> &[ConnectedPort]; } impl StateManageable for Xxx { fn state(&self) -> OperationalState; fn set_state(&mut self, state: OperationalState) -> Result<(), ComponentError>; // ... } ``` ### Pump Details **Performance Curves:** ``` Head: H = a₀ + a₁Q + a₂Q² + a₃Q³ Efficiency: η = b₀ + b₁Q + b₂Q² Power: P_hydraulic = ρ × g × Q × H / η ``` **Affinity Laws (implemented in polynomials.rs):** ```rust AffinityLaws::scale_flow(flow, speed_ratio) // Q₂ = Q₁ × (N₂/N₁) AffinityLaws::scale_head(head, speed_ratio) // H₂ = H₁ × (N₂/N₁)² AffinityLaws::scale_power(power, speed_ratio) // P₂ = P₁ × (N₂/N₁)³ ``` ### Pipe Details **Darcy-Weisbach Equation:** ``` ΔP = f × (L/D) × (ρ × v² / 2) ``` **Haaland Friction Factor:** ``` 1/√f = -1.8 × log10[(ε/D/3.7)^1.11 + 6.9/Re] ``` **Roughness Constants (pipe.rs:roughness):** ```rust SMOOTH: 1.5e-6 // Copper, plastic STEEL_COMMERCIAL: 4.5e-5 GALVANIZED_IRON: 1.5e-4 CAST_IRON: 2.6e-4 CONCRETE: 1.0e-3 PLASTIC: 1.5e-6 ``` **Zero-Flow Regularization:** - Re clamped to MIN_REYNOLDS = 1.0 (prevents division by zero) - Consistent with Story 3.5 zero-flow branch handling ### Fan Details **Standard Air Properties (fan.rs:standard_air):** ```rust DENSITY: 1.204 kg/m³ // at 20°C, 101325 Pa CP: 1005.0 J/(kg·K) ``` **Total Pressure:** ``` P_total = P_static + P_velocity = P_static + ½ρv² ``` ### File Locations ``` crates/components/src/ ├── pump.rs # ✅ DONE - 780 lines ├── pipe.rs # ✅ DONE - 1010 lines ├── fan.rs # ✅ DONE - 636 lines ├── polynomials.rs # ✅ DONE - 702 lines ├── vfd.rs # 🔴 TODO - NEW FILE └── lib.rs # Add pub mod vfd; and re-exports ``` ### Testing Requirements **Existing Tests (do not modify):** - `pump.rs`: 20+ unit tests covering curves, affinity laws, residuals - `pipe.rs`: 25+ unit tests covering geometry, friction, pressure drop - `fan.rs`: 15+ unit tests covering curves, pressure, power **New Tests Required for VFD:** - Vfd creation and wrapping - Speed bounds enforcement - Component trait delegation - Integration: Vfd + Pump circuit ### Common Pitfalls to Avoid - ❌ Do NOT reimplement Pump, Pipe, Fan - they are complete - ❌ Do NOT use bare f64 for physical quantities - ❌ Do NOT use unwrap/expect in production code - ❌ Do NOT break existing tests - ❌ Ensure Vfd is object-safe (if using trait objects) ### References - **FR6-FR8:** Operational states ON/OFF/BYPASS [Source: epics.md#Story 1.7] - **FR40:** Incompressible fluids support [Source: epics.md#Story 2.7] - **Component Model:** Trait-based with Type-State [Source: architecture.md#Component Model] - **Zero-Panic Policy:** Result [Source: architecture.md#Error Handling Strategy] - **Story 1.7:** StateManageable trait (implemented in Pump, Pipe, Fan) - **Story 3.5:** Zero-flow branch handling (implemented in pipe.rs) ### Previous Story Intelligence **From Story 1.7 (Component State Machine):** - StateManageable trait with state(), set_state(), can_transition_to() - OperationalState enum: On, Off, Bypass - State history for debugging - All implemented correctly in Pump, Pipe, Fan **From Story 4.2 (Newton-Raphson):** - Components provide jacobian_entries() - Pump, Pipe, Fan already provide numerical Jacobians **From Story 5.2 (Bounded Control Variables):** - BoundedVariable with clip_step() - Vfd speed should use BoundedVariable for inverse control ## Dev Agent Record ### Agent Model Used {{agent_model_name_version}} ### Debug Log References ### Completion Notes List ### File List - `crates/components/src/vfd.rs` - NEW: Vfd wrapper component - `crates/components/src/lib.rs` - Add vfd module and re-exports