Files
Entropyk/crates/solver/tests/_tmp_analytic.rs
sepehr 3358b74342 Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 22:46:46 +02:00

245 lines
9.3 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! End-to-end integration test for the **emergent-pressure** refrigeration cycle.
//!
//! This test assembles the REAL thermodynamic components
//! (`IsentropicCompressor`, `Condenser`, `IsenthalpicExpansionValve`,
//! `Evaporator`) — not mocks — with a real CoolProp fluid backend and solves the
//! canonical 4-component loop with the Newton solver.
//!
//! Unlike the fixed-design-point path (where the compressor pins
//! `P_cond = P_sat(t_cond_k)` and the EXV pins `P_evap = P_sat(t_evap_k)`), every
//! component here runs in **emergent-pressure mode**:
//!
//! | Component | emergent equations | pins |
//! |-----------|--------------------|------|
//! | Compressor | ṁ = ρ_suc·V_s·N·η_vol ; h_dis(P_suc,h_suc,P_dis) | ṁ, h_dis |
//! | Condenser | P2=P1 ; ṁ(h1h2)=ε·C·(T_cond(P1)T_sec,in) ; h2=h_satliq(P1)cp·ΔT_sc | **P_cond** |
//! | EXV | h3=h2 (isenthalpic only) | h3 |
//! | Evaporator | P4=P3 ; ṁ(h4h3)=ε·C·(T_sec,inT_evap(P3)) ; h4=h(P3,T_evap+SH) | **P_evap** |
//!
//! DoF (same-branch series loop): 2 + 3 + 1 + 3 = **9 equations / 9 unknowns**.
//! The condensing/evaporating pressures are therefore EMERGENT: they are
//! determined by the heat-exchanger ↔ secondary balance, not imposed by the
//! compressor/EXV design points. The test verifies that varying the secondary
//! (water) inlet temperature genuinely moves the emergent pressures and COP.
//!
//! Requires the `coolprop` feature (entropy + saturation properties), which the
//! mock `TestBackend` does not provide:
//! cargo test -p entropyk-solver --features coolprop --test emergent_pressure_cycle
#![cfg(feature = "coolprop")]
use std::sync::Arc;
use entropyk_components::isentropic_compressor::VolumetricEfficiency;
use entropyk_components::{Condenser, Evaporator, IsenthalpicExpansionValve, IsentropicCompressor};
use entropyk_fluids::{CoolPropBackend, FluidBackend};
use entropyk_solver::solver::{NewtonConfig, Solver};
use entropyk_solver::system::System;
/// State-vector layout (CM1.4 same-branch series loop, 9 unknowns):
/// `[ṁ, P0,h0, P1,h1, P2,h2, P3,h3]` where
/// E0 comp→cond, E1 cond→exv, E2 exv→evap, E3 evap→comp.
const N_STATE: usize = 9;
/// Result of a converged emergent-pressure solve, in engineering units.
struct CycleResult {
m_dot: f64, // kg/s
p_cond: f64, // Pa (emergent condensing pressure, edge E0)
p_evap: f64, // Pa (emergent evaporating pressure, edge E3)
w_comp: f64, // W (compression power)
q_evap: f64, // W (cooling capacity)
cop: f64, // - (Q_evap / W_comp)
}
/// Assembles and solves the emergent-pressure cycle for the given secondary
/// (water) inlet temperatures and returns the converged operating point.
fn solve_emergent_cycle(cond_sec_temp_k: f64, evap_sec_temp_k: f64) -> CycleResult {
let backend: Arc<dyn FluidBackend> = Arc::new(CoolPropBackend::new());
let fluid = "R134a";
// ── Compressor: emergent ṁ via volumetric displacement ────────────────────
// ṁ = ρ_suc · V_s · N · η_vol. V_s·N ≈ 3.25e-3 m³/s ⇒ ṁ ≈ 0.05 kg/s.
let comp = Box::new(
IsentropicCompressor::new(0.70, 318.15, 278.15, 5.0)
.with_refrigerant(fluid)
.with_fluid_backend(backend.clone())
.with_displacement(6.5e-5, 50.0, VolumetricEfficiency::Constant(0.92)),
);
// ── Condenser: emergent P_cond via subcooling outlet closure ──────────────
let cond = Box::new(
Condenser::new(766.0)
.with_refrigerant(fluid)
.with_fluid_backend(backend.clone())
.with_secondary_stream(cond_sec_temp_k, 1500.0)
.with_emergent_pressure(5.0),
);
// ── EXV: emergent (isenthalpic only, drops the P_evap fix) ────────────────
let exv = Box::new(
IsenthalpicExpansionValve::new(278.15)
.with_refrigerant(fluid)
.with_fluid_backend(backend.clone())
.with_emergent_pressure(),
);
// ── Evaporator: emergent P_evap via superheat outlet closure ──────────────
let evap = Box::new(
Evaporator::new(1468.0)
.with_refrigerant(fluid)
.with_fluid_backend(backend.clone())
.with_secondary_stream(evap_sec_temp_k, 2000.0)
.with_emergent_pressure(),
);
let mut system = System::new();
let n_comp = system.add_component(comp);
let n_cond = system.add_component(cond);
let n_exv = system.add_component(exv);
let n_evap = system.add_component(evap);
system.add_edge(n_comp, n_cond).unwrap(); // E0 comp→cond
system.add_edge(n_cond, n_exv).unwrap(); // E1 cond→exv
system.add_edge(n_exv, n_evap).unwrap(); // E2 exv→evap
system.add_edge(n_evap, n_comp).unwrap(); // E3 evap→comp
system.finalize().unwrap();
// DoF must be exactly balanced (2+3+1+3 = 9 == 9 unknowns).
assert_eq!(
system.full_state_vector_len(),
N_STATE,
"emergent same-branch loop must be 1 ṁ + 4×2(P,h) = 9 unknowns"
);
// Physically-consistent seed near the expected operating point.
// P_sat(R134a): 5 °C ≈ 3.50 bar, 45 °C ≈ 11.6 bar.
let initial_state = vec![
0.05, // ṁ [kg/s]
11.6e5, 445e3, // E0 comp→cond : P_cond, h_dis
11.6e5, 262e3, // E1 cond→exv : P_cond, h_liq
3.50e5, 262e3, // E2 exv→evap : P_evap, h (isenthalpic)
3.50e5, 405e3, // E3 evap→comp : P_evap, h_suction (superheated)
];
let mut config = NewtonConfig {
max_iterations: 200,
tolerance: 1e-6,
line_search: true,
use_numerical_jacobian: false,
initial_state: Some(initial_state),
..NewtonConfig::default()
};
let converged = config
.solve(&mut system)
.expect("emergent-pressure cycle must converge");
let sv = &converged.state;
let m_dot = sv[0];
let (p_cond, h_dis) = (sv[1], sv[2]);
let h_cond_out = sv[4];
let (p_evap, h_suc) = (sv[7], sv[8]);
let h_evap_in = sv[6];
let h_evap_out = sv[8];
let w_comp = m_dot * (h_dis - h_suc);
let q_evap = m_dot * (h_evap_out - h_evap_in);
// Sanity: subcooled liquid at condenser outlet, superheated vapour at suction.
assert!(h_dis > h_suc, "discharge enthalpy must exceed suction");
assert!(
h_cond_out < h_suc,
"condenser outlet must be subcooled liquid"
);
assert!(w_comp > 0.0, "compression power must be positive");
assert!(q_evap > 0.0, "cooling capacity must be positive");
CycleResult {
m_dot,
p_cond,
p_evap,
w_comp,
q_evap,
cop: q_evap / w_comp,
}
}
/// The emergent-pressure loop must converge and produce a physical operating
/// point (positive capacity, positive power, plausible pressures/COP).
#[test]
fn test_emergent_cycle_converges_to_physical_point() {
let r = solve_emergent_cycle(303.15, 285.15); // cond water 30 °C, evap water 12 °C
// Emergent pressures land in a physically reasonable R134a window.
assert!(
(5.0e5..20.0e5).contains(&r.p_cond),
"emergent P_cond out of range: {:.0} Pa",
r.p_cond
);
assert!(
(1.5e5..6.0e5).contains(&r.p_evap),
"emergent P_evap out of range: {:.0} Pa",
r.p_evap
);
assert!(
r.p_cond > r.p_evap,
"condensing must exceed evaporating pressure"
);
assert!(r.m_dot > 0.0, "mass flow must be positive: {}", r.m_dot);
assert!(
(1.5..12.0).contains(&r.cop),
"COP out of physical range: {:.2}",
r.cop
);
}
/// **Core emergence claim**: warming the condenser secondary (water) inlet must
/// raise the emergent condensing pressure and reduce COP — the machine
/// performance is genuinely qualified by the secondary conditions, not fixed by
/// compressor design points.
#[test]
fn test_warmer_condenser_water_raises_pcond_and_lowers_cop() {
let cool = solve_emergent_cycle(303.15, 285.15); // 30 °C condenser water
let warm = solve_emergent_cycle(313.15, 285.15); // 40 °C condenser water
assert!(
warm.p_cond > cool.p_cond + 1.0e4,
"warmer condenser water must raise emergent P_cond: {:.0} → {:.0} Pa",
cool.p_cond,
warm.p_cond
);
assert!(
warm.w_comp > cool.w_comp,
"higher lift must increase compression power: {:.0} → {:.0} W",
cool.w_comp,
warm.w_comp
);
assert!(
warm.cop < cool.cop,
"warmer condenser water must lower COP: {:.2} → {:.2}",
cool.cop,
warm.cop
);
}
/// Warming the evaporator secondary (water/brine) inlet must raise the emergent
/// evaporating pressure and increase cooling capacity.
#[test]
fn test_warmer_evaporator_water_raises_pevap_and_capacity() {
let cold = solve_emergent_cycle(303.15, 283.15); // 10 °C evaporator water
let warm = solve_emergent_cycle(303.15, 291.15); // 18 °C evaporator water
assert!(
warm.p_evap > cold.p_evap + 1.0e4,
"warmer evaporator water must raise emergent P_evap: {:.0} → {:.0} Pa",
cold.p_evap,
warm.p_evap
);
assert!(
warm.q_evap > cold.q_evap,
"warmer evaporator water must increase capacity: {:.0} → {:.0} W",
cold.q_evap,
warm.q_evap
);
}