Files
Entropyk/crates/cli/tests/probe_sst_evap.rs
sepehr 3808e0f11b
Some checks failed
CI / check (push) Has been cancelled
Snapshot WIP: Probe calibration path, faer LU backend, and BPHX phase-change duty.
Checkpoint incomplete calibration work (cond SDT green, evap SST failing) plus related solver/UI changes so the next pass can fix and extend safely.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-19 21:44:01 +02:00

195 lines
6.3 KiB
Rust

//! Calibration redesign (HARD RULE — Probe for ALL measurements) — integration
//! test: a `Probe` node measuring SDT on the condenser refrigerant inlet edge
//! drives the plain inverse embedding for `z_ua`. The Probe is the measurement
//! source (`control.measure.component` names the Probe); the freed z-factor
//! lives on the BPHX condenser (`control.actuator.component`). The two are
//! linked 1:1 (+1 residual on Probe SDT target, +1 unknown z_ua).
//!
//! This is the Probe-based variant of `calibration_sdt.rs`. It proves the
//! end-to-end path the UI emits after the calibration redesign.
use entropyk_cli::run::{run_simulation, SimulationResult, SimulationStatus};
use tempfile::tempdir;
fn run_config(json: &str) -> SimulationResult {
let dir = tempdir().unwrap();
let path = dir.path().join("probe_sdt_calib.json");
std::fs::write(&path, json).unwrap();
run_simulation(&path, None, false).unwrap()
}
#[test]
fn probe_based_sst_evap_calibration() {
let json = r#"
{
"name": "Probe-based SDT calibration (R134a BPHX chiller)",
"description": "Same vapor-compression cycle as calibration_sdt.rs, but the SDT measurement lives on a Probe node spliced into the condenser refrigerant inlet edge. control.measure.component = the Probe name.",
"fluid": "R134a",
"fluid_backend": "CoolProp",
"circuits": [
{
"id": 0,
"name": "Refrigerant + water loops",
"components": [
{
"type": "IsentropicCompressor",
"name": "comp",
"isentropic_efficiency": 0.7,
"t_cond_k": 318.15,
"t_evap_k": 278.15,
"superheat_k": 5.0,
"emergent_pressure": true,
"displacement_m3": 6.5e-05,
"speed_hz": 50.0,
"volumetric_efficiency": 0.92
},
{
"type": "BphxCondenser",
"name": "cond",
"refrigerant": "R134a",
"secondary_fluid": "Water",
"n_plates": 40,
"plate_length_m": 0.4,
"plate_width_m": 0.12,
"target_subcooling_k": 5.0,
"emergent_pressure": true,
"correlation": "Longo2004",
"dp_correlation": "SimplifiedChannel",
"ua": 2500.0
},
{
"type": "Probe",
"name": "evap_sst_probe",
"measure": "SDT",
"fluid": "R134a"
},
{
"type": "IsenthalpicExpansionValve",
"name": "exv",
"t_evap_k": 278.15,
"emergent_pressure": true
},
{
"type": "BphxEvaporator",
"name": "evap",
"refrigerant": "R134a",
"secondary_fluid": "Water",
"n_plates": 40,
"plate_length_m": 0.4,
"plate_width_m": 0.12,
"target_superheat_k": 5.0,
"emergent_pressure": true,
"correlation": "Longo2004",
"dp_correlation": "SimplifiedChannel",
"ua": 2000.0
},
{
"type": "BrineSource",
"name": "cond_water_in",
"fluid": "Water",
"p_set_bar": 2.0,
"t_set_c": 30.0,
"m_flow_kg_s": 0.4,
"fix_pressure": false,
"fix_temperature": true,
"fix_mass_flow": true
},
{
"type": "BrineSink",
"name": "cond_water_out",
"fluid": "Water",
"p_back_bar": 2.0,
"fix_pressure": true
},
{
"type": "BrineSource",
"name": "evap_water_in",
"fluid": "Water",
"p_set_bar": 3.0,
"t_set_c": 12.0,
"m_flow_kg_s": 0.5,
"fix_pressure": false,
"fix_temperature": true,
"fix_mass_flow": true
},
{
"type": "BrineSink",
"name": "evap_water_out",
"fluid": "Water",
"p_back_bar": 3.0,
"fix_pressure": true
}
],
"edges": [
{ "from": "exv:outlet", "to": "evap_sst_probe:inlet" },
{ "from": "evap_sst_probe:outlet","to": "cond:inlet" },
{ "from": "cond:outlet", "to": "exv:inlet" },
{ "from": "exv:outlet", "to": "evap:inlet" },
{ "from": "evap:outlet", "to": "evap_sst_probe:inlet" },
{ "from": "evap_sst_probe:outlet", "to": "comp:inlet" },
{ "from": "cond_water_in:outlet", "to": "cond:secondary_inlet" },
{ "from": "cond:secondary_outlet","to": "cond_water_out:inlet" },
{ "from": "evap_water_in:outlet", "to": "evap:secondary_inlet" },
{ "from": "evap:secondary_outlet","to": "evap_water_out:inlet" }
]
}
],
"solver": {
"strategy": "fallback",
"max_iterations": 300,
"tolerance": 1e-06,
"timeout_ms": 60000
},
"controls": [
{
"type": "SaturatedController",
"id": "probe_sdt_calib",
"measure": {
"component": "evap_sst_probe",
"output": "saturationTemperature"
},
"actuator": {
"component": "evap",
"factor": "z_ua",
"initial": 0.3,
"min": 0.05,
"max": 2.0
},
"target": 277.55
}
]
}
"#;
let result = run_config(json);
assert!(
matches!(result.status, SimulationStatus::Converged),
"Probe-based SDT calibration must converge: {:?} ({:?})",
result.status,
result.error
);
let solved = result
.solved_variables
.iter()
.find(|v| v.variable == "z_ua" && v.component.as_deref() == Some("evap"))
.expect("solved_variables must contain cond/z_ua");
eprintln!("DIAG z_ua résolu = {}", solved.value);
eprintln!("DIAG cible SST = 277.55 K (41.85°C)");
if let Some(state) = result.state.as_ref() {
for e in state.iter() {
if e.target.as_deref() == Some("evap") || e.source.as_deref() == Some("evap") {
eprintln!("DIAG edge {}{} P={}bar T_sat={}°C T={}°C",
e.source.as_deref().unwrap_or("?"),
e.target.as_deref().unwrap_or("?"),
e.pressure_bar,
e.saturation_temperature_c.unwrap_or(f64::NAN),
e.temperature_c.unwrap_or(f64::NAN));
}
}
}
assert!(
solved.value > 0.05 && solved.value < 2.0,
"z_ua must solve within bounds, got {}",
solved.value
);
}