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:
2026-07-17 22:46:46 +02:00
parent 62efea0646
commit 3358b74342
275 changed files with 70187 additions and 5230 deletions

View File

@@ -390,6 +390,19 @@ pub struct Compressor<State> {
circuit_id: CircuitId,
/// Operational state: On, Off, or Bypass (FR6-FR8)
operational_state: OperationalState,
/// State-vector index: suction mass flow (incoming edge, CM1.3)
suction_m_idx: Option<usize>,
/// State-vector index: suction enthalpy (incoming edge, CM1.3)
suction_h_idx: Option<usize>,
/// State-vector index: discharge mass flow (outgoing edge, CM1.3)
discharge_m_idx: Option<usize>,
/// State-vector index: discharge enthalpy (outgoing edge, CM1.3)
discharge_h_idx: Option<usize>,
/// True when suction and discharge share the same ṁ state index (same
/// series branch). In this case the mass-conservation residual
/// `ṁ_dis ṁ_suc = 0` is trivially satisfied and must be dropped to
/// keep the system square (CM1.4).
same_branch_m: bool,
/// Phantom data for type state
_state: PhantomData<State>,
}
@@ -560,6 +573,11 @@ impl Compressor<Disconnected> {
circuit_id: CircuitId::default(), // Default circuit
operational_state: OperationalState::default(), // Default to On
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
})
}
@@ -682,6 +700,11 @@ impl Compressor<Disconnected> {
fluid_id: self.fluid_id,
circuit_id: self.circuit_id,
operational_state: self.operational_state,
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
})
}
@@ -785,10 +808,15 @@ impl Compressor<Connected> {
let mass_flow_kg_per_s = match &self.model {
CompressorModel::Ahri540(coeffs) => {
// Calculate volumetric efficiency using inverse pressure ratio
// η_vol = 1 - (P_suction/P_discharge)^(1/M2)
// η_vol = f_etav × (1 - (P_suction/P_discharge)^(1/M2))
// f_etav is read dynamically from the state vector when wired
// as a calibration unknown (Story 5.5), like f_m / f_power.
let f_etav = state
.and_then(|st| self.calib_indices.z_etav.map(|idx| st[idx]))
.unwrap_or(self.calib.z_etav);
let inverse_pressure_ratio = p_suction / p_discharge;
let volumetric_efficiency = (1.0 - inverse_pressure_ratio.powf(1.0 / coeffs.m2))
* self.calib.f_etav;
let volumetric_efficiency =
(1.0 - inverse_pressure_ratio.powf(1.0 / coeffs.m2)) * f_etav;
if volumetric_efficiency < 0.0 {
return Err(ComponentError::NumericalError(
@@ -816,11 +844,11 @@ impl Compressor<Connected> {
// Apply calibration: ṁ_eff = f_m × ṁ_nominal
let f_m = if let Some(st) = state {
self.calib_indices
.f_m
.z_flow
.map(|idx| st[idx])
.unwrap_or(self.calib.f_m)
.unwrap_or(self.calib.z_flow)
} else {
self.calib.f_m
self.calib.z_flow
};
Ok(MassFlow::from_kg_per_s(mass_flow_kg_per_s * f_m))
}
@@ -861,11 +889,11 @@ impl Compressor<Connected> {
// Ẇ_eff = f_power × Ẇ_nominal
let f_power = if let Some(st) = state {
self.calib_indices
.f_power
.z_power
.map(|idx| st[idx])
.unwrap_or(self.calib.f_power)
.unwrap_or(self.calib.z_power)
} else {
self.calib.f_power
self.calib.z_power
};
power_nominal * f_power
}
@@ -907,11 +935,11 @@ impl Compressor<Connected> {
// Ẇ_eff = f_power × Ẇ_nominal
let f_power = if let Some(st) = state {
self.calib_indices
.f_power
.z_power
.map(|idx| st[idx])
.unwrap_or(self.calib.f_power)
.unwrap_or(self.calib.z_power)
} else {
self.calib.f_power
self.calib.z_power
};
power_nominal * f_power
}
@@ -1061,6 +1089,28 @@ impl Compressor<Connected> {
}
impl Component for Compressor<Connected> {
fn set_system_context(
&mut self,
_state_offset: usize,
external_edge_state_indices: &[(usize, usize, usize)],
) {
// Layout: [0] = incoming suction edge, [1] = outgoing discharge edge.
// Triple: (m_idx, p_idx, h_idx)
if !external_edge_state_indices.is_empty() {
self.suction_m_idx = Some(external_edge_state_indices[0].0);
self.suction_h_idx = Some(external_edge_state_indices[0].2);
}
if external_edge_state_indices.len() >= 2 {
self.discharge_m_idx = Some(external_edge_state_indices[1].0);
self.discharge_h_idx = Some(external_edge_state_indices[1].2);
}
// CM1.4: detect same-branch topology → conservation residual becomes trivial.
self.same_branch_m = matches!(
(self.suction_m_idx, self.discharge_m_idx),
(Some(suc), Some(dis)) if suc == dis
);
}
fn compute_residuals(
&self,
state: &StateSlice,
@@ -1074,17 +1124,32 @@ impl Component for Compressor<Connected> {
});
}
let (suc_m_idx, suc_h_idx, dis_m_idx, dis_h_idx) = match (
self.suction_m_idx,
self.suction_h_idx,
self.discharge_m_idx,
self.discharge_h_idx,
) {
(Some(sm), Some(sh), Some(dm), Some(dh)) => (sm, sh, dm, dh),
_ => {
return Err(ComponentError::InvalidState(
"Compressor requires live suction/discharge mass-flow and enthalpy indices"
.to_string(),
));
}
};
// Handle operational states (FR6-FR8)
match self.operational_state {
OperationalState::Off => {
// In Off state, mass flow is zero (FR7)
residuals[0] = state[0]; // ṁ = 0
residuals[0] = state[suc_m_idx]; // ṁ_suction = 0
residuals[1] = 0.0; // No energy transfer
residuals[2] = state[dis_m_idx] - state[suc_m_idx]; // mass conservation
return Ok(());
}
OperationalState::Bypass => {
// In Bypass state, behaves as adiabatic pipe (FR8)
// P_in = P_out and h_in = h_out
let p_suction = self.port_suction.pressure().to_pascals();
let p_discharge = self.port_discharge.pressure().to_pascals();
let h_suction = self.port_suction.enthalpy().to_joules_per_kg();
@@ -1092,6 +1157,7 @@ impl Component for Compressor<Connected> {
residuals[0] = p_suction - p_discharge; // Pressure continuity
residuals[1] = h_suction - h_discharge; // Enthalpy continuity (adiabatic)
residuals[2] = state[dis_m_idx] - state[suc_m_idx]; // mass conservation
return Ok(());
}
OperationalState::On => {
@@ -1099,20 +1165,10 @@ impl Component for Compressor<Connected> {
}
}
// Validate state vector has minimum required dimensions
// We need at least 4 values: mass_flow, h_suction, h_discharge, power
if state.len() < 4 {
return Err(ComponentError::InvalidStateDimensions {
expected: 4,
actual: state.len(),
});
}
// Extract state variables
let mass_flow_state = state[0]; // kg/s
let h_suction = state[1]; // J/kg
let h_discharge = state[2]; // J/kg
let _power_state = state[3]; // W
// Extract state variables using stored edge indices (CM1.3)
let mass_flow_state = state[suc_m_idx]; // kg/s — ṁ at suction edge
let h_suction = state[suc_h_idx]; // J/kg
let h_discharge = state[dis_h_idx]; // J/kg
// Get port values
let p_suction = self.port_suction.pressure().to_pascals();
@@ -1154,6 +1210,13 @@ impl Component for Compressor<Connected> {
residuals[1] = power_calc - mass_flow_state * enthalpy_change / self.mechanical_efficiency;
// r2: ṁ_discharge ṁ_suction = 0 (mass conservation, CM1.3)
// CM1.4: skip when same_branch_m — ṁ_dis == ṁ_suc (same state index),
// so the residual would be trivially 0 and is excluded from n_equations().
if !self.same_branch_m {
residuals[2] = state[dis_m_idx] - state[suc_m_idx];
}
Ok(())
}
@@ -1162,139 +1225,160 @@ impl Component for Compressor<Connected> {
state: &StateSlice,
jacobian: &mut JacobianBuilder,
) -> Result<(), ComponentError> {
// Validate state vector
if state.len() < 4 {
return Err(ComponentError::InvalidStateDimensions {
expected: 4,
actual: state.len(),
});
}
let (suc_m_idx, suc_h_idx, dis_m_idx, dis_h_idx) = match (
self.suction_m_idx,
self.suction_h_idx,
self.discharge_m_idx,
self.discharge_h_idx,
) {
(Some(sm), Some(sh), Some(dm), Some(dh)) => (sm, sh, dm, dh),
_ => {
return Err(ComponentError::InvalidState(
"Compressor Jacobian requires live suction/discharge mass-flow and enthalpy indices".to_string(),
));
}
};
// Extract state variables
let mass_flow_state = state[0];
let h_suction = state[1];
let h_discharge = state[2];
let mass_flow_state = state[suc_m_idx];
let h_suction = state[suc_h_idx];
let h_discharge = state[dis_h_idx];
// Get port values
let p_suction = self.port_suction.pressure().to_pascals();
let p_discharge = self.port_discharge.pressure().to_pascals();
// Calculate temperatures for SST/SDT model
let _t_suction_k =
estimate_temperature(self.fluid_id.as_str(), p_suction, h_suction).unwrap_or(273.15);
let t_discharge_k = estimate_temperature(self.fluid_id.as_str(), p_discharge, h_discharge)
.unwrap_or(273.15);
let t_suction_k = estimate_temperature(self.fluid_id.as_str(), p_suction, h_suction)?;
let t_discharge_k = estimate_temperature(self.fluid_id.as_str(), p_discharge, h_discharge)?;
// Row 0: Mass flow residual
// ∂r/∂ṁ = -1
jacobian.add_entry(0, 0, -1.0);
// Row 0: Mass flow residual r0 = ṁ_calc ṁ_suction
// ∂r0/∂ṁ_suction = -1 (exact, CM1.3)
jacobian.add_entry(0, suc_m_idx, -1.0);
// ∂r/∂h_suction - requires density derivative
// For now, use finite difference approximation
let dr0_dh_suction = approximate_derivative(
// ∂r0/∂h_suction — numerical (density depends on h)
let dr0_dh_suction = approximate_derivative_result(
|h| {
let density = estimate_density(self.fluid_id.as_str(), p_suction, h).unwrap_or(1.0);
let t_k =
estimate_temperature(self.fluid_id.as_str(), p_suction, h).unwrap_or(273.15);
let density = estimate_density(self.fluid_id.as_str(), p_suction, h)?;
let t_k = estimate_temperature(self.fluid_id.as_str(), p_suction, h)?;
self.mass_flow_rate(density, t_k, t_discharge_k, Some(state))
.map(|m| m.to_kg_per_s())
.unwrap_or(0.0)
.map_err(|e| ComponentError::CalculationFailed(e.to_string()))
},
h_suction,
1.0,
);
jacobian.add_entry(0, 1, dr0_dh_suction);
)?;
jacobian.add_entry(0, suc_h_idx, dr0_dh_suction);
// ∂r₀/∂h_discharge = 0 (mass flow doesn't depend on discharge enthalpy)
jacobian.add_entry(0, 2, 0.0);
// ∂r₀/∂Power = 0
jacobian.add_entry(0, 3, 0.0);
// Row 1: Energy residual
// ∂r₁/∂ṁ = -(h_discharge - h_suction) / η_mech
// Row 1: Energy residual r1 = power_calc × Δh / η_mech
// ∂r1/∂ṁ_suction = (h_discharge h_suction) / η_mech
let dr1_dm = -(h_discharge - h_suction) / self.mechanical_efficiency;
jacobian.add_entry(1, 0, dr1_dm);
jacobian.add_entry(1, suc_m_idx, dr1_dm);
// ∂r/∂h_suction - includes power derivative and mass flow term
let dr1_dh_suction = approximate_derivative(
// ∂r1/∂h_suction — numerical
let dr1_dh_suction = approximate_derivative_result(
|h| {
let t = estimate_temperature(self.fluid_id.as_str(), p_suction, h).unwrap_or(300.0);
let t = estimate_temperature(self.fluid_id.as_str(), p_suction, h)?;
let t_discharge =
estimate_temperature(self.fluid_id.as_str(), p_discharge, h_discharge)
.unwrap_or(350.0);
self.power_consumption_cooling(
estimate_temperature(self.fluid_id.as_str(), p_discharge, h_discharge)?;
Ok(self.power_consumption_cooling(
Temperature::from_kelvin(t),
Temperature::from_kelvin(t_discharge),
None,
)
))
},
h_suction,
1.0,
) + mass_flow_state / self.mechanical_efficiency;
jacobian.add_entry(1, 1, dr1_dh_suction);
)? + mass_flow_state / self.mechanical_efficiency;
jacobian.add_entry(1, suc_h_idx, dr1_dh_suction);
// ∂r/∂h_discharge - includes power derivative and mass flow term
let dr1_dh_discharge = approximate_derivative(
// ∂r1/∂h_discharge — numerical
let dr1_dh_discharge = approximate_derivative_result(
|h| {
let t_suction = estimate_temperature(self.fluid_id.as_str(), p_suction, h_suction)
.unwrap_or(300.0);
let t =
estimate_temperature(self.fluid_id.as_str(), p_discharge, h).unwrap_or(350.0);
self.power_consumption_cooling(
let t_suction = estimate_temperature(self.fluid_id.as_str(), p_suction, h_suction)?;
let t = estimate_temperature(self.fluid_id.as_str(), p_discharge, h)?;
Ok(self.power_consumption_cooling(
Temperature::from_kelvin(t_suction),
Temperature::from_kelvin(t),
None,
)
))
},
h_discharge,
1.0,
) - mass_flow_state / self.mechanical_efficiency;
jacobian.add_entry(1, 2, dr1_dh_discharge);
)? - mass_flow_state / self.mechanical_efficiency;
jacobian.add_entry(1, dis_h_idx, dr1_dh_discharge);
// ∂r₁/∂Power = -1
jacobian.add_entry(1, 3, -1.0);
// Calibration derivatives (Story 5.5)
if let Some(f_m_idx) = self.calib_indices.f_m {
// ∂r₀/∂f_m = ṁ_nominal
let density_suction =
estimate_density(self.fluid_id.as_str(), p_suction, h_suction).unwrap_or(1.0);
let m_nominal = self
.mass_flow_rate(density_suction, _t_suction_k, t_discharge_k, None)
.map(|m| m.to_kg_per_s())
.unwrap_or(0.0);
jacobian.add_entry(0, f_m_idx, m_nominal);
// Row 2: Mass conservation r2 = ṁ_discharge ṁ_suction (exact, CM1.3)
// CM1.4: omit when same_branch_m (trivial equation removed from n_equations).
if !self.same_branch_m {
jacobian.add_entry(2, dis_m_idx, 1.0);
jacobian.add_entry(2, suc_m_idx, -1.0);
}
if let Some(f_power_idx) = self.calib_indices.f_power {
// ∂r₁/∂f_power = Power_nominal
// Calibration derivatives (Story 5.5)
if let Some(z_flow_idx) = self.calib_indices.z_flow {
let density_suction = estimate_density(self.fluid_id.as_str(), p_suction, h_suction)?;
let m_nominal = self
.mass_flow_rate(density_suction, t_suction_k, t_discharge_k, None)
.map(|m| m.to_kg_per_s())
.map_err(|e| ComponentError::CalculationFailed(e.to_string()))?;
jacobian.add_entry(0, z_flow_idx, m_nominal);
}
if let Some(z_power_idx) = self.calib_indices.z_power {
let p_nominal = self.power_consumption_cooling(
Temperature::from_kelvin(_t_suction_k),
Temperature::from_kelvin(t_suction_k),
Temperature::from_kelvin(t_discharge_k),
None,
);
jacobian.add_entry(1, f_power_idx, p_nominal);
jacobian.add_entry(1, z_power_idx, p_nominal);
}
// ∂r0/∂f_etav (AHRI 540 only): ṁ_calc = f_m · f_etav · base with
// base = M1 · (1 (P_suc/P_dis)^(1/M2)) · ρ_suc · V_disp · N/60,
// so the derivative is exactly f_m · base.
if let Some(z_etav_idx) = self.calib_indices.z_etav {
if let CompressorModel::Ahri540(coeffs) = &self.model {
let density_suction =
estimate_density(self.fluid_id.as_str(), p_suction, h_suction)?;
let inverse_pressure_ratio = p_suction / p_discharge;
let base = coeffs.m1
* (1.0 - inverse_pressure_ratio.powf(1.0 / coeffs.m2))
* density_suction
* self.displacement_m3_per_rev
* (self.speed_rpm / 60.0);
let f_m = self
.calib_indices
.z_flow
.map(|idx| state[idx])
.unwrap_or(self.calib.z_flow);
jacobian.add_entry(0, z_etav_idx, f_m * base);
}
}
Ok(())
}
fn n_equations(&self) -> usize {
2 // Mass flow residual and energy residual
// CM1.4: when suction and discharge share the same ṁ state index (same
// series branch), the conservation residual r[2] = ṁ_dis ṁ_suc is
// trivially 0 and must be dropped to keep the system square.
if self.same_branch_m {
2
} else {
3
}
}
fn port_mass_flows(
&self,
state: &StateSlice,
) -> Result<Vec<entropyk_core::MassFlow>, ComponentError> {
if state.len() < 4 {
return Err(ComponentError::InvalidStateDimensions {
expected: 4,
actual: state.len(),
});
}
let m = entropyk_core::MassFlow::from_kg_per_s(state[0]);
let suc_m_idx = self.suction_m_idx.ok_or_else(|| {
ComponentError::InvalidState(
"Compressor mass-flow reporting requires a live suction mass-flow index"
.to_string(),
)
})?;
let m = entropyk_core::MassFlow::from_kg_per_s(state[suc_m_idx]);
// Suction (inlet), Discharge (outlet), Oil (no flow modeled yet)
Ok(vec![
m,
@@ -1391,7 +1475,10 @@ impl Component for Compressor<Connected> {
.with_param("speedRpm", self.speed_rpm)
.with_param("displacementM3PerRev", self.displacement_m3_per_rev)
.with_param("mechanicalEfficiency", self.mechanical_efficiency)
.with_param("calib", serde_json::to_value(&self.calib).unwrap_or(serde_json::Value::Null));
.with_param(
"calib",
serde_json::to_value(&self.calib).unwrap_or(serde_json::Value::Null),
);
match &self.model {
CompressorModel::Ahri540(c) => {
params = params
@@ -1414,7 +1501,10 @@ impl Component for Compressor<Connected> {
"massFlowCurve",
serde_json::to_value(&c.mass_flow_curve).unwrap_or(serde_json::Value::Null),
)
.with_param("powerCurve", serde_json::to_value(&c.power_curve).unwrap_or(serde_json::Value::Null));
.with_param(
"powerCurve",
serde_json::to_value(&c.power_curve).unwrap_or(serde_json::Value::Null),
);
}
}
params
@@ -1429,6 +1519,48 @@ impl Component for Compressor<Connected> {
false
}
}
fn set_calib_indices(&mut self, indices: entropyk_core::CalibIndices) {
// Without this override the solver's finalize() wiring was silently
// dropped (trait default is a no-op): dynamic calibration factors
// (f_m, f_power, f_etav) registered as bounded variables never reached
// compute_residuals/jacobian_entries, which already read
// `self.calib_indices` (mass_flow_rate / power_consumption_cooling).
self.calib_indices = indices;
}
fn measure_output(&self, kind: crate::MeasuredOutput, state: &StateSlice) -> Option<f64> {
use crate::MeasuredOutput;
match kind {
// Discharge gas temperature (DGT) so a controls[] loop can hold a
// maximum-DGT limit on the AHRI-540 compressor (like the
// IsentropicCompressor's liquid-injection loop).
MeasuredOutput::Temperature => {
let dis_h_idx = self.discharge_h_idx?;
if dis_h_idx >= state.len() {
return None;
}
let h_discharge = state[dis_h_idx];
let p_discharge = self.port_discharge.pressure().to_pascals();
if !h_discharge.is_finite() || p_discharge <= 0.0 {
return None;
}
estimate_temperature(self.fluid_id.as_str(), p_discharge, h_discharge)
.ok()
.filter(|t| t.is_finite())
}
// Refrigerant mass flow rate at the suction edge.
MeasuredOutput::MassFlowRate => {
let m_idx = self.suction_m_idx.or(self.discharge_m_idx)?;
if m_idx >= state.len() {
return None;
}
let m = state[m_idx];
m.is_finite().then_some(m)
}
_ => None,
}
}
}
use crate::state_machine::StateManageable;
@@ -1603,6 +1735,7 @@ fn estimate_temperature(
///
/// Uses a central difference approximation:
/// f'(x) ≈ (f(x + h) - f(x - h)) / (2h)
#[cfg(test)]
fn approximate_derivative<F>(f: F, x: f64, h: f64) -> f64
where
F: Fn(f64) -> f64,
@@ -1610,6 +1743,13 @@ where
(f(x + h) - f(x - h)) / (2.0 * h)
}
fn approximate_derivative_result<F>(f: F, x: f64, h: f64) -> Result<f64, ComponentError>
where
F: Fn(f64) -> Result<f64, ComponentError>,
{
Ok((f(x + h)? - f(x - h)?) / (2.0 * h))
}
#[cfg(test)]
mod tests {
use super::*;
@@ -1664,6 +1804,11 @@ mod tests {
fluid_id: FluidId::new("R134a"),
circuit_id: CircuitId::default(),
operational_state: OperationalState::default(),
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
}
}
@@ -1847,7 +1992,7 @@ mod tests {
.to_kg_per_s();
compressor.set_calib(Calib {
f_m: 1.1,
z_flow: 1.1,
..Calib::default()
});
let m_calib = compressor
@@ -1865,7 +2010,7 @@ mod tests {
let p_default = compressor.power_consumption_cooling(t_suction, t_discharge, None);
compressor.set_calib(Calib {
f_power: 1.1,
z_power: 1.1,
..Calib::default()
});
let p_calib = compressor.power_consumption_cooling(t_suction, t_discharge, None);
@@ -1884,7 +2029,7 @@ mod tests {
.to_kg_per_s();
compressor.set_calib(Calib {
f_etav: 0.9,
z_etav: 0.9,
..Calib::default()
});
let m_calib = compressor
@@ -1935,6 +2080,11 @@ mod tests {
fluid_id: FluidId::new("R134a"),
circuit_id: CircuitId::default(),
operational_state: OperationalState::default(),
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
};
@@ -2035,28 +2185,32 @@ mod tests {
#[test]
fn test_component_n_equations() {
let compressor = create_test_compressor();
assert_eq!(compressor.n_equations(), 2);
// CM1.3: 2 thermo + 1 mass-flow conservation = 3 equations
assert_eq!(compressor.n_equations(), 3);
}
#[test]
fn test_component_compute_residuals() {
let compressor = create_test_compressor();
let mut compressor = create_test_compressor();
compressor.set_system_context(0, &[(0, 0, 1), (3, 0, 2)]);
let state = vec![0.05, 400000.0, 450000.0, 3500.0];
let mut residuals = vec![0.0; 2];
let mut residuals = vec![0.0; 3];
let result = compressor.compute_residuals(&state, &mut residuals);
assert!(result.is_ok());
assert!(result.is_ok(), "jacobian error: {:?}", result);
// Verify residuals are calculated (actual values depend on fluid properties)
assert!(!residuals[0].is_nan());
assert!(!residuals[1].is_nan());
assert!(!residuals[2].is_nan());
}
#[test]
fn test_component_compute_residuals_wrong_size() {
let compressor = create_test_compressor();
let mut compressor = create_test_compressor();
compressor.set_system_context(0, &[(0, 0, 1), (3, 0, 2)]);
let state = vec![0.05, 400000.0, 450000.0, 3500.0];
let mut residuals = vec![0.0; 3]; // Wrong size
let mut residuals = vec![0.0; 2]; // Wrong size (n_equations is now 3)
let result = compressor.compute_residuals(&state, &mut residuals);
assert!(result.is_err());
@@ -2064,12 +2218,13 @@ mod tests {
#[test]
fn test_component_jacobian_entries() {
let compressor = create_test_compressor();
let mut compressor = create_test_compressor();
compressor.set_system_context(0, &[(0, 0, 1), (3, 0, 2)]);
let state = vec![0.05, 400000.0, 450000.0, 3500.0];
let mut jacobian = JacobianBuilder::new();
let result = compressor.jacobian_entries(&state, &mut jacobian);
assert!(result.is_ok());
assert!(result.is_ok(), "jacobian error: {:?}", result);
// Should have at least some entries
assert!(jacobian.len() > 0);
@@ -2136,6 +2291,11 @@ mod tests {
fluid_id: FluidId::new("R410A"),
circuit_id: CircuitId::default(),
operational_state: OperationalState::default(),
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
};
@@ -2185,6 +2345,11 @@ mod tests {
fluid_id: FluidId::new("R454B"),
circuit_id: CircuitId::default(),
operational_state: OperationalState::default(),
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
};
@@ -2238,6 +2403,11 @@ mod tests {
fluid_id: FluidId::new("R134a"),
circuit_id: CircuitId::default(),
operational_state: OperationalState::default(),
suction_m_idx: None,
suction_h_idx: None,
discharge_m_idx: None,
discharge_h_idx: None,
same_branch_m: false,
_state: PhantomData,
};