//! 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 ; ṁ(h1−h2)=ε·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 ; ṁ(h4−h3)=ε·C·(T_sec,in−T_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 = 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 ); }