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:
@@ -1,29 +1,23 @@
|
||||
use entropyk_core::{Pressure, Temperature, Enthalpy};
|
||||
use entropyk_fluids::backend::FluidBackend;
|
||||
use entropyk_fluids::coolprop::CoolPropBackend;
|
||||
use entropyk_fluids::tabular_backend::TabularBackend;
|
||||
use entropyk_fluids::incompressible::IncompressibleBackend;
|
||||
use entropyk_fluids::types::{FluidId, FluidState, Property};
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "coolprop")]
|
||||
fn test_tabular_vs_coolprop_r134a() {
|
||||
let coolprop = CoolPropBackend::new();
|
||||
let mut tabular = TabularBackend::new();
|
||||
|
||||
|
||||
// Load table (making sure path is correct relative to workspace root)
|
||||
let manifest_dir = env!("CARGO_MANIFEST_DIR");
|
||||
let path = std::path::Path::new(manifest_dir).join("../../crates/fluids/data/r134a.json");
|
||||
tabular.load_table(&path).expect("Failed to load R134a table");
|
||||
tabular
|
||||
.load_table(&path)
|
||||
.expect("Failed to load R134a table");
|
||||
|
||||
let fluid = FluidId::new("R134a");
|
||||
|
||||
|
||||
// Use grid points from r134a.json to minimize interpolation error
|
||||
let points = [
|
||||
(1.0, 25.0), // 1 bar, 25°C -> in JSON: 4.4
|
||||
(2.0, 25.0), // 2 bar, 25°C -> in JSON: 8.5
|
||||
(5.0, 25.0), // 5 bar, 25°C -> in JSON: 20.0
|
||||
(1.0, 25.0), // 1 bar, 25°C -> in JSON: 4.4
|
||||
(2.0, 25.0), // 2 bar, 25°C -> in JSON: 8.5
|
||||
(5.0, 25.0), // 5 bar, 25°C -> in JSON: 20.0
|
||||
];
|
||||
|
||||
for (p_bar, t_c) in points {
|
||||
@@ -31,15 +25,23 @@ fn test_tabular_vs_coolprop_r134a() {
|
||||
let t = Temperature::from_celsius(t_c);
|
||||
let state = FluidState::from_pt(p, t);
|
||||
|
||||
let rho_c = coolprop.property(fluid.clone(), Property::Density, state.clone()).unwrap();
|
||||
let rho_t = tabular.property(fluid.clone(), Property::Density, state.clone()).unwrap();
|
||||
|
||||
let rho_c = coolprop
|
||||
.property(fluid.clone(), Property::Density, state.clone())
|
||||
.unwrap();
|
||||
let rho_t = tabular
|
||||
.property(fluid.clone(), Property::Density, state.clone())
|
||||
.unwrap();
|
||||
|
||||
// 20% tolerance due to very coarse placeholder tabular data
|
||||
assert_relative_eq!(rho_t, rho_c, max_relative = 0.20);
|
||||
|
||||
let h_c = coolprop.property(fluid.clone(), Property::Enthalpy, state.clone()).unwrap();
|
||||
let h_t = tabular.property(fluid.clone(), Property::Enthalpy, state).unwrap();
|
||||
|
||||
let h_c = coolprop
|
||||
.property(fluid.clone(), Property::Enthalpy, state.clone())
|
||||
.unwrap();
|
||||
let h_t = tabular
|
||||
.property(fluid.clone(), Property::Enthalpy, state)
|
||||
.unwrap();
|
||||
|
||||
assert_relative_eq!(h_t, h_c, max_relative = 0.20);
|
||||
}
|
||||
}
|
||||
@@ -52,26 +54,28 @@ fn test_incompressible_vs_coolprop_water() {
|
||||
let fluid = FluidId::new("Water");
|
||||
|
||||
// Liquid water states
|
||||
let points = [
|
||||
(1.0, 20.0),
|
||||
(5.0, 50.0),
|
||||
(10.0, 80.0),
|
||||
];
|
||||
let points = [(1.0, 20.0), (5.0, 50.0), (10.0, 80.0)];
|
||||
|
||||
for (p_bar, t_c) in points {
|
||||
let p = Pressure::from_bar(p_bar);
|
||||
let t = Temperature::from_celsius(t_c);
|
||||
let state = FluidState::from_pt(p, t);
|
||||
|
||||
let rho_c = coolprop.property(fluid.clone(), Property::Density, state.clone()).unwrap();
|
||||
let rho_i = incomp.property(fluid.clone(), Property::Density, state.clone()).unwrap();
|
||||
|
||||
let rho_c = coolprop
|
||||
.property(fluid.clone(), Property::Density, state.clone())
|
||||
.unwrap();
|
||||
let rho_i = incomp
|
||||
.property(fluid.clone(), Property::Density, state.clone())
|
||||
.unwrap();
|
||||
|
||||
// Incompressible models are approximations, check for 0.5% agreement
|
||||
assert_relative_eq!(rho_i, rho_c, max_relative = 0.005);
|
||||
|
||||
let cp_c = coolprop.property(fluid.clone(), Property::Cp, state.clone()).unwrap();
|
||||
let cp_c = coolprop
|
||||
.property(fluid.clone(), Property::Cp, state.clone())
|
||||
.unwrap();
|
||||
let cp_i = incomp.property(fluid.clone(), Property::Cp, state).unwrap();
|
||||
|
||||
|
||||
assert_relative_eq!(cp_i, cp_c, max_relative = 0.005);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,33 +1,31 @@
|
||||
use entropyk_core::{Pressure, Temperature};
|
||||
use entropyk_fluids::backend::FluidBackend;
|
||||
use entropyk_fluids::coolprop::CoolPropBackend;
|
||||
use entropyk_fluids::cached_backend::CachedBackend;
|
||||
use entropyk_fluids::types::{FluidId, FluidState, Property};
|
||||
use rayon::prelude::*;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "coolprop")]
|
||||
fn test_cache_concurrent_access() {
|
||||
let inner = CoolPropBackend::new();
|
||||
let cached = CachedBackend::new(inner);
|
||||
let fluid = FluidId::new("R134a");
|
||||
|
||||
|
||||
// Generate many states
|
||||
let states: Vec<_> = (0..100).map(|i| {
|
||||
FluidState::from_pt(
|
||||
Pressure::from_bar(1.0 + (i as f64) * 0.1),
|
||||
Temperature::from_celsius(25.0)
|
||||
)
|
||||
}).collect();
|
||||
let states: Vec<_> = (0..100)
|
||||
.map(|i| {
|
||||
FluidState::from_pt(
|
||||
Pressure::from_bar(1.0 + (i as f64) * 0.1),
|
||||
Temperature::from_celsius(25.0),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Parallel execution via Rayon
|
||||
states.par_iter().for_each(|state| {
|
||||
// First call - populates cache
|
||||
let rho1 = cached.property(fluid.clone(), Property::Density, state.clone()).unwrap();
|
||||
let rho1 = cached
|
||||
.property(fluid.clone(), Property::Density, state.clone())
|
||||
.unwrap();
|
||||
// Second call - should hit cache
|
||||
let rho2 = cached.property(fluid.clone(), Property::Density, state.clone()).unwrap();
|
||||
|
||||
let rho2 = cached
|
||||
.property(fluid.clone(), Property::Density, state.clone())
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(rho1, rho2);
|
||||
});
|
||||
}
|
||||
@@ -38,23 +36,30 @@ fn test_cache_quantization_hit() {
|
||||
let inner = CoolPropBackend::new();
|
||||
let cached = CachedBackend::new(inner);
|
||||
let fluid = FluidId::new("R134a");
|
||||
|
||||
|
||||
let p = Pressure::from_bar(10.0);
|
||||
let t = Temperature::from_kelvin(300.0);
|
||||
let state1 = FluidState::from_pt(p, t);
|
||||
|
||||
|
||||
// Result 1
|
||||
let rho1 = cached.property(fluid.clone(), Property::Density, state1).unwrap();
|
||||
|
||||
let rho1 = cached
|
||||
.property(fluid.clone(), Property::Density, state1)
|
||||
.unwrap();
|
||||
|
||||
// State 2: very small perturbation (within 1e-10 relative)
|
||||
// Quantization is at 1e-9, so this SHOULD hit the same cache line
|
||||
let t2 = Temperature::from_kelvin(300.0 + 1e-11);
|
||||
let state2 = FluidState::from_pt(p, t2);
|
||||
|
||||
|
||||
// If it hits the cache, it returns EXACTLY rho1 (even if physical value changed by 1e-12)
|
||||
let rho2 = cached.property(fluid.clone(), Property::Density, state2).unwrap();
|
||||
|
||||
assert_eq!(rho1, rho2, "Cache quantization fail: small pertubations should return cached value");
|
||||
let rho2 = cached
|
||||
.property(fluid.clone(), Property::Density, state2)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
rho1, rho2,
|
||||
"Cache quantization fail: small pertubations should return cached value"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -63,19 +68,23 @@ fn test_cache_quantization_miss() {
|
||||
let inner = CoolPropBackend::new();
|
||||
let cached = CachedBackend::new(inner);
|
||||
let fluid = FluidId::new("R134a");
|
||||
|
||||
|
||||
let p = Pressure::from_bar(10.0);
|
||||
let t = Temperature::from_kelvin(300.0);
|
||||
let state1 = FluidState::from_pt(p, t);
|
||||
|
||||
let rho1 = cached.property(fluid.clone(), Property::Density, state1).unwrap();
|
||||
|
||||
|
||||
let rho1 = cached
|
||||
.property(fluid.clone(), Property::Density, state1)
|
||||
.unwrap();
|
||||
|
||||
// Large perturbation (1e-6) - should be a cache miss and calculate new value
|
||||
let t2 = Temperature::from_kelvin(300.0 + 1e-6);
|
||||
let state2 = FluidState::from_pt(p, t2);
|
||||
|
||||
let rho2 = cached.property(fluid.clone(), Property::Density, state2).unwrap();
|
||||
|
||||
|
||||
let rho2 = cached
|
||||
.property(fluid.clone(), Property::Density, state2)
|
||||
.unwrap();
|
||||
|
||||
// Value should be slightly different, not identical to cached one
|
||||
assert_ne!(rho1, rho2);
|
||||
assert_relative_eq!(rho1, rho2, max_relative = 1e-4);
|
||||
|
||||
@@ -1,44 +1,30 @@
|
||||
use entropyk_core::{Pressure, Temperature};
|
||||
use entropyk_fluids::backend::FluidBackend;
|
||||
use entropyk_fluids::coolprop::CoolPropBackend;
|
||||
use entropyk_fluids::types::{FluidId, FluidState, Property};
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "coolprop")]
|
||||
fn test_co2_damping_near_critical() {
|
||||
let inner = CoolPropBackend::new();
|
||||
let damped = CoolPropBackend::with_damping();
|
||||
let fluid = FluidId::new("CO2");
|
||||
|
||||
|
||||
// Near critical point of CO2: Tc=304.13K, Pc=7.3773 MPa
|
||||
let tc = 304.13;
|
||||
let pc = 7.3773e6;
|
||||
|
||||
|
||||
// Query points approaching the critical point
|
||||
let temperatures = [
|
||||
tc * 0.95,
|
||||
tc * 0.99,
|
||||
tc * 0.999,
|
||||
tc,
|
||||
tc * 1.001,
|
||||
tc * 1.05
|
||||
];
|
||||
let temperatures = [tc * 0.95, tc * 0.99, tc * 0.999, tc, tc * 1.001, tc * 1.05];
|
||||
|
||||
for t_k in temperatures {
|
||||
let state = FluidState::from_pt(
|
||||
Pressure::from_pascals(pc),
|
||||
Temperature::from_kelvin(t_k)
|
||||
);
|
||||
let state = FluidState::from_pt(Pressure::from_pascals(pc), Temperature::from_kelvin(t_k));
|
||||
|
||||
// Raw CoolProp might return very large values or NaN for Cp near critical
|
||||
let cp_inner = inner.property(fluid.clone(), Property::Cp, state.clone()).unwrap_or(f64::NAN);
|
||||
let cp_inner = inner
|
||||
.property(fluid.clone(), Property::Cp, state.clone())
|
||||
.unwrap_or(f64::NAN);
|
||||
let cp_damped = damped.property(fluid.clone(), Property::Cp, state).unwrap();
|
||||
|
||||
// Damped value must be finite and respect cp_max (default 1e6)
|
||||
assert!(cp_damped.is_finite());
|
||||
assert!(cp_damped <= 2e6); // Some margin over default cp_max
|
||||
|
||||
|
||||
if cp_inner.is_finite() && cp_inner < 1e4 {
|
||||
// Far from critical, they should be identical
|
||||
assert_relative_eq!(cp_damped, cp_inner, max_relative = 0.01);
|
||||
@@ -57,13 +43,17 @@ fn test_damping_smoothness() {
|
||||
// Small step near critical to check for discontinuities
|
||||
let t1 = tc - 0.001;
|
||||
let t2 = tc + 0.001;
|
||||
|
||||
|
||||
let state1 = FluidState::from_pt(Pressure::from_pascals(pc), Temperature::from_kelvin(t1));
|
||||
let state2 = FluidState::from_pt(Pressure::from_pascals(pc), Temperature::from_kelvin(t2));
|
||||
|
||||
let cp1 = damped.property(fluid.clone(), Property::Cp, state1).unwrap();
|
||||
let cp2 = damped.property(fluid.clone(), Property::Cp, state2).unwrap();
|
||||
|
||||
|
||||
let cp1 = damped
|
||||
.property(fluid.clone(), Property::Cp, state1)
|
||||
.unwrap();
|
||||
let cp2 = damped
|
||||
.property(fluid.clone(), Property::Cp, state2)
|
||||
.unwrap();
|
||||
|
||||
// Sigmoid damping should ensure finite delta
|
||||
assert!((cp1 - cp2).abs() < 50000.0);
|
||||
assert!((cp1 - cp2).abs() < 50000.0);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! damping stability near the critical point, and cache integrity.
|
||||
|
||||
pub mod backend_consistency;
|
||||
pub mod mixture_glide;
|
||||
pub mod damping_stability;
|
||||
pub mod cache_integrity;
|
||||
pub mod damping_stability;
|
||||
pub mod mixture_glide;
|
||||
pub mod r134a_cycle;
|
||||
|
||||
@@ -1,32 +1,26 @@
|
||||
use entropyk_core::{Pressure, Temperature, Enthalpy};
|
||||
use entropyk_fluids::backend::FluidBackend;
|
||||
use entropyk_fluids::coolprop::CoolPropBackend;
|
||||
use entropyk_fluids::mixture::Mixture;
|
||||
use entropyk_fluids::types::{FluidId, FluidState, Property};
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "coolprop")]
|
||||
fn test_mixture_glide_r454b() {
|
||||
let backend = CoolPropBackend::new();
|
||||
|
||||
// R410A composition (mass fractions)
|
||||
let mixture = Mixture::from_mass_fractions(&[
|
||||
("R32", 0.5),
|
||||
("R125", 0.5),
|
||||
]).unwrap();
|
||||
|
||||
let p = Pressure::from_bar(10.0);
|
||||
|
||||
// R410A composition (mass fractions)
|
||||
let mixture = Mixture::from_mass_fractions(&[("R32", 0.5), ("R125", 0.5)]).unwrap();
|
||||
|
||||
let p = Pressure::from_bar(10.0);
|
||||
|
||||
let t_bubble = backend.bubble_point(p, &mixture).unwrap();
|
||||
let t_dew = backend.dew_point(p, &mixture).unwrap();
|
||||
let glide = backend.temperature_glide(p, &mixture).unwrap();
|
||||
|
||||
// R410A is near-azeotropic, glide should be very small (< 0.2K)
|
||||
assert!(t_dew.to_kelvin() >= t_bubble.to_kelvin() - 0.1);
|
||||
assert!(t_dew.to_kelvin() >= t_bubble.to_kelvin() - 0.1);
|
||||
assert!(glide < 0.5);
|
||||
assert_relative_eq!(glide, t_dew.to_kelvin() - t_bubble.to_kelvin(), epsilon = 1e-6);
|
||||
|
||||
assert_relative_eq!(
|
||||
glide,
|
||||
t_dew.to_kelvin() - t_bubble.to_kelvin(),
|
||||
epsilon = 1e-6
|
||||
);
|
||||
|
||||
// Typically glide for R454B is around 1.5K at 10 bar
|
||||
assert!(glide > 0.5 && glide < 3.0);
|
||||
}
|
||||
@@ -35,34 +29,37 @@ fn test_mixture_glide_r454b() {
|
||||
#[cfg(feature = "coolprop")]
|
||||
fn test_mixture_ph_state() {
|
||||
let backend = CoolPropBackend::new();
|
||||
let mixture = Mixture::from_mass_fractions(&[
|
||||
("R32", 0.689),
|
||||
("R1234yf", 0.311),
|
||||
]).unwrap();
|
||||
let mixture = Mixture::from_mass_fractions(&[("R32", 0.689), ("R1234yf", 0.311)]).unwrap();
|
||||
|
||||
let p = Pressure::from_bar(10.0);
|
||||
|
||||
|
||||
// Middle of two-phase region (Quality ~ 0.5)
|
||||
let h_bubble = backend.property(
|
||||
FluidId::new("R454B"),
|
||||
Property::Enthalpy,
|
||||
FluidState::from_px_mix(p, 0.0.into(), mixture.clone())
|
||||
).unwrap();
|
||||
let h_dew = backend.property(
|
||||
FluidId::new("R454B"),
|
||||
Property::Enthalpy,
|
||||
FluidState::from_px_mix(p, 1.0.into(), mixture.clone())
|
||||
).unwrap();
|
||||
|
||||
let h_bubble = backend
|
||||
.property(
|
||||
FluidId::new("R454B"),
|
||||
Property::Enthalpy,
|
||||
FluidState::from_px_mix(p, 0.0.into(), mixture.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
let h_dew = backend
|
||||
.property(
|
||||
FluidId::new("R454B"),
|
||||
Property::Enthalpy,
|
||||
FluidState::from_px_mix(p, 1.0.into(), mixture.clone()),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let h_mid = Enthalpy::from_joules_per_kg((h_bubble + h_dew) / 2.0);
|
||||
let state = FluidState::from_ph_mix(p, h_mid, mixture.clone());
|
||||
|
||||
let t = backend.property(FluidId::new("R454B"), Property::Temperature, state).unwrap();
|
||||
|
||||
let t = backend
|
||||
.property(FluidId::new("R454B"), Property::Temperature, state)
|
||||
.unwrap();
|
||||
|
||||
// Temperature in two-phase should be between bubble and dew point
|
||||
let t_bubble = backend.bubble_point(p, &mixture).unwrap();
|
||||
let t_dew = backend.dew_point(p, &mixture).unwrap();
|
||||
|
||||
// Use some epsilon for equality
|
||||
|
||||
// Use some epsilon for equality
|
||||
assert!(t >= t_bubble.to_kelvin() - 0.5 && t <= t_dew.to_kelvin() + 0.5);
|
||||
}
|
||||
|
||||
@@ -77,8 +77,14 @@ fn test_r134a_saturation_against_nist() {
|
||||
);
|
||||
|
||||
eprintln!("=== R134a Saturation (CoolProp vs NIST) ===");
|
||||
eprintln!("T=0°C: P_sat={:.0} Pa, h_f={:.0}, h_g={:.0}", p_sat_0c, hf_0c, hg_0c);
|
||||
eprintln!("T=40°C: P_sat={:.0} Pa, h_f={:.0}, h_g={:.0}", p_sat_40c, hf_40c, hg_40c);
|
||||
eprintln!(
|
||||
"T=0°C: P_sat={:.0} Pa, h_f={:.0}, h_g={:.0}",
|
||||
p_sat_0c, hf_0c, hg_0c
|
||||
);
|
||||
eprintln!(
|
||||
"T=40°C: P_sat={:.0} Pa, h_f={:.0}, h_g={:.0}",
|
||||
p_sat_40c, hf_40c, hg_40c
|
||||
);
|
||||
}
|
||||
|
||||
/// T-CYCLE-01b: Full vapor-compression cycle verification.
|
||||
@@ -112,7 +118,10 @@ fn test_r134a_simple_cycle() {
|
||||
// === Saturation pressures ===
|
||||
let p_evap = unsafe { entropyk_coolprop_sys::props_si_tq("P", t_evap_k, 1.0, fluid) };
|
||||
let p_cond = unsafe { entropyk_coolprop_sys::props_si_tq("P", t_cond_k, 1.0, fluid) };
|
||||
assert!(!p_evap.is_nan() && !p_cond.is_nan(), "NaN saturation pressures");
|
||||
assert!(
|
||||
!p_evap.is_nan() && !p_cond.is_nan(),
|
||||
"NaN saturation pressures"
|
||||
);
|
||||
|
||||
// === State 1: Evaporator outlet (superheated) ===
|
||||
let h1 = unsafe { entropyk_coolprop_sys::props_si_pt("H", p_evap, t_evap_k + tsh, fluid) };
|
||||
@@ -153,11 +162,21 @@ fn test_r134a_simple_cycle() {
|
||||
|
||||
let eta_is = 0.85; // isentropic efficiency
|
||||
let h2 = h1 + (h2s - h1) / eta_is;
|
||||
assert!(!h2.is_nan(), "h2 is NaN: h1={}, h2s={}, eta_is={}", h1, h2s, eta_is);
|
||||
assert!(
|
||||
!h2.is_nan(),
|
||||
"h2 is NaN: h1={}, h2s={}, eta_is={}",
|
||||
h1,
|
||||
h2s,
|
||||
eta_is
|
||||
);
|
||||
|
||||
// === Mass flow rate ===
|
||||
let delta_h_evap = h1 - h4;
|
||||
assert!(delta_h_evap > 0.0, "delta_h_evap must be positive, got {}", delta_h_evap);
|
||||
assert!(
|
||||
delta_h_evap > 0.0,
|
||||
"delta_h_evap must be positive, got {}",
|
||||
delta_h_evap
|
||||
);
|
||||
let m_dot = q_evap_target / delta_h_evap;
|
||||
|
||||
// === Energy ===
|
||||
@@ -197,7 +216,12 @@ fn test_r134a_simple_cycle() {
|
||||
);
|
||||
|
||||
// Q_cond > Q_evap
|
||||
assert!(q_cond > q_evap, "Q_cond ({:.0}) must exceed Q_evap ({:.0})", q_cond, q_evap);
|
||||
assert!(
|
||||
q_cond > q_evap,
|
||||
"Q_cond ({:.0}) must exceed Q_evap ({:.0})",
|
||||
q_cond,
|
||||
q_evap
|
||||
);
|
||||
|
||||
// First Law: < 3% (2% ideal, but binary search on h2s introduces small error)
|
||||
assert!(
|
||||
@@ -217,14 +241,16 @@ fn test_r134a_simple_cycle() {
|
||||
assert!(
|
||||
cop_heating > cop_cooling,
|
||||
"COP_heating ({:.3}) > COP_cooling ({:.3})",
|
||||
cop_heating, cop_cooling
|
||||
cop_heating,
|
||||
cop_cooling
|
||||
);
|
||||
|
||||
// Second Law: COP < Carnot
|
||||
assert!(
|
||||
cop_cooling < cop_carnot,
|
||||
"COP ({:.3}) must be < Carnot ({:.3})",
|
||||
cop_cooling, cop_carnot
|
||||
cop_cooling,
|
||||
cop_carnot
|
||||
);
|
||||
|
||||
// Second Law efficiency: 0.40-0.60
|
||||
@@ -235,14 +261,24 @@ fn test_r134a_simple_cycle() {
|
||||
);
|
||||
|
||||
// Isenthalpic: h3 = h4
|
||||
assert!((h4 - h3).abs() < 1e-6, "Isenthalpic violated: h3={:.2}, h4={:.2}", h3, h4);
|
||||
assert!(
|
||||
(h4 - h3).abs() < 1e-6,
|
||||
"Isenthalpic violated: h3={:.2}, h4={:.2}",
|
||||
h3,
|
||||
h4
|
||||
);
|
||||
|
||||
// Pressure ratio ~3.5
|
||||
let pr = p_cond / p_evap;
|
||||
assert!(pr > 2.5 && pr < 5.0, "PR = {:.2}, expected ~3.5", pr);
|
||||
|
||||
eprintln!("=== T-CYCLE-01 Results ===");
|
||||
eprintln!("P_evap = {:.3} bar, P_cond = {:.3} bar (PR = {:.2})", p_evap / 1e5, p_cond / 1e5, pr);
|
||||
eprintln!(
|
||||
"P_evap = {:.3} bar, P_cond = {:.3} bar (PR = {:.2})",
|
||||
p_evap / 1e5,
|
||||
p_cond / 1e5,
|
||||
pr
|
||||
);
|
||||
eprintln!("h1 = {:.0} J/kg (evap outlet)", h1);
|
||||
eprintln!("h2 = {:.0} J/kg (comp discharge)", h2);
|
||||
eprintln!("h3 = {:.0} J/kg (cond outlet)", h3);
|
||||
@@ -251,7 +287,10 @@ fn test_r134a_simple_cycle() {
|
||||
eprintln!("W_comp = {:.0} W", w_comp);
|
||||
eprintln!("Q_evap = {:.0} W", q_evap);
|
||||
eprintln!("Q_cond = {:.0} W", q_cond);
|
||||
eprintln!("COP_cool = {:.3}, COP_heat = {:.3}", cop_cooling, cop_heating);
|
||||
eprintln!(
|
||||
"COP_cool = {:.3}, COP_heat = {:.3}",
|
||||
cop_cooling, cop_heating
|
||||
);
|
||||
eprintln!("COP_Carnot = {:.3}, eta_II = {:.3}", cop_carnot, eta_ii);
|
||||
eprintln!("First Law error = {:.4}%", first_law_error * 100.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user