Snapshot WIP: solver HP epic progress, BPHX/HX physics, BMAD skill refresh.
Some checks failed
CI / check (push) Has been cancelled

Capture uncommitted solver robustness work (regularization, domain errors, linear solver lifecycle, tube DP/MSH), web workbench updates, and synced BMAD skills across IDE agent folders before starting BPHX pressure-drop.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-19 16:35:31 +02:00
parent 88620790d6
commit 5bd180b5b8
1363 changed files with 101041 additions and 58547 deletions

View File

@@ -325,9 +325,7 @@ mod tests {
#[test]
fn test_bitzer_get_compressor_4nfc_20y() {
let backend = BitzerBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("4NFC-20Y")
.unwrap();
let coeffs = backend.get_compressor_coefficients("4NFC-20Y").unwrap();
assert_eq!(coeffs.model, "4NFC-20Y");
assert_eq!(coeffs.manufacturer, "Bitzer");

View File

@@ -7,9 +7,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use crate::error::VendorError;
use crate::vendor_api::{
BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend,
};
use crate::vendor_api::{BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend};
/// Backend for Copeland (Emerson) scroll compressor data.
///
@@ -87,12 +85,11 @@ impl CopelandBackend {
/// Load the compressor index and pre-cache all referenced models.
fn load_index(&mut self) -> Result<(), VendorError> {
let index_path = self.data_path.join("compressors").join("index.json");
let index_content = std::fs::read_to_string(&index_path).map_err(|e| {
VendorError::IoError {
let index_content =
std::fs::read_to_string(&index_path).map_err(|e| VendorError::IoError {
path: index_path.display().to_string(),
source: e,
}
})?;
})?;
let models: Vec<String> = serde_json::from_str(&index_content)?;
@@ -103,7 +100,11 @@ impl CopelandBackend {
self.sorted_models.push(model);
}
Err(e) => {
log::warn!("[entropyk-vendors] Skipping Copeland model {}: {}", model, e);
log::warn!(
"[entropyk-vendors] Skipping Copeland model {}: {}",
model,
e
);
}
}
}
@@ -201,9 +202,7 @@ mod tests {
#[test]
fn test_copeland_get_compressor_zp54() {
let backend = CopelandBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("ZP54KCE-TFD")
.unwrap();
let coeffs = backend.get_compressor_coefficients("ZP54KCE-TFD").unwrap();
assert_eq!(coeffs.model, "ZP54KCE-TFD");
assert_eq!(coeffs.manufacturer, "Copeland");
@@ -221,9 +220,7 @@ mod tests {
#[test]
fn test_copeland_get_compressor_zp49() {
let backend = CopelandBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("ZP49KCE-TFD")
.unwrap();
let coeffs = backend.get_compressor_coefficients("ZP49KCE-TFD").unwrap();
assert_eq!(coeffs.model, "ZP49KCE-TFD");
assert_eq!(coeffs.manufacturer, "Copeland");
@@ -235,9 +232,7 @@ mod tests {
#[test]
fn test_copeland_validity_range() {
let backend = CopelandBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("ZP54KCE-TFD")
.unwrap();
let coeffs = backend.get_compressor_coefficients("ZP54KCE-TFD").unwrap();
assert!((coeffs.validity.t_suction_min - (-10.0)).abs() < 1e-10);
assert!((coeffs.validity.t_suction_max - 20.0).abs() < 1e-10);

View File

@@ -7,9 +7,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use crate::error::VendorError;
use crate::vendor_api::{
BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend,
};
use crate::vendor_api::{BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend};
/// Backend for Danfoss scroll compressor data.
///
@@ -87,12 +85,11 @@ impl DanfossBackend {
/// Load the compressor index and pre-cache all referenced models.
fn load_index(&mut self) -> Result<(), VendorError> {
let index_path = self.data_path.join("compressors").join("index.json");
let index_content = std::fs::read_to_string(&index_path).map_err(|e| {
VendorError::IoError {
let index_content =
std::fs::read_to_string(&index_path).map_err(|e| VendorError::IoError {
path: index_path.display().to_string(),
source: e,
}
})?;
})?;
let models: Vec<String> = serde_json::from_str(&index_content).map_err(|e| {
VendorError::InvalidFormat(format!("Parse error in {}: {}", index_path.display(), e))
@@ -205,9 +202,12 @@ mod tests {
PathBuf::from("data")
}
});
let backend = DanfossBackend::from_path(base_path.join("danfoss"));
assert!(backend.is_ok(), "DanfossBackend::from_path() should succeed");
assert!(
backend.is_ok(),
"DanfossBackend::from_path() should succeed"
);
}
#[test]
@@ -229,9 +229,7 @@ mod tests {
#[test]
fn test_danfoss_get_compressor_sh140() {
let backend = DanfossBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("SH140-4")
.unwrap();
let coeffs = backend.get_compressor_coefficients("SH140-4").unwrap();
assert_eq!(coeffs.model, "SH140-4");
assert_eq!(coeffs.manufacturer, "Danfoss");
@@ -253,9 +251,7 @@ mod tests {
#[test]
fn test_danfoss_get_compressor_sh090() {
let backend = DanfossBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("SH090-4")
.unwrap();
let coeffs = backend.get_compressor_coefficients("SH090-4").unwrap();
assert_eq!(coeffs.model, "SH090-4");
assert_eq!(coeffs.manufacturer, "Danfoss");
@@ -269,9 +265,7 @@ mod tests {
#[test]
fn test_danfoss_validity_range() {
let backend = DanfossBackend::new().unwrap();
let coeffs = backend
.get_compressor_coefficients("SH140-4")
.unwrap();
let coeffs = backend.get_compressor_coefficients("SH140-4").unwrap();
assert!((coeffs.validity.t_suction_min - (-15.0)).abs() < 1e-10);
assert!((coeffs.validity.t_suction_max - 15.0).abs() < 1e-10);

View File

@@ -6,6 +6,6 @@
/// Copeland (Emerson) compressor data backend.
pub mod copeland;
pub mod bitzer;
/// Danfoss (11.14), Bitzer (11.15) compressor data backends.
pub mod danfoss;
pub mod bitzer;

View File

@@ -7,9 +7,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use crate::error::VendorError;
use crate::vendor_api::{
BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend,
};
use crate::vendor_api::{BphxParameters, CompressorCoefficients, UaCalcParams, VendorBackend};
/// Backend for SWEP brazed-plate heat exchanger data.
///
@@ -87,12 +85,11 @@ impl SwepBackend {
/// Load the BPHX index and pre-cache all referenced models.
fn load_index(&mut self) -> Result<(), VendorError> {
let index_path = self.data_path.join("bphx").join("index.json");
let index_content = std::fs::read_to_string(&index_path).map_err(|e| {
VendorError::IoError {
let index_content =
std::fs::read_to_string(&index_path).map_err(|e| VendorError::IoError {
path: index_path.display().to_string(),
source: e,
}
})?;
})?;
let models: Vec<String> = serde_json::from_str(&index_content)?;
@@ -114,10 +111,7 @@ impl SwepBackend {
/// Load a single BPHX model from its JSON file.
fn load_model(&self, model: &str) -> Result<BphxParameters, VendorError> {
let model_path = self
.data_path
.join("bphx")
.join(format!("{}.json", model));
let model_path = self.data_path.join("bphx").join(format!("{}.json", model));
let content = std::fs::read_to_string(&model_path).map_err(|e| VendorError::IoError {
path: model_path.display().to_string(),

View File

@@ -24,8 +24,8 @@ pub mod heat_exchangers;
pub use compressors::bitzer::BitzerBackend;
pub use compressors::copeland::CopelandBackend;
pub use compressors::danfoss::DanfossBackend;
pub use heat_exchangers::swep::SwepBackend;
pub use error::VendorError;
pub use heat_exchangers::swep::SwepBackend;
pub use vendor_api::{
BphxParameters, CompressorCoefficients, CompressorValidityRange, UaCalcParams, UaCurve,
VendorBackend,

View File

@@ -69,14 +69,14 @@ impl<'de> Deserialize<'de> for CompressorValidityRange {
}
let inner = Inner::deserialize(deserializer)?;
if inner.t_suction_min > inner.t_suction_max {
return Err(serde::de::Error::custom(format!(
"Invalid suction temperature range: min ({} °C) > max ({} °C)",
inner.t_suction_min, inner.t_suction_max
)));
}
if inner.t_discharge_min > inner.t_discharge_max {
return Err(serde::de::Error::custom(format!(
"Invalid discharge temperature range: min ({} °C) > max ({} °C)",
@@ -141,14 +141,16 @@ impl<'de> Deserialize<'de> for UaCurve {
}
let mut inner = Inner::deserialize(deserializer)?;
// Sort points by mass_flow_ratio (x-axis) to ensure interpolation works correctly
// even if the JSON is out of order. f64 sorting requires partial_cmp.
inner.points.sort_by(|a, b| {
a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal)
});
inner
.points
.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
Ok(Self { points: inner.points })
Ok(Self {
points: inner.points,
})
}
}
@@ -259,7 +261,9 @@ mod tests {
model: "ZP54KCE-TFD".into(),
manufacturer: "Copeland".into(),
refrigerant: "R410A".into(),
capacity_coeffs: [18000.0, 350.0, -120.0, 2.5, 1.8, -4.2, 0.05, 0.03, -0.02, 0.01],
capacity_coeffs: [
18000.0, 350.0, -120.0, 2.5, 1.8, -4.2, 0.05, 0.03, -0.02, 0.01,
],
power_coeffs: [4500.0, 95.0, 45.0, 0.8, 0.5, 1.2, 0.02, 0.01, 0.01, 0.005],
mass_flow_coeffs: None,
validity: CompressorValidityRange {
@@ -402,7 +406,10 @@ mod tests {
#[test]
fn test_vendor_error_display_invalid_format() {
let err = VendorError::InvalidFormat("missing capacity_coeffs".into());
assert_eq!(err.to_string(), "Invalid data format: missing capacity_coeffs");
assert_eq!(
err.to_string(),
"Invalid data format: missing capacity_coeffs"
);
}
#[test]