Files
Entropyk/_bmad-output/implementation-artifacts/1-4-compressor-component-ahri-540.md
sepehr 5bd180b5b8
Some checks failed
CI / check (push) Has been cancelled
Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 16:35:31 +02:00

17 KiB
Raw Blame History

Story 1.4: Compressor Component (AHRI 540)

Status: done

Story

As a thermodynamic engineer, I want to model a compressor using AHRI 540 standard coefficients or SST/SDT polynomial curves, so that I can simulate real compressor behavior from manufacturer data.

Acceptance Criteria

  1. Compressor Structure (AC: #1)

    • Define Compressor<State> struct with suction and discharge ports (Type-State pattern)
    • Support two performance models via CompressorModel enum: Ahri540(Ahri540Coefficients) and SstSdt(SstSdtCoefficients)
    • Store 10 AHRI 540 coefficients (M1-M10) in Ahri540Coefficients struct
    • Store SST/SDT polynomial curves in SstSdtCoefficients (mass_flow_curve + power_curve as Polynomial2D)
    • Include rotational speed (RPM), displacement volume, and mechanical efficiency
    • Include Calib struct (f_m, f_power, f_etav) for calibration factors
    • Include CalibIndices for solver-level embedded calibration variables
    • Include CircuitId for multi-circuit machine support (FR9)
    • Include OperationalState (On, Off, Bypass) for FR6-FR8
    • Include edge state indices (suction_m_idx, suction_h_idx, discharge_m_idx, discharge_h_idx) for CM1.3
  2. AHRI 540 Performance Equations (AC: #2)

    • Mass flow: ṁ = M1 × (1 - (P_suction/P_discharge)^(1/M2)) × ρ_suction × V_disp × N/60 × f_etav × f_m
    • Power (cooling): Ẇ = (M3 + M4 × (P_discharge/P_suction) + M5 × T_suction + M6 × T_discharge) × f_power
    • Power (heating): Ẇ = (M7 + M8 × (P_discharge/P_suction) + M9 × T_suction + M10 × T_discharge) × f_power
    • Cooling capacity: Q̇_cool = ṁ × (h_evap_out - h_evap_in)
    • Heating capacity: Q̇_heat = ṁ × (h_cond_out - h_cond_in)
    • COP calculation: COP = Q̇ / Ẇ
  3. SST/SDT Polynomial Model (AC: #3)

    • Mass flow: ṁ = Σ a_ij × SST^i × SDT^j via Polynomial2D
    • Power: Ẇ = Σ b_ij × SST^i × SDT^j via Polynomial2D
    • Support bilinear and biquadratic configurations
    • SstSdtCoefficients::bilinear() and ::biquadratic() factory methods
    • SST/SDT model does not distinguish cooling/heating (single power curve)
  4. Residual Computation — 3 equations (AC: #4)

    • n_equations() returns 3 (upgraded from 2 to include CM1.3 mass conservation)
    • r0: Mass flow continuity — ṁ_calc ṁ_state = 0
    • r1: Energy balance — Ẇ_calc ṁ_state × (h_discharge h_suction) / η_mech = 0
    • r2: Mass conservation (CM1.3) — ṁ_discharge ṁ_suction = 0
    • OperationalState::Offr0 = ṁ_suction, r1 = 0, r2 = ṁ_discharge ṁ_suction
    • OperationalState::Bypassr0 = P_suction P_discharge, r1 = h_suction h_discharge, r2 = ṁ_discharge ṁ_suction
  5. Jacobian Entries (AC: #5)

    • Row 0: ∂r0/∂ṁ_suction = -1 (exact, CM1.3)
    • Row 0: ∂r0/∂h_suction — numerical via approximate_derivative (density depends on h)
    • Row 1: ∂r1/∂ṁ_suction = (h_discharge h_suction) / η_mech (exact)
    • Row 1: ∂r1/∂h_suction, ∂r1/∂h_discharge — numerical (power depends on temperature estimates)
    • Row 2: ∂r2/∂ṁ_discharge = +1, ∂r2/∂ṁ_suction = -1 (exact, CM1.3)
    • PARTIAL: Derivatives ∂r0/∂h and ∂r1/∂h use finite-difference approximation (temperature estimation not differentiable analytically in current placeholder backend)
  6. Component Trait Integration (AC: #6)

    • Implement Component trait for Compressor<Connected>
    • get_ports() returns empty slice (lifetime constraint — use get_ports_slice() instead)
    • get_ports_slice() returns [&Port<Connected>; 2] as workaround
    • set_system_context() stores edge indices for CM1.3 integration
    • energy_transfers() returns (electrical_power, heat_rejection) for energy validation
    • signature() returns "Compressor(fluid=X, circuit=Y)"
  7. Validation & Testing (AC: #7)

    • Unit tests for Ahri540Coefficients storage and validation
    • Unit tests for SstSdtCoefficients bilinear and biquadratic
    • Unit tests for mass flow calculation (AHRI 540 + SST/SDT)
    • Unit tests for power consumption (cooling + heating modes)
    • Unit tests for residuals at equilibrium
    • Jacobian finite-difference verification
    • Tests with R134a, R410A, R454B refrigerants
    • Tests for Off and Bypass operational states

Tasks / Subtasks

  • Create crates/components/src/compressor.rs module (AC: #1)
    • Define Ahri540Coefficients struct with M1-M10 fields and validate()
    • Define SstSdtCoefficients struct wrapping Polynomial2D
    • Define CompressorModel enum (Ahri540 | SstSdt)
    • Define Compressor<State> with all fields (model, ports, calib, circuit_id, edge indices)
    • Implement Compressor::new() for AHRI 540 constructor
    • Implement Compressor::with_model() for generic model constructor
    • Implement connect() method (Type-State transition Disconnected → Connected)
  • Create crates/components/src/polynomials.rs module (AC: #3)
    • Define Polynomial2D struct for 2D polynomial evaluation
    • Implement evaluate(x, y), validate(), bilinear(), biquadratic()
  • Implement AHRI 540 calculations (AC: #2)
    • mass_flow_rate(density, sst_k, sdt_k, state) — dispatches to model
    • power_consumption_cooling(t_suction, t_discharge, state)
    • power_consumption_heating(t_suction, t_discharge, state)
    • cooling_capacity(), heating_capacity(), cop()
  • Implement residual computation (AC: #4)
    • 3-equation system: r0 mass flow, r1 energy, r2 mass conservation
    • OperationalState dispatch (On/Off/Bypass)
    • Calibration factors applied via Calib and CalibIndices
  • Implement Jacobian entries (AC: #5)
    • Exact entries where analytically tractable
    • approximate_derivative() helper for numerical differentiation
  • Implement Component trait (AC: #6)
    • compute_residuals, jacobian_entries, n_equations, get_ports
    • set_system_context for CM1.3 edge index injection
    • energy_transfers, signature
    • get_ports_slice() workaround
  • Register exports (AC: #6)
    • crates/components/src/lib.rs — pub mod compressor; pub mod polynomials; re-exports
    • crates/entropyk/src/lib.rs — re-export compressor types
  • Write comprehensive tests (AC: #7)

Dev Notes

Architecture Context

Critical patterns established here (followed by ALL subsequent components):

// Type-State pattern: Disconnected → Connected
pub struct Compressor<State> {
    model: CompressorModel,
    port_suction: Port<State>,
    port_discharge: Port<State>,
    speed_rpm: f64,
    displacement_m3_per_rev: f64,
    mechanical_efficiency: f64,
    calib: Calib,                          // Static calibration factors
    calib_indices: CalibIndices,            // Dynamic solver-embedded indices
    fluid_id: FluidId,
    circuit_id: CircuitId,
    operational_state: OperationalState,
    suction_m_idx: Option<usize>,           // CM1.3: injected by set_system_context
    suction_h_idx: Option<usize>,
    discharge_m_idx: Option<usize>,
    discharge_h_idx: Option<usize>,
    _state: PhantomData<State>,
}

Performance model dispatch:

pub enum CompressorModel {
    Ahri540(Ahri540Coefficients),
    SstSdt(SstSdtCoefficients),
}

Compressor::new() takes Ahri540Coefficients directly; Compressor::with_model() takes CompressorModel.

3-equation system (post CM1.3 upgrade):

n_equations() = 3
r0: ṁ_calc(model)  ṁ_state   = 0    (mass flow matching)
r1: Ẇ_calc × Δh / η_mech = 0    (energy balance)
r2: ṁ_discharge  ṁ_suction   = 0    (mass conservation per edge)

CM1.3 state variable layout (edge-based):

  • set_system_context(state_offset, external_edge_state_indices) injects (m_idx, p_idx, h_idx) per edge
  • Edge 0 = suction (incoming), Edge 1 = discharge (outgoing)
  • Fallback to positional defaults [0,1,3,2] when indices not set (unit tests)

Calibration factors (FR43):

// Calib struct (entropyk_core)
pub struct Calib {
    pub f_m: f64,      // Mass flow correction factor
    pub f_power: f64,  // Power correction factor
    pub f_etav: f64,   // Volumetric efficiency correction
    // ... other factors
}
// Applied as: ṁ_eff = f_etav × ṁ_vol × f_m
//             Ẇ_eff = f_power × Ẇ_nominal

CalibIndices holds Option<usize> indices into the state vector so calibration variables can be solver unknowns (inverse calibration, FR45/FR51).

get_ports() FIXME — known limitation:

// Component trait get_ports() returns empty slice — lifetime constraint.
// Use get_ports_slice() for actual port access:
pub fn get_ports_slice(&self) -> [&Port<Connected>; 2]

This is a known API limitation tracked for future refactor.

Fluid property placeholders: Internal functions estimate_density(fluid_id, p_pa, h_jkg) and estimate_temperature(fluid_id, p_pa, h_jkg) use lookup tables for R134a, R410A, R454B. These are temporary and will be replaced by full CoolProp integration via FluidBackend (Story 2.2 / Epic 8). When real backend is available, suction_state() / discharge_state() methods provide full ThermoState.

Technical Requirements

AHRI 540 Mass Flow (note inverse ratio — p_suction/p_discharge):

η_vol = 1  (P_suction / P_discharge)^(1/M2)   ← inverse ratio ensures η_vol ∈ [0,1]
ṁ = M1 × η_vol × ρ_suction × V_disp × (N/60) × f_etav × f_m

Do NOT use (P_discharge/P_suction) for volumetric efficiency — negative η_vol would result.

SST/SDT Polynomial2D:

// Polynomial2D evaluates Σ c[i][j] × x^i × y^j
// bilinear: 2×2 coefficient matrix
// biquadratic: 3×3 coefficient matrix
pub struct Polynomial2D { coefficients: Vec<Vec<f64>> }

Rust naming conventions:

  • Struct: CamelCaseCompressor, Ahri540Coefficients, SstSdtCoefficients, CompressorModel
  • Methods: snake_casecompute_residuals, mass_flow_rate, power_consumption_cooling
  • Generic param: State (not S)

File Structure

crates/
├── components/
│   ├── Cargo.toml               # [dev-dependencies] approx = "0.5"
│   └── src/
│       ├── lib.rs               # pub mod compressor; pub mod polynomials; re-exports
│       ├── compressor.rs        # This story (2276 lines)
│       ├── polynomials.rs       # Polynomial2D (used by SstSdtCoefficients)
│       └── port.rs              # Port<State> from Story 1.3
├── core/
│   └── src/
│       ├── calib.rs             # Calib, CalibIndices types (Story 7.6)
│       └── types.rs             # Pressure, Temperature, Enthalpy, MassFlow (Story 1.2)
└── entropyk/
    └── src/
        └── lib.rs               # Re-exports: pub use entropyk_components::compressor::*

Testing Requirements

Required test coverage:

// Tests live in #[cfg(test)] mod tests at bottom of compressor.rs
// Helper: test_coefficients() returns standard Ahri540Coefficients for tests

// 1. Coefficient storage/validation
#[test] fn test_ahri540_coefficients_storage()
#[test] fn test_ahri540_coefficients_validate_invalid_m2()
#[test] fn test_sst_sdt_bilinear_creation()
#[test] fn test_sst_sdt_biquadratic_creation()

// 2. Mass flow
#[test] fn test_mass_flow_calculation()
#[test] fn test_mass_flow_with_high_pressure_ratio()
#[test] fn test_sst_sdt_mass_flow()

// 3. Power consumption
#[test] fn test_power_consumption_cooling()
#[test] fn test_power_consumption_heating()

// 4. Residuals
#[test] fn test_residuals_3_equations()
#[test] fn test_off_state_residuals()
#[test] fn test_bypass_state_residuals()

// 5. Jacobian
#[test] fn test_jacobian_entries()      // finite difference verification

// 6. Refrigerants
#[test] fn test_compressor_with_r134a()
#[test] fn test_compressor_with_r410a()
#[test] fn test_compressor_with_r454b()

References

  • AHRI 540 Standard: Air-Conditioning, Heating, and Refrigeration Institute Standard 540
  • Inverse pressure ratio decision: Using P_suction/P_discharge (not P_discharge/P_suction) ensures η_vol ≥ 0 — see compressor.rs line ~806
  • Component Trait: [Source: crates/components/src/lib.rs]Component, ConnectedPort, ResidualVector, JacobianBuilder
  • CM1.3 edge-based mass flow: [Source: _bmad-output/implementation-artifacts/cm-1-3-mass-flow-newton-unknown.md]
  • Calib / CalibIndices: [Source: crates/core/src/calib.rs] — FR43, FR45, FR51
  • OperationalState: [Source: crates/components/src/lib.rs] — FR6, FR7, FR8
  • CircuitId: [Source: crates/core/src/types.rs] — FR9
  • Polynomial2D: [Source: crates/components/src/polynomials.rs]
  • FR1: Compressor AHRI 540 — [Source: _bmad-output/planning-artifacts/epics.md#FR1]
  • Architecture Component Model: [Source: _bmad-output/planning-artifacts/architecture.md#Component Model]

Senior Developer Review (AI)

Review Date: 2026-02-15 Review Outcome: Approved → Fixed Reviewer: opencode/kimi-k2.5-free (code-review workflow)

Issues Found and Fixed

🔴 HIGH (2 fixed):

  1. Documentation Error — Mass flow equation in docstring showed wrong pressure ratio direction. Fixed to show P_suction/P_discharge.
  2. AC #5 Partial Implementation — Jacobian uses finite differences for ∂r/∂h derivatives. Marked AC as PARTIAL.

🟡 MEDIUM (4 fixed): 3. get_ports() Returns Empty — Lifetime constraint. Added get_ports_slice() workaround. 4. Missing R454B Test — Added R454B test case. 5. File List IncompleteCargo.toml and polynomials.rs not documented. Added. 6. Non-existent Dependency — Removed entropyk-fluids reference (not a separate crate).

🟢 LOW (2 fixed): 7. Placeholder Documentation — Added explicit Story 2.2 / Epic 8 references for CoolProp integration. 8. Test Quality — Improved test_mass_flow_with_high_pressure_ratio clarity.

Action Items

  • All HIGH and MEDIUM issues fixed
  • All tests pass (61 unit + 20 doc tests at time of original review)
  • Story status updated to "done"

Dev Agent Record

Agent Model Used

opencode/kimi-k2.5-free

Debug Log References

  • Implementation completed: 2026-02-15
  • Code review completed: 2026-02-15
  • Story context refreshed: 2026-06-26 (reflects evolved codebase: SST/SDT model, CM1.3 integration, Calib)
  • All tests passing: 61+ unit tests
  • Clippy validation: Zero warnings

Completion Notes List

Original Implementation (2026-02-15):

  • Created crates/components/src/compressor.rs with complete Compressor component
  • Implemented Ahri540Coefficients with validation for all 10 coefficients
  • Implemented Type-State pattern Compressor<Disconnected> / Compressor<Connected>
  • Implemented AHRI 540 mass flow with inverse pressure ratio for volumetric efficiency
  • Implemented power equations for cooling (M3-M6) and heating (M7-M10) modes
  • Implemented full Component trait integration

Post-implementation Evolutions (reflected in current codebase):

  • SST/SDT model added (SstSdtCoefficients, Polynomial2D, CompressorModel enum)
  • CM1.3 upgrade: n_equations() now returns 3 (added mass conservation residual r2)
  • set_system_context() added: edge index injection for per-edge (ṁ,P,h) state variables
  • Calibration integration: Calib and CalibIndices fields, factors applied in calculations
  • energy_transfers() added for energy validation framework
  • suction_state() / discharge_state() added for full ThermoState access via FluidBackend
  • OperationalState support (On/Off/Bypass) per FR6-FR8

Technical Decisions:

  • Inverse pressure ratio (P_suction/P_discharge) for volumetric efficiency — ensures η_vol ∈ [0,1]
  • CompressorModel enum rather than trait object — avoids object-safety issues with Clone/PartialEq
  • Finite-difference approximation for ∂r/∂h Jacobian — placeholder until real FluidBackend
  • get_ports() returns empty slice (lifetime constraint) — get_ports_slice() as workaround
  • R454B uses R410A properties as approximation in placeholder backend

File List

Created:

  1. crates/components/src/compressor.rs — Compressor component (~2276 lines)
  2. crates/components/src/polynomials.rsPolynomial2D for SST/SDT model

Modified:

  1. crates/components/src/lib.rs — Added pub mod compressor, pub mod polynomials, re-exports
  2. crates/components/Cargo.toml — Added approx = "0.5" dev-dependency
  3. crates/entropyk/src/lib.rs — Re-exported compressor types

Dependencies

# crates/components/Cargo.toml
[dependencies]
entropyk-core = { path = "../core" }
entropyk-fluids = { path = "../fluids" }   # for suction_state()/discharge_state()
thiserror = "1.0"
serde = { version = "1.0", features = ["derive"] }

[dev-dependencies]
approx = "0.5"

Ultimate context engine analysis completed — story refreshed 2026-06-26 to reflect evolved implementation (SST/SDT model, CM1.3 3-equation system, Calib integration)