Entropyk/_bmad-output/implementation-artifacts/2-3-tabular-interpolation-backend.md

11 KiB

Story 2.3: Tabular Interpolation Backend

Status: done

Story

As a performance-critical user, I want pre-computed NIST tables with fast interpolation, so that queries are 100x faster than direct EOS.

Acceptance Criteria

  1. TabularBackend Implementation (AC: #1)

    • Implement TabularBackend struct in crates/fluids/src/tabular_backend.rs
    • Implement full FluidBackend trait (property, critical_point, is_fluid_available, phase, list_fluids)
    • Load tabular data from files (format: JSON or binary)
  2. Accuracy Requirement (AC: #2)

    • Results deviate < 0.01% from NIST REFPROP (or CoolProp as proxy) for all supported properties
    • Validation against reference values for R134a at representative states (R410A, CO2 when tables added)
  3. Performance Requirement (AC: #3)

    • Query time < 1μs per property lookup (excluding first load)
    • No heap allocation in hot path (interpolation loop)
    • Pre-allocated lookup structures
  4. Interpolation Algorithm (AC: #4)

    • Support (P, T), (P, h), (P, x) state inputs
    • Bilinear or bicubic interpolation for smooth derivatives (solver Jacobian)
    • Extrapolation handling: return error or clamp with clear semantics

Tasks / Subtasks

  • Design table data format and structure (AC: #1)
    • Define grid structure (P_min, P_max, T_min, T_max, step sizes)
    • Properties to store: density, enthalpy, entropy, cp, cv, etc.
    • Saturation tables (P_sat, T_sat) for two-phase
  • Implement table loader (AC: #1)
    • Load from JSON or compact binary format
    • Validate table integrity on load
    • Support multiple fluids (one file per fluid or multi-fluid archive)
  • Implement TabularBackend struct (AC: #1, #4)
    • Implement FluidBackend trait methods
    • Bilinear interpolation for (P, T) single-phase
    • (P, h) lookup: interpolate in appropriate table or use Newton iteration
    • (P, x) two-phase: linear interpolation between sat liquid and sat vapor
  • Implement critical_point from table metadata (AC: #1)
    • Store Tc, Pc, rho_c in table header
    • Return CriticalPoint for FluidBackend trait
  • Add table generation tool/script (AC: #2)
    • Use CoolProp to pre-compute tables (or REFPROP if available)
    • Generate tables for R134a (R410A, CO2 require CoolProp - deferred)
    • Validate generated tables against CoolProp spot checks (when CoolProp available)
  • Performance validation (AC: #3)
    • Benchmark: 10k property queries in < 10ms
    • Ensure no Vec::new() or allocation in property() hot path
  • Accuracy validation (AC: #2)
    • Test suite comparing TabularBackend vs CoolPropBackend (when available)
    • assert_relative_eq with epsilon = 0.0001 (0.01%)

Dev Notes

Previous Story Intelligence

From Story 2-1 (Fluid Backend Trait Abstraction):

  • FluidBackend trait in crates/fluids/src/backend.rs with: property(), critical_point(), is_fluid_available(), phase(), list_fluids()
  • Use FluidId, Property, ThermoState, CriticalPoint, Phase from crates/fluids/src/types.rs
  • FluidResult<T> = Result<T, FluidError> from crates/fluids/src/errors.rs
  • TestBackend exists as reference implementation

From Story 2-2 (CoolProp Integration):

  • CoolPropBackend wraps coolprop-sys; TabularBackend is alternative backend
  • TabularBackend must match same trait interface for solver to switch transparently
  • CoolProp can be used to generate tabular data files (table generation tool)
  • Supported properties: Density, Enthalpy, Entropy, Cp, Cv, Temperature, Pressure, Quality, etc.
  • Supported state inputs: (P,T), (P,h), (P,x), (P,s), (T,x)

Architecture Context

Fluid Backend Design (from Architecture):

trait FluidBackend {
    fn property(&self, fluid: FluidId, property: Property, state: ThermoState) -> FluidResult<f64>;
    fn critical_point(&self, fluid: FluidId) -> FluidResult<CriticalPoint>;
    fn is_fluid_available(&self, fluid: &FluidId) -> bool;
    fn phase(&self, fluid: FluidId, state: ThermoState) -> FluidResult<Phase>;
    fn list_fluids(&self) -> Vec<FluidId>;
}

struct TabularBackend {
    // Pre-loaded tables: HashMap<FluidId, FluidTable>
    // No allocation in property() - use pre-indexed grids
}

Performance Requirements (NFR):

  • No dynamic allocation in solver loop
  • Pre-allocated buffers only
  • Query time < 1μs for 100x speedup vs direct EOS

Standards Compliance (from PRD):

  • Tabular interpolation achieving < 0.01% deviation from NIST while being 100x faster than direct EOS calls

Workspace Structure

Location: crates/fluids/

crates/fluids/
├── Cargo.toml
├── build.rs
├── coolprop-sys/               # From Story 2-2
└── src/
    ├── lib.rs
    ├── backend.rs             # FluidBackend trait (DONE)
    ├── coolprop.rs            # Story 2-2 (in progress)
    ├── tabular_backend.rs     # THIS STORY - TabularBackend
    ├── tabular/
    │   ├── mod.rs
    │   ├── table.rs           # Table structure, loader
    │   ├── interpolate.rs      # Bilinear/bicubic interpolation
    │   └── generator.rs       # Optional: table generation from CoolProp
    ├── test_backend.rs
    ├── types.rs
    └── errors.rs

Technical Requirements

Interpolation Strategy:

  • Single-phase (P, T): 2D grid, bilinear interpolation. Ensure C1 continuity for solver Jacobian.
  • (P, h): Either (1) pre-compute h(P,T) and invert via Newton, or (2) store h grid and interpolate T from (P,h).
  • Two-phase (P, x): Linear blend: prop = (1-x)prop_liq + xprop_vap. Saturation values from P_sat table.
  • Extrapolation: Return FluidError::InvalidState or document clamp behavior.

Table Format (suggested):

  • JSON: human-readable, easy to generate. Slower load.
  • Binary: compact, fast load. Use serde + bincode or custom layout.
  • Grid: P_log or P_linear, T_linear. Typical: 50-200 P points, 100-500 T points per phase.

No Allocation in Hot Path:

  • Store tables in Vec or arrays at load time
  • Interpolation uses indices and references only
  • No Vec::new(), HashMap::get with string keys in tight loop - use FluidId index or pre-resolved table ref

Dependencies:

  • serde + serde_json for table loading (or bincode for binary)
  • ndarray (optional) for grid ops - or manual indexing
  • approx for validation tests

Error Handling

Add to FluidError if needed:

#[error("State ({p:.2} Pa, {t:.2} K) outside table bounds for {fluid}")]
OutOfBounds { fluid: String, p: f64, t: f64 },

#[error("Table file not found: {path}")]
TableNotFound { path: String },

Testing Requirements

Required Tests:

  • Load table for R134a, query density at (1 bar, 25°C) - compare to known value
  • Query all Property variants - ensure no panic
  • (P, h) and (P, x) state inputs - verify consistency
  • Out-of-bounds state returns error
  • Benchmark: 10_000 property() calls in < 10ms (release build)
  • Accuracy: assert_relative_eq!(tabular_val, coolprop_val, epsilon = 0.0001) where CoolProp available

Validation Values (from CoolProp/NIST):

  • R134a at 1 bar, 25°C: rho ≈ 1205 kg/m³, h ≈ 200 kJ/kg
  • CO2 at 7 MPa, 35°C (supercritical): density ~400 kg/m³

Project Structure Notes

Alignment:

  • Follows crates/fluids/ structure from architecture
  • TabularBackend implements same FluidBackend trait as CoolPropBackend
  • Table files: suggest crates/fluids/data/ or assets/ - document in README

Dependencies:

  • entropyk_core for Pressure, Temperature, Enthalpy, etc.
  • Reuse FluidId, Property, ThermoState, CriticalPoint, Phase from fluids crate

References

  • Epic 2 Story 2.3: [Source: planning-artifacts/epics.md#Story 2.3]
  • FR26: Tabular interpolation 100x performance [Source: planning-artifacts/epics.md]
  • Architecture Fluid Backend: [Source: planning-artifacts/architecture.md#Fluid Properties Backend]
  • Story 2-1: [Source: implementation-artifacts/2-1-fluid-backend-trait-abstraction.md]
  • Story 2-2: [Source: implementation-artifacts/2-2-coolprop-integration-sys-crate.md]
  • Standards: NIST REFPROP as gold standard, < 0.01% deviation [Source: planning-artifacts/epics.md]

Dev Agent Record

Agent Model Used

Cursor/Composer

Debug Log References

N/A

Completion Notes List

  • TabularBackend implemented in tabular_backend.rs with full FluidBackend trait
  • Code review fixes (2026-02-15): Fixed partial_cmp().unwrap() panic risk (NaN handling); removed unwrap() in property_two_phase; saturation table length mismatch now returns error; added assert_relative_eq accuracy tests; added release benchmark test; generator R410A/CO2 deferred to CoolProp integration
  • JSON table format with single-phase (P,T) grid and saturation line
  • Bilinear interpolation for single-phase; Newton iteration for (P,h); linear blend for (P,x)
  • R134a table in crates/fluids/data/r134a.json with reference values
  • Table generator in tabular/generator.rs: generate_from_coolprop implemented (Story 2-2 complete)
  • FluidError extended with OutOfBounds and TableNotFound
  • All 32 fluids tests pass; benchmark: 10k queries < 100ms (debug) / < 10ms (release)
  • Fixed coolprop.rs unused imports when coolprop feature disabled
  • Post-2.2 alignment (2026-02-15): generate_from_coolprop fully implemented; test_tabular_vs_coolprop_accuracy compares TabularBackend vs CoolPropBackend; test_generated_table_vs_coolprop_spot_checks validates CoolProp-generated tables; Dev Notes: tabular.rs → tabular_backend.rs

File List

  1. crates/fluids/src/tabular_backend.rs - TabularBackend implementation
  2. crates/fluids/src/tabular/mod.rs - Tabular module
  3. crates/fluids/src/tabular/table.rs - FluidTable, loader
  4. crates/fluids/src/tabular/interpolate.rs - Bilinear interpolation
  5. crates/fluids/src/tabular/generator.rs - Table generation (CoolProp stub)
  6. crates/fluids/data/r134a.json - R134a property table
  7. crates/fluids/src/errors.rs - OutOfBounds, TableNotFound variants
  8. crates/fluids/Cargo.toml - serde_json dependency
  9. crates/fluids/src/lib.rs - TabularBackend export
  10. crates/fluids/src/coolprop.rs - Fix unused imports (cfg)

Senior Developer Review (AI)

Review Date: 2026-02-15
Outcome: Changes Requested → Fixed

Action Items Addressed:

  • [HIGH] partial_cmp().unwrap() panic risk in interpolate.rs - Added NaN check and unwrap_or(Ordering::Equal)
  • [HIGH] unwrap() in tabular_backend.rs:180 - Refactored to use t_sat from first at_pressure call
  • [MEDIUM] Saturation table silent drop - Now returns FluidError::InvalidState on length mismatch
  • [MEDIUM] Accuracy validation - Added assert_relative_eq tests (grid point + interpolated)
  • [MEDIUM] Release benchmark - Added test_tabular_benchmark_10k_queries_release (run with cargo test --release)
  • [MEDIUM] Generator R410A/CO2 - Story updated: deferred until CoolProp integration