# 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<Connected>
  - [x] Add StateManageable trait for Pump<Connected>
  - [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<T> 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<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**
```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<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):**
```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<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 component
- `crates/components/src/lib.rs` - Add vfd module and re-exports
