Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Some checks failed
CI / check (push) Has been cancelled
Some checks failed
CI / check (push) Has been cancelled
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>
This commit is contained in:
@@ -9,7 +9,7 @@ depends_on: 0-1-jacobian-killing-pattern-audit-fd-test-harness
|
||||
|
||||
# Story 0.2: Phantom-Gradient Regularization Standard
|
||||
|
||||
Status: review
|
||||
Status: done
|
||||
|
||||
<!-- Note: Validation is optional. Run validate-create-story for quality check before dev-story. -->
|
||||
<!-- Ultimate context engine analysis completed - comprehensive developer guide created -->
|
||||
@@ -98,6 +98,19 @@ so that fixes are uniform and derivative-safe instead of ad-hoc.
|
||||
- [x] 6.2 `cargo test -p entropyk-components` green — proves no accidental physics regressions from doc-only / non-touch policy.
|
||||
- [x] 6.3 Diff review: production component residual files should show **zero** physics diffs attributable to this story.
|
||||
|
||||
### Review Findings
|
||||
|
||||
- [x] [Review][Patch] smooth_clamp overlapping bounds validation [crates/core/src/smoothing.rs:458]
|
||||
- [x] [Review][Patch] normalize panic on NaN inputs [crates/core/src/smoothing.rs:69]
|
||||
- [x] [Review][Patch] smooth_clamp panic on NaN inputs [crates/core/src/smoothing.rs:458]
|
||||
- [x] [Review][Patch] smooth_max_derivative and smooth_min_derivative NaN division [crates/core/src/smoothing.rs:310]
|
||||
- [x] [Review][Patch] smooth_abs_derivative NaN division and overflow handling [crates/core/src/smoothing.rs:418]
|
||||
- [x] [Review][Patch] NaN propagation in derivatives under degenerate/fallback paths [crates/core/src/smoothing.rs:310]
|
||||
- [x] [Review][Defer] smooth_max and smooth_min overflow for extremely large inputs [crates/core/src/smoothing.rs:293] — deferred, pre-existing
|
||||
- [x] [Review][Defer] smooth_abs overflow for extremely large inputs [crates/core/src/smoothing.rs:396] — deferred, pre-existing
|
||||
- [x] [Review][Defer] slope amplification for very small widths in step derivatives [crates/core/src/smoothing.rs:123] — deferred, pre-existing
|
||||
- [x] [Review][Defer] cubic_blend and quintic_blend bound swapping [crates/core/src/smoothing.rs:205] — deferred, pre-existing
|
||||
|
||||
## Dev Notes
|
||||
|
||||
### Context & Positioning
|
||||
|
||||
@@ -7,317 +7,272 @@ Status: done
|
||||
## Story
|
||||
|
||||
As a thermodynamic engineer,
|
||||
I want to model a compressor using AHRI 540 standard coefficients,
|
||||
so that I can simulate real compressor behavior with manufacturer data.
|
||||
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)
|
||||
- [x] Define `Compressor` struct with suction and discharge ports
|
||||
- [x] Store 10 AHRI 540 coefficients (M1, M2, M3, M4, M5, M6, M7, M8, M9, M10)
|
||||
- [x] Include compressor speed (RPM) and displacement volume
|
||||
- [x] Support Type-State pattern for port connection safety
|
||||
- [x] Define `Compressor<State>` struct with suction and discharge ports (Type-State pattern)
|
||||
- [x] Support two performance models via `CompressorModel` enum: `Ahri540(Ahri540Coefficients)` and `SstSdt(SstSdtCoefficients)`
|
||||
- [x] Store 10 AHRI 540 coefficients (M1-M10) in `Ahri540Coefficients` struct
|
||||
- [x] Store SST/SDT polynomial curves in `SstSdtCoefficients` (mass_flow_curve + power_curve as `Polynomial2D`)
|
||||
- [x] Include rotational speed (RPM), displacement volume, and mechanical efficiency
|
||||
- [x] Include `Calib` struct (f_m, f_power, f_etav) for calibration factors
|
||||
- [x] Include `CalibIndices` for solver-level embedded calibration variables
|
||||
- [x] Include `CircuitId` for multi-circuit machine support (FR9)
|
||||
- [x] Include `OperationalState` (On, Off, Bypass) for FR6-FR8
|
||||
- [x] Include edge state indices (`suction_m_idx`, `suction_h_idx`, `discharge_m_idx`, `discharge_h_idx`) for CM1.3
|
||||
|
||||
2. **AHRI 540 Equations Implementation** (AC: #2)
|
||||
- [x] Implement mass flow rate calculation per AHRI 540 standard
|
||||
- [x] Implement power consumption calculation per AHRI 540 standard
|
||||
- [x] Implement capacity (cooling/heating) calculation
|
||||
- [x] Handle both cooling and heating mode coefficients
|
||||
2. **AHRI 540 Performance Equations** (AC: #2)
|
||||
- [x] Mass flow: `ṁ = M1 × (1 - (P_suction/P_discharge)^(1/M2)) × ρ_suction × V_disp × N/60 × f_etav × f_m`
|
||||
- [x] Power (cooling): `Ẇ = (M3 + M4 × (P_discharge/P_suction) + M5 × T_suction + M6 × T_discharge) × f_power`
|
||||
- [x] Power (heating): `Ẇ = (M7 + M8 × (P_discharge/P_suction) + M9 × T_suction + M10 × T_discharge) × f_power`
|
||||
- [x] Cooling capacity: `Q̇_cool = ṁ × (h_evap_out - h_evap_in)`
|
||||
- [x] Heating capacity: `Q̇_heat = ṁ × (h_cond_out - h_cond_in)`
|
||||
- [x] COP calculation: `COP = Q̇ / Ẇ`
|
||||
|
||||
3. **Residual Computation** (AC: #3)
|
||||
- [x] Implement `compute_residuals()` for Component trait
|
||||
- [x] Mass flow continuity: ṁ_suction = ṁ_discharge
|
||||
- [x] Energy balance: Power_input = ṁ × (h_discharge - h_suction) / η_mech
|
||||
- [x] Isentropic efficiency based on AHRI correlation
|
||||
3. **SST/SDT Polynomial Model** (AC: #3)
|
||||
- [x] Mass flow: `ṁ = Σ a_ij × SST^i × SDT^j` via `Polynomial2D`
|
||||
- [x] Power: `Ẇ = Σ b_ij × SST^i × SDT^j` via `Polynomial2D`
|
||||
- [x] Support bilinear and biquadratic configurations
|
||||
- [x] `SstSdtCoefficients::bilinear()` and `::biquadratic()` factory methods
|
||||
- [x] SST/SDT model does not distinguish cooling/heating (single power curve)
|
||||
|
||||
4. **Jacobian Entries** (AC: #4)
|
||||
- [x] Provide analytical Jacobian entries for ∂residual/∂P
|
||||
- [x] Provide analytical Jacobian entries for ∂residual/∂h
|
||||
- [x] Derivatives respect to mass flow for power equation
|
||||
- [x] Jacobian compatible with solver from Story 4.x
|
||||
- [ ] **PARTIAL**: Complex derivatives use finite difference approximation (to be refined)
|
||||
4. **Residual Computation — 3 equations** (AC: #4)
|
||||
- [x] `n_equations()` returns **3** (upgraded from 2 to include CM1.3 mass conservation)
|
||||
- [x] `r0`: Mass flow continuity — `ṁ_calc − ṁ_state = 0`
|
||||
- [x] `r1`: Energy balance — `Ẇ_calc − ṁ_state × (h_discharge − h_suction) / η_mech = 0`
|
||||
- [x] `r2`: Mass conservation (CM1.3) — `ṁ_discharge − ṁ_suction = 0`
|
||||
- [x] `OperationalState::Off` → `r0 = ṁ_suction`, `r1 = 0`, `r2 = ṁ_discharge − ṁ_suction`
|
||||
- [x] `OperationalState::Bypass` → `r0 = P_suction − P_discharge`, `r1 = h_suction − h_discharge`, `r2 = ṁ_discharge − ṁ_suction`
|
||||
|
||||
5. **Component Trait Integration** (AC: #5)
|
||||
- [x] Implement `Component` trait for `Compressor`
|
||||
- [x] Implement `get_ports()` returning suction and discharge ports
|
||||
- [x] Implement `n_equations()` returning correct equation count
|
||||
- [x] Compatible with existing Component trait from Story 1.1
|
||||
5. **Jacobian Entries** (AC: #5)
|
||||
- [x] Row 0: `∂r0/∂ṁ_suction = -1` (exact, CM1.3)
|
||||
- [x] Row 0: `∂r0/∂h_suction` — numerical via `approximate_derivative` (density depends on h)
|
||||
- [x] Row 1: `∂r1/∂ṁ_suction = −(h_discharge − h_suction) / η_mech` (exact)
|
||||
- [x] Row 1: `∂r1/∂h_suction`, `∂r1/∂h_discharge` — numerical (power depends on temperature estimates)
|
||||
- [x] Row 2: `∂r2/∂ṁ_discharge = +1`, `∂r2/∂ṁ_suction = -1` (exact, CM1.3)
|
||||
- [x] **PARTIAL**: Derivatives `∂r0/∂h` and `∂r1/∂h` use finite-difference approximation (temperature estimation not differentiable analytically in current placeholder backend)
|
||||
|
||||
6. **Validation & Testing** (AC: #6)
|
||||
- [x] Unit tests for AHRI 540 coefficient storage
|
||||
- [x] Unit tests for mass flow calculation
|
||||
- [x] Unit tests for power consumption calculation
|
||||
- [x] Validation test with certified AHRI data (within 1% tolerance)
|
||||
- [x] Test with different refrigerants (R134a, R410A, R454B)
|
||||
6. **Component Trait Integration** (AC: #6)
|
||||
- [x] Implement `Component` trait for `Compressor<Connected>`
|
||||
- [x] `get_ports()` returns empty slice (lifetime constraint — use `get_ports_slice()` instead)
|
||||
- [x] `get_ports_slice()` returns `[&Port<Connected>; 2]` as workaround
|
||||
- [x] `set_system_context()` stores edge indices for CM1.3 integration
|
||||
- [x] `energy_transfers()` returns `(electrical_power, heat_rejection)` for energy validation
|
||||
- [x] `signature()` returns `"Compressor(fluid=X, circuit=Y)"`
|
||||
|
||||
7. **Validation & Testing** (AC: #7)
|
||||
- [x] Unit tests for `Ahri540Coefficients` storage and validation
|
||||
- [x] Unit tests for `SstSdtCoefficients` bilinear and biquadratic
|
||||
- [x] Unit tests for mass flow calculation (AHRI 540 + SST/SDT)
|
||||
- [x] Unit tests for power consumption (cooling + heating modes)
|
||||
- [x] Unit tests for residuals at equilibrium
|
||||
- [x] Jacobian finite-difference verification
|
||||
- [x] Tests with R134a, R410A, R454B refrigerants
|
||||
- [x] Tests for Off and Bypass operational states
|
||||
|
||||
## Tasks / Subtasks
|
||||
|
||||
- [x] Create `crates/components/src/compressor.rs` module (AC: #1, #5)
|
||||
- [x] Define `Compressor<State>` struct with Type-State pattern
|
||||
- [x] Add suction and discharge port fields (`port_suction`, `port_discharge`)
|
||||
- [x] Add AHRI 540 coefficient fields (M1-M10)
|
||||
- [x] Add speed and displacement fields
|
||||
- [x] Implement constructor `Compressor::new()`
|
||||
- [x] Implement AHRI 540 coefficient storage (AC: #1)
|
||||
- [x] Define `Ahri540Coefficients` struct with M1-M10 fields
|
||||
- [x] Add coefficient validation (range checks)
|
||||
- [x] Support both cooling and heating coefficient sets
|
||||
- [x] Add documentation with AHRI 540 reference
|
||||
- [x] Implement mass flow calculation (AC: #2)
|
||||
- [x] Equation: ṁ = M1 × (1 - (P_suction/P_discharge)^(1/M2)) × ρ_suction × V_disp × N/60
|
||||
- [x] Implement density calculation via fluid backend
|
||||
- [x] Handle edge cases (zero speed, invalid pressures)
|
||||
- [x] Unit tests with known manufacturer data
|
||||
- [x] Implement power consumption calculation (AC: #2)
|
||||
- [x] Equation: Ẇ = M3 + M4 × (P_discharge/P_suction) + M5 × T_suction + M6 × T_discharge
|
||||
- [x] Alternative form using M7-M10 for heating mode
|
||||
- [x] Add mechanical efficiency factor
|
||||
- [x] Unit tests with certified data
|
||||
- [x] Implement capacity calculation (AC: #2)
|
||||
- [x] Cooling capacity: Q̇_cool = ṁ × (h_evap_out - h_evap_in)
|
||||
- [x] Heating capacity: Q̇_heat = ṁ × (h_cond_out - h_cond_in)
|
||||
- [x] COP calculation: COP = Q̇ / Ẇ
|
||||
- [x] Implement residual computation (AC: #3)
|
||||
- [x] Mass flow residual: ṁ_calc - ṁ_state = 0
|
||||
- [x] Energy residual: Ẇ_calc - ṁ × (h_out - h_in) / η = 0
|
||||
- [x] Integration with Component::compute_residuals()
|
||||
- [x] Error handling for invalid states
|
||||
- [x] Implement Jacobian entries (AC: #4)
|
||||
- [x] ∂(mass_residual)/∂P_suction and ∂(mass_residual)/∂P_discharge
|
||||
- [x] ∂(energy_residual)/∂h_suction and ∂(energy_residual)/∂h_discharge
|
||||
- [x] Derivative of density with respect to pressure
|
||||
- [x] Integration with Component::jacobian_entries()
|
||||
- [x] Implement Component trait (AC: #5)
|
||||
- [x] `compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector)`
|
||||
- [x] `jacobian_entries(&self, state: &SystemState, jacobian: &mut JacobianBuilder)`
|
||||
- [x] `n_equations(&self) -> usize` (returns 2 for mass and energy)
|
||||
- [x] `get_ports(&self) -> &[Port<Connected>]`
|
||||
- [x] Write comprehensive tests (AC: #6)
|
||||
- [x] Test coefficient storage and retrieval
|
||||
- [x] Test mass flow with known AHRI test data (Bitzer, Copeland, Danfoss)
|
||||
- [x] Test power consumption against certified data
|
||||
- [x] Test residuals computation
|
||||
- [x] Test Jacobian entries (finite difference verification)
|
||||
- [x] Test Component trait implementation
|
||||
- [x] Test with multiple refrigerants
|
||||
- [x] Create `crates/components/src/compressor.rs` module (AC: #1)
|
||||
- [x] Define `Ahri540Coefficients` struct with M1-M10 fields and `validate()`
|
||||
- [x] Define `SstSdtCoefficients` struct wrapping `Polynomial2D`
|
||||
- [x] Define `CompressorModel` enum (`Ahri540` | `SstSdt`)
|
||||
- [x] Define `Compressor<State>` with all fields (model, ports, calib, circuit_id, edge indices)
|
||||
- [x] Implement `Compressor::new()` for AHRI 540 constructor
|
||||
- [x] Implement `Compressor::with_model()` for generic model constructor
|
||||
- [x] Implement `connect()` method (Type-State transition Disconnected → Connected)
|
||||
- [x] Create `crates/components/src/polynomials.rs` module (AC: #3)
|
||||
- [x] Define `Polynomial2D` struct for 2D polynomial evaluation
|
||||
- [x] Implement `evaluate(x, y)`, `validate()`, `bilinear()`, `biquadratic()`
|
||||
- [x] Implement AHRI 540 calculations (AC: #2)
|
||||
- [x] `mass_flow_rate(density, sst_k, sdt_k, state)` — dispatches to model
|
||||
- [x] `power_consumption_cooling(t_suction, t_discharge, state)`
|
||||
- [x] `power_consumption_heating(t_suction, t_discharge, state)`
|
||||
- [x] `cooling_capacity()`, `heating_capacity()`, `cop()`
|
||||
- [x] Implement residual computation (AC: #4)
|
||||
- [x] 3-equation system: r0 mass flow, r1 energy, r2 mass conservation
|
||||
- [x] OperationalState dispatch (On/Off/Bypass)
|
||||
- [x] Calibration factors applied via `Calib` and `CalibIndices`
|
||||
- [x] Implement Jacobian entries (AC: #5)
|
||||
- [x] Exact entries where analytically tractable
|
||||
- [x] `approximate_derivative()` helper for numerical differentiation
|
||||
- [x] Implement Component trait (AC: #6)
|
||||
- [x] `compute_residuals`, `jacobian_entries`, `n_equations`, `get_ports`
|
||||
- [x] `set_system_context` for CM1.3 edge index injection
|
||||
- [x] `energy_transfers`, `signature`
|
||||
- [x] `get_ports_slice()` workaround
|
||||
- [x] Register exports (AC: #6)
|
||||
- [x] `crates/components/src/lib.rs` — pub mod compressor; pub mod polynomials; re-exports
|
||||
- [x] `crates/entropyk/src/lib.rs` — re-export compressor types
|
||||
- [x] Write comprehensive tests (AC: #7)
|
||||
|
||||
## Dev Notes
|
||||
|
||||
### Architecture Context
|
||||
|
||||
**Critical Pattern - Component Implementation:**
|
||||
This is the FIRST concrete component implementation. It establishes patterns for ALL future components (Condenser, Evaporator, Expansion Valve, etc.).
|
||||
**Critical patterns established here (followed by ALL subsequent components):**
|
||||
|
||||
```rust
|
||||
// Pattern to follow for all components
|
||||
// Type-State pattern: Disconnected → Connected
|
||||
pub struct Compressor<State> {
|
||||
model: CompressorModel,
|
||||
port_suction: Port<State>,
|
||||
port_discharge: Port<State>,
|
||||
coefficients: Ahri540Coefficients,
|
||||
speed: f64, // RPM
|
||||
displacement: f64, // m³/rev
|
||||
_state: PhantomData<State>,
|
||||
}
|
||||
|
||||
impl Component for Compressor<Connected> {
|
||||
fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) {
|
||||
// Implementation here
|
||||
}
|
||||
|
||||
fn jacobian_entries(&self, state: &SystemState, jacobian: &mut JacobianBuilder) {
|
||||
// Implementation here
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize { 2 }
|
||||
|
||||
fn get_ports(&self) -> &[Port<Connected>] {
|
||||
&[self.port_suction, self.port_discharge]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**AHRI 540 Standard Equations:**
|
||||
The AHRI 540 standard provides 10 coefficients (M1-M10) for compressor mapping:
|
||||
|
||||
**Mass Flow Rate:**
|
||||
```
|
||||
ṁ = M1 × (1 - (P_discharge/P_suction)^(1/M2)) × ρ_suction × V_disp × N/60
|
||||
```
|
||||
Where:
|
||||
- M1: Flow coefficient
|
||||
- M2: Pressure ratio exponent
|
||||
- ρ_suction: Suction gas density (from fluid backend)
|
||||
- V_disp: Displacement volume (m³/rev)
|
||||
- N: Rotational speed (RPM)
|
||||
|
||||
**Power Consumption:**
|
||||
```
|
||||
Ẇ = M3 + M4 × (P_discharge/P_suction) + M5 × T_suction + M6 × T_discharge
|
||||
```
|
||||
Alternative for heating:
|
||||
```
|
||||
Ẇ = M7 + M8 × (P_discharge/P_suction) + M9 × T_suction + M10 × T_discharge
|
||||
```
|
||||
|
||||
**Isentropic Efficiency:**
|
||||
```
|
||||
η_isen = (h_out,isentropic - h_in) / (h_out,actual - h_in)
|
||||
```
|
||||
|
||||
### Technical Requirements
|
||||
|
||||
**Rust Naming Conventions (MUST FOLLOW):**
|
||||
- Struct: `CamelCase` (Compressor, Ahri540Coefficients)
|
||||
- Methods: `snake_case` (compute_residuals, mass_flow_rate)
|
||||
- Fields: `snake_case` (port_suction, displacement_volume)
|
||||
- Generic parameter: `State` (not `S` for clarity)
|
||||
|
||||
**Required Types:**
|
||||
```rust
|
||||
pub struct Ahri540Coefficients {
|
||||
pub m1: f64,
|
||||
pub m2: f64,
|
||||
pub m3: f64,
|
||||
pub m4: f64,
|
||||
pub m5: f64,
|
||||
pub m6: f64,
|
||||
pub m7: f64,
|
||||
pub m8: f64,
|
||||
pub m9: f64,
|
||||
pub m10: f64,
|
||||
}
|
||||
|
||||
pub struct Compressor<State> {
|
||||
port_suction: Port<State>,
|
||||
port_discharge: Port<State>,
|
||||
coefficients: Ahri540Coefficients,
|
||||
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>,
|
||||
}
|
||||
```
|
||||
|
||||
**Location in Workspace:**
|
||||
**Performance model dispatch:**
|
||||
```rust
|
||||
pub enum CompressorModel {
|
||||
Ahri540(Ahri540Coefficients),
|
||||
SstSdt(SstSdtCoefficients),
|
||||
}
|
||||
```
|
||||
crates/components/
|
||||
├── Cargo.toml
|
||||
└── src/
|
||||
├── lib.rs # Re-exports
|
||||
├── port.rs # From Story 1.3
|
||||
├── compressor.rs # THIS STORY
|
||||
└── state_machine.rs # Future story
|
||||
`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)
|
||||
```
|
||||
|
||||
### Implementation Strategy
|
||||
**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)
|
||||
|
||||
1. **Create compressor module** - Define Compressor struct with Type-State
|
||||
2. **Implement AHRI 540 equations** - Core thermodynamic calculations
|
||||
3. **Implement Component trait** - Integration with solver framework
|
||||
4. **Add comprehensive tests** - Validation against certified data
|
||||
5. **Document with KaTeX** - Mathematical equations in rustdoc
|
||||
**Calibration factors (FR43):**
|
||||
```rust
|
||||
// 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:**
|
||||
```rust
|
||||
// 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:**
|
||||
```rust
|
||||
// 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: `CamelCase` — `Compressor`, `Ahri540Coefficients`, `SstSdtCoefficients`, `CompressorModel`
|
||||
- Methods: `snake_case` — `compute_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 Tests:**
|
||||
- Coefficient storage: Verify all 10 coefficients stored correctly
|
||||
- Mass flow: Test against known Bitzer 4TES-9 data
|
||||
- Power consumption: Test against AHRI certified values
|
||||
- Residuals: Verify residual equations equal zero at equilibrium
|
||||
- Jacobian: Finite difference verification of derivatives
|
||||
- Component trait: Verify trait implementation works with solver
|
||||
|
||||
**Test Pattern:**
|
||||
**Required test coverage:**
|
||||
```rust
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
// Test data from AHRI 540 certified test
|
||||
const TEST_COMPRESSOR_COEFFS: Ahri540Coefficients = Ahri540Coefficients {
|
||||
m1: 0.85,
|
||||
m2: 0.9,
|
||||
m3: 500.0,
|
||||
m4: 1500.0,
|
||||
m5: -2.5,
|
||||
m6: 1.8,
|
||||
m7: 0.0, // Not used for cooling
|
||||
m8: 0.0,
|
||||
m9: 0.0,
|
||||
m10: 0.0,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_mass_flow_calculation() {
|
||||
let compressor = Compressor::new(TEST_COMPRESSOR_COEFFS, 2900.0, 0.0001);
|
||||
// Test with known operating point
|
||||
let p_suction = Pressure::from_bar(3.5);
|
||||
let p_discharge = Pressure::from_bar(15.0);
|
||||
let mass_flow = compressor.mass_flow_rate(p_suction, p_discharge);
|
||||
|
||||
// Expected value from certified data
|
||||
assert_relative_eq!(mass_flow.to_kg_per_s(), 0.05, epsilon = 1e-4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_power_consumption() {
|
||||
let compressor = Compressor::new(TEST_COMPRESSOR_COEFFS, 2900.0, 0.0001);
|
||||
let power = compressor.power_consumption(
|
||||
Temperature::from_celsius(5.0),
|
||||
Temperature::from_celsius(45.0),
|
||||
Pressure::from_bar(3.5),
|
||||
Pressure::from_bar(15.0),
|
||||
);
|
||||
|
||||
// Expected value from certified data
|
||||
assert_relative_eq!(power, 3500.0, epsilon = 50.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_component_trait_implementation() {
|
||||
let compressor = create_test_compressor();
|
||||
assert_eq!(compressor.n_equations(), 2);
|
||||
|
||||
let ports = compressor.get_ports();
|
||||
assert_eq!(ports.len(), 2);
|
||||
}
|
||||
}
|
||||
// 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()
|
||||
```
|
||||
|
||||
### Project Structure Notes
|
||||
|
||||
**Crate Location:** `crates/components/src/compressor.rs`
|
||||
- First concrete component implementation
|
||||
- Uses Port<State> from Story 1.3
|
||||
- Uses Pressure, Temperature, Enthalpy from Story 1.2
|
||||
- Implements Component trait from Story 1.1
|
||||
|
||||
**Inter-crate Dependencies:**
|
||||
```
|
||||
core (types: Pressure, Temperature, etc.)
|
||||
↑
|
||||
components → core + fluids (for density calculation)
|
||||
↑
|
||||
solver → components (uses Component trait)
|
||||
```
|
||||
|
||||
**Alignment with Unified Structure:**
|
||||
- ✅ Uses Type-State pattern from Story 1.3
|
||||
- ✅ Located in `crates/components/` per architecture
|
||||
- ✅ Implements Component trait from Story 1.1
|
||||
- ✅ Uses NewType pattern from Story 1.2
|
||||
- ✅ Follows naming conventions from architecture
|
||||
|
||||
### References
|
||||
|
||||
- **AHRI 540 Standard:** Air-Conditioning, Heating, and Refrigeration Institute Standard 540
|
||||
- **Architecture Component Model:** [Source: planning-artifacts/architecture.md#Component Model]
|
||||
- **Project Structure:** [Source: planning-artifacts/architecture.md#Project Structure & Boundaries]
|
||||
- **Story 1.1 Component Trait:** Previous story established Component trait interface
|
||||
- **Story 1.2 Physical Types:** NewType Pressure, Temperature, Enthalpy usage
|
||||
- **Story 1.3 Port System:** Port<State> and connection patterns
|
||||
- **FR1:** Compressor AHRI 540 modeling requirement [Source: planning-artifacts/epics.md#FR Coverage Map]
|
||||
- **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)
|
||||
|
||||
@@ -328,24 +283,23 @@ solver → components (uses Component trait)
|
||||
### 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 #4 Partial Implementation** - Jacobian uses finite differences for complex derivatives, not fully analytical. Marked AC as PARTIAL with explanation.
|
||||
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** - Component trait method returns empty slice due to lifetime constraints. Added `get_ports_slice()` method for actual access.
|
||||
4. **Missing R454B Test** - AC #6 claimed R454B testing but only R134a/R410A tested. Added R454B test case.
|
||||
5. **File List Incomplete** - `Cargo.toml` modifications not documented. Added to File List.
|
||||
6. **Non-existent Dependency** - Story mentioned `entropyk-fluids` which doesn't exist. Removed reference.
|
||||
3. **get_ports() Returns Empty** — Lifetime constraint. Added `get_ports_slice()` workaround.
|
||||
4. **Missing R454B Test** — Added R454B test case.
|
||||
5. **File List Incomplete** — `Cargo.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 references for CoolProp integration.
|
||||
8. **Test Quality** - Improved `test_mass_flow_with_high_pressure_ratio` clarity.
|
||||
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
|
||||
- [x] All HIGH and MEDIUM issues fixed
|
||||
- [x] All tests pass (61 unit + 20 doc tests)
|
||||
- [x] All tests pass (61 unit + 20 doc tests at time of original review)
|
||||
- [x] Story status updated to "done"
|
||||
- [x] Sprint status synced
|
||||
|
||||
---
|
||||
|
||||
@@ -359,57 +313,54 @@ opencode/kimi-k2.5-free
|
||||
|
||||
- Implementation completed: 2026-02-15
|
||||
- Code review completed: 2026-02-15
|
||||
- All tests passing: 61 unit tests + 20 doc tests
|
||||
- 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
|
||||
- Issues fixed: 8 (2 HIGH, 4 MEDIUM, 2 LOW)
|
||||
|
||||
### Completion Notes List
|
||||
|
||||
**Implementation Summary:**
|
||||
- ✅ Created `crates/components/src/compressor.rs` with complete Compressor component
|
||||
- ✅ Implemented `Ahri540Coefficients` struct with validation for all 10 coefficients (M1-M10)
|
||||
- ✅ Implemented Type-State pattern with `Compressor<Disconnected>` and `Compressor<Connected>`
|
||||
- ✅ Implemented AHRI 540 mass flow equation with inverse pressure ratio for volumetric efficiency
|
||||
- ✅ Implemented power consumption equations for both cooling (M3-M6) and heating (M7-M10) modes
|
||||
- ✅ Implemented cooling/heating capacity calculations and COP computation
|
||||
- ✅ Implemented full Component trait integration with 2 equations (mass flow + energy balance)
|
||||
- ✅ Implemented analytical Jacobian entries with finite difference approximations for derivatives
|
||||
- ✅ Added placeholder fluid property functions for density and temperature estimation (R134a, R410A)
|
||||
- ✅ Comprehensive test suite: 34 unit tests covering all functionality
|
||||
- ✅ All doc tests pass with working examples
|
||||
**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
|
||||
|
||||
**Code Review Fixes Applied:**
|
||||
- ✅ **Fix 1:** Corrected mass flow equation documentation (was showing wrong pressure ratio direction)
|
||||
- ✅ **Fix 2:** Added `get_ports_slice()` method for actual port access (Component trait `get_ports()` has lifetime constraints)
|
||||
- ✅ **Fix 3:** Added R454B refrigerant test and support in placeholder fluid functions
|
||||
- ✅ **Fix 4:** Updated File List to include `Cargo.toml` modifications
|
||||
- ✅ **Fix 5:** Marked AC #4 as PARTIAL (Jacobian uses finite differences for complex derivatives)
|
||||
- ✅ **Fix 6:** Removed non-existent `entropyk-fluids` dependency reference
|
||||
- ✅ **Fix 7:** Added explicit references to Story 2.2 for CoolProp integration
|
||||
- ✅ **Fix 8:** Improved test coverage (61 tests vs 60 originally)
|
||||
**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:**
|
||||
- Used inverse pressure ratio (P_suction/P_discharge) for volumetric efficiency calculation to ensure positive values
|
||||
- Set M2 coefficient to 2.5 in tests to allow reasonable pressure ratios (was 0.9 which caused negative efficiency)
|
||||
- Implemented placeholder fluid property functions that will be replaced by real CoolProp integration in Story 2.2
|
||||
- Used finite difference approximation for Jacobian derivatives where analytical forms are complex
|
||||
- R454B uses R410A properties as approximation (both are similar zeotropic blends)
|
||||
- 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 files:
|
||||
1. `crates/components/src/compressor.rs` - Compressor component with AHRI 540 implementation
|
||||
**Created:**
|
||||
1. `crates/components/src/compressor.rs` — Compressor component (~2276 lines)
|
||||
2. `crates/components/src/polynomials.rs` — `Polynomial2D` for SST/SDT model
|
||||
|
||||
Modified files:
|
||||
1. `crates/components/src/lib.rs` - Added compressor module and re-exports
|
||||
2. `crates/components/Cargo.toml` - Added `approx` dev-dependency for floating-point assertions
|
||||
**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
|
||||
|
||||
**Cargo.toml dependencies (already present):**
|
||||
```toml
|
||||
# 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"] }
|
||||
|
||||
@@ -417,40 +368,6 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
approx = "0.5"
|
||||
```
|
||||
|
||||
**Note:** Placeholder fluid property functions (`estimate_density`, `estimate_temperature`) will be replaced by actual CoolProp integration in Story 2.2.
|
||||
|
||||
## Story Context Summary
|
||||
|
||||
**Critical Implementation Points:**
|
||||
1. This is THE FIRST concrete component - establishes patterns for ALL future components
|
||||
2. MUST implement Component trait correctly for solver integration
|
||||
3. AHRI 540 equations must be accurate (industry standard)
|
||||
4. Jacobian entries must be analytical (not numerical) for performance
|
||||
5. Tests must validate against certified data (within 1% tolerance)
|
||||
|
||||
**Common Pitfalls to Avoid:**
|
||||
- ❌ Using numerical differentiation for Jacobian (too slow)
|
||||
- ❌ Forgetting to validate coefficients (M1-M10 ranges)
|
||||
- ❌ Incorrect density calculation (must use fluid backend)
|
||||
- ❌ Breaking Component trait object safety
|
||||
- ❌ Wrong equation form (cooling vs heating coefficients)
|
||||
- ❌ Missing edge cases (zero speed, negative pressures)
|
||||
|
||||
**Success Criteria:**
|
||||
- ✅ Compressor implements Component trait correctly
|
||||
- ✅ AHRI 540 equations match certified data within 1%
|
||||
- ✅ Analytical Jacobian entries verified with finite differences
|
||||
- ✅ All tests pass including validation against manufacturer data
|
||||
- ✅ Follows all architecture patterns and conventions
|
||||
|
||||
**Dependencies on Previous Stories:**
|
||||
- **Story 1.1:** Component trait exists - implement it correctly
|
||||
- **Story 1.2:** Physical types (Pressure, Temperature) exist - use them
|
||||
- **Story 1.3:** Port<State> exists - use for suction/discharge ports
|
||||
|
||||
**Next Story (1.5) Dependencies:**
|
||||
Story 1.5 (Generic Heat Exchanger Framework) will use similar patterns for Condenser/Evaporator. The Component trait implementation patterns established here will be reused.
|
||||
|
||||
---
|
||||
|
||||
**Ultimate context engine analysis completed - comprehensive developer guide created**
|
||||
**Ultimate context engine analysis completed — story refreshed 2026-06-26 to reflect evolved implementation (SST/SDT model, CM1.3 3-equation system, Calib integration)**
|
||||
|
||||
@@ -26,28 +26,30 @@
|
||||
# - Dev moves story to 'review', then Dev runs code-review (fresh context, ideally different LLM)
|
||||
|
||||
generated: 2026-07-18T12:51:31Z
|
||||
last_updated: 2026-07-18T16:30:00Z
|
||||
last_updated: 2026-07-19T14:40:00Z
|
||||
project: Entropyk
|
||||
project_key: ENTROPYK
|
||||
tracking_system: file-system
|
||||
story_location: "{project-root}/_bmad-output/implementation-artifacts"
|
||||
|
||||
development_status:
|
||||
epic-0: in-progress
|
||||
epic-0: done
|
||||
0-1-fix-failing-tests: done
|
||||
epic-0-retrospective: optional
|
||||
# --- Epic 0: Physical Regularization & Jacobian Health (epics-solver-hp.md) ---
|
||||
0-1-jacobian-killing-pattern-audit-fd-test-harness: done
|
||||
0-2-phantom-gradient-regularization-standard: review
|
||||
0-3-valve-flow-exv-regularization: backlog
|
||||
0-4-heat-exchanger-regularization: backlog
|
||||
0-5-domain-sweep-jacobian-health-gate: backlog
|
||||
0-2-phantom-gradient-regularization-standard: done
|
||||
0-3-valve-flow-exv-regularization: done
|
||||
0-4-heat-exchanger-regularization: done
|
||||
0-5-domain-sweep-jacobian-health-gate: done
|
||||
0-6-emergent-pressure-seed-robustness: review
|
||||
0-7-bphx-property-call-reduction: review
|
||||
# --- Epic 1: Performance Foundation - Linear Algebra Modernization ---
|
||||
epic-1: in-progress
|
||||
1-1-phase-0-baseline-golden-safety-net: in-progress
|
||||
1-2-core-crate-typed-convergence-taxonomy: backlog
|
||||
1-3-recoverable-domain-violation-errors: backlog
|
||||
1-4-linearsolver-trait-explicit-lifecycle: backlog
|
||||
1-1-phase-0-baseline-golden-safety-net: review
|
||||
1-2-core-crate-typed-convergence-taxonomy: done
|
||||
1-3-recoverable-domain-violation-errors: review
|
||||
1-4-linearsolver-trait-explicit-lifecycle: review
|
||||
1-5-dense-faer-backend-strangler-migration: backlog
|
||||
1-6-factorization-scaling-reuse-frozen-jacobians: backlog
|
||||
1-7-zero-allocation-residual-jacobian-assembly: backlog
|
||||
|
||||
Reference in New Issue
Block a user