feat(python): implement python bindings for all components and solvers
This commit is contained in:
@@ -382,6 +382,8 @@ pub struct Compressor<State> {
|
||||
mechanical_efficiency: f64,
|
||||
/// Calibration factors: ṁ_eff = f_m × ṁ_nominal, Ẇ_eff = f_power × Ẇ_nominal, etc.
|
||||
calib: Calib,
|
||||
/// Calibration indices to extract factors dynamically from SystemState
|
||||
calib_indices: entropyk_core::CalibIndices,
|
||||
/// Fluid identifier for density lookups
|
||||
fluid_id: FluidId,
|
||||
/// Circuit identifier for multi-circuit machines (FR9)
|
||||
@@ -553,7 +555,9 @@ impl Compressor<Disconnected> {
|
||||
displacement_m3_per_rev,
|
||||
mechanical_efficiency,
|
||||
calib: Calib::default(),
|
||||
calib_indices: entropyk_core::CalibIndices::default(),
|
||||
fluid_id,
|
||||
|
||||
circuit_id: CircuitId::default(), // Default circuit
|
||||
operational_state: OperationalState::default(), // Default to On
|
||||
_state: PhantomData,
|
||||
@@ -708,6 +712,7 @@ impl Compressor<Connected> {
|
||||
density_suction: f64,
|
||||
sst_k: f64,
|
||||
sdt_k: f64,
|
||||
state: Option<&SystemState>,
|
||||
) -> Result<MassFlow, ComponentError> {
|
||||
if density_suction < 0.0 {
|
||||
return Err(ComponentError::InvalidState(
|
||||
@@ -762,7 +767,12 @@ impl Compressor<Connected> {
|
||||
};
|
||||
|
||||
// Apply calibration: ṁ_eff = f_m × ṁ_nominal
|
||||
Ok(MassFlow::from_kg_per_s(mass_flow_kg_per_s * self.calib.f_m))
|
||||
let f_m = if let Some(st) = state {
|
||||
self.calib_indices.f_m.map(|idx| st[idx]).unwrap_or(self.calib.f_m)
|
||||
} else {
|
||||
self.calib.f_m
|
||||
};
|
||||
Ok(MassFlow::from_kg_per_s(mass_flow_kg_per_s * f_m))
|
||||
}
|
||||
|
||||
/// Calculates the power consumption (cooling mode).
|
||||
@@ -783,6 +793,7 @@ impl Compressor<Connected> {
|
||||
&self,
|
||||
t_suction: Temperature,
|
||||
t_discharge: Temperature,
|
||||
state: Option<&SystemState>,
|
||||
) -> f64 {
|
||||
let power_nominal = match &self.model {
|
||||
CompressorModel::Ahri540(coeffs) => {
|
||||
@@ -798,7 +809,12 @@ impl Compressor<Connected> {
|
||||
}
|
||||
};
|
||||
// Ẇ_eff = f_power × Ẇ_nominal
|
||||
power_nominal * self.calib.f_power
|
||||
let f_power = if let Some(st) = state {
|
||||
self.calib_indices.f_power.map(|idx| st[idx]).unwrap_or(self.calib.f_power)
|
||||
} else {
|
||||
self.calib.f_power
|
||||
};
|
||||
power_nominal * f_power
|
||||
}
|
||||
|
||||
/// Calculates the power consumption (heating mode).
|
||||
@@ -819,6 +835,7 @@ impl Compressor<Connected> {
|
||||
&self,
|
||||
t_suction: Temperature,
|
||||
t_discharge: Temperature,
|
||||
state: Option<&SystemState>,
|
||||
) -> f64 {
|
||||
let power_nominal = match &self.model {
|
||||
CompressorModel::Ahri540(coeffs) => {
|
||||
@@ -835,7 +852,12 @@ impl Compressor<Connected> {
|
||||
}
|
||||
};
|
||||
// Ẇ_eff = f_power × Ẇ_nominal
|
||||
power_nominal * self.calib.f_power
|
||||
let f_power = if let Some(st) = state {
|
||||
self.calib_indices.f_power.map(|idx| st[idx]).unwrap_or(self.calib.f_power)
|
||||
} else {
|
||||
self.calib.f_power
|
||||
};
|
||||
power_nominal * f_power
|
||||
}
|
||||
|
||||
/// Calculates the cooling capacity.
|
||||
@@ -1049,13 +1071,14 @@ impl Component for Compressor<Connected> {
|
||||
// In the future, this will come from the fluid property backend
|
||||
let density_suction = estimate_density(self.fluid_id.as_str(), p_suction, h_suction)?;
|
||||
let mass_flow_calc = self
|
||||
.mass_flow_rate(density_suction, t_suction_k, t_discharge_k)?
|
||||
.mass_flow_rate(density_suction, t_suction_k, t_discharge_k, Some(state))?
|
||||
.to_kg_per_s();
|
||||
|
||||
// Calculate power consumption
|
||||
let power_calc = self.power_consumption_cooling(
|
||||
Temperature::from_kelvin(t_suction_k),
|
||||
Temperature::from_kelvin(t_discharge_k),
|
||||
Some(state)
|
||||
);
|
||||
|
||||
// Residual 0: Mass flow continuity
|
||||
@@ -1109,7 +1132,7 @@ impl Component for Compressor<Connected> {
|
||||
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);
|
||||
self.mass_flow_rate(density, t_k, t_discharge_k)
|
||||
self.mass_flow_rate(density, t_k, t_discharge_k, Some(state))
|
||||
.map(|m| m.to_kg_per_s())
|
||||
.unwrap_or(0.0)
|
||||
},
|
||||
@@ -1139,6 +1162,7 @@ impl Component for Compressor<Connected> {
|
||||
self.power_consumption_cooling(
|
||||
Temperature::from_kelvin(t),
|
||||
Temperature::from_kelvin(t_discharge),
|
||||
None
|
||||
)
|
||||
},
|
||||
h_suction,
|
||||
@@ -1156,6 +1180,7 @@ impl Component for Compressor<Connected> {
|
||||
self.power_consumption_cooling(
|
||||
Temperature::from_kelvin(t_suction),
|
||||
Temperature::from_kelvin(t),
|
||||
None
|
||||
)
|
||||
},
|
||||
h_discharge,
|
||||
@@ -1166,6 +1191,25 @@ impl Component for Compressor<Connected> {
|
||||
// ∂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);
|
||||
}
|
||||
|
||||
if let Some(f_power_idx) = self.calib_indices.f_power {
|
||||
// ∂r₁/∂f_power = Power_nominal
|
||||
let p_nominal = self.power_consumption_cooling(
|
||||
Temperature::from_kelvin(_t_suction_k),
|
||||
Temperature::from_kelvin(t_discharge_k),
|
||||
None
|
||||
);
|
||||
jacobian.add_entry(1, f_power_idx, p_nominal);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1390,6 +1434,7 @@ mod tests {
|
||||
displacement_m3_per_rev: 0.0001,
|
||||
mechanical_efficiency: 0.85,
|
||||
calib: Calib::default(),
|
||||
calib_indices: entropyk_core::CalibIndices::default(),
|
||||
fluid_id: FluidId::new("R134a"),
|
||||
circuit_id: CircuitId::default(),
|
||||
operational_state: OperationalState::default(),
|
||||
@@ -1548,7 +1593,7 @@ mod tests {
|
||||
let t_discharge_k = 318.15; // 45°C in Kelvin
|
||||
|
||||
let mass_flow = compressor
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k)
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k, None)
|
||||
.unwrap();
|
||||
|
||||
// Verify mass flow is positive
|
||||
@@ -1571,7 +1616,7 @@ mod tests {
|
||||
let t_suction_k = 278.15; // 5°C in Kelvin
|
||||
let t_discharge_k = 318.15; // 45°C in Kelvin
|
||||
let m_default = compressor
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k)
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k, None)
|
||||
.unwrap()
|
||||
.to_kg_per_s();
|
||||
|
||||
@@ -1580,7 +1625,7 @@ mod tests {
|
||||
..Calib::default()
|
||||
});
|
||||
let m_calib = compressor
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k)
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k, None)
|
||||
.unwrap()
|
||||
.to_kg_per_s();
|
||||
assert_relative_eq!(m_calib / m_default, 1.1, epsilon = 1e-10);
|
||||
@@ -1591,13 +1636,13 @@ mod tests {
|
||||
let mut compressor = create_test_compressor();
|
||||
let t_suction = Temperature::from_celsius(5.0);
|
||||
let t_discharge = Temperature::from_celsius(45.0);
|
||||
let p_default = compressor.power_consumption_cooling(t_suction, t_discharge);
|
||||
let p_default = compressor.power_consumption_cooling(t_suction, t_discharge, None);
|
||||
|
||||
compressor.set_calib(Calib {
|
||||
f_power: 1.1,
|
||||
..Calib::default()
|
||||
});
|
||||
let p_calib = compressor.power_consumption_cooling(t_suction, t_discharge);
|
||||
let p_calib = compressor.power_consumption_cooling(t_suction, t_discharge, None);
|
||||
assert_relative_eq!(p_calib / p_default, 1.1, epsilon = 1e-10);
|
||||
}
|
||||
|
||||
@@ -1606,7 +1651,7 @@ mod tests {
|
||||
let compressor = create_test_compressor();
|
||||
let t_suction_k = 278.15; // 5°C in Kelvin
|
||||
let t_discharge_k = 318.15; // 45°C in Kelvin
|
||||
let result = compressor.mass_flow_rate(-10.0, t_suction_k, t_discharge_k);
|
||||
let result = compressor.mass_flow_rate(-10.0, t_suction_k, t_discharge_k, None);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -1637,6 +1682,7 @@ mod tests {
|
||||
displacement_m3_per_rev: 0.0001,
|
||||
mechanical_efficiency: 0.85,
|
||||
calib: Calib::default(),
|
||||
calib_indices: entropyk_core::CalibIndices::default(),
|
||||
fluid_id: FluidId::new("R134a"),
|
||||
circuit_id: CircuitId::default(),
|
||||
operational_state: OperationalState::default(),
|
||||
@@ -1645,7 +1691,7 @@ mod tests {
|
||||
|
||||
let t_suction_k = 278.15; // 5°C in Kelvin
|
||||
let t_discharge_k = 318.15; // 45°C in Kelvin
|
||||
let result = compressor.mass_flow_rate(20.0, t_suction_k, t_discharge_k);
|
||||
let result = compressor.mass_flow_rate(20.0, t_suction_k, t_discharge_k, None);
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
@@ -1655,7 +1701,7 @@ mod tests {
|
||||
let t_suction = Temperature::from_celsius(5.0);
|
||||
let t_discharge = Temperature::from_celsius(45.0);
|
||||
|
||||
let power = compressor.power_consumption_cooling(t_suction, t_discharge);
|
||||
let power = compressor.power_consumption_cooling(t_suction, t_discharge, None);
|
||||
|
||||
// Verify power is positive
|
||||
assert!(power > 0.0);
|
||||
@@ -1677,7 +1723,7 @@ mod tests {
|
||||
let t_suction = Temperature::from_celsius(5.0);
|
||||
let t_discharge = Temperature::from_celsius(45.0);
|
||||
|
||||
let power = compressor.power_consumption_heating(t_suction, t_discharge);
|
||||
let power = compressor.power_consumption_heating(t_suction, t_discharge, None);
|
||||
|
||||
// Verify calculation: M7 + M8 * PR + M9 * T_suction + M10 * T_discharge
|
||||
// Using 6.0/3.5 pressure ratio from create_test_compressor
|
||||
@@ -1837,6 +1883,7 @@ mod tests {
|
||||
displacement_m3_per_rev: 0.00008,
|
||||
mechanical_efficiency: 0.88,
|
||||
calib: Calib::default(),
|
||||
calib_indices: entropyk_core::CalibIndices::default(),
|
||||
fluid_id: FluidId::new("R410A"),
|
||||
circuit_id: CircuitId::default(),
|
||||
operational_state: OperationalState::default(),
|
||||
@@ -1847,13 +1894,13 @@ mod tests {
|
||||
let t_suction_k = 283.15; // 10°C in Kelvin
|
||||
let t_discharge_k = 323.15; // 50°C in Kelvin
|
||||
let mass_flow = compressor
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k)
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k, None)
|
||||
.unwrap();
|
||||
assert!(mass_flow.to_kg_per_s() > 0.0);
|
||||
|
||||
let t_suction = Temperature::from_celsius(10.0);
|
||||
let t_discharge = Temperature::from_celsius(50.0);
|
||||
let power = compressor.power_consumption_cooling(t_suction, t_discharge);
|
||||
let power = compressor.power_consumption_cooling(t_suction, t_discharge, None);
|
||||
assert!(power > 0.0);
|
||||
}
|
||||
|
||||
@@ -1885,6 +1932,7 @@ mod tests {
|
||||
displacement_m3_per_rev: 0.00008,
|
||||
mechanical_efficiency: 0.88,
|
||||
calib: Calib::default(),
|
||||
calib_indices: entropyk_core::CalibIndices::default(),
|
||||
fluid_id: FluidId::new("R454B"),
|
||||
circuit_id: CircuitId::default(),
|
||||
operational_state: OperationalState::default(),
|
||||
@@ -1896,13 +1944,13 @@ mod tests {
|
||||
let t_suction_k = 283.15; // 10°C in Kelvin
|
||||
let t_discharge_k = 323.15; // 50°C in Kelvin
|
||||
let mass_flow = compressor
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k)
|
||||
.mass_flow_rate(density, t_suction_k, t_discharge_k, None)
|
||||
.unwrap();
|
||||
assert!(mass_flow.to_kg_per_s() > 0.0);
|
||||
|
||||
let t_suction = Temperature::from_celsius(10.0);
|
||||
let t_discharge = Temperature::from_celsius(50.0);
|
||||
let power = compressor.power_consumption_cooling(t_suction, t_discharge);
|
||||
let power = compressor.power_consumption_cooling(t_suction, t_discharge, None);
|
||||
assert!(power > 0.0);
|
||||
}
|
||||
|
||||
@@ -1937,6 +1985,7 @@ mod tests {
|
||||
displacement_m3_per_rev: 0.0001,
|
||||
mechanical_efficiency: 0.85,
|
||||
calib: Calib::default(),
|
||||
calib_indices: entropyk_core::CalibIndices::default(),
|
||||
fluid_id: FluidId::new("R134a"),
|
||||
circuit_id: CircuitId::default(),
|
||||
operational_state: OperationalState::default(),
|
||||
@@ -1948,7 +1997,7 @@ mod tests {
|
||||
let t_discharge_k = 323.15; // 50°C in Kelvin
|
||||
// With high pressure ratio, volumetric efficiency might be negative
|
||||
// depending on M2 value
|
||||
let result = compressor.mass_flow_rate(density, t_suction_k, t_discharge_k);
|
||||
let result = compressor.mass_flow_rate(density, t_suction_k, t_discharge_k, None);
|
||||
// This may fail due to negative volumetric efficiency
|
||||
// which is expected behavior
|
||||
if result.is_ok() {
|
||||
|
||||
Reference in New Issue
Block a user