Add diagram workbench UI with Modelica DoF coaching and ISO glyphs.
Ship the Next.js cycle editor with CAD chrome, technical HX symbols, Fixed/Free boundary guidance, and secondary water/air pressure drop support in the solver stack. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -8,8 +8,6 @@
|
||||
//! - Bounds enforcement
|
||||
//! - JSON round-trip of CalibrationResult
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, StateSlice,
|
||||
};
|
||||
@@ -18,13 +16,14 @@ use entropyk_solver::{
|
||||
inverse::calibration::{
|
||||
CalibFactor, CalibRequest, CalibrationMode, CalibrationProblem, CalibrationTarget,
|
||||
},
|
||||
NewtonConfig, Solver, System,
|
||||
NewtonConfig, System,
|
||||
};
|
||||
|
||||
/// Mock component whose capacity scales linearly with f_ua.
|
||||
/// Capacity = base_capacity * f_ua, where base_capacity = 4000.0 W.
|
||||
struct MockCalibratedHx {
|
||||
calib_indices: CalibIndices,
|
||||
#[allow(dead_code)] // Set by the fixture constructor; documents intended capacity scaling.
|
||||
base_capacity: f64,
|
||||
}
|
||||
|
||||
@@ -43,9 +42,10 @@ impl Component for MockCalibratedHx {
|
||||
state: &StateSlice,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
// Fix edge states to known values
|
||||
residuals[0] = state[0] - 300.0;
|
||||
residuals[1] = state[1] - 400.0;
|
||||
// Fix edge states to known values.
|
||||
// CM1.2: per-edge state is (ṁ, P, h); skip ṁ at index 0.
|
||||
residuals[0] = state[1] - 300.0;
|
||||
residuals[1] = state[2] - 400.0;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -54,8 +54,8 @@ impl Component for MockCalibratedHx {
|
||||
_state: &StateSlice,
|
||||
jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
jacobian.add_entry(0, 0, 1.0);
|
||||
jacobian.add_entry(1, 1, 1.0);
|
||||
jacobian.add_entry(0, 1, 1.0);
|
||||
jacobian.add_entry(1, 2, 1.0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ fn test_single_factor_calibration_f_ua() {
|
||||
|
||||
let problem = CalibrationProblem::new()
|
||||
.add_request(CalibRequest::new(
|
||||
CalibFactor::FUa,
|
||||
CalibFactor::ZUa,
|
||||
"evaporator",
|
||||
(0.1, 10.0),
|
||||
1.0,
|
||||
@@ -102,7 +102,7 @@ fn test_single_factor_calibration_f_ua() {
|
||||
let result = problem.calibrate(&mut sys, &config).unwrap();
|
||||
|
||||
assert!(result.converged, "Calibration should converge");
|
||||
let f_ua = result.estimated_factor("evaporator.f_ua").unwrap();
|
||||
let f_ua = result.estimated_factor("evaporator.z_ua").unwrap();
|
||||
// The mock capacity is extracted via extract_constraint_values_with_controls,
|
||||
// which uses the actual solver. Since the mock is simplified, we just verify
|
||||
// convergence and that a factor was returned.
|
||||
@@ -119,8 +119,12 @@ fn test_sequential_mode_is_default() {
|
||||
#[test]
|
||||
fn test_problem_dof_validation() {
|
||||
let sys = System::new();
|
||||
let p = CalibrationProblem::new()
|
||||
.add_request(CalibRequest::new(CalibFactor::FUa, "evaporator", (0.1, 10.0), 1.0));
|
||||
let p = CalibrationProblem::new().add_request(CalibRequest::new(
|
||||
CalibFactor::ZUa,
|
||||
"evaporator",
|
||||
(0.1, 10.0),
|
||||
1.0,
|
||||
));
|
||||
// Only 1 request, 0 targets → DoF mismatch
|
||||
let err = p.validate(&sys).unwrap_err();
|
||||
assert!(format!("{err}").contains("DoF mismatch"));
|
||||
@@ -130,7 +134,12 @@ fn test_problem_dof_validation() {
|
||||
fn test_problem_missing_component() {
|
||||
let sys = System::new();
|
||||
let p = CalibrationProblem::new()
|
||||
.add_request(CalibRequest::new(CalibFactor::FUa, "nonexistent", (0.1, 10.0), 1.0))
|
||||
.add_request(CalibRequest::new(
|
||||
CalibFactor::ZUa,
|
||||
"nonexistent",
|
||||
(0.1, 10.0),
|
||||
1.0,
|
||||
))
|
||||
.add_target(CalibrationTarget::capacity("nonexistent", 4015.0));
|
||||
let err = p.validate(&sys).unwrap_err();
|
||||
assert!(format!("{err}").contains("not registered"));
|
||||
@@ -142,7 +151,7 @@ fn test_bounds_validation_on_request() {
|
||||
|
||||
let problem = CalibrationProblem::new()
|
||||
.add_request(CalibRequest::new(
|
||||
CalibFactor::FUa,
|
||||
CalibFactor::ZUa,
|
||||
"evaporator",
|
||||
(0.1, 10.0),
|
||||
0.05, // initial value below min bound
|
||||
@@ -159,28 +168,29 @@ fn test_bounds_validation_on_request() {
|
||||
fn test_calibration_result_json_roundtrip() {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut result =
|
||||
entropyk_solver::inverse::calibration::CalibrationResult {
|
||||
estimated_factors: HashMap::new(),
|
||||
residuals: HashMap::new(),
|
||||
mape: 0.0,
|
||||
max_abs_error: 0.0,
|
||||
iterations: 0,
|
||||
converged: false,
|
||||
saturated_factors: Vec::new(),
|
||||
};
|
||||
let mut result = entropyk_solver::inverse::calibration::CalibrationResult {
|
||||
estimated_factors: HashMap::new(),
|
||||
residuals: HashMap::new(),
|
||||
mape: 0.0,
|
||||
max_abs_error: 0.0,
|
||||
iterations: 0,
|
||||
converged: false,
|
||||
saturated_factors: Vec::new(),
|
||||
};
|
||||
result
|
||||
.estimated_factors
|
||||
.insert("evaporator.f_ua".to_string(), 1.15);
|
||||
.insert("evaporator.z_ua".to_string(), 1.15);
|
||||
result
|
||||
.estimated_factors
|
||||
.insert("compressor.f_m".to_string(), 0.95);
|
||||
result.residuals.insert("evaporator.f_ua".to_string(), 0.02);
|
||||
.insert("compressor.z_flow".to_string(), 0.95);
|
||||
result.residuals.insert("evaporator.z_ua".to_string(), 0.02);
|
||||
result.mape = 1.5;
|
||||
result.max_abs_error = 0.05;
|
||||
result.iterations = 42;
|
||||
result.converged = true;
|
||||
result.saturated_factors.push("compressor.f_m".to_string());
|
||||
result
|
||||
.saturated_factors
|
||||
.push("compressor.z_flow".to_string());
|
||||
|
||||
let json = serde_json::to_string(&result).unwrap();
|
||||
let result2: entropyk_solver::inverse::calibration::CalibrationResult =
|
||||
@@ -191,8 +201,13 @@ fn test_calibration_result_json_roundtrip() {
|
||||
#[test]
|
||||
fn test_calib_factor_ordering() {
|
||||
let order = CalibFactor::calibration_order();
|
||||
assert_eq!(order[0], CalibFactor::FM, "f_m should come first");
|
||||
assert_eq!(order[2], CalibFactor::FUa, "f_ua should come third");
|
||||
assert_eq!(order[0], CalibFactor::ZFlow, "f_m should come first");
|
||||
assert_eq!(
|
||||
order[1],
|
||||
CalibFactor::ZFlowEco,
|
||||
"economizer flow should follow suction flow"
|
||||
);
|
||||
assert_eq!(order[3], CalibFactor::ZUa, "f_ua should come fourth");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user