feat(components): add ThermoState generators and Eurovent backend demo

This commit is contained in:
Sepehr
2026-02-20 22:01:38 +01:00
parent 375d288950
commit 4a40fddfe3
271 changed files with 28614 additions and 447 deletions

View 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());
}