5.8 KiB
5.8 KiB
Entropyk: Comprehensive Examples Suite
This document provides advanced modeling scenarios for Entropyk across its multi-platform ecosystem.
1. Multi-Circuit Industrial Chiller (Rust)
Modeling a water-cooled chiller where a refrigerant loop (R134a) and a water loop are coupled via an evaporator (bridge).
1.1 System Architecture
graph LR
subgraph Circuit_0 [Circuit 0: Refrigerant]
comp[Compressor] --> cond[Condenser]
cond --> valve[Expansion Valve]
valve --> evap_a[Evaporator Side A]
evap_a --> comp
end
subgraph Circuit_1 [Circuit 1: Water Loop]
pump[Pump] --> evap_b[Evaporator Side B]
evap_b --> building[Building Load]
building --> pump
end
evap_a <-.->|Thermal Coupling| evap_b
1.2 Implementation Detail
use entropyk_components::{Compressor, HeatExchanger, Pump};
use entropyk_solver::{System, NewtonConfig, ThermalCoupling};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut system = System::new();
// Circuit 0: Refrigerant Loop
let comp = system.add_component(Compressor::new(coeffs, ...));
let cond = system.add_component(HeatExchanger::new_condenser(ua_air));
let valve = system.add_component(ExpansionValve::new(cv));
let evap = system.add_component(HeatExchanger::new_bridge(ua_water)); // COUPLING POINT
system.add_edge_in_circuit(comp, cond, 0)?;
system.add_edge_in_circuit(cond, valve, 0)?;
system.add_edge_in_circuit(valve, evap.side_a, 0)?;
system.add_edge_in_circuit(evap.side_a, comp, 0)?;
// Circuit 1: Water loop
let pump = system.add_component(Pump::new(curve));
let building = system.add_component(HeatExchanger::new_load(50_000.0)); // 50kW Load
system.add_edge_in_circuit(pump, evap.side_b, 1)?;
system.add_edge_in_circuit(evap.side_b, building, 1)?;
system.add_edge_in_circuit(building, pump, 1)?;
system.finalize()?;
// Simultaneous Multi-Circuit Solve
let solver = NewtonConfig::default().with_line_search(true);
let state = solver.solve(&mut system)?;
println!("Chiller System COP: {}", state.cop());
Ok(())
}
1.3 Control & Coupling Logic
The solver treats both circuits as a unified graph. The HeatExchanger bridge enforces the following boundary conditions:
- Energy Balance:
\dot{Q}_{refrig} = \dot{Q}_{water}(assuming no ambient loss). - Temperature Coupling: The effectiveness-NTU model internally calculates the heat transfer based on the inlet temperatures of both circuits.
- Unified Jacobian: The solver constructs a single Jacobian matrix where off-diagonal blocks represent the thermal coupling, allowing for simultaneous convergence of both loops.
2. Inverse Control & Parameter Estimation (Python)
Finding the Heat Exchanger Fouling (UA) by matching simulation to sensor data.
2.1 Control Flow Diagram
sequenceDiagram
participant U as User (Script)
participant S as System Solver
participant P as Physical Model
participant C as Constraint Engine
U->>S: Define Architecture & Constraints
Note over S,C: Link Constraint (Temp) to Control (UA)
loop Iterations (Newton-Raphson)
S->>P: Compute Residuals F(x)
P->>S: Physical Violations
S->>C: Compute Constraint Gradients (dC/dua)
C->>S: Jacobian Block
S->>S: Solve Augmented System [J | G]
S->>S: Update State (x) & Control (ua)
end
S->>U: Converged Parameters (UA)
2.2 Implementation Breakdown
import entropyk as ek
# 1. Define physical system
sys = ek.System()
hx = sys.add_component(ek.HeatExchanger(ua=5000.0)) # Initial guess
# 2. Add Constraint: We KNOW the exit temperature from a sensor
# Target: Exit port of HX must be 280.15 K
sys.add_constraint(
node_id=hx,
variable="exit_temp",
target=280.15,
tolerance=0.01
)
# 3. Designate UA as a "Calibration Variable" (Solver will tune this)
sys.link_constraint_to_control(hx, "ua", bounds=(1000.0, 10000.0))
# 4. Solve Sparse Inverse Problem
solver = ek.NewtonConfig(inverse_mode=True)
result = solver.solve(sys)
print(f"Estimated UA based on sensor: {hx.ua:.2f} W/K")
2.3 Logic Breakdown: The Augmented Matrix
In standard "Forward" mode, the solver solves F(x) = 0. In "Inverse" mode, we add a constraint C(x, u) = 0 (where u is our control, e.g., UA). The solver internally solves:
\begin{bmatrix}
\mathcal{J}_x & \mathcal{G}_u \\
\mathcal{C}_x & 0
\end{bmatrix}
\begin{bmatrix} \Delta x \\ \Delta u \end{bmatrix} =
-\begin{bmatrix} F(x) \\ C(x, u) \end{bmatrix}
\mathcal{J}_x: Standard physical Jacobian.\mathcal{G}_u: Sensitivity of physics to the control variable (how a change in UA affects mass/energy residuals).\mathcal{C}_x: Sensitivity of the constraint to state variables.\Delta u: The correction to our estimated parameter (UA) to satisfy the sensor target.
3. Real-Time HIL Integration (C FFI)
Zero-allocation solving for embedded controllers at 100Hz.
#include "entropyk.h"
int main() {
// 1. Initialize system once (pre-allocate hooks)
ek_system_t* sys = ek_system_create();
ek_compressor_t* comp = ek_compressor_create(coeffs);
ek_system_add_component(sys, comp);
// ... connections ...
ek_system_finalize(sys);
// 2. Control Loop (10ms steps)
while (running) {
// Update boundary conditions (e.g. ambient T)
ek_system_set_source_temp(sys, source_node, get_sensor_t());
// Solve using previous state as hot-start
ek_converged_state_t* res = ek_solve(sys, PICARD_STRATEGY);
if (ek_converged_state_is_ok(res)) {
float p_disch = ek_converged_state_get_p(res, discharge_port);
apply_to_plc(p_disch);
}
ek_converged_state_free(res);
}
ek_system_free(sys);
}