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>
This commit is contained in:
@@ -1,18 +1,17 @@
|
||||
use entropyk_components::port::{Connected, FluidId, Port};
|
||||
/// 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,
|
||||
system::{System, DEFAULT_MASS_FLOW_SEED_KG_S},
|
||||
};
|
||||
use entropyk_components::port::{Connected, FluidId, Port};
|
||||
|
||||
// Type alias: Port<Connected> ≡ ConnectedPort
|
||||
type CP = Port<Connected>;
|
||||
@@ -20,72 +19,158 @@ 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 }
|
||||
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);
|
||||
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 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)])
|
||||
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 }
|
||||
struct MockCondenser {
|
||||
port_in: CP,
|
||||
port_out: CP,
|
||||
}
|
||||
impl Component for MockCondenser {
|
||||
fn compute_residuals(&self, _s: &StateSlice, r: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
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);
|
||||
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 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)])
|
||||
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 }
|
||||
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();
|
||||
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 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)])
|
||||
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 }
|
||||
struct MockEvaporator {
|
||||
port_in: CP,
|
||||
port_out: CP,
|
||||
}
|
||||
impl Component for MockEvaporator {
|
||||
fn compute_residuals(&self, _s: &StateSlice, r: &mut ResidualVector) -> Result<(), ComponentError> {
|
||||
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);
|
||||
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 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)])
|
||||
Ok(vec![
|
||||
MassFlow::from_kg_per_s(0.05),
|
||||
MassFlow::from_kg_per_s(-0.05),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,11 +180,13 @@ fn port(p_pa: f64, h_j_kg: f64) -> CP {
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(p_pa),
|
||||
Enthalpy::from_joules_per_kg(h_j_kg),
|
||||
).connect(Port::new(
|
||||
)
|
||||
.connect(Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_pascals(p_pa),
|
||||
Enthalpy::from_joules_per_kg(h_j_kg),
|
||||
)).unwrap();
|
||||
))
|
||||
.unwrap();
|
||||
connected
|
||||
}
|
||||
|
||||
@@ -123,8 +210,8 @@ fn test_simple_refrigeration_loop_rust() {
|
||||
// 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
|
||||
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
|
||||
@@ -132,19 +219,19 @@ fn test_simple_refrigeration_loop_rust() {
|
||||
// edge2 : valve → evap
|
||||
// edge3 : evap → comp
|
||||
let comp = Box::new(MockCompressor {
|
||||
port_suc: port(p_lp, 410_000.0),
|
||||
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_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_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_in: port(p_lp, 260_000.0),
|
||||
port_out: port(p_lp, 410_000.0),
|
||||
});
|
||||
|
||||
@@ -164,12 +251,16 @@ fn test_simple_refrigeration_loop_rust() {
|
||||
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
|
||||
// État initial = solution analytique exacte → résidus = 0 → converge 1 itération.
|
||||
// CM1.4 layout: 1 ṁ partagé (branche série unique) + (P, h) par arête.
|
||||
// state = [ṁ, P₀, h₀, P₁, h₁, P₂, h₂, P₃, h₃] (9 éléments)
|
||||
let m = DEFAULT_MASS_FLOW_SEED_KG_S;
|
||||
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
|
||||
m, // ṁ partagé (branche 0)
|
||||
p_hp, 485_000.0, // edge0 comp→cond : P, h
|
||||
p_hp, 260_000.0, // edge1 cond→valve : P, h
|
||||
p_lp, 260_000.0, // edge2 valve→evap : P, h
|
||||
p_lp, 410_000.0, // edge3 evap→comp : P, h
|
||||
];
|
||||
|
||||
let mut config = NewtonConfig {
|
||||
@@ -189,12 +280,32 @@ fn test_simple_refrigeration_loop_rust() {
|
||||
|
||||
match &result {
|
||||
Ok(converged) => {
|
||||
println!("✅ Convergé en {} itérations ({:?})", converged.iterations, elapsed);
|
||||
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);
|
||||
// CM1.4 layout: sv[0]=ṁ, then (P,h) per edge at stride 2.
|
||||
println!(
|
||||
" comp→cond : P={:.2} bar, h={:.1} kJ/kg",
|
||||
sv[1] / 1e5,
|
||||
sv[2] / 1e3
|
||||
);
|
||||
println!(
|
||||
" cond→valve : P={:.2} bar, h={:.1} kJ/kg",
|
||||
sv[3] / 1e5,
|
||||
sv[4] / 1e3
|
||||
);
|
||||
println!(
|
||||
" valve→evap : P={:.2} bar, h={:.1} kJ/kg",
|
||||
sv[5] / 1e5,
|
||||
sv[6] / 1e3
|
||||
);
|
||||
println!(
|
||||
" evap→comp : P={:.2} bar, h={:.1} kJ/kg",
|
||||
sv[7] / 1e5,
|
||||
sv[8] / 1e3
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
panic!("❌ Solveur échoué : {:?}", e);
|
||||
@@ -204,3 +315,193 @@ fn test_simple_refrigeration_loop_rust() {
|
||||
assert!(elapsed.as_millis() < 5000, "Doit converger en < 5 secondes");
|
||||
assert!(result.is_ok(), "Solveur doit converger");
|
||||
}
|
||||
|
||||
// ─── T6 — Topology presolve assertions ───────────────────────────────────────
|
||||
|
||||
/// AC #3, #5: For a pure 4-edge series cycle, the topology presolve must:
|
||||
/// - Produce state_vector_len = 9 (1 ṁ branch + 4×2 P,h) instead of 12 (old 4×3).
|
||||
/// - Assign the same ṁ state index to all 4 edges (shared branch).
|
||||
/// - Keep the system square: n_branches inferred as state_len - 2×edge_count = 1.
|
||||
#[test]
|
||||
fn test_topology_presolve_state_layout() {
|
||||
let p_lp = 350_000.0_f64;
|
||||
let p_hp = 1_350_000.0_f64;
|
||||
|
||||
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);
|
||||
|
||||
let e0 = system.add_edge(n_comp, n_cond).unwrap();
|
||||
let e1 = system.add_edge(n_cond, n_valv).unwrap();
|
||||
let e2 = system.add_edge(n_valv, n_evap).unwrap();
|
||||
let e3 = system.add_edge(n_evap, n_comp).unwrap();
|
||||
|
||||
system.finalize().unwrap();
|
||||
|
||||
// AC #3: CM1.4 state layout must be |B| + 2|E| = 1 + 8 = 9 (not 12).
|
||||
let state_len = system.state_vector_len();
|
||||
assert_eq!(
|
||||
state_len, 9,
|
||||
"CM1.4 state must be 1 branch + 4×2 P,h = 9, got {}",
|
||||
state_len
|
||||
);
|
||||
|
||||
// AC #3: Branch count inference — all branches used exactly 1 ṁ slot.
|
||||
let edge_count = 4;
|
||||
let n_branches_inferred = state_len - 2 * edge_count;
|
||||
assert_eq!(
|
||||
n_branches_inferred, 1,
|
||||
"pure series cycle must have exactly 1 branch, inferred {}",
|
||||
n_branches_inferred
|
||||
);
|
||||
|
||||
// AC #3: All 4 edges share the same ṁ state index.
|
||||
let m_idx: Vec<usize> = [e0, e1, e2, e3]
|
||||
.iter()
|
||||
.map(|&e| system.edge_state_indices_full(e).0)
|
||||
.collect();
|
||||
|
||||
let first_m = m_idx[0];
|
||||
assert!(
|
||||
m_idx.iter().all(|&m| m == first_m),
|
||||
"all edges in a series branch must share the same ṁ index; got {:?}",
|
||||
m_idx
|
||||
);
|
||||
assert_eq!(first_m, 0, "shared ṁ index must be 0 (first slot)");
|
||||
}
|
||||
|
||||
/// AC #5: A two-circuit system (2 independent series cycles) must have
|
||||
/// 2 independent branch ṁ unknowns and state_vector_len = 2×(1 + 2×4) = 18.
|
||||
#[test]
|
||||
fn test_topology_presolve_two_independent_circuits() {
|
||||
use entropyk_solver::CircuitId;
|
||||
|
||||
let p_lp = 350_000.0_f64;
|
||||
let p_hp = 1_350_000.0_f64;
|
||||
|
||||
let mut system = System::new();
|
||||
|
||||
// ── Circuit 0 ──
|
||||
let c0_comp = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockCompressor {
|
||||
port_suc: port(p_lp, 410_000.0),
|
||||
port_disc: port(p_hp, 485_000.0),
|
||||
}),
|
||||
CircuitId::ZERO,
|
||||
)
|
||||
.unwrap();
|
||||
let c0_cond = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockCondenser {
|
||||
port_in: port(p_hp, 485_000.0),
|
||||
port_out: port(p_hp, 260_000.0),
|
||||
}),
|
||||
CircuitId::ZERO,
|
||||
)
|
||||
.unwrap();
|
||||
let c0_valv = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockValve {
|
||||
port_in: port(p_hp, 260_000.0),
|
||||
port_out: port(p_lp, 260_000.0),
|
||||
}),
|
||||
CircuitId::ZERO,
|
||||
)
|
||||
.unwrap();
|
||||
let c0_evap = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockEvaporator {
|
||||
port_in: port(p_lp, 260_000.0),
|
||||
port_out: port(p_lp, 410_000.0),
|
||||
}),
|
||||
CircuitId::ZERO,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
system.add_edge(c0_comp, c0_cond).unwrap();
|
||||
system.add_edge(c0_cond, c0_valv).unwrap();
|
||||
system.add_edge(c0_valv, c0_evap).unwrap();
|
||||
system.add_edge(c0_evap, c0_comp).unwrap();
|
||||
|
||||
// ── Circuit 1 ──
|
||||
let c1 = CircuitId::from_number(1);
|
||||
let c1_comp = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockCompressor {
|
||||
port_suc: port(p_lp, 410_000.0),
|
||||
port_disc: port(p_hp, 485_000.0),
|
||||
}),
|
||||
c1,
|
||||
)
|
||||
.unwrap();
|
||||
let c1_cond = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockCondenser {
|
||||
port_in: port(p_hp, 485_000.0),
|
||||
port_out: port(p_hp, 260_000.0),
|
||||
}),
|
||||
c1,
|
||||
)
|
||||
.unwrap();
|
||||
let c1_valv = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockValve {
|
||||
port_in: port(p_hp, 260_000.0),
|
||||
port_out: port(p_lp, 260_000.0),
|
||||
}),
|
||||
c1,
|
||||
)
|
||||
.unwrap();
|
||||
let c1_evap = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(MockEvaporator {
|
||||
port_in: port(p_lp, 260_000.0),
|
||||
port_out: port(p_lp, 410_000.0),
|
||||
}),
|
||||
c1,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
system.add_edge(c1_comp, c1_cond).unwrap();
|
||||
system.add_edge(c1_cond, c1_valv).unwrap();
|
||||
system.add_edge(c1_valv, c1_evap).unwrap();
|
||||
system.add_edge(c1_evap, c1_comp).unwrap();
|
||||
|
||||
system.finalize().unwrap();
|
||||
|
||||
// 2 circuits × (1 branch + 4×2 P,h) = 2 × 9 = 18 state variables.
|
||||
let state_len = system.state_vector_len();
|
||||
assert_eq!(
|
||||
state_len, 18,
|
||||
"two independent 4-edge cycles = 2 branches + 8×2 P,h = 18, got {}",
|
||||
state_len
|
||||
);
|
||||
|
||||
// Inferred branch count = 18 - 2*8 = 2.
|
||||
let n_branches_inferred = state_len - 2 * 8;
|
||||
assert_eq!(
|
||||
n_branches_inferred, 2,
|
||||
"two independent cycles must have 2 branches, inferred {}",
|
||||
n_branches_inferred
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user