feat(components): add ThermoState generators and Eurovent backend demo
This commit is contained in:
563
demo/src/bin/chiller.rs
Normal file
563
demo/src/bin/chiller.rs
Normal file
@@ -0,0 +1,563 @@
|
||||
//! Demo Entropyk - Chiller System Simulation
|
||||
//!
|
||||
//! Complete refrigeration system with:
|
||||
//! - Air-cooled condenser (35°C ambient air)
|
||||
//! - BPHE evaporator (water 12°C → 7°C)
|
||||
//! - Compressor
|
||||
//! - Expansion valve (EXV)
|
||||
//!
|
||||
//! System topology:
|
||||
//! Circuit 0 (Refrigerant R410A):
|
||||
//! Compressor → Condenser → EXV → Evaporator → Compressor
|
||||
//!
|
||||
//! Circuit 1 (Water/Glycol):
|
||||
//! Pump → Evaporator (heat exchange) → Pump
|
||||
|
||||
use colored::Colorize;
|
||||
use entropyk_components::heat_exchanger::{CondenserCoil, Evaporator};
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, JacobianBuilder, ResidualVector, SystemState,
|
||||
};
|
||||
use entropyk_core::{MassFlow, Pressure, Temperature, ThermalConductance};
|
||||
use entropyk_solver::{
|
||||
compute_coupling_heat, coupling_groups, has_circular_dependencies, System, ThermalCoupling,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
fn print_header(title: &str) {
|
||||
println!();
|
||||
println!("{}", "═".repeat(70).cyan());
|
||||
println!("{}", format!(" {}", title).cyan().bold());
|
||||
println!("{}", "═".repeat(70).cyan());
|
||||
}
|
||||
|
||||
fn print_section(title: &str) {
|
||||
println!();
|
||||
println!("{}", format!("▶ {}", title).yellow().bold());
|
||||
println!("{}", "─".repeat(50).yellow());
|
||||
}
|
||||
|
||||
fn print_subsection(title: &str) {
|
||||
println!();
|
||||
println!(" {}", format!("◆ {}", title).white().bold());
|
||||
}
|
||||
|
||||
// Simple placeholder component for demo
|
||||
struct PlaceholderComponent {
|
||||
name: String,
|
||||
n_eqs: usize,
|
||||
}
|
||||
|
||||
impl PlaceholderComponent {
|
||||
fn new(name: &str) -> Box<dyn Component> {
|
||||
Box::new(Self {
|
||||
name: name.to_string(),
|
||||
n_eqs: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn with_equations(name: &str, n_eqs: usize) -> Box<dyn Component> {
|
||||
Box::new(Self {
|
||||
name: name.to_string(),
|
||||
n_eqs,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for PlaceholderComponent {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
for r in residuals.iter_mut() {
|
||||
*r = 0.0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
_jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n_eqs
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[entropyk_components::ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for PlaceholderComponent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("PlaceholderComponent")
|
||||
.field("name", &self.name)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// MAIN DEMO
|
||||
// ============================================================================
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"{}",
|
||||
"\n╔══════════════════════════════════════════════════════════════════╗".green()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"║ ENTROPYK - Water Chiller System Demo ║"
|
||||
.green()
|
||||
.bold()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"║ Condenser: Air-cooled (35°C ambient) ║".green()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"║ Evaporator: BPHE (water 12°C → 7°C) ║".green()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"╚══════════════════════════════════════════════════════════════════╝\n".green()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// Part 1: Design Point Analysis
|
||||
// ========================================
|
||||
print_header("Design Point Analysis");
|
||||
|
||||
println!();
|
||||
println!("{}", " Water Chiller - Cooling Mode".white());
|
||||
println!("{}", " ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━".white());
|
||||
|
||||
// Water side
|
||||
let t_water_in = Temperature::from_celsius(12.0);
|
||||
let t_water_out = Temperature::from_celsius(7.0);
|
||||
let water_flow = MassFlow::from_kg_per_s(0.5); // 0.5 kg/s ≈ 30 L/min
|
||||
let cp_water = 4186.0; // J/(kg·K)
|
||||
|
||||
let q_evap =
|
||||
water_flow.to_kg_per_s() * cp_water * (t_water_in.to_celsius() - t_water_out.to_celsius());
|
||||
|
||||
println!();
|
||||
println!("{}", " Water Side (Evaporator Load)".cyan());
|
||||
println!(" T_water_in: {:.1}°C", t_water_in.to_celsius());
|
||||
println!(" T_water_out: {:.1}°C", t_water_out.to_celsius());
|
||||
println!(
|
||||
" ṁ_water: {:.2} kg/s ({:.0} L/min)",
|
||||
water_flow.to_kg_per_s(),
|
||||
water_flow.to_kg_per_s() * 60.0
|
||||
);
|
||||
println!(
|
||||
" Q_evap: {:.0} W = {:.1} kW",
|
||||
q_evap,
|
||||
q_evap / 1000.0
|
||||
);
|
||||
|
||||
// Air side (condenser)
|
||||
let t_amb = Temperature::from_celsius(35.0);
|
||||
let approach_cond = 10.0; // K approach temperature
|
||||
let t_cond = Temperature::from_celsius(t_amb.to_celsius() + approach_cond);
|
||||
|
||||
// Estimate compressor power (assuming COP ≈ 3.5)
|
||||
let cop_estimate = 3.5;
|
||||
let w_comp = q_evap / cop_estimate;
|
||||
let q_cond = q_evap + w_comp;
|
||||
|
||||
println!();
|
||||
println!("{}", " Air Side (Condenser Rejection)".cyan());
|
||||
println!(" T_ambient: {:.1}°C", t_amb.to_celsius());
|
||||
println!(" Approach: {:.1} K", approach_cond);
|
||||
println!(
|
||||
" T_cond: {:.1}°C ({:.2} K)",
|
||||
t_cond.to_celsius(),
|
||||
t_cond.to_kelvin()
|
||||
);
|
||||
println!(
|
||||
" Q_cond: {:.0} W = {:.1} kW",
|
||||
q_cond,
|
||||
q_cond / 1000.0
|
||||
);
|
||||
|
||||
println!();
|
||||
println!("{}", " Compressor".cyan());
|
||||
println!(
|
||||
" W_comp: {:.0} W = {:.2} kW",
|
||||
w_comp,
|
||||
w_comp / 1000.0
|
||||
);
|
||||
println!(" COP (est): {:.1}", cop_estimate);
|
||||
|
||||
// Evaporator temperature
|
||||
let approach_evap = 5.0; // K
|
||||
let t_evap = Temperature::from_celsius(t_water_out.to_celsius() - approach_evap);
|
||||
|
||||
println!();
|
||||
println!("{}", " Refrigerant Cycle (R410A)".cyan());
|
||||
println!(
|
||||
" T_evap: {:.1}°C ({:.2} K)",
|
||||
t_evap.to_celsius(),
|
||||
t_evap.to_kelvin()
|
||||
);
|
||||
println!(
|
||||
" T_cond: {:.1}°C ({:.2} K)",
|
||||
t_cond.to_celsius(),
|
||||
t_cond.to_kelvin()
|
||||
);
|
||||
println!(
|
||||
" ΔT_lift: {:.1} K",
|
||||
t_cond.to_kelvin() - t_evap.to_kelvin()
|
||||
);
|
||||
|
||||
// Pressure estimates for R410A (approximate)
|
||||
let p_evap = Pressure::from_bar(8.0); // ~5°C saturation for R410A
|
||||
let p_cond = Pressure::from_bar(24.0); // ~45°C saturation for R410A
|
||||
let pressure_ratio = p_cond.to_bar() / p_evap.to_bar();
|
||||
|
||||
println!(" P_evap: {:.1} bar", p_evap.to_bar());
|
||||
println!(" P_cond: {:.1} bar", p_cond.to_bar());
|
||||
println!(" PR: {:.2}", pressure_ratio);
|
||||
|
||||
// Heat exchanger sizing
|
||||
println!();
|
||||
println!("{}", " Heat Exchanger Sizing".cyan());
|
||||
|
||||
// Condenser UA (air-cooled, typical 0.1-0.2 kW/K per kW capacity)
|
||||
let ua_cond = q_cond / (t_cond.to_kelvin() - t_amb.to_kelvin());
|
||||
println!(
|
||||
" UA_condenser: {:.0} W/K = {:.1} kW/K",
|
||||
ua_cond,
|
||||
ua_cond / 1000.0
|
||||
);
|
||||
|
||||
// Evaporator UA (BPHE, water-to-refrigerant)
|
||||
let delta_t1 = t_water_in.to_kelvin() - t_evap.to_kelvin();
|
||||
let delta_t2 = t_water_out.to_kelvin() - t_evap.to_kelvin();
|
||||
let lmtd_evap = (delta_t1 - delta_t2) / (delta_t1 / delta_t2).ln();
|
||||
let ua_evap = q_evap / lmtd_evap;
|
||||
println!(" LMTD_evap: {:.1} K", lmtd_evap);
|
||||
println!(
|
||||
" UA_evaporator: {:.0} W/K = {:.1} kW/K",
|
||||
ua_evap,
|
||||
ua_evap / 1000.0
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// Part 2: Create Heat Exchangers (Real Components)
|
||||
// ========================================
|
||||
print_header("Component Creation");
|
||||
|
||||
// Condenser (air-cooled)
|
||||
print_section("Creating Air-Cooled Condenser");
|
||||
let condenser = CondenserCoil::with_saturation_temp(ua_cond, t_cond.to_kelvin());
|
||||
println!(" UA: {:.0} W/K", condenser.ua());
|
||||
println!(
|
||||
" T_sat: {:.1}°C ({:.2} K)",
|
||||
t_cond.to_celsius(),
|
||||
t_cond.to_kelvin()
|
||||
);
|
||||
println!(" Equations: {}", condenser.n_equations());
|
||||
println!(" {} Condenser coil created", "✓".green());
|
||||
|
||||
// Evaporator (BPHE water-to-refrigerant)
|
||||
print_section("Creating BPHE Evaporator");
|
||||
let evaporator = Evaporator::with_superheat(ua_evap, t_evap.to_kelvin(), 5.0);
|
||||
println!(" UA: {:.0} W/K", evaporator.ua());
|
||||
println!(
|
||||
" T_sat: {:.1}°C ({:.2} K)",
|
||||
t_evap.to_celsius(),
|
||||
t_evap.to_kelvin()
|
||||
);
|
||||
println!(" Superheat target: 5 K");
|
||||
println!(" Equations: {}", evaporator.n_equations());
|
||||
println!(" {} BPHE evaporator created", "✓".green());
|
||||
|
||||
// ========================================
|
||||
// Part 3: System Topology
|
||||
// ========================================
|
||||
print_header("System Topology");
|
||||
|
||||
print_section("Creating Multi-Circuit System");
|
||||
|
||||
let mut system = System::new();
|
||||
|
||||
// Circuit 0: Refrigerant
|
||||
let n_comp = system
|
||||
.add_component_to_circuit(
|
||||
PlaceholderComponent::with_equations("Compressor", 2),
|
||||
entropyk_solver::CircuitId(0),
|
||||
)
|
||||
.unwrap();
|
||||
let n_cond = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(CondenserCoil::with_saturation_temp(
|
||||
ua_cond,
|
||||
t_cond.to_kelvin(),
|
||||
)),
|
||||
entropyk_solver::CircuitId(0),
|
||||
)
|
||||
.unwrap();
|
||||
let n_exv = system
|
||||
.add_component_to_circuit(
|
||||
PlaceholderComponent::with_equations("ExpansionValve", 1),
|
||||
entropyk_solver::CircuitId(0),
|
||||
)
|
||||
.unwrap();
|
||||
let n_evap = system
|
||||
.add_component_to_circuit(
|
||||
Box::new(Evaporator::with_superheat(ua_evap, t_evap.to_kelvin(), 5.0)),
|
||||
entropyk_solver::CircuitId(0),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Circuit 1: Water
|
||||
let n_pump = system
|
||||
.add_component_to_circuit(
|
||||
PlaceholderComponent::new("WaterPump"),
|
||||
entropyk_solver::CircuitId(1),
|
||||
)
|
||||
.unwrap();
|
||||
let n_load = system
|
||||
.add_component_to_circuit(
|
||||
PlaceholderComponent::new("CoolingLoad"),
|
||||
entropyk_solver::CircuitId(1),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
println!(" Circuit 0 (Refrigerant R410A):");
|
||||
println!(" [{}] Compressor", n_comp.index());
|
||||
println!(" [{}] CondenserCoil", n_cond.index());
|
||||
println!(" [{}] ExpansionValve", n_exv.index());
|
||||
println!(" [{}] Evaporator (BPHE)", n_evap.index());
|
||||
println!();
|
||||
println!(" Circuit 1 (Water/Glycol):");
|
||||
println!(" [{}] WaterPump", n_pump.index());
|
||||
println!(" [{}] CoolingLoad", n_load.index());
|
||||
|
||||
// Connect refrigerant cycle
|
||||
print_subsection("Connecting Refrigerant Circuit");
|
||||
system.add_edge(n_comp, n_cond).unwrap();
|
||||
system.add_edge(n_cond, n_exv).unwrap();
|
||||
system.add_edge(n_exv, n_evap).unwrap();
|
||||
system.add_edge(n_evap, n_comp).unwrap();
|
||||
println!(" Compressor → Condenser → EXV → Evaporator → Compressor");
|
||||
println!(" {} Refrigerant cycle connected", "✓".green());
|
||||
|
||||
// Connect water cycle (independent circuit - no cross-circuit flow edges!)
|
||||
print_subsection("Connecting Water Circuit");
|
||||
system.add_edge(n_pump, n_load).unwrap();
|
||||
system.add_edge(n_load, n_pump).unwrap();
|
||||
println!(" Pump → CoolingLoad → Pump (independent closed loop)");
|
||||
println!(" {} Water circuit connected", "✓".green());
|
||||
println!();
|
||||
println!(
|
||||
" {} Thermal coupling handles heat transfer between circuits",
|
||||
"Note:".cyan()
|
||||
);
|
||||
println!(" (No cross-circuit flow edges allowed)");
|
||||
|
||||
println!();
|
||||
println!(
|
||||
" {} circuits, {} components, {} flow edges",
|
||||
system.circuit_count(),
|
||||
system.node_count(),
|
||||
system.edge_count()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// Part 4: Thermal Coupling
|
||||
// ========================================
|
||||
print_header("Thermal Coupling");
|
||||
|
||||
print_section("Adding Heat Exchanger Coupling");
|
||||
|
||||
// The evaporator thermally couples water to refrigerant
|
||||
// Water is hot side, refrigerant is cold side (evaporating)
|
||||
let coupling = ThermalCoupling::new(
|
||||
entropyk_solver::CircuitId(1), // Hot: water circuit
|
||||
entropyk_solver::CircuitId(0), // Cold: refrigerant circuit (evaporating)
|
||||
ThermalConductance::from_watts_per_kelvin(ua_evap),
|
||||
)
|
||||
.with_efficiency(0.95);
|
||||
|
||||
let idx = system.add_thermal_coupling(coupling.clone()).unwrap();
|
||||
println!(" Coupling [{}]:", idx);
|
||||
println!(
|
||||
" Hot circuit: Circuit 1 (Water @ {:.1}°C)",
|
||||
t_water_in.to_celsius()
|
||||
);
|
||||
println!(
|
||||
" Cold circuit: Circuit 0 (R410A @ {:.1}°C)",
|
||||
t_evap.to_celsius()
|
||||
);
|
||||
println!(" UA: {:.0} W/K", coupling.ua.to_watts_per_kelvin());
|
||||
println!(" Efficiency: {:.0}%", coupling.efficiency * 100.0);
|
||||
println!(" {} Thermal coupling added", "✓".green());
|
||||
|
||||
// Compute heat transfer at design point
|
||||
print_subsection("Heat Transfer at Design Point");
|
||||
let q_calc = compute_coupling_heat(&coupling, t_water_in, t_evap);
|
||||
println!(" T_hot (water): {:.1}°C", t_water_in.to_celsius());
|
||||
println!(" T_cold (ref): {:.1}°C", t_evap.to_celsius());
|
||||
println!(
|
||||
" ΔT: {:.1} K",
|
||||
t_water_in.to_kelvin() - t_evap.to_kelvin()
|
||||
);
|
||||
println!(
|
||||
" Q_calc: {:.0} W = {:.1} kW",
|
||||
q_calc,
|
||||
q_calc / 1000.0
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// Part 5: Circular Dependency Check
|
||||
// ========================================
|
||||
print_header("Solver Strategy");
|
||||
|
||||
print_section("Coupling Analysis");
|
||||
|
||||
let couplings = system.thermal_couplings();
|
||||
let has_cycle = has_circular_dependencies(couplings);
|
||||
|
||||
println!(
|
||||
" Circular dependency: {}",
|
||||
if has_cycle { "YES".red() } else { "NO".green() }
|
||||
);
|
||||
|
||||
let groups = coupling_groups(couplings);
|
||||
println!(" Coupling groups: {:?}", groups);
|
||||
|
||||
if has_cycle {
|
||||
println!();
|
||||
println!(
|
||||
" {} Circuits with mutual coupling must be solved SIMULTANEOUSLY",
|
||||
"→".yellow()
|
||||
);
|
||||
println!(" (Newton-Raphson on combined system)");
|
||||
} else {
|
||||
println!();
|
||||
println!(" {} Circuits can be solved SEQUENTIALLY", "→".green());
|
||||
println!(" (Water circuit → Refrigerant circuit)");
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Part 6: Finalize System
|
||||
// ========================================
|
||||
print_header("System Finalization");
|
||||
|
||||
match system.finalize() {
|
||||
Ok(()) => {
|
||||
println!(" {} System finalized successfully", "✓".green());
|
||||
println!(
|
||||
" {} state variables (P, h per edge)",
|
||||
system.state_vector_len()
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
println!(" {} Finalization error: {:?}", "✗".red(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Summary
|
||||
// ========================================
|
||||
print_header("System Summary");
|
||||
|
||||
println!();
|
||||
println!(
|
||||
"{}",
|
||||
" ┌─────────────────────────────────────────────────────────────┐".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ WATER CHILLER SYSTEM │".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" ├─────────────────────────────────────────────────────────────┤".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ REFRIGERANT CIRCUIT (R410A) │".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ Compressor: 2900 RPM, 30cc, η=85% │".white()
|
||||
);
|
||||
println!(
|
||||
" │ Condenser: Air-cooled, UA={:.0} W/K │",
|
||||
ua_cond
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ EXV: Isenthalpic, 100% open │".white()
|
||||
);
|
||||
println!(
|
||||
" │ Evaporator: BPHE, UA={:.0} W/K, SH=5K │",
|
||||
ua_evap
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" ├─────────────────────────────────────────────────────────────┤".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ WATER CIRCUIT │".white()
|
||||
);
|
||||
println!(
|
||||
" │ Pump: {:.2} kg/s, ΔP=200 kPa │",
|
||||
0.5
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ Inlet: 12°C │".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ Outlet: 7°C │".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" ├─────────────────────────────────────────────────────────────┤".white()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" │ PERFORMANCE (Design Point) │".white()
|
||||
);
|
||||
println!(
|
||||
" │ Q_evap: {:.1} kW │",
|
||||
q_evap / 1000.0
|
||||
);
|
||||
println!(
|
||||
" │ Q_cond: {:.1} kW │",
|
||||
q_cond / 1000.0
|
||||
);
|
||||
println!(
|
||||
" │ W_comp: {:.2} kW │",
|
||||
w_comp / 1000.0
|
||||
);
|
||||
println!(
|
||||
" │ COP: {:.1} │",
|
||||
q_evap / w_comp
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
" └─────────────────────────────────────────────────────────────┘".white()
|
||||
);
|
||||
|
||||
println!();
|
||||
println!("{}", "═".repeat(70).cyan());
|
||||
println!(
|
||||
"{}",
|
||||
" Next: Implement solver (Epic 4) to run full simulation".cyan()
|
||||
);
|
||||
println!("{}", "═".repeat(70).cyan());
|
||||
}
|
||||
@@ -104,6 +104,7 @@ fn main() {
|
||||
"Water",
|
||||
));
|
||||
|
||||
let cond_state = condenser_with_backend.hot_inlet_state().ok();
|
||||
let cond = system.add_component_to_circuit(Box::new(condenser_with_backend), CircuitId(0)).unwrap(); // 40°C condensing backed by TestBackend
|
||||
|
||||
let exv = system.add_component_to_circuit(SimpleComponent::new("ExpansionValve", 1), CircuitId(0)).unwrap();
|
||||
@@ -285,6 +286,15 @@ fn main() {
|
||||
));
|
||||
println!(" {} Next step: connect to CoolPropBackend when `vendor/` CoolProp C++ is supplied.",
|
||||
"→".cyan());
|
||||
|
||||
if let Some(state) = cond_state {
|
||||
println!("\n {} Retrieved full ThermoState from Condenser hot inlet (before solve):", "✓".green());
|
||||
println!(" - Pressure: {:.2} bar", state.pressure.to_bar());
|
||||
println!(" - Temperature: {:.2} °C", state.temperature.to_celsius());
|
||||
println!(" - Enthalpy: {:.2} kJ/kg", state.enthalpy.to_joules_per_kg() / 1000.0);
|
||||
println!(" - Density: {:.2} kg/m³", state.density);
|
||||
println!(" - Phase: {:?}", state.phase);
|
||||
}
|
||||
|
||||
println!("\n{}", "═".repeat(70).cyan());
|
||||
}
|
||||
|
||||
32
demo/src/bin/expansion_valve.rs
Normal file
32
demo/src/bin/expansion_valve.rs
Normal file
@@ -0,0 +1,32 @@
|
||||
//! Exemple: Détendeur (expansion valve)
|
||||
//!
|
||||
//! Modélisation d'une détente isenthalpique.
|
||||
//!
|
||||
//! Exécuter: cargo run -p entropyk-demo --bin expansion_valve
|
||||
|
||||
use entropyk_components::expansion_valve::ExpansionValve;
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Exemple: Détendeur (Expansion Valve) ===\n");
|
||||
|
||||
let inlet = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(10.0),
|
||||
Enthalpy::from_joules_per_kg(250_000.0),
|
||||
);
|
||||
let outlet = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(10.0),
|
||||
Enthalpy::from_joules_per_kg(250_000.0),
|
||||
);
|
||||
|
||||
let valve = ExpansionValve::new(inlet, outlet, Some(1.0))?;
|
||||
|
||||
println!("Détendeur créé:");
|
||||
println!(" - Fluide: {}", valve.fluid_id());
|
||||
println!(" - Ouverture: {:?}", valve.opening());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
40
demo/src/bin/pipe.rs
Normal file
40
demo/src/bin/pipe.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
//! Exemple: Conduite (pipe)
|
||||
//!
|
||||
//! Perte de charge avec Darcy-Weisbach.
|
||||
//!
|
||||
//! Exécuter: cargo run -p entropyk-demo --bin pipe
|
||||
|
||||
use entropyk_components::pipe::{Pipe, PipeGeometry, roughness};
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Exemple: Conduite (Pipe) ===\n");
|
||||
|
||||
let geometry = PipeGeometry::new(
|
||||
10.0,
|
||||
0.022,
|
||||
roughness::SMOOTH,
|
||||
)?;
|
||||
|
||||
let inlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(2.0),
|
||||
Enthalpy::from_joules_per_kg(84_000.0),
|
||||
);
|
||||
let outlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(2.0),
|
||||
Enthalpy::from_joules_per_kg(84_000.0),
|
||||
);
|
||||
|
||||
let pipe = Pipe::new(geometry, inlet, outlet, 998.0, 0.001)?;
|
||||
|
||||
println!("Conduite créée:");
|
||||
println!(" - Longueur: {} m", pipe.geometry().length_m);
|
||||
println!(" - Diamètre: {} m", pipe.geometry().diameter_m);
|
||||
println!(" - Rugosité: {} m", pipe.geometry().roughness_m);
|
||||
println!(" - Fluide: {}", pipe.fluid_id());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
43
demo/src/bin/ports.rs
Normal file
43
demo/src/bin/ports.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
//! Exemple: Ports et connexions
|
||||
//!
|
||||
//! Démonstration du Type-State pattern pour les ports thermodynamiques.
|
||||
//!
|
||||
//! Exécuter: cargo run -p entropyk-demo --bin ports
|
||||
|
||||
use entropyk_components::port::{ConnectionError, FluidId, Port};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
|
||||
fn main() -> Result<(), ConnectionError> {
|
||||
println!("=== Exemple: Ports et Connexions ===\n");
|
||||
|
||||
let port1 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0),
|
||||
);
|
||||
|
||||
let port2 = Port::new(
|
||||
FluidId::new("R134a"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(400_000.0),
|
||||
);
|
||||
|
||||
println!("Port 1: fluide={}, P={:.2} bar, h={:.0} J/kg",
|
||||
port1.fluid_id(),
|
||||
port1.pressure().to_bar(),
|
||||
port1.enthalpy().to_joules_per_kg()
|
||||
);
|
||||
|
||||
let (mut connected1, _connected2) = port1.connect(port2)?;
|
||||
println!("\n✅ Ports connectés avec succès!");
|
||||
|
||||
connected1.set_pressure(Pressure::from_bar(1.5));
|
||||
connected1.set_enthalpy(Enthalpy::from_joules_per_kg(450_000.0));
|
||||
|
||||
println!("Port 1 modifié: P={:.2} bar, h={:.0} J/kg",
|
||||
connected1.pressure().to_bar(),
|
||||
connected1.enthalpy().to_joules_per_kg()
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
43
demo/src/bin/pump.rs
Normal file
43
demo/src/bin/pump.rs
Normal file
@@ -0,0 +1,43 @@
|
||||
//! Exemple: Pompe
|
||||
//!
|
||||
//! Courbes de performance polynomiales.
|
||||
//!
|
||||
//! Exécuter: cargo run -p entropyk-demo --bin pump
|
||||
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_components::pump::{Pump, PumpCurves};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("=== Exemple: Pompe ===\n");
|
||||
|
||||
let curves = PumpCurves::quadratic(
|
||||
30.0, -10.0, -50.0,
|
||||
0.5, 0.3, -0.5,
|
||||
)?;
|
||||
|
||||
let inlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(100_000.0),
|
||||
);
|
||||
let outlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(100_000.0),
|
||||
);
|
||||
|
||||
let pump = Pump::new(curves, inlet, outlet, 1000.0)?;
|
||||
|
||||
println!("Pompe créée:");
|
||||
println!(" - Fluide: {}", pump.fluid_id());
|
||||
println!(" - Densité: {} kg/m³", pump.fluid_density());
|
||||
|
||||
for q in [0.0, 0.05, 0.1, 0.2] {
|
||||
let head = pump.curves().head_at_flow(q);
|
||||
let eff = pump.curves().efficiency_at_flow(q);
|
||||
println!(" - Q={:.2} m³/s: H={:.2} m, η={:.1}%", q, head, eff * 100.0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
131
demo/src/bin/pump_compressor_polynomials.rs
Normal file
131
demo/src/bin/pump_compressor_polynomials.rs
Normal file
@@ -0,0 +1,131 @@
|
||||
//! Exemple: Pompe et compresseur avec polynômes
|
||||
//!
|
||||
//! Démontre l'utilisation des polynômes pour modéliser:
|
||||
//! - Pompe: courbes H(Q) et η(Q) avec Polynomial1D
|
||||
//! - Compresseur: modèle SST/SDT avec Polynomial2D
|
||||
//! - Lois d'affinité pour variation de vitesse
|
||||
//!
|
||||
//! Exécuter: cargo run -p entropyk-demo --bin pump_compressor_polynomials
|
||||
|
||||
use entropyk_components::compressor::SstSdtCoefficients;
|
||||
use entropyk_components::polynomials::{AffinityLaws, Polynomial1D};
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_components::pump::{Pump, PumpCurves};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
println!("╔══════════════════════════════════════════════════════════════╗");
|
||||
println!("║ Exemple: Pompe et Compresseur avec Polynômes ║");
|
||||
println!("╚══════════════════════════════════════════════════════════════╝\n");
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 1. POLYNÔMES 1D - Courbes de pompe
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
println!("📐 1. Polynômes 1D (Pompe)\n");
|
||||
|
||||
// H = 30 - 10*Q - 50*Q² (hauteur en m, Q en m³/s)
|
||||
let head_poly = Polynomial1D::quadratic(30.0, -10.0, -50.0);
|
||||
// η = 0.5 + 0.3*Q - 0.5*Q² (rendement 0-1)
|
||||
let eff_poly = Polynomial1D::quadratic(0.5, 0.3, -0.5);
|
||||
|
||||
println!(" Courbe hauteur: H = 30 - 10*Q - 50*Q²");
|
||||
println!(" Courbe rendement: η = 0.5 + 0.3*Q - 0.5*Q²\n");
|
||||
|
||||
for q in [0.0, 0.05, 0.1, 0.15, 0.2] {
|
||||
let h = head_poly.evaluate(q);
|
||||
let eta = eff_poly.evaluate(q);
|
||||
println!(" Q={:.2} m³/s → H={:.2} m, η={:.1}%", q, h, eta.clamp(0.0, 1.0) * 100.0);
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 2. POMPE avec courbes polynomiales
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
println!("\n🔧 2. Pompe (PumpCurves polynomiales)\n");
|
||||
|
||||
let curves = PumpCurves::quadratic(
|
||||
30.0, -10.0, -50.0, // H = h0 + h1*Q + h2*Q²
|
||||
0.5, 0.3, -0.5, // η = e0 + e1*Q + e2*Q²
|
||||
)?;
|
||||
|
||||
let inlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(100_000.0),
|
||||
);
|
||||
let outlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(100_000.0),
|
||||
);
|
||||
|
||||
let pump = Pump::new(curves, inlet, outlet, 1000.0)?;
|
||||
|
||||
println!(" Pompe créée (eau, ρ=1000 kg/m³)");
|
||||
println!(" Point nominal Q=0.1 m³/s: H={:.2} m, η={:.1}%\n",
|
||||
pump.curves().head_at_flow(0.1),
|
||||
pump.curves().efficiency_at_flow(0.1) * 100.0
|
||||
);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 3. POLYNÔMES 2D - Modèle compresseur SST/SDT
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
println!("📐 3. Polynômes 2D (Compresseur SST/SDT)\n");
|
||||
|
||||
// Modèle bilinéaire: ṁ = a00 + a10*SST + a01*SDT + a11*SST*SDT
|
||||
// Ẇ = b00 + b10*SST + b01*SDT + b11*SST*SDT
|
||||
let sst_sdt = SstSdtCoefficients::bilinear(
|
||||
0.05, 0.001, 0.0005, 0.00001, // débit (kg/s)
|
||||
1000.0, 50.0, 30.0, 0.5, // puissance (W)
|
||||
);
|
||||
|
||||
println!(" Modèle: ṁ = f(SST, SDT), Ẇ = g(SST, SDT)");
|
||||
println!(" SST = température saturation aspiration (K)");
|
||||
println!(" SDT = température saturation refoulement (K)\n");
|
||||
|
||||
// Conditions typiques: évaporation -5°C (268K), condensation 40°C (313K)
|
||||
let sst_evap = 268.15; // -5°C
|
||||
let sdt_cond = 313.15; // 40°C
|
||||
|
||||
let mass_flow = sst_sdt.mass_flow_at(sst_evap, sdt_cond);
|
||||
let power = sst_sdt.power_at(sst_evap, sdt_cond);
|
||||
|
||||
println!(" SST={:.1} K (-5°C), SDT={:.1} K (40°C):", sst_evap, sdt_cond);
|
||||
println!(" → ṁ = {:.4} kg/s", mass_flow);
|
||||
println!(" → Ẇ = {:.0} W\n", power);
|
||||
|
||||
// Grille de conditions
|
||||
println!(" Grille de performance:");
|
||||
println!(" {:>8} | {:>8} {:>8} {:>8} {:>8}", "SST\\SDT", "303K", "308K", "313K", "318K");
|
||||
println!(" {} | {} {} {} {}", "--------", "--------", "--------", "--------", "--------");
|
||||
|
||||
for sst in [263.15, 268.15, 273.15] {
|
||||
print!(" {:>6.0}K |", sst);
|
||||
for sdt in [303.15, 308.15, 313.15, 318.15] {
|
||||
let m = sst_sdt.mass_flow_at(sst, sdt);
|
||||
print!(" {:>7.3} ", m);
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
// 4. Lois d'affinité (variation de vitesse)
|
||||
// ═══════════════════════════════════════════════════════════════
|
||||
println!("\n📐 4. Lois d'affinité (pompe/ventilateur à vitesse variable)\n");
|
||||
|
||||
let speed_ratios = [1.0, 0.8, 0.6, 0.5];
|
||||
|
||||
println!(" À 50% vitesse: Q₂=0.5*Q₁, H₂=0.25*H₁, P₂=0.125*P₁\n");
|
||||
println!(" {:>10} | {:>10} {:>10} {:>10}", "Vitesse", "Q ratio", "H ratio", "P ratio");
|
||||
println!(" {} | {} {} {}", "----------", "----------", "----------", "----------");
|
||||
|
||||
for &ratio in &speed_ratios {
|
||||
// AffinityLaws: Q₂=scale_flow(Q₁), H₂=scale_head(H₁), P₂=scale_power(P₁)
|
||||
let q_ratio = AffinityLaws::scale_flow(1.0, ratio);
|
||||
let h_ratio = AffinityLaws::scale_head(1.0, ratio);
|
||||
let p_ratio = AffinityLaws::scale_power(1.0, ratio);
|
||||
println!(" {:>8.0}% | {:>10.2} {:>10.2} {:>10.2}", ratio * 100.0, q_ratio, h_ratio, p_ratio);
|
||||
}
|
||||
|
||||
println!("\n✅ Exemple terminé !");
|
||||
Ok(())
|
||||
}
|
||||
428
demo/src/bin/thermal_coupling.rs
Normal file
428
demo/src/bin/thermal_coupling.rs
Normal file
@@ -0,0 +1,428 @@
|
||||
//! Demo Entropyk - Thermal Coupling Between Circuits
|
||||
//!
|
||||
//! This example demonstrates:
|
||||
//! - Multi-circuit system creation (2 circuits)
|
||||
//! - Component placement in circuits
|
||||
//! - Thermal coupling between circuits (heat exchanger)
|
||||
//! - Circular dependency detection
|
||||
//! - Heat transfer computation
|
||||
|
||||
use colored::Colorize;
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, JacobianBuilder, ResidualVector, SystemState,
|
||||
};
|
||||
use entropyk_core::{Temperature, ThermalConductance};
|
||||
use entropyk_solver::{
|
||||
compute_coupling_heat, coupling_groups, has_circular_dependencies, CircuitId, System,
|
||||
ThermalCoupling,
|
||||
};
|
||||
use std::fmt;
|
||||
|
||||
fn print_header(title: &str) {
|
||||
println!();
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
println!("{}", format!(" {}", title).cyan().bold());
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
}
|
||||
|
||||
fn print_section(title: &str) {
|
||||
println!();
|
||||
println!("{}", format!("▶ {}", title).yellow().bold());
|
||||
println!("{}", "─".repeat(40).yellow());
|
||||
}
|
||||
|
||||
struct SimpleComponent {
|
||||
name: String,
|
||||
n_eqs: usize,
|
||||
}
|
||||
|
||||
impl SimpleComponent {
|
||||
fn new(name: &str) -> Box<dyn Component> {
|
||||
Box::new(Self {
|
||||
name: name.to_string(),
|
||||
n_eqs: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SimpleComponent {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
for r in residuals.iter_mut().take(self.n_eqs) {
|
||||
*r = 0.0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
_jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n_eqs
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[entropyk_components::ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SimpleComponent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SimpleComponent")
|
||||
.field("name", &self.name)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"{}",
|
||||
"\n╔══════════════════════════════════════════════════════════╗".green()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"║ ENTROPYK - Thermal Coupling Demo (Story 3.4) ║"
|
||||
.green()
|
||||
.bold()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"╚══════════════════════════════════════════════════════════╝\n".green()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// PART 1: Basic Thermal Coupling
|
||||
// ========================================
|
||||
print_header("Part 1: Basic Thermal Coupling");
|
||||
|
||||
print_section("Creating ThermalCoupling struct");
|
||||
let coupling = ThermalCoupling::new(
|
||||
CircuitId(0), // Hot circuit (refrigerant)
|
||||
CircuitId(1), // Cold circuit (water/glycol)
|
||||
ThermalConductance::from_watts_per_kelvin(5000.0), // 5 kW/K UA value
|
||||
)
|
||||
.with_efficiency(0.95); // 95% heat exchanger efficiency
|
||||
|
||||
println!(" {} {:?}", "Coupling:".white(), coupling);
|
||||
println!(
|
||||
" {} {} W/K",
|
||||
"UA:".white(),
|
||||
coupling.ua.to_watts_per_kelvin()
|
||||
);
|
||||
println!(
|
||||
" {} {:.0}%",
|
||||
"Efficiency:".white(),
|
||||
coupling.efficiency * 100.0
|
||||
);
|
||||
|
||||
print_section("Computing heat transfer");
|
||||
let t_hot = Temperature::from_celsius(45.0); // Refrigerant condensing at 45°C
|
||||
let t_cold = Temperature::from_celsius(35.0); // Water entering at 35°C
|
||||
|
||||
let q = compute_coupling_heat(&coupling, t_hot, t_cold);
|
||||
|
||||
println!(
|
||||
" {} {:.1}°C ({:.1} K)",
|
||||
"T_hot:".white(),
|
||||
t_hot.to_celsius(),
|
||||
t_hot.to_kelvin()
|
||||
);
|
||||
println!(
|
||||
" {} {:.1}°C ({:.1} K)",
|
||||
"T_cold:".white(),
|
||||
t_cold.to_celsius(),
|
||||
t_cold.to_kelvin()
|
||||
);
|
||||
println!(
|
||||
" {} {:.1} K",
|
||||
"ΔT:".white(),
|
||||
t_hot.to_kelvin() - t_cold.to_kelvin()
|
||||
);
|
||||
println!();
|
||||
println!(
|
||||
" {} {:.1} W = {:.2} kW",
|
||||
"Heat transfer (Q):".green().bold(),
|
||||
q,
|
||||
q / 1000.0
|
||||
);
|
||||
println!(
|
||||
" {} Q > 0 means heat flows INTO cold circuit",
|
||||
"Sign convention:".white()
|
||||
);
|
||||
|
||||
// Energy conservation demonstration
|
||||
println!();
|
||||
println!("{}", " Energy Conservation:".cyan());
|
||||
let q_into_cold = q;
|
||||
let q_out_of_hot = -q;
|
||||
println!(
|
||||
" Q_cold = {:.2} kW (heat received)",
|
||||
q_into_cold / 1000.0
|
||||
);
|
||||
println!(
|
||||
" Q_hot = {:.2} kW (heat rejected)",
|
||||
q_out_of_hot / 1000.0
|
||||
);
|
||||
println!(" {} Q_cold + Q_hot = 0 ✓", "Check:".green());
|
||||
|
||||
// ========================================
|
||||
// PART 2: Multi-Circuit System
|
||||
// ========================================
|
||||
print_header("Part 2: Multi-Circuit System");
|
||||
|
||||
print_section("Creating 2-circuit heat pump system");
|
||||
let mut system = System::new();
|
||||
|
||||
// Circuit 0: Refrigerant circuit
|
||||
let comp = system
|
||||
.add_component_to_circuit(SimpleComponent::new("Compressor"), CircuitId(0))
|
||||
.unwrap();
|
||||
let cond = system
|
||||
.add_component_to_circuit(SimpleComponent::new("Condenser"), CircuitId(0))
|
||||
.unwrap();
|
||||
let valve = system
|
||||
.add_component_to_circuit(SimpleComponent::new("ExpansionValve"), CircuitId(0))
|
||||
.unwrap();
|
||||
let evap = system
|
||||
.add_component_to_circuit(SimpleComponent::new("Evaporator"), CircuitId(0))
|
||||
.unwrap();
|
||||
|
||||
// Circuit 1: Water/glycol circuit
|
||||
let pump = system
|
||||
.add_component_to_circuit(SimpleComponent::new("Pump"), CircuitId(1))
|
||||
.unwrap();
|
||||
let hx = system
|
||||
.add_component_to_circuit(SimpleComponent::new("HeatExchanger"), CircuitId(1))
|
||||
.unwrap();
|
||||
|
||||
println!(" Circuit 0 (Refrigerant):");
|
||||
println!(" - Compressor, Condenser, ExpansionValve, Evaporator");
|
||||
println!(" Circuit 1 (Water/Glycol):");
|
||||
println!(" - Pump, HeatExchanger");
|
||||
|
||||
// Connect refrigerant circuit (cycle)
|
||||
system.add_edge(comp, cond).unwrap();
|
||||
system.add_edge(cond, valve).unwrap();
|
||||
system.add_edge(valve, evap).unwrap();
|
||||
system.add_edge(evap, comp).unwrap();
|
||||
|
||||
// Connect water circuit (simple loop)
|
||||
system.add_edge(pump, hx).unwrap();
|
||||
system.add_edge(hx, pump).unwrap();
|
||||
|
||||
println!();
|
||||
println!(
|
||||
" {} {} circuits, {} components, {} flow edges",
|
||||
"System:".white(),
|
||||
system.circuit_count(),
|
||||
system.node_count(),
|
||||
system.edge_count()
|
||||
);
|
||||
|
||||
print_section("Adding thermal coupling between circuits");
|
||||
let thermal_coupling = ThermalCoupling::new(
|
||||
CircuitId(0), // Hot: refrigerant condenser
|
||||
CircuitId(1), // Cold: water circuit heat exchanger
|
||||
ThermalConductance::from_watts_per_kelvin(8000.0),
|
||||
);
|
||||
|
||||
match system.add_thermal_coupling(thermal_coupling.clone()) {
|
||||
Ok(idx) => println!(" {} Coupling added at index {}", "✓".green(), idx),
|
||||
Err(e) => println!(" {} Error: {:?}", "✗".red(), e),
|
||||
}
|
||||
|
||||
println!();
|
||||
println!(
|
||||
" {} {}",
|
||||
"Couplings:".white(),
|
||||
system.thermal_coupling_count()
|
||||
);
|
||||
for (i, c) in system.thermal_couplings().iter().enumerate() {
|
||||
println!(
|
||||
" [{}] Circuit {} → Circuit {} (UA = {} W/K)",
|
||||
i,
|
||||
c.hot_circuit.0,
|
||||
c.cold_circuit.0,
|
||||
c.ua.to_watts_per_kelvin()
|
||||
);
|
||||
}
|
||||
|
||||
// Finalize system
|
||||
match system.finalize() {
|
||||
Ok(()) => println!("\n {} System finalized successfully", "✓".green()),
|
||||
Err(e) => println!("\n {} Finalize error: {:?}", "✗".red(), e),
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// PART 3: Circular Dependency Detection
|
||||
// ========================================
|
||||
print_header("Part 3: Circular Dependency Detection");
|
||||
|
||||
print_section("Scenario A: Single coupling (no cycle)");
|
||||
let couplings_a = vec![ThermalCoupling::new(
|
||||
CircuitId(0),
|
||||
CircuitId(1),
|
||||
ThermalConductance::from_watts_per_kelvin(1000.0),
|
||||
)];
|
||||
let has_cycle_a = has_circular_dependencies(&couplings_a);
|
||||
println!(" Couplings: Circuit 0 → Circuit 1");
|
||||
println!(
|
||||
" {} {}",
|
||||
"Circular dependency:".white(),
|
||||
if has_cycle_a {
|
||||
"YES (solve simultaneously)".red()
|
||||
} else {
|
||||
"NO (solve sequentially)".green()
|
||||
}
|
||||
);
|
||||
|
||||
let groups_a = coupling_groups(&couplings_a);
|
||||
println!(" {} {:?}", "Coupling groups:".white(), groups_a);
|
||||
|
||||
print_section("Scenario B: Mutual coupling (cycle!)");
|
||||
let couplings_b = vec![
|
||||
ThermalCoupling::new(
|
||||
CircuitId(0),
|
||||
CircuitId(1),
|
||||
ThermalConductance::from_watts_per_kelvin(1000.0),
|
||||
),
|
||||
ThermalCoupling::new(
|
||||
CircuitId(1),
|
||||
CircuitId(0), // Back-coupling!
|
||||
ThermalConductance::from_watts_per_kelvin(500.0),
|
||||
),
|
||||
];
|
||||
let has_cycle_b = has_circular_dependencies(&couplings_b);
|
||||
println!(" Couplings:");
|
||||
println!(" Circuit 0 → Circuit 1");
|
||||
println!(" Circuit 1 → Circuit 0 (back-coupling!)");
|
||||
println!();
|
||||
println!(
|
||||
" {} {}",
|
||||
"Circular dependency:".white(),
|
||||
if has_cycle_b {
|
||||
"YES (solve simultaneously)".red()
|
||||
} else {
|
||||
"NO (solve sequentially)".green()
|
||||
}
|
||||
);
|
||||
|
||||
let groups_b = coupling_groups(&couplings_b);
|
||||
println!(" {} {:?}", "Coupling groups:".white(), groups_b);
|
||||
if groups_b.iter().any(|g| g.len() > 1) {
|
||||
println!(
|
||||
" {} Circuits in same group must be solved together",
|
||||
"→".yellow()
|
||||
);
|
||||
}
|
||||
|
||||
print_section("Scenario C: Chain + mutual (complex)");
|
||||
let couplings_c = vec![
|
||||
ThermalCoupling::new(
|
||||
CircuitId(0),
|
||||
CircuitId(1),
|
||||
ThermalConductance::from_watts_per_kelvin(1000.0),
|
||||
),
|
||||
ThermalCoupling::new(
|
||||
CircuitId(1),
|
||||
CircuitId(0),
|
||||
ThermalConductance::from_watts_per_kelvin(500.0),
|
||||
), // 0↔1 cycle
|
||||
ThermalCoupling::new(
|
||||
CircuitId(2),
|
||||
CircuitId(3),
|
||||
ThermalConductance::from_watts_per_kelvin(800.0),
|
||||
), // independent
|
||||
];
|
||||
let has_cycle_c = has_circular_dependencies(&couplings_c);
|
||||
println!(" Couplings:");
|
||||
println!(" Circuit 0 ↔ Circuit 1 (mutual)");
|
||||
println!(" Circuit 2 → Circuit 3 (independent)");
|
||||
println!();
|
||||
println!(
|
||||
" {} {}",
|
||||
"Circular dependency:".white(),
|
||||
if has_cycle_c {
|
||||
"YES".red()
|
||||
} else {
|
||||
"NO".green()
|
||||
}
|
||||
);
|
||||
|
||||
let groups_c = coupling_groups(&couplings_c);
|
||||
println!(" {} {:?}", "Coupling groups:".white(), groups_c);
|
||||
println!(
|
||||
" {} [0,1] together, [2] independent, [3] independent",
|
||||
"→".yellow()
|
||||
);
|
||||
|
||||
// ========================================
|
||||
// PART 4: Error Handling
|
||||
// ========================================
|
||||
print_header("Part 4: Error Handling");
|
||||
|
||||
print_section("Invalid circuit validation");
|
||||
let mut sys_test = System::new();
|
||||
sys_test
|
||||
.add_component_to_circuit(SimpleComponent::new("A"), CircuitId(0))
|
||||
.unwrap();
|
||||
// Circuit 1 has NO components!
|
||||
|
||||
let invalid_coupling = ThermalCoupling::new(
|
||||
CircuitId(0),
|
||||
CircuitId(1), // This circuit doesn't exist!
|
||||
ThermalConductance::from_watts_per_kelvin(1000.0),
|
||||
);
|
||||
|
||||
match sys_test.add_thermal_coupling(invalid_coupling) {
|
||||
Ok(_) => println!(" {} Unexpected success!", "✗".red()),
|
||||
Err(e) => {
|
||||
println!(" {} Correctly rejected invalid coupling", "✓".green());
|
||||
println!(" {} {}", "Error:".white(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Summary
|
||||
// ========================================
|
||||
print_header("Summary");
|
||||
|
||||
println!();
|
||||
println!(
|
||||
" {} ThermalCoupling struct with hot/cold circuits + UA + efficiency",
|
||||
"✓".green()
|
||||
);
|
||||
println!(
|
||||
" {} compute_coupling_heat() with sign convention (Q > 0 = heat into cold)",
|
||||
"✓".green()
|
||||
);
|
||||
println!(
|
||||
" {} has_circular_dependencies() via petgraph cycle detection",
|
||||
"✓".green()
|
||||
);
|
||||
println!(
|
||||
" {} coupling_groups() via Kosaraju SCC for solver strategy",
|
||||
"✓".green()
|
||||
);
|
||||
println!(
|
||||
" {} System.add_thermal_coupling() with circuit validation",
|
||||
"✓".green()
|
||||
);
|
||||
println!(" {} InvalidCircuitForCoupling error handling", "✓".green());
|
||||
|
||||
println!();
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
println!(
|
||||
"{}",
|
||||
" Demo complete! Run 'cargo run --bin thermal-coupling' again.".cyan()
|
||||
);
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
}
|
||||
313
demo/src/bin/ui_server.rs
Normal file
313
demo/src/bin/ui_server.rs
Normal file
@@ -0,0 +1,313 @@
|
||||
//! Serveur UI Entropyk - Utilise les composants Rust réels pour les calculs.
|
||||
//!
|
||||
//! Lance l'UI et une API qui exécute les calculs avec les vrais composants.
|
||||
//!
|
||||
//! cargo run -p entropyk-demo --bin ui-server
|
||||
|
||||
use axum::{
|
||||
extract::Json,
|
||||
routing::post,
|
||||
Router,
|
||||
};
|
||||
use entropyk_components::compressor::SstSdtCoefficients;
|
||||
use entropyk_components::pipe::{friction_factor, Pipe, PipeGeometry};
|
||||
use entropyk_components::pump::{Pump, PumpCurves};
|
||||
use entropyk_components::port::{FluidId, Port};
|
||||
use entropyk_core::{Enthalpy, Pressure};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct ComponentConfig {
|
||||
id: String,
|
||||
#[serde(rename = "type")]
|
||||
comp_type: String,
|
||||
label: String,
|
||||
config: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct CalculateRequest {
|
||||
components: Vec<ComponentConfig>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct ComponentResult {
|
||||
id: String,
|
||||
label: String,
|
||||
#[serde(rename = "type")]
|
||||
comp_type: String,
|
||||
results: serde_json::Value,
|
||||
error: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct CalculateResponse {
|
||||
results: Vec<ComponentResult>,
|
||||
}
|
||||
|
||||
fn parse_coeffs(s: &str) -> Vec<f64> {
|
||||
s.split(',')
|
||||
.filter_map(|x| x.trim().parse::<f64>().ok())
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn calculate(
|
||||
axum::extract::Json(req): axum::extract::Json<CalculateRequest>,
|
||||
) -> axum::Json<CalculateResponse> {
|
||||
let mut results = Vec::new();
|
||||
|
||||
for comp in req.components {
|
||||
let result = match comp.comp_type.as_str() {
|
||||
"pump" => calc_pump(&comp),
|
||||
"compressor" => calc_compressor(&comp),
|
||||
"pipe" => calc_pipe(&comp),
|
||||
"valve" => calc_valve(&comp),
|
||||
_ => ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: comp.comp_type.clone(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some("Type inconnu".to_string()),
|
||||
},
|
||||
};
|
||||
results.push(result);
|
||||
}
|
||||
|
||||
Json(CalculateResponse { results })
|
||||
}
|
||||
|
||||
fn calc_pump(comp: &ComponentConfig) -> ComponentResult {
|
||||
let config = &comp.config;
|
||||
let head_coeffs = config
|
||||
.get("head_coeffs")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("30,-10,-50");
|
||||
let eff_coeffs = config
|
||||
.get("eff_coeffs")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("0.5,0.3,-0.5");
|
||||
let density = config
|
||||
.get("density")
|
||||
.and_then(|v| v.as_f64())
|
||||
.unwrap_or(1000.0);
|
||||
|
||||
let h = parse_coeffs(head_coeffs);
|
||||
let e = parse_coeffs(eff_coeffs);
|
||||
|
||||
if h.len() < 3 || e.len() < 3 {
|
||||
return ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pump".to_string(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some("Coefficients insuffisants (min 3 pour H et η)".to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
match PumpCurves::quadratic(h[0], h[1], h[2], e[0], e[1], e[2]) {
|
||||
Ok(curves) => {
|
||||
let inlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(100_000.0),
|
||||
);
|
||||
let outlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(1.0),
|
||||
Enthalpy::from_joules_per_kg(100_000.0),
|
||||
);
|
||||
|
||||
match Pump::new(curves, inlet, outlet, density) {
|
||||
Ok(pump) => {
|
||||
let points: Vec<_> = [0.0, 0.05, 0.1, 0.15, 0.2]
|
||||
.iter()
|
||||
.map(|&q| {
|
||||
serde_json::json!({
|
||||
"Q_m3_s": q,
|
||||
"H_m": pump.curves().head_at_flow(q),
|
||||
"efficiency": pump.curves().efficiency_at_flow(q)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pump".to_string(),
|
||||
results: serde_json::json!({
|
||||
"curve_points": points,
|
||||
"density_kg_m3": density
|
||||
}),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
Err(e) => ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pump".to_string(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some(e.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(e) => ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pump".to_string(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some(e.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_compressor(comp: &ComponentConfig) -> ComponentResult {
|
||||
let config = &comp.config;
|
||||
let mass_s = config
|
||||
.get("mass_coeffs")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("0.05,0.001,0.0005,0.00001");
|
||||
let power_s = config
|
||||
.get("power_coeffs")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or("1000,50,30,0.5");
|
||||
|
||||
let m = parse_coeffs(mass_s);
|
||||
let p = parse_coeffs(power_s);
|
||||
|
||||
if m.len() < 4 || p.len() < 4 {
|
||||
return ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "compressor".to_string(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some("Coefficients SST/SDT: 4 valeurs (a00,a10,a01,a11)".to_string()),
|
||||
};
|
||||
}
|
||||
|
||||
let sst_sdt = SstSdtCoefficients::bilinear(
|
||||
m[0], m[1], m[2], m[3],
|
||||
p[0], p[1], p[2], p[3],
|
||||
);
|
||||
|
||||
let sst = 268.15;
|
||||
let sdt = 313.15;
|
||||
let mass_flow = sst_sdt.mass_flow_at(sst, sdt);
|
||||
let power = sst_sdt.power_at(sst, sdt);
|
||||
|
||||
ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "compressor".to_string(),
|
||||
results: serde_json::json!({
|
||||
"SST_K": sst,
|
||||
"SDT_K": sdt,
|
||||
"mass_flow_kg_s": mass_flow,
|
||||
"power_W": power
|
||||
}),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_pipe(comp: &ComponentConfig) -> ComponentResult {
|
||||
let config = &comp.config;
|
||||
let length = config.get("length").and_then(|v| v.as_f64()).unwrap_or(10.0);
|
||||
let diameter = config.get("diameter").and_then(|v| v.as_f64()).unwrap_or(0.022);
|
||||
let rough = config.get("roughness").and_then(|v| v.as_f64()).unwrap_or(1.5e-6);
|
||||
|
||||
match PipeGeometry::new(length, diameter, rough) {
|
||||
Ok(geometry) => {
|
||||
let inlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(2.0),
|
||||
Enthalpy::from_joules_per_kg(84_000.0),
|
||||
);
|
||||
let outlet = Port::new(
|
||||
FluidId::new("Water"),
|
||||
Pressure::from_bar(2.0),
|
||||
Enthalpy::from_joules_per_kg(84_000.0),
|
||||
);
|
||||
|
||||
match Pipe::new(geometry, inlet, outlet, 998.0, 0.001) {
|
||||
Ok(_pipe) => {
|
||||
let flow = 0.01;
|
||||
let area = geometry.area();
|
||||
let velocity = flow / area;
|
||||
let re = velocity * diameter * 998.0 / 0.001;
|
||||
let rel_rough = rough / diameter;
|
||||
let f = friction_factor::haaland(rel_rough, re);
|
||||
let dp = f * (length / diameter) * (998.0 * velocity * velocity / 2.0);
|
||||
|
||||
ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pipe".to_string(),
|
||||
results: serde_json::json!({
|
||||
"length_m": length,
|
||||
"diameter_m": diameter,
|
||||
"pressure_drop_Pa_at_0.01_m3_s": dp,
|
||||
"reynolds_at_0.01_m3_s": re
|
||||
}),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
Err(e) => ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pipe".to_string(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some(e.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
Err(e) => ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "pipe".to_string(),
|
||||
results: serde_json::json!({}),
|
||||
error: Some(e.to_string()),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_valve(comp: &ComponentConfig) -> ComponentResult {
|
||||
let config = &comp.config;
|
||||
let opening = config.get("opening").and_then(|v| v.as_f64()).unwrap_or(1.0);
|
||||
|
||||
ComponentResult {
|
||||
id: comp.id.clone(),
|
||||
label: comp.label.clone(),
|
||||
comp_type: "valve".to_string(),
|
||||
results: serde_json::json!({
|
||||
"opening": opening,
|
||||
"note": "Détendeur isenthalpique - calcul complet avec solveur"
|
||||
}),
|
||||
error: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let port = std::env::var("PORT").unwrap_or_else(|_| "3030".to_string());
|
||||
let addr = format!("0.0.0.0:{}", port);
|
||||
|
||||
let ui_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("../ui");
|
||||
println!("Entropyk UI - http://localhost:{}", port);
|
||||
println!("Dossier UI: {}", ui_path.display());
|
||||
|
||||
let app = Router::new()
|
||||
.route("/api/calculate", post(calculate))
|
||||
.nest_service("/", ServeDir::new(ui_path));
|
||||
|
||||
let listener = match tokio::net::TcpListener::bind(&addr).await {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
eprintln!("Erreur: impossible de lier le port {} ({})", port, e);
|
||||
eprintln!(" → Port déjà utilisé? Essayez: PORT=3031 cargo run -p entropyk-demo --bin ui-server");
|
||||
eprintln!(" → Ou tuez le processus: lsof -ti:{} | xargs kill", port);
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
axum::serve(listener, app).await.unwrap();
|
||||
}
|
||||
107
demo/src/main.rs
Normal file
107
demo/src/main.rs
Normal file
@@ -0,0 +1,107 @@
|
||||
//! Demo Entropyk - Test du State Machine (ON/OFF/BYPASS)
|
||||
//!
|
||||
//! Ce fichier montre comment utiliser OperationalState et CircuitId
|
||||
|
||||
use colored::Colorize;
|
||||
use entropyk_components::state_machine::{CircuitId, OperationalState};
|
||||
|
||||
fn print_header(title: &str) {
|
||||
println!();
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
println!("{}", format!(" {}", title).cyan().bold());
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!(
|
||||
"{}",
|
||||
"\n╔══════════════════════════════════════════════════════════╗".green()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"║ DEMO ENTROPYK - State Machine (ON/OFF/BYPASS) ║"
|
||||
.green()
|
||||
.bold()
|
||||
);
|
||||
println!(
|
||||
"{}",
|
||||
"╚══════════════════════════════════════════════════════════╝\n".green()
|
||||
);
|
||||
|
||||
print_header("États Opérationnels");
|
||||
|
||||
println!();
|
||||
println!("Les composants peuvent être dans 3 états:");
|
||||
println!();
|
||||
|
||||
for state in [
|
||||
OperationalState::On,
|
||||
OperationalState::Off,
|
||||
OperationalState::Bypass,
|
||||
] {
|
||||
println!(" {:?}:", state);
|
||||
println!(" - Actif: {}", state.is_active());
|
||||
println!(
|
||||
" - Multiplicateur débit: {:.1}",
|
||||
state.mass_flow_multiplier()
|
||||
);
|
||||
|
||||
match state {
|
||||
OperationalState::On => {
|
||||
println!(" → Composant fonctionne normalement");
|
||||
}
|
||||
OperationalState::Off => {
|
||||
println!(" → Composant arrêté, débit = 0");
|
||||
}
|
||||
OperationalState::Bypass => {
|
||||
println!(" → Composant court-circuité (P_in = P_out, h_in = h_out)");
|
||||
}
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
||||
print_header("CircuitId (Multi-Circuit)");
|
||||
|
||||
println!();
|
||||
println!("Un système peut avoir jusqu'à 5 circuits indépendants:");
|
||||
println!();
|
||||
|
||||
let circuits = vec![
|
||||
CircuitId::new("primary"),
|
||||
CircuitId::new("secondary"),
|
||||
CircuitId::default(),
|
||||
];
|
||||
|
||||
for circuit in &circuits {
|
||||
println!(" Circuit: {} (as_str: \"{}\")", circuit, circuit.as_str());
|
||||
}
|
||||
|
||||
println!();
|
||||
println!(" Utilisation typique:");
|
||||
println!(" - Circuit 0: Boucle réfrigérant principale");
|
||||
println!(" - Circuit 1: Circuit eau/glycol");
|
||||
println!(" - Circuit 2: Circuit secondaire (optionnel)");
|
||||
|
||||
print_header("Exemples d'utilisation");
|
||||
|
||||
println!();
|
||||
println!(" // Créer un système multi-circuit");
|
||||
println!(" let mut system = System::new();");
|
||||
println!(" system.add_component_to_circuit(compressor, CircuitId(0));");
|
||||
println!(" system.add_component_to_circuit(pump, CircuitId(1));");
|
||||
println!();
|
||||
println!(" // Couplage thermique entre circuits");
|
||||
println!(" let coupling = ThermalCoupling::new(");
|
||||
println!(" CircuitId(0), // chaud");
|
||||
println!(" CircuitId(1), // froid");
|
||||
println!(" ThermalConductance::from_watts_per_kelvin(5000.0),");
|
||||
println!(" );");
|
||||
|
||||
println!();
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
println!(
|
||||
"{}",
|
||||
" Voir 'cargo run --bin thermal-coupling' pour la démo complète".cyan()
|
||||
);
|
||||
println!("{}", "═".repeat(60).cyan());
|
||||
}
|
||||
Reference in New Issue
Block a user