//! Calibration redesign (HARD RULE — Probe for ALL measurements) — integration //! test: a `Probe` measuring SDT + free `cond/z_ua` via model `embeddings[]` //! (Modelica unknown + equation — not SaturatedController / controls[]). 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_sdt_calibration_converges_and_solves_z_ua() { 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": "cond_sdt_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": "comp:outlet", "to": "cond_sdt_probe:inlet" }, { "from": "cond_sdt_probe:outlet","to": "cond:inlet" }, { "from": "cond:outlet", "to": "exv:inlet" }, { "from": "exv:outlet", "to": "evap:inlet" }, { "from": "evap: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 }, "embeddings": [ { "id": "emb_cond_z_ua", "unknown": { "component": "cond", "factor": "z_ua", "start": 0.3, "min": 0.05, "max": 2.0 }, "equation": { "component": "cond_sdt_probe", "output": "saturationTemperature", "value": 315.0 } } ] } "#; 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("cond")) .expect("solved_variables must contain cond/z_ua"); eprintln!("DIAG z_ua résolu = {}", solved.value); eprintln!("DIAG cible SDT = 315.0 K (41.85°C)"); if let Some(state) = result.state.as_ref() { for e in state.iter() { if e.target.as_deref() == Some("cond") || e.source.as_deref() == Some("cond") { 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)); } } } let mut sdt_c = None; if let Some(state) = result.state.as_ref() { for e in state.iter() { if e.target.as_deref() == Some("cond") || e.source.as_deref() == Some("cond_sdt_probe") { if let Some(ts) = e.saturation_temperature_c { sdt_c = Some(ts); } } } } let sdt_k = sdt_c.expect("must read SDT near condenser inlet probe") + 273.15; assert!( (sdt_k - 315.0).abs() < 0.5, "SDT must hit target 315.0 K within 0.5 K, got {sdt_k}" ); assert!( solved.value > 0.05 && solved.value < 2.0, "z_ua must solve within bounds, got {}", solved.value ); }