207 lines
9.3 KiB
Rust
207 lines
9.3 KiB
Rust
/// Test d'intégration : boucle réfrigération simple R134a en Rust natif.
|
|
///
|
|
/// Ce test valide que le solveur Newton converge sur un cycle 4 composants
|
|
/// en utilisant des mock components algébriques linéaires dont les équations
|
|
/// sont mathématiquement cohérentes (ferment la boucle).
|
|
|
|
use entropyk_components::{
|
|
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
|
|
};
|
|
use entropyk_core::{Enthalpy, MassFlow, Pressure};
|
|
use entropyk_solver::{
|
|
solver::{NewtonConfig, Solver},
|
|
system::System,
|
|
};
|
|
use entropyk_components::port::{Connected, FluidId, Port};
|
|
|
|
// Type alias: Port<Connected> ≡ ConnectedPort
|
|
type CP = Port<Connected>;
|
|
|
|
// ─── Mock compresseur ─────────────────────────────────────────────────────────
|
|
// r[0] = p_disc - (p_suc + 1 MPa)
|
|
// r[1] = h_disc - (h_suc + 75 kJ/kg)
|
|
struct MockCompressor { port_suc: CP, port_disc: CP }
|
|
impl Component for MockCompressor {
|
|
fn compute_residuals(&self, _s: &StateSlice, r: &mut ResidualVector) -> Result<(), ComponentError> {
|
|
r[0] = self.port_disc.pressure().to_pascals() - (self.port_suc.pressure().to_pascals() + 1_000_000.0);
|
|
r[1] = self.port_disc.enthalpy().to_joules_per_kg() - (self.port_suc.enthalpy().to_joules_per_kg() + 75_000.0);
|
|
Ok(())
|
|
}
|
|
fn jacobian_entries(&self, _s: &StateSlice, _j: &mut JacobianBuilder) -> Result<(), ComponentError> { Ok(()) }
|
|
fn n_equations(&self) -> usize { 2 }
|
|
fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
|
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
|
Ok(vec![MassFlow::from_kg_per_s(0.05), MassFlow::from_kg_per_s(-0.05)])
|
|
}
|
|
}
|
|
|
|
// ─── Mock condenseur ──────────────────────────────────────────────────────────
|
|
// r[0] = p_out - p_in
|
|
// r[1] = h_out - (h_in - 225 kJ/kg)
|
|
struct MockCondenser { port_in: CP, port_out: CP }
|
|
impl Component for MockCondenser {
|
|
fn compute_residuals(&self, _s: &StateSlice, r: &mut ResidualVector) -> Result<(), ComponentError> {
|
|
r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
|
|
r[1] = self.port_out.enthalpy().to_joules_per_kg() - (self.port_in.enthalpy().to_joules_per_kg() - 225_000.0);
|
|
Ok(())
|
|
}
|
|
fn jacobian_entries(&self, _s: &StateSlice, _j: &mut JacobianBuilder) -> Result<(), ComponentError> { Ok(()) }
|
|
fn n_equations(&self) -> usize { 2 }
|
|
fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
|
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
|
Ok(vec![MassFlow::from_kg_per_s(0.05), MassFlow::from_kg_per_s(-0.05)])
|
|
}
|
|
}
|
|
|
|
// ─── Mock détendeur ───────────────────────────────────────────────────────────
|
|
// r[0] = p_out - (p_in - 1 MPa)
|
|
// r[1] = h_out - h_in
|
|
struct MockValve { port_in: CP, port_out: CP }
|
|
impl Component for MockValve {
|
|
fn compute_residuals(&self, _s: &StateSlice, r: &mut ResidualVector) -> Result<(), ComponentError> {
|
|
r[0] = self.port_out.pressure().to_pascals() - (self.port_in.pressure().to_pascals() - 1_000_000.0);
|
|
r[1] = self.port_out.enthalpy().to_joules_per_kg() - self.port_in.enthalpy().to_joules_per_kg();
|
|
Ok(())
|
|
}
|
|
fn jacobian_entries(&self, _s: &StateSlice, _j: &mut JacobianBuilder) -> Result<(), ComponentError> { Ok(()) }
|
|
fn n_equations(&self) -> usize { 2 }
|
|
fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
|
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
|
Ok(vec![MassFlow::from_kg_per_s(0.05), MassFlow::from_kg_per_s(-0.05)])
|
|
}
|
|
}
|
|
|
|
// ─── Mock évaporateur ─────────────────────────────────────────────────────────
|
|
// r[0] = p_out - p_in
|
|
// r[1] = h_out - (h_in + 150 kJ/kg)
|
|
struct MockEvaporator { port_in: CP, port_out: CP }
|
|
impl Component for MockEvaporator {
|
|
fn compute_residuals(&self, _s: &StateSlice, r: &mut ResidualVector) -> Result<(), ComponentError> {
|
|
r[0] = self.port_out.pressure().to_pascals() - self.port_in.pressure().to_pascals();
|
|
r[1] = self.port_out.enthalpy().to_joules_per_kg() - (self.port_in.enthalpy().to_joules_per_kg() + 150_000.0);
|
|
Ok(())
|
|
}
|
|
fn jacobian_entries(&self, _s: &StateSlice, _j: &mut JacobianBuilder) -> Result<(), ComponentError> { Ok(()) }
|
|
fn n_equations(&self) -> usize { 2 }
|
|
fn get_ports(&self) -> &[ConnectedPort] { &[] }
|
|
fn port_mass_flows(&self, _: &StateSlice) -> Result<Vec<MassFlow>, ComponentError> {
|
|
Ok(vec![MassFlow::from_kg_per_s(0.05), MassFlow::from_kg_per_s(-0.05)])
|
|
}
|
|
}
|
|
|
|
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
fn port(p_pa: f64, h_j_kg: f64) -> CP {
|
|
let (connected, _) = Port::new(
|
|
FluidId::new("R134a"),
|
|
Pressure::from_pascals(p_pa),
|
|
Enthalpy::from_joules_per_kg(h_j_kg),
|
|
).connect(Port::new(
|
|
FluidId::new("R134a"),
|
|
Pressure::from_pascals(p_pa),
|
|
Enthalpy::from_joules_per_kg(h_j_kg),
|
|
)).unwrap();
|
|
connected
|
|
}
|
|
|
|
// ─── Test ─────────────────────────────────────────────────────────────────────
|
|
#[test]
|
|
fn test_simple_refrigeration_loop_rust() {
|
|
// Les équations :
|
|
// Comp : p0 = p3 + 1 MPa ; h0 = h3 + 75 kJ/kg
|
|
// Cond : p1 = p0 ; h1 = h0 - 225 kJ/kg
|
|
// Valve : p2 = p1 - 1 MPa ; h2 = h1
|
|
// Evap : p3 = p2 ; h3 = h2 + 150 kJ/kg
|
|
//
|
|
// Bilan enthalpique en boucle : 75 - 225 + 150 = 0 → fermé ✓
|
|
// Bilan pressionnel en boucle : +1 - 0 - 1 - 0 = 0 → fermé ✓
|
|
//
|
|
// Solution analytique (8 inconnues, 8 équations → infinité de solutions
|
|
// dépendant du point de référence, mais le solveur en trouve une) :
|
|
// En posant h3 = 410 kJ/kg, p3 = 350 kPa :
|
|
// h0 = 485, p0 = 1.35 MPa
|
|
// h1 = 260, p1 = 1.35 MPa
|
|
// h2 = 260, p2 = 350 kPa
|
|
// h3 = 410, p3 = 350 kPa
|
|
|
|
let p_lp = 350_000.0_f64; // Pa
|
|
let p_hp = 1_350_000.0_f64; // Pa = p_lp + 1 MPa
|
|
|
|
// Les 4 bords (edge) du cycle :
|
|
// edge0 : comp → cond
|
|
// edge1 : cond → valve
|
|
// edge2 : valve → evap
|
|
// edge3 : evap → comp
|
|
let comp = Box::new(MockCompressor {
|
|
port_suc: port(p_lp, 410_000.0),
|
|
port_disc: port(p_hp, 485_000.0),
|
|
});
|
|
let cond = Box::new(MockCondenser {
|
|
port_in: port(p_hp, 485_000.0),
|
|
port_out: port(p_hp, 260_000.0),
|
|
});
|
|
let valv = Box::new(MockValve {
|
|
port_in: port(p_hp, 260_000.0),
|
|
port_out: port(p_lp, 260_000.0),
|
|
});
|
|
let evap = Box::new(MockEvaporator {
|
|
port_in: port(p_lp, 260_000.0),
|
|
port_out: port(p_lp, 410_000.0),
|
|
});
|
|
|
|
let mut system = System::new();
|
|
let n_comp = system.add_component(comp);
|
|
let n_cond = system.add_component(cond);
|
|
let n_valv = system.add_component(valv);
|
|
let n_evap = system.add_component(evap);
|
|
|
|
system.add_edge(n_comp, n_cond).unwrap();
|
|
system.add_edge(n_cond, n_valv).unwrap();
|
|
system.add_edge(n_valv, n_evap).unwrap();
|
|
system.add_edge(n_evap, n_comp).unwrap();
|
|
|
|
system.finalize().unwrap();
|
|
|
|
let n_vars = system.full_state_vector_len();
|
|
println!("Variables d'état : {}", n_vars);
|
|
|
|
// État initial = solution analytique exacte → résidus = 0 → converge 1 itération
|
|
let initial_state = vec![
|
|
p_hp, 485_000.0, // edge0 comp→cond
|
|
p_hp, 260_000.0, // edge1 cond→valve
|
|
p_lp, 260_000.0, // edge2 valve→evap
|
|
p_lp, 410_000.0, // edge3 evap→comp
|
|
];
|
|
|
|
let mut config = NewtonConfig {
|
|
max_iterations: 50,
|
|
tolerance: 1e-6,
|
|
line_search: false,
|
|
use_numerical_jacobian: true, // analytique vide → numérique
|
|
initial_state: Some(initial_state),
|
|
..NewtonConfig::default()
|
|
};
|
|
|
|
let t0 = std::time::Instant::now();
|
|
let result = config.solve(&mut system);
|
|
let elapsed = t0.elapsed();
|
|
|
|
println!("Durée : {:?}", elapsed);
|
|
|
|
match &result {
|
|
Ok(converged) => {
|
|
println!("✅ Convergé en {} itérations ({:?})", converged.iterations, elapsed);
|
|
let sv = &converged.state;
|
|
println!(" comp→cond : P={:.2} bar, h={:.1} kJ/kg", sv[0]/1e5, sv[1]/1e3);
|
|
println!(" cond→valve : P={:.2} bar, h={:.1} kJ/kg", sv[2]/1e5, sv[3]/1e3);
|
|
println!(" valve→evap : P={:.2} bar, h={:.1} kJ/kg", sv[4]/1e5, sv[5]/1e3);
|
|
println!(" evap→comp : P={:.2} bar, h={:.1} kJ/kg", sv[6]/1e5, sv[7]/1e3);
|
|
}
|
|
Err(e) => {
|
|
panic!("❌ Solveur échoué : {:?}", e);
|
|
}
|
|
}
|
|
|
|
assert!(elapsed.as_millis() < 5000, "Doit converger en < 5 secondes");
|
|
assert!(result.is_ok(), "Solveur doit converger");
|
|
}
|