//! 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, StateSlice}; 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 { #[allow(clippy::new_ret_no_self)] fn new(name: &str) -> Box { Box::new(Self { name: name.to_string(), n_eqs: 0, }) } } impl Component for SimpleComponent { fn compute_residuals( &self, _state: &StateSlice, residuals: &mut ResidualVector, ) -> Result<(), ComponentError> { for r in residuals.iter_mut().take(self.n_eqs) { *r = 0.0; } Ok(()) } fn jacobian_entries( &self, _state: &StateSlice, _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()); }