8.8 KiB
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
-
Pump Component (AC: #1) ✅ IMPLEMENTED
- Pump with polynomial performance curves (head, efficiency)
- Affinity laws for variable speed operation (Q ∝ N, H ∝ N², P ∝ N³)
- Component trait implementation with compute_residuals, jacobian_entries
- StateManageable trait (ON/OFF/BYPASS states)
- Hydraulic power calculation: P = Q × ΔP / η
-
Pipe Component (AC: #2) ✅ IMPLEMENTED
- Darcy-Weisbach pressure drop calculation
- Haaland friction factor (laminar + turbulent)
for_incompressible()andfor_refrigerant()constructors- PipeGeometry with roughness constants
- Calib (f_dp) for calibration
- Component and StateManageable traits
-
Fan Component (AC: #3) ✅ IMPLEMENTED
- Fan with polynomial performance curves (static pressure, efficiency)
- Affinity laws for variable speed operation
- Static pressure and total pressure (with velocity pressure)
- Component and StateManageable traits
-
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
-
Integration Tests (AC: #5) ⚠️ PARTIAL
- Pump unit tests (20+ tests in pump.rs)
- Pipe unit tests (25+ tests in pipe.rs)
- Fan unit tests (15+ tests in fan.rs)
- VFD integration tests
- Full circuit simulation with Pump + Pipe + VFD
Tasks / Subtasks
-
Pump Implementation (AC: #1)
- Create PumpCurves struct with quadratic/cubic curves
- Implement AffinityLaws scaling in pump.rs
- Add Component trait for Pump
- Add StateManageable trait for Pump
- Implement compute_residuals with ON/OFF/BYPASS handling
- Write 20+ unit tests
-
Pipe Implementation (AC: #2)
- Create PipeGeometry struct with roughness constants
- Implement Darcy-Weisbach equation
- Implement Haaland friction factor
- Add
for_incompressible()andfor_refrigerant()constructors - Add Component and StateManageable traits
- Write 25+ unit tests
-
Fan Implementation (AC: #3)
- Create FanCurves struct with polynomial curves
- Implement static_pressure_rise and total_pressure_rise
- Add Component and StateManageable traits
- 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_ratiofield (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)
pub struct Vfd<T: Component> {
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
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:
impl Component for Xxx<Connected> {
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<Connected> {
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):
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):
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):
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, residualspipe.rs: 25+ unit tests covering geometry, friction, pressure dropfan.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<T, ThermoError> [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 componentcrates/components/src/lib.rs- Add vfd module and re-exports