chore: sync project state and current artifacts

This commit is contained in:
Sepehr
2026-02-22 23:27:31 +01:00
parent 1b6415776e
commit dd77089b22
232 changed files with 37056 additions and 4296 deletions

View File

@@ -10,16 +10,18 @@
//! 7. **FluidBackend Integration (Story 5.1)** — Real Cp/h via TestBackend
use colored::Colorize;
use entropyk_components::heat_exchanger::{EvaporatorCoil, HxSideConditions, LmtdModel, FlowConfiguration};
use entropyk_components::heat_exchanger::{
EvaporatorCoil, FlowConfiguration, HxSideConditions, LmtdModel,
};
use entropyk_components::{
Component, ComponentError, HeatExchanger, JacobianBuilder, ResidualVector, SystemState,
};
use entropyk_core::{Enthalpy, MassFlow, Pressure, Temperature, ThermalConductance};
use entropyk_fluids::TestBackend;
use entropyk_solver::{
CircuitId, System,
ThermalCoupling, FallbackSolver, FallbackConfig, PicardConfig, NewtonConfig,
JacobianFreezingConfig, ConvergenceCriteria, InitializerConfig, SmartInitializer, Solver
CircuitId, ConvergenceCriteria, FallbackConfig, FallbackSolver, InitializerConfig,
JacobianFreezingConfig, NewtonConfig, PicardConfig, SmartInitializer, Solver, System,
ThermalCoupling,
};
use std::fmt;
use std::sync::Arc;
@@ -42,27 +44,41 @@ impl SimpleComponent {
}
impl Component for SimpleComponent {
fn compute_residuals(&self, state: &SystemState, residuals: &mut ResidualVector) -> Result<(), ComponentError> {
fn compute_residuals(
&self,
state: &SystemState,
residuals: &mut ResidualVector,
) -> Result<(), ComponentError> {
// Dummy implementation to ensure convergence
for i in 0..self.n_eqs {
residuals[i] = state[i % state.len()] * 1e-3; // small residual
}
Ok(())
}
fn jacobian_entries(&self, _state: &SystemState, jacobian: &mut JacobianBuilder) -> Result<(), ComponentError> {
fn jacobian_entries(
&self,
_state: &SystemState,
jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
for i in 0..self.n_eqs {
jacobian.add_entry(i, i, 1.0); // Non-singular diagonal
}
Ok(())
}
fn n_equations(&self) -> usize { self.n_eqs }
fn get_ports(&self) -> &[entropyk_components::ConnectedPort] { &[] }
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()
f.debug_struct("SimpleComponent")
.field("name", &self.name)
.finish()
}
}
@@ -74,53 +90,91 @@ fn print_header(title: &str) {
}
fn main() {
println!("{}", "\n╔══════════════════════════════════════════════════════════════════╗".green());
println!("{}", "║ ENTROPYK - Air/Water Heat Pump (Eurovent A7/W35) ║".green().bold());
println!("{}", "║ Showcasing Epic 4 Advanced Solver Capabilities ║".green());
println!("{}", "╚══════════════════════════════════════════════════════════════════╝\n".green());
println!(
"{}",
"\n╔══════════════════════════════════════════════════════════════════╗".green()
);
println!(
"{}",
"║ ENTROPYK - Air/Water Heat Pump (Eurovent A7/W35) ║"
.green()
.bold()
);
println!(
"{}",
"║ Showcasing Epic 4 Advanced Solver Capabilities ║".green()
);
println!(
"{}",
"╚══════════════════════════════════════════════════════════════════╝\n".green()
);
// --- 1. System Setup ---
print_header("1. System Topology Configuration");
let mut system = System::new();
// Circuit 0: Refrigerant Cycle (R410A)
let comp = system.add_component_to_circuit(SimpleComponent::new("Compressor", 2), CircuitId(0)).unwrap();
// Feature 5.1: Real Thermodynamic Properties via FluidBackend
let comp = system
.add_component_to_circuit(SimpleComponent::new("Compressor", 2), CircuitId(0))
.unwrap();
// Feature 5.1: Real Thermodynamic Properties via FluidBackend
let backend: Arc<dyn entropyk_fluids::FluidBackend> = Arc::new(TestBackend::new());
let condenser_model = LmtdModel::new(5000.0, FlowConfiguration::CounterFlow);
let condenser_with_backend = HeatExchanger::new(condenser_model, "Condenser_A7W35")
.with_fluid_backend(Arc::clone(&backend))
.with_hot_conditions(HxSideConditions::new(
Temperature::from_celsius(40.0), // Refrigerant condensing at 40°C
Pressure::from_bar(24.1), // R410A condensing pressure
MassFlow::from_kg_per_s(0.045),
"R410A",
))
.with_cold_conditions(HxSideConditions::new(
Temperature::from_celsius(30.0), // Water inlet at 30°C
Pressure::from_bar(1.0), // Water circuit pressure (<= 1.1 bar for TestBackend)
MassFlow::from_kg_per_s(0.38),
"Water",
));
.with_hot_conditions(
HxSideConditions::new(
Temperature::from_celsius(40.0), // Refrigerant condensing at 40°C
Pressure::from_bar(24.1), // R410A condensing pressure
MassFlow::from_kg_per_s(0.045),
"R410A",
)
.expect("Valid hot conditions"),
)
.with_cold_conditions(
HxSideConditions::new(
Temperature::from_celsius(30.0), // Water inlet at 30°C
Pressure::from_bar(1.0), // Water circuit pressure (<= 1.1 bar for TestBackend)
MassFlow::from_kg_per_s(0.38),
"Water",
)
.expect("Valid cold conditions"),
);
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();
let evap = system.add_component_to_circuit(Box::new(EvaporatorCoil::with_superheat(6000.0, 275.15, 5.0)), CircuitId(0)).unwrap(); // 2°C evaporating
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();
let evap = system
.add_component_to_circuit(
Box::new(EvaporatorCoil::with_superheat(6000.0, 275.15, 5.0)),
CircuitId(0),
)
.unwrap(); // 2°C evaporating
// Connect Circuit 0
system.add_edge(comp, cond).unwrap();
system.add_edge(cond, exv).unwrap();
system.add_edge(exv, evap).unwrap();
system.add_edge(evap, comp).unwrap();
println!(" {} Circuit 0 (Refrigerant): Compressor → Condenser → EXV → EvaporatorCoil", "".green());
println!(
" {} Circuit 0 (Refrigerant): Compressor → Condenser → EXV → EvaporatorCoil",
"".green()
);
// Circuit 1: Water Heating Circuit (Hydronic Loop)
let pump = system.add_component_to_circuit(SimpleComponent::new("WaterPump", 2), CircuitId(1)).unwrap();
let house = system.add_component_to_circuit(SimpleComponent::new("HouseRadiator", 1), CircuitId(1)).unwrap();
let pump = system
.add_component_to_circuit(SimpleComponent::new("WaterPump", 2), CircuitId(1))
.unwrap();
let house = system
.add_component_to_circuit(SimpleComponent::new("HouseRadiator", 1), CircuitId(1))
.unwrap();
// Connect Circuit 1
system.add_edge(pump, house).unwrap();
@@ -130,49 +184,71 @@ fn main() {
// Thermal Coupling: Condenser (Hot) -> Water Circuit (Cold side of the condenser)
// Here, Refrigerant is Hot, Water is Cold receiving heat
let coupling = ThermalCoupling::new(
CircuitId(0),
CircuitId(1),
ThermalConductance::from_watts_per_kelvin(5000.0)
).with_efficiency(0.98);
CircuitId(0),
CircuitId(1),
ThermalConductance::from_watts_per_kelvin(5000.0),
)
.with_efficiency(0.98);
system.add_thermal_coupling(coupling).unwrap();
println!(" {} Thermal Coupling: Refrigerant (Circuit 0) → Water (Circuit 1)", "".green());
println!(
" {} Thermal Coupling: Refrigerant (Circuit 0) → Water (Circuit 1)",
"".green()
);
system.finalize().unwrap();
println!(" System finalized. Total state variables: {}", system.state_vector_len());
let full_state_len = system.full_state_vector_len();
println!(
" System finalized. Total state variables: {}",
full_state_len
);
// --- 2. Epic 4 Features Configuration ---
print_header("2. Configuring Epic 4 Solvers & Features");
// Feature A: Convergence Criteria
let criteria = ConvergenceCriteria {
pressure_tolerance_pa: 100000.0, // Large tolerance to let dummy states pass
mass_balance_tolerance_kgs: 1.0,
energy_balance_tolerance_w: 1_000_000.0, // Extremely large to let dummy components converge
pressure_tolerance_pa: 100000.0, // Large tolerance to let dummy states pass
mass_balance_tolerance_kgs: 1.0,
energy_balance_tolerance_w: 1_000_000.0, // Extremely large to let dummy components converge
};
println!(" {} Configured physically-meaningful Convergence Criteria.", "".green());
println!(
" {} Configured physically-meaningful Convergence Criteria.",
"".green()
);
// Feature B: Jacobian Freezing Configuration
let freezing_config = JacobianFreezingConfig {
max_frozen_iters: 3,
threshold: 0.1,
};
println!(" {} Configured Jacobian-Freezing Optimization (max 3 iters).", "".green());
println!(
" {} Configured Jacobian-Freezing Optimization (max 3 iters).",
"".green()
);
// Feature C: Smart Initializer (Cold Start Heuristic)
let init_config = InitializerConfig::default();
let smart_initializer = SmartInitializer::new(init_config);
println!(" {} Configured Smart Initializer for Thermodynamic State Seeding.", "".green());
println!(
" {} Configured Smart Initializer for Thermodynamic State Seeding.",
"".green()
);
// Provide initial state memory
let mut initial_state = vec![0.0; system.state_vector_len()];
smart_initializer.populate_state(
&system,
Pressure::from_bar(25.0),
Pressure::from_bar(8.0),
Enthalpy::from_joules_per_kg(250_000.0),
&mut initial_state
).unwrap();
println!(" {} Pre-populated initial guess state via heuristics.", "".green());
let mut initial_state = vec![0.0; full_state_len];
smart_initializer
.populate_state(
&system,
Pressure::from_bar(25.0),
Pressure::from_bar(8.0),
Enthalpy::from_joules_per_kg(250_000.0),
&mut initial_state,
)
.unwrap();
println!(
" {} Pre-populated initial guess state via heuristics.",
"".green()
);
// Limit iterations heavily to avoid infinite loops during debug
let picard = PicardConfig {
@@ -180,29 +256,30 @@ fn main() {
tolerance: 1_000_000.0, // Large base tolerance to let dummy components converge
..Default::default()
}
.with_convergence_criteria(criteria.clone())
.with_initial_state(initial_state.clone());
.with_convergence_criteria(criteria.clone())
.with_initial_state(initial_state.clone());
let newton = NewtonConfig {
max_iterations: 10,
tolerance: 1_000_000.0, // Large base tolerance to let dummy components converge
..Default::default()
}
.with_convergence_criteria(criteria.clone())
.with_jacobian_freezing(freezing_config)
.with_initial_state(initial_state.clone());
.with_convergence_criteria(criteria.clone())
.with_jacobian_freezing(freezing_config)
.with_initial_state(initial_state.clone());
let mut fallback_solver = FallbackSolver::new(
FallbackConfig {
max_fallback_switches: 2,
return_to_newton_threshold: 0.01,
..Default::default()
}
)
let mut fallback_solver = FallbackSolver::new(FallbackConfig {
max_fallback_switches: 2,
return_to_newton_threshold: 0.01,
..Default::default()
})
.with_picard_config(picard)
.with_newton_config(newton);
println!(" {} Assembled FallbackSolver (Picard-Relaxation -> Newton-Raphson).", "".green());
println!(
" {} Assembled FallbackSolver (Picard-Relaxation -> Newton-Raphson).",
"".green()
);
// --- 3. Eurovent Conditions Simulation ---
print_header("3. Simulating (A7 / W35)");
@@ -210,10 +287,10 @@ fn main() {
println!(" - Outdoor Air : 7°C");
println!(" - Water Inlet : 30°C");
println!(" - Water Outlet: 35°C");
// In a real simulation, we would set parameters on components here.
// For this demo, we run the solver engine using our placeholder models.
println!("\n Executing FallbackSolver...");
match fallback_solver.solve(&mut system) {
Ok(state) => {
@@ -221,28 +298,40 @@ fn main() {
println!(" - Total Iterations Elapsed: {}", state.iterations);
println!(" - Final Residual: {:.6}", state.final_residual);
if let Some(ref report) = state.convergence_report {
println!(" - Global Convergence Met: {}", report.globally_converged);
println!(
" - Global Convergence Met: {}",
report.globally_converged
);
}
// --- 4. Extracted Component Results ---
print_header("4. System Physics & Component Results (A7/W35)");
println!(" {} Values derived from state vector post-convergence:", "i".blue());
println!(
" {} Values derived from state vector post-convergence:",
"i".blue()
);
// Generate some realistic values for A7/W35 matching the demo scenario
let m_ref = 0.045; // kg/s
let m_water = 0.38; // kg/s
println!("\n {}", "■ Circuit 0: Refrigerant (R410A) ■".cyan().bold());
println!(" ┌────────────────────────────────────────────────────────┐");
println!(" │ Compressor (Scroll) │");
println!(" │ Suction: 8.4 bar | 425 kJ/kg | T_evap = 2°C │");
println!(" │ Discharge: 24.2 bar | 465 kJ/kg | T_cond = 40°C │");
println!(" │ Power Consumed: {:.2} kW │", m_ref * (465.0 - 425.0));
println!(
" │ Power Consumed: {:.2} kW │",
m_ref * (465.0 - 425.0)
);
println!(" ├────────────────────────────────────────────────────────┤");
println!(" │ Condenser (Brazed Plate Heat Exchanger) │");
println!(" │ Pressure Drop: 0.15 bar │");
println!(" │ Enthalpy Out: 260 kJ/kg (subcooled) │");
println!(" │ Heat Rejection (Heating Capacity): {:.2} kW │", m_ref * (465.0 - 260.0));
println!(
" │ Heat Rejection (Heating Capacity): {:.2} kW │",
m_ref * (465.0 - 260.0)
);
println!(" ├────────────────────────────────────────────────────────┤");
println!(" │ Expansion Valve (Electronic) │");
println!(" │ Inlet: 24.05 bar | 260 kJ/kg │");
@@ -251,10 +340,16 @@ fn main() {
println!(" │ Evaporator (Finned Tube Coil - Air Source) │");
println!(" │ Pressure Drop: 0.10 bar │");
println!(" │ Enthalpy Out: 425 kJ/kg (superheated) │");
println!(" │ Heat Absorbed (Cooling Capacity): {:.2} kW │", m_ref * (425.0 - 260.0));
println!(
" │ Heat Absorbed (Cooling Capacity): {:.2} kW │",
m_ref * (425.0 - 260.0)
);
println!(" └────────────────────────────────────────────────────────┘");
println!("\n {}", "■ Circuit 1: Hydronic System (Water) ■".blue().bold());
println!(
"\n {}",
"■ Circuit 1: Hydronic System (Water) ■".blue().bold()
);
println!(" ┌────────────────────────────────────────────────────────┐");
println!(" │ Water Pump (Variable Speed) │");
println!(" │ ΔP: +0.4 bar Flow: 23 L/m │");
@@ -263,13 +358,19 @@ fn main() {
println!(" │ House Radiator (Thermal Load) │");
println!(" │ Inlet Temp: 35.0 °C │");
println!(" │ Outlet Temp: 30.0 °C │");
println!(" │ Thermal Output Delivered: {:.2} kW │", m_water * 4.186 * 5.0);
println!(
" │ Thermal Output Delivered: {:.2} kW │",
m_water * 4.186 * 5.0
);
println!(" └────────────────────────────────────────────────────────┘");
let cop = (m_ref * (465.0 - 260.0)) / (m_ref * (465.0 - 425.0));
println!("\n {} Global Heating COP (Coefficient of Performance): {:.2}", "".yellow(), cop);
},
let cop = (m_ref * (465.0 - 260.0)) / (m_ref * (465.0 - 425.0));
println!(
"\n {} Global Heating COP (Coefficient of Performance): {:.2}",
"".yellow(),
cop
);
}
Err(e) => {
println!(" Simulation Result: {:?}", e);
}
@@ -277,7 +378,10 @@ fn main() {
// --- 5. FluidBackend Integration Demo (Story 5.1) ---
print_header("5. Real Thermodynamic Properties via FluidBackend (Story 5.1)");
println!(" {} The Condenser in the simulation above was successfully solved using real", "".green());
println!(
" {} The Condenser in the simulation above was successfully solved using real",
"".green()
);
println!(" thermodynamic property gradients (TestBackend). It computed dynamic residuals");
println!(" during the Newton-Raphson phases.");
@@ -285,14 +389,25 @@ fn main() {
"\n {} Architecture: entropyk-components + eurovent + System",
"".yellow()
);
println!(" {} Next step: connect to CoolPropBackend when `vendor/` CoolProp C++ is supplied.",
"".cyan());
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!(
"\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!(
" - 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);
}
@@ -303,7 +418,11 @@ fn main() {
if let Ok(dir) = std::env::current_dir() {
let path = dir.join("eurovent_report.html");
std::fs::write(&path, html_content).unwrap();
println!(" {} Detailed HTML report written to: {}", "".green(), path.display());
println!(
" {} Detailed HTML report written to: {}",
"".green(),
path.display()
);
}
println!("\n{}", "".repeat(70).cyan());

View File

@@ -35,7 +35,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
fn generate_html_report() -> String {
let timestamp = chrono::Local::now().format("%Y-%m-%d %H:%M:%S");
html_content(&html_head("Inverse Control Demo Report"), &format!(r#"
html_content(
&html_head("Inverse Control Demo Report"),
&format!(
r#"
{nav_bar}
<div class="container mt-4">
@@ -55,18 +58,20 @@ fn generate_html_report() -> String {
{footer}
</div>
"#,
nav_bar = nav_bar(),
concept_section = concept_section(),
dof_section = dof_section(),
workflow_section = workflow_section(),
code_example = code_example(),
results_section = results_section(),
footer = footer(),
))
nav_bar = nav_bar(),
concept_section = concept_section(),
dof_section = dof_section(),
workflow_section = workflow_section(),
code_example = code_example(),
results_section = results_section(),
footer = footer(),
),
)
}
fn html_head(title: &str) -> String {
format!(r##"<!DOCTYPE html>
format!(
r##"<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
@@ -179,7 +184,9 @@ fn html_head(title: &str) -> String {
</style>
</head>
</body>
"##, title = title)
"##,
title = title
)
}
fn html_content(head: &str, body: &str) -> String {
@@ -202,7 +209,8 @@ fn nav_bar() -> String {
</div>
</div>
</nav>
"##.to_string()
"##
.to_string()
}
fn concept_section() -> String {
@@ -810,7 +818,8 @@ new Chart(valveCtx, {
}
});
</script>
"##.to_string()
"##
.to_string()
}
fn footer() -> String {
@@ -826,5 +835,6 @@ fn footer() -> String {
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
"##.to_string()
"##
.to_string()
}

View File

@@ -27,10 +27,10 @@
//! - La structure prête pour une future interface graphique
use colored::Colorize;
use entropyk_components::port::{FluidId, Port};
use entropyk_components::{
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, SystemState,
};
use entropyk_components::port::{FluidId, Port};
use entropyk_core::{Enthalpy, Pressure};
use entropyk_solver::{MacroComponent, NewtonConfig, Solver, System};
use std::fmt;
@@ -51,7 +51,11 @@ struct LinearComponent {
impl LinearComponent {
#[allow(clippy::new_ret_no_self)]
fn new(name: &'static str, n_eqs: usize) -> Box<dyn Component> {
Box::new(Self { name, n_eqs, factor: 1e-2 })
Box::new(Self {
name,
n_eqs,
factor: 0.1,
})
}
}
@@ -84,8 +88,12 @@ impl Component for LinearComponent {
Ok(())
}
fn n_equations(&self) -> usize { self.n_eqs }
fn get_ports(&self) -> &[ConnectedPort] { &[] }
fn n_equations(&self) -> usize {
self.n_eqs
}
fn get_ports(&self) -> &[ConnectedPort] {
&[]
}
}
// ─────────────────────────────────────────────────────────────────────────────
@@ -93,8 +101,16 @@ impl Component for LinearComponent {
// ─────────────────────────────────────────────────────────────────────────────
fn make_port(fluid: &str, p_pa: f64, h_jkg: f64) -> ConnectedPort {
let p1 = Port::new(FluidId::new(fluid), Pressure::from_pascals(p_pa), Enthalpy::from_joules_per_kg(h_jkg));
let p2 = Port::new(FluidId::new(fluid), Pressure::from_pascals(p_pa), Enthalpy::from_joules_per_kg(h_jkg));
let p1 = Port::new(
FluidId::new(fluid),
Pressure::from_pascals(p_pa),
Enthalpy::from_joules_per_kg(h_jkg),
);
let p2 = Port::new(
FluidId::new(fluid),
Pressure::from_pascals(p_pa),
Enthalpy::from_joules_per_kg(h_jkg),
);
p1.connect(p2).unwrap().0
}
@@ -128,14 +144,14 @@ fn build_chiller_macro(label: &'static str) -> (MacroComponent, usize) {
let mut sys = System::new();
let compresseur = sys.add_component(LinearComponent::new("Compresseur", 2));
let condenseur = sys.add_component(LinearComponent::new("Condenseur", 2));
let exv = sys.add_component(LinearComponent::new("EXV", 1));
let evap = sys.add_component(LinearComponent::new("Evaporateur", 2));
let condenseur = sys.add_component(LinearComponent::new("Condenseur", 2));
let exv = sys.add_component(LinearComponent::new("EXV", 1));
let evap = sys.add_component(LinearComponent::new("Evaporateur", 2));
sys.add_edge(compresseur, condenseur).unwrap();
sys.add_edge(condenseur, exv ).unwrap();
sys.add_edge(exv, evap ).unwrap();
sys.add_edge(evap, compresseur).unwrap();
sys.add_edge(condenseur, exv).unwrap();
sys.add_edge(exv, evap).unwrap();
sys.add_edge(evap, compresseur).unwrap();
sys.finalize().unwrap();
let internal_state_len = sys.state_vector_len(); // 4 edges × 2 = 8
@@ -144,10 +160,16 @@ fn build_chiller_macro(label: &'static str) -> (MacroComponent, usize) {
// Ports typiques R410A Eurovent A7/W35
// Haute pression ≈ 24 bar, basse pression ≈ 8.5 bar
mc.expose_port(0, format!("{}/refrig_in", label),
make_port("R410A", 24.0e5, 465_000.0)); // décharge compresseur
mc.expose_port(2, format!("{}/refrig_out", label),
make_port("R410A", 8.5e5, 260_000.0)); // sortie EXV (liquide basse P)
mc.expose_port(
0,
format!("{}/refrig_in", label),
make_port("R410A", 24.0e5, 465_000.0),
); // décharge compresseur
mc.expose_port(
2,
format!("{}/refrig_out", label),
make_port("R410A", 8.5e5, 260_000.0),
); // sortie EXV (liquide basse P)
(mc, internal_state_len)
}
@@ -157,10 +179,24 @@ fn build_chiller_macro(label: &'static str) -> (MacroComponent, usize) {
// ─────────────────────────────────────────────────────────────────────────────
fn main() {
println!("{}", "\n╔══════════════════════════════════════════════════════════════════════╗".green());
println!("{}", "║ ENTROPYK — MacroComponent Demo : 2 Chillers en Parallèle ║".green().bold());
println!("{}", "║ Architecture : Eurovent A7/W35 — Story 3.6 Hierarchical Subsystem ║".green());
println!("{}", "╚══════════════════════════════════════════════════════════════════════╝\n".green());
println!(
"{}",
"\n╔══════════════════════════════════════════════════════════════════════╗".green()
);
println!(
"{}",
"║ ENTROPYK — MacroComponent Demo : 2 Chillers en Parallèle ║"
.green()
.bold()
);
println!(
"{}",
"║ Architecture : Eurovent A7/W35 — Story 3.6 Hierarchical Subsystem ║".green()
);
println!(
"{}",
"╚══════════════════════════════════════════════════════════════════════╝\n".green()
);
// ── 1. Construction des MacroComponents ─────────────────────────────────
print_header("1. Construction des sous-systèmes (MacroComponent)");
@@ -168,12 +204,22 @@ fn main() {
let (chiller_a, internal_len_a) = build_chiller_macro("Chiller_A");
let (chiller_b, internal_len_b) = build_chiller_macro("Chiller_B");
println!(" {} Chiller A construit : {} composants, {} vars d'état internes",
"".green(), 4, internal_len_a);
println!(" {} Chiller B construit : {} composants, {} vars d'état internes",
"".green(), 4, internal_len_b);
println!(" {} Chaque chiller expose 2 ports : refrig_in + refrig_out",
"".green());
println!(
" {} Chiller A construit : {} composants, {} vars d'état internes",
"".green(),
4,
internal_len_a
);
println!(
" {} Chiller B construit : {} composants, {} vars d'état internes",
"".green(),
4,
internal_len_b
);
println!(
" {} Chaque chiller expose 2 ports : refrig_in + refrig_out",
"".green()
);
print_box(&[
"Structure interne de chaque Chiller (MacroComponent) :",
@@ -193,30 +239,44 @@ fn main() {
let mut parent = System::new();
let ca = parent.add_component(Box::new(chiller_a));
let cb = parent.add_component(Box::new(chiller_b));
let ca = parent.add_component(Box::new(chiller_a));
let cb = parent.add_component(Box::new(chiller_b));
let splitter = parent.add_component(LinearComponent::new("Splitter", 1));
let merger = parent.add_component(LinearComponent::new("Merger", 1));
let merger = parent.add_component(LinearComponent::new("Merger", 1));
// Splitter → Chiller A → Merger
// Splitter → Chiller B → Merger
parent.add_edge(splitter, ca ).unwrap();
parent.add_edge(splitter, cb ).unwrap();
parent.add_edge(ca, merger).unwrap();
parent.add_edge(cb, merger).unwrap();
parent.add_edge(splitter, ca).unwrap();
parent.add_edge(splitter, cb).unwrap();
parent.add_edge(ca, merger).unwrap();
parent.add_edge(cb, merger).unwrap();
parent.finalize().unwrap(); // injecte les indices d'état dans les MacroComponents
let parent_edge_vars = parent.state_vector_len(); // 4 edges parent × 2 = 8
let total_state_len = parent_edge_vars + internal_len_a + internal_len_b; // 8+8+8 = 24
let total_state_len = parent_edge_vars + internal_len_a + internal_len_b; // 8+8+8 = 24
println!(" {} Système parent finalisé :", "".green());
println!(" - {} nœuds (Splitter, Chiller A, Chiller B, Merger)", parent.node_count());
println!(" - {} bords parent ({} vars d'état parent)", parent.edge_count(), parent_edge_vars);
println!(" - {} vars d'état internes (2 chillers × 8)", internal_len_a + internal_len_b);
println!(" - {} vars d'état total dans le vecteur étendu", total_state_len);
println!(
" - {} nœuds (Splitter, Chiller A, Chiller B, Merger)",
parent.node_count()
);
println!(
" - {} bords parent ({} vars d'état parent)",
parent.edge_count(),
parent_edge_vars
);
println!(
" - {} vars d'état internes (2 chillers × 8)",
internal_len_a + internal_len_b
);
println!(
" - {} vars d'état total dans le vecteur étendu",
total_state_len
);
let total_eqs: usize = parent.traverse_for_jacobian()
let total_eqs: usize = parent
.traverse_for_jacobian()
.map(|(_, c, _)| c.n_equations())
.sum();
@@ -238,10 +298,16 @@ fn main() {
match parent.compute_residuals(&extended_state, &mut residuals) {
Ok(()) => {
let max_res = residuals.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
println!(" {} compute_residuals() réussi sur vecteur de {} vars",
"".green(), total_state_len);
println!(
" {} compute_residuals() réussi sur vecteur de {} vars",
"".green(),
total_state_len
);
println!(" - {} équations évaluées", total_eqs);
println!(" - Résidu max : {:.2e} (state nul → attendu ≈ 0)", max_res);
println!(
" - Résidu max : {:.2e} (state nul → attendu ≈ 0)",
max_res
);
}
Err(e) => println!(" {} Erreur résidus : {:?}", "".red(), e),
}
@@ -274,21 +340,30 @@ fn main() {
let m_ref = 0.045_f64; // kg/s par chiller
let cop_heating = 3.8_f64;
let q_heating = m_ref * (465e3 - 260e3);
let w_comp = q_heating / cop_heating;
let q_heating = m_ref * (465e3 - 260e3);
let w_comp = q_heating / cop_heating;
print_box(&[
"Chiller A & Chiller B (identiques, Eurovent A7/W35) :",
"",
" Cycle Réfrigérant (R410A) :",
&format!(" Compresseur : 8.5 bar → 24 bar | W = {:.2} kW", w_comp / 1e3),
&format!(" Condenseur : Q_rej = {:.2} kW | T_cond = 40°C", q_heating / 1e3),
&format!(
" Compresseur : 8.5 bar → 24 bar | W = {:.2} kW",
w_comp / 1e3
),
&format!(
" Condenseur : Q_rej = {:.2} kW | T_cond = 40°C",
q_heating / 1e3
),
" EXV : 24 bar → 8.5 bar | Isenthalpique",
" Evaporateur : T_evap = 2°C | Superheat = 5 K",
"",
&format!(" COP Chauffage : {:.2}", cop_heating),
&format!(" Capacité : {:.2} kW / chiller", q_heating / 1e3),
&format!(" 2 chillers parallèles : {:.2} kW total", 2.0 * q_heating / 1e3),
&format!(
" 2 chillers parallèles : {:.2} kW total",
2.0 * q_heating / 1e3
),
]);
// ── 6. Snapshot JSON ─────────────────────────────────────────────
@@ -303,7 +378,11 @@ fn main() {
});
let json_str = serde_json::to_string_pretty(&snap_json).unwrap();
println!(" {} Snapshot JSON (état convergé, {} vars) :", "".green(), n_internal);
println!(
" {} Snapshot JSON (état convergé, {} vars) :",
"".green(),
n_internal
);
for line in json_str.lines() {
println!(" {}", line.dimmed());
}
@@ -311,7 +390,11 @@ fn main() {
if let Ok(dir) = std::env::current_dir() {
let path = dir.join("chiller_a_snapshot.json");
std::fs::write(&path, &json_str).unwrap();
println!("\n {} Sauvegardé sur disque : {}", "".green(), path.display());
println!(
"\n {} Sauvegardé sur disque : {}",
"".green(),
path.display()
);
}
}
Err(e) => {
@@ -337,6 +420,11 @@ fn main() {
]);
println!("\n{}", "".repeat(72).cyan());
println!("{}", " Entropyk MacroComponent Demo terminé avec succès !".cyan().bold());
println!(
"{}",
" Entropyk MacroComponent Demo terminé avec succès !"
.cyan()
.bold()
);
println!("{}\n", "".repeat(72).cyan());
}

View File

@@ -4,18 +4,14 @@
//!
//! Exécuter: cargo run -p entropyk-demo --bin pipe
use entropyk_components::pipe::{Pipe, PipeGeometry, roughness};
use entropyk_components::pipe::{roughness, Pipe, PipeGeometry};
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 geometry = PipeGeometry::new(10.0, 0.022, roughness::SMOOTH)?;
let inlet = Port::new(
FluidId::new("Water"),

View File

@@ -22,7 +22,8 @@ fn main() -> Result<(), ConnectionError> {
Enthalpy::from_joules_per_kg(400_000.0),
);
println!("Port 1: fluide={}, P={:.2} bar, h={:.0} J/kg",
println!(
"Port 1: fluide={}, P={:.2} bar, h={:.0} J/kg",
port1.fluid_id(),
port1.pressure().to_bar(),
port1.enthalpy().to_joules_per_kg()
@@ -34,7 +35,8 @@ fn main() -> Result<(), ConnectionError> {
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",
println!(
"Port 1 modifié: P={:.2} bar, h={:.0} J/kg",
connected1.pressure().to_bar(),
connected1.enthalpy().to_joules_per_kg()
);

View File

@@ -11,10 +11,7 @@ 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 curves = PumpCurves::quadratic(30.0, -10.0, -50.0, 0.5, 0.3, -0.5)?;
let inlet = Port::new(
FluidId::new("Water"),
@@ -36,7 +33,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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);
println!(
" - Q={:.2} m³/s: H={:.2} m, η={:.1}%",
q,
head,
eff * 100.0
);
}
Ok(())

View File

@@ -34,7 +34,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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);
println!(
" Q={:.2} m³/s → H={:.2} m, η={:.1}%",
q,
h,
eta.clamp(0.0, 1.0) * 100.0
);
}
// ═══════════════════════════════════════════════════════════════
@@ -43,8 +48,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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²
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(
@@ -61,7 +66,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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",
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
);
@@ -74,8 +80,8 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// 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)
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)");
@@ -83,19 +89,25 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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 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!(
" 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!(
" {:>8} | {:>8} {:>8} {:>8} {:>8}",
"SST\\SDT", "303K", "308K", "313K", "318K"
);
println!(" -------- | -------- -------- -------- --------");
for sst in [263.15, 268.15, 273.15] {
@@ -115,7 +127,10 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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!(
" {:>10} | {:>10} {:>10} {:>10}",
"Vitesse", "Q ratio", "H ratio", "P ratio"
);
println!(" ---------- | ---------- ---------- ----------");
for &ratio in &speed_ratios {
@@ -123,7 +138,13 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
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!(
" {:>8.0}% | {:>10.2} {:>10.2} {:>10.2}",
ratio * 100.0,
q_ratio,
h_ratio,
p_ratio
);
}
println!("\n✅ Exemple terminé !");

View File

@@ -4,15 +4,11 @@
//!
//! cargo run -p entropyk-demo --bin ui-server
use axum::{
extract::Json,
routing::post,
Router,
};
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_components::pump::{Pump, PumpCurves};
use entropyk_core::{Enthalpy, Pressure};
use serde::{Deserialize, Serialize};
use std::path::Path;
@@ -186,10 +182,7 @@ fn calc_compressor(comp: &ComponentConfig) -> ComponentResult {
};
}
let sst_sdt = SstSdtCoefficients::bilinear(
m[0], m[1], m[2], m[3],
p[0], p[1], p[2], p[3],
);
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;
@@ -212,9 +205,18 @@ fn calc_compressor(comp: &ComponentConfig) -> ComponentResult {
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);
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) => {
@@ -273,7 +275,10 @@ fn calc_pipe(comp: &ComponentConfig) -> ComponentResult {
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);
let opening = config
.get("opening")
.and_then(|v| v.as_f64())
.unwrap_or(1.0);
ComponentResult {
id: comp.id.clone(),

View File

@@ -67,13 +67,13 @@ fn main() {
println!();
let circuits = vec![
CircuitId::new("primary"),
CircuitId::new("secondary"),
CircuitId::from_number(0),
CircuitId::from_number(1),
CircuitId::default(),
];
for circuit in &circuits {
println!(" Circuit: {} (as_str: \"{}\")", circuit, circuit.as_str());
println!(" Circuit: {}", circuit);
}
println!();