Files
Entropyk/bindings/python/src/components.rs

975 lines
28 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Python wrappers for Entropyk thermodynamic components.
//!
//! Components are wrapped with simplified Pythonic constructors.
//! Real thermodynamic components use the python_components module
//! with actual CoolProp-based physics.
use pyo3::exceptions::PyValueError;
use pyo3::prelude::*;
use entropyk_components::{
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, SystemState,
};
// =============================================================================
// Compressor - Real AHRI 540 Implementation
// =============================================================================
/// A compressor component using AHRI 540 performance model with real physics.
///
/// Uses CoolProp for thermodynamic property calculations.
///
/// Example::
///
/// comp = Compressor(
/// m1=0.85, m2=2.5,
/// m3=500.0, m4=1500.0, m5=-2.5, m6=1.8,
/// m7=600.0, m8=1600.0, m9=-3.0, m10=2.0,
/// speed_rpm=2900.0,
/// displacement=0.0001,
/// efficiency=0.85,
/// fluid="R134a",
/// )
#[pyclass(name = "Compressor", module = "entropyk")]
#[derive(Clone)]
pub struct PyCompressor {
pub(crate) inner: entropyk_components::PyCompressorReal,
}
#[pymethods]
impl PyCompressor {
/// Create a Compressor with AHRI 540 coefficients.
#[new]
#[pyo3(signature = (
m1=0.85, m2=2.5,
m3=500.0, m4=1500.0, m5=-2.5, m6=1.8,
m7=600.0, m8=1600.0, m9=-3.0, m10=2.0,
speed_rpm=2900.0,
displacement=0.0001,
efficiency=0.85,
fluid="R134a"
))]
#[allow(clippy::too_many_arguments)]
fn new(
m1: f64,
m2: f64,
m3: f64,
m4: f64,
m5: f64,
m6: f64,
m7: f64,
m8: f64,
m9: f64,
m10: f64,
speed_rpm: f64,
displacement: f64,
efficiency: f64,
fluid: &str,
) -> PyResult<Self> {
if speed_rpm <= 0.0 {
return Err(PyValueError::new_err("speed_rpm must be positive"));
}
if displacement <= 0.0 {
return Err(PyValueError::new_err("displacement must be positive"));
}
if !(0.0..=1.0).contains(&efficiency) {
return Err(PyValueError::new_err(
"efficiency must be between 0.0 and 1.0",
));
}
let inner =
entropyk_components::PyCompressorReal::new(fluid, speed_rpm, displacement, efficiency)
.with_coefficients(m1, m2, m3, m4, m5, m6, m7, m8, m9, m10);
Ok(PyCompressor { inner })
}
/// Speed in RPM.
#[getter]
fn speed(&self) -> f64 {
self.inner.speed_rpm
}
/// Isentropic efficiency (01).
#[getter]
fn efficiency_value(&self) -> f64 {
self.inner.efficiency
}
/// Fluid name.
#[getter]
fn fluid_name(&self) -> String {
self.inner.fluid.0.clone()
}
fn __repr__(&self) -> String {
format!(
"Compressor(speed={:.0} RPM, η={:.2}, fluid={})",
self.inner.speed_rpm, self.inner.efficiency, self.inner.fluid.0
)
}
}
impl PyCompressor {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(self.inner.clone())
}
}
// =============================================================================
// Condenser - Real Heat Exchanger with Water Side
// =============================================================================
/// A condenser with water-side heat transfer.
///
/// Uses ε-NTU method with CoolProp for refrigerant properties.
///
/// Example::
///
/// cond = Condenser(ua=5000.0, fluid="R134a", water_temp=30.0, water_flow=0.5)
#[pyclass(name = "Condenser", module = "entropyk")]
#[derive(Clone)]
pub struct PyCondenser {
pub(crate) ua: f64,
pub(crate) fluid: String,
pub(crate) water_temp: f64,
pub(crate) water_flow: f64,
}
#[pymethods]
impl PyCondenser {
#[new]
#[pyo3(signature = (ua=5000.0, fluid="R134a", water_temp=30.0, water_flow=0.5))]
fn new(ua: f64, fluid: &str, water_temp: f64, water_flow: f64) -> PyResult<Self> {
if ua <= 0.0 {
return Err(PyValueError::new_err("ua must be positive"));
}
if water_flow <= 0.0 {
return Err(PyValueError::new_err("water_flow must be positive"));
}
Ok(PyCondenser {
ua,
fluid: fluid.to_string(),
water_temp,
water_flow,
})
}
/// Thermal conductance UA in W/K.
#[getter]
fn ua_value(&self) -> f64 {
self.ua
}
fn __repr__(&self) -> String {
format!(
"Condenser(UA={:.1} W/K, fluid={}, water={:.1}°C)",
self.ua, self.fluid, self.water_temp
)
}
}
impl PyCondenser {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyHeatExchangerReal::condenser(
self.ua,
&self.fluid,
self.water_temp,
self.water_flow,
))
}
}
// =============================================================================
// Evaporator - Real Heat Exchanger with Water Side
// =============================================================================
/// An evaporator with water-side heat transfer.
///
/// Uses ε-NTU method with CoolProp for refrigerant properties.
///
/// Example::
///
/// evap = Evaporator(ua=3000.0, fluid="R134a", water_temp=12.0, water_flow=0.4)
#[pyclass(name = "Evaporator", module = "entropyk")]
#[derive(Clone)]
pub struct PyEvaporator {
pub(crate) ua: f64,
pub(crate) fluid: String,
pub(crate) water_temp: f64,
pub(crate) water_flow: f64,
}
#[pymethods]
impl PyEvaporator {
#[new]
#[pyo3(signature = (ua=3000.0, fluid="R134a", water_temp=12.0, water_flow=0.4))]
fn new(ua: f64, fluid: &str, water_temp: f64, water_flow: f64) -> PyResult<Self> {
if ua <= 0.0 {
return Err(PyValueError::new_err("ua must be positive"));
}
if water_flow <= 0.0 {
return Err(PyValueError::new_err("water_flow must be positive"));
}
Ok(PyEvaporator {
ua,
fluid: fluid.to_string(),
water_temp,
water_flow,
})
}
/// Thermal conductance UA in W/K.
#[getter]
fn ua_value(&self) -> f64 {
self.ua
}
fn __repr__(&self) -> String {
format!(
"Evaporator(UA={:.1} W/K, fluid={}, water={:.1}°C)",
self.ua, self.fluid, self.water_temp
)
}
}
impl PyEvaporator {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyHeatExchangerReal::evaporator(
self.ua,
&self.fluid,
self.water_temp,
self.water_flow,
))
}
}
// =============================================================================
// Economizer
// =============================================================================
/// An economizer (subcooler / internal heat exchanger) component.
#[pyclass(name = "Economizer", module = "entropyk")]
#[derive(Clone)]
pub struct PyEconomizer {
pub(crate) ua: f64,
}
#[pymethods]
impl PyEconomizer {
#[new]
#[pyo3(signature = (ua=2000.0))]
fn new(ua: f64) -> PyResult<Self> {
if ua <= 0.0 {
return Err(PyValueError::new_err("ua must be positive"));
}
Ok(PyEconomizer { ua })
}
fn __repr__(&self) -> String {
format!("Economizer(UA={:.1} W/K)", self.ua)
}
}
impl PyEconomizer {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::Economizer::new(self.ua))
}
}
// =============================================================================
// Expansion Valve - Real Isenthalpic
// =============================================================================
/// An expansion valve with isenthalpic throttling.
///
/// Example::
///
/// valve = ExpansionValve(fluid="R134a", opening=0.5)
#[pyclass(name = "ExpansionValve", module = "entropyk")]
#[derive(Clone)]
pub struct PyExpansionValve {
pub(crate) fluid: String,
pub(crate) opening: f64,
}
#[pymethods]
impl PyExpansionValve {
#[new]
#[pyo3(signature = (fluid="R134a", opening=0.5))]
fn new(fluid: &str, opening: f64) -> PyResult<Self> {
if !(0.0..=1.0).contains(&opening) {
return Err(PyValueError::new_err("opening must be between 0.0 and 1.0"));
}
Ok(PyExpansionValve {
fluid: fluid.to_string(),
opening,
})
}
/// Fluid name.
#[getter]
fn fluid_name(&self) -> &str {
&self.fluid
}
/// Valve opening (01).
#[getter]
fn opening_value(&self) -> f64 {
self.opening
}
fn __repr__(&self) -> String {
format!(
"ExpansionValve(fluid={}, opening={:.2})",
self.fluid, self.opening
)
}
}
impl PyExpansionValve {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyExpansionValveReal::new(
&self.fluid,
self.opening,
))
}
}
// =============================================================================
// Pipe - Real Pressure Drop
// =============================================================================
/// A pipe component with Darcy-Weisbach pressure drop.
#[pyclass(name = "Pipe", module = "entropyk")]
#[derive(Clone)]
pub struct PyPipe {
pub(crate) inner: entropyk_components::PyPipeReal,
}
#[pymethods]
impl PyPipe {
#[new]
#[pyo3(signature = (length=10.0, diameter=0.05, fluid="R134a"))]
fn new(length: f64, diameter: f64, fluid: &str) -> PyResult<Self> {
if length <= 0.0 {
return Err(PyValueError::new_err("length must be positive"));
}
if diameter <= 0.0 {
return Err(PyValueError::new_err("diameter must be positive"));
}
Ok(PyPipe {
inner: entropyk_components::PyPipeReal::new(length, diameter, fluid),
})
}
fn __repr__(&self) -> String {
format!(
"Pipe(L={:.2}m, D={:.4}m, fluid={})",
self.inner.length, self.inner.diameter, self.inner.fluid.0
)
}
}
impl PyPipe {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(self.inner.clone())
}
}
// =============================================================================
// Pump
// =============================================================================
/// A pump component for liquid flow.
#[pyclass(name = "Pump", module = "entropyk")]
#[derive(Clone)]
pub struct PyPump {
pub(crate) pressure_rise_pa: f64,
pub(crate) efficiency: f64,
}
#[pymethods]
impl PyPump {
#[new]
#[pyo3(signature = (pressure_rise_pa=200000.0, efficiency=0.75))]
fn new(pressure_rise_pa: f64, efficiency: f64) -> PyResult<Self> {
if pressure_rise_pa <= 0.0 {
return Err(PyValueError::new_err("pressure_rise_pa must be positive"));
}
if !(0.0..=1.0).contains(&efficiency) {
return Err(PyValueError::new_err(
"efficiency must be between 0.0 and 1.0",
));
}
Ok(PyPump {
pressure_rise_pa,
efficiency,
})
}
fn __repr__(&self) -> String {
format!(
"Pump(ΔP={:.0} Pa, η={:.2})",
self.pressure_rise_pa, self.efficiency
)
}
}
impl PyPump {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyPipeReal::new(1.0, 0.05, "Water"))
}
}
// =============================================================================
// Fan
// =============================================================================
/// A fan component for air flow.
#[pyclass(name = "Fan", module = "entropyk")]
#[derive(Clone)]
pub struct PyFan {
pub(crate) pressure_rise_pa: f64,
pub(crate) efficiency: f64,
}
#[pymethods]
impl PyFan {
#[new]
#[pyo3(signature = (pressure_rise_pa=500.0, efficiency=0.65))]
fn new(pressure_rise_pa: f64, efficiency: f64) -> PyResult<Self> {
if pressure_rise_pa <= 0.0 {
return Err(PyValueError::new_err("pressure_rise_pa must be positive"));
}
if !(0.0..=1.0).contains(&efficiency) {
return Err(PyValueError::new_err(
"efficiency must be between 0.0 and 1.0",
));
}
Ok(PyFan {
pressure_rise_pa,
efficiency,
})
}
fn __repr__(&self) -> String {
format!(
"Fan(ΔP={:.0} Pa, η={:.2})",
self.pressure_rise_pa, self.efficiency
)
}
}
impl PyFan {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyPipeReal::new(1.0, 0.1, "Air"))
}
}
// =============================================================================
// FlowSplitter
// =============================================================================
/// A flow splitter that divides a stream into branches.
#[pyclass(name = "FlowSplitter", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowSplitter {
pub(crate) n_outlets: usize,
}
#[pymethods]
impl PyFlowSplitter {
#[new]
#[pyo3(signature = (n_outlets=2))]
fn new(n_outlets: usize) -> PyResult<Self> {
if n_outlets < 2 {
return Err(PyValueError::new_err("n_outlets must be >= 2"));
}
Ok(PyFlowSplitter { n_outlets })
}
fn __repr__(&self) -> String {
format!("FlowSplitter(n_outlets={})", self.n_outlets)
}
}
impl PyFlowSplitter {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyFlowSplitterReal::new(self.n_outlets))
}
}
// =============================================================================
// FlowMerger
// =============================================================================
/// A flow merger that combines branches into one.
#[pyclass(name = "FlowMerger", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowMerger {
pub(crate) n_inlets: usize,
}
#[pymethods]
impl PyFlowMerger {
#[new]
#[pyo3(signature = (n_inlets=2))]
fn new(n_inlets: usize) -> PyResult<Self> {
if n_inlets < 2 {
return Err(PyValueError::new_err("n_inlets must be >= 2"));
}
Ok(PyFlowMerger { n_inlets })
}
fn __repr__(&self) -> String {
format!("FlowMerger(n_inlets={})", self.n_inlets)
}
}
impl PyFlowMerger {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyFlowMergerReal::new(self.n_inlets))
}
}
// =============================================================================
// FlowSource - Real Boundary Condition
// =============================================================================
/// A boundary condition representing a mass flow source.
///
/// Example::
///
/// source = FlowSource(pressure_pa=101325.0, temperature_k=300.0, fluid="Water")
#[pyclass(name = "FlowSource", module = "entropyk")]
#[derive(Clone)]
pub struct PyFlowSource {
pub(crate) pressure_pa: f64,
pub(crate) temperature_k: f64,
pub(crate) fluid: String,
}
#[pymethods]
impl PyFlowSource {
#[new]
#[pyo3(signature = (pressure_pa=101325.0, temperature_k=300.0, fluid="Water"))]
fn new(py: Python<'_>, pressure_pa: f64, temperature_k: f64, fluid: &str) -> PyResult<Self> {
let warnings = py.import_bound("warnings")?;
warnings.call_method1(
"warn",
(
"FlowSource is deprecated. Use RefrigerantSource, BrineSource, or AirSource instead.",
py.get_type_bound::<pyo3::exceptions::PyDeprecationWarning>(),
2,
),
)?;
if pressure_pa <= 0.0 {
return Err(PyValueError::new_err("pressure_pa must be positive"));
}
if temperature_k <= 0.0 {
return Err(PyValueError::new_err("temperature_k must be positive"));
}
Ok(PyFlowSource {
pressure_pa,
temperature_k,
fluid: fluid.to_string(),
})
}
fn __repr__(&self) -> String {
format!(
"FlowSource(P={:.0} Pa, T={:.1} K, fluid={})",
self.pressure_pa, self.temperature_k, self.fluid
)
}
}
impl PyFlowSource {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyFlowSourceReal::new(
&self.fluid,
self.pressure_pa,
self.temperature_k,
))
}
}
// =============================================================================
// FlowSink
// =============================================================================
/// A boundary condition representing a mass flow sink.
#[pyclass(name = "FlowSink", module = "entropyk")]
#[derive(Clone, Default)]
pub struct PyFlowSink;
#[pymethods]
impl PyFlowSink {
#[new]
fn new(py: Python<'_>) -> PyResult<Self> {
let warnings = py.import_bound("warnings")?;
warnings.call_method1(
"warn",
(
"FlowSink is deprecated. Use RefrigerantSink, BrineSink, or AirSink instead.",
py.get_type_bound::<pyo3::exceptions::PyDeprecationWarning>(),
2,
),
)?;
Ok(PyFlowSink)
}
fn __repr__(&self) -> String {
"FlowSink()".to_string()
}
}
impl PyFlowSink {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyFlowSinkReal::default())
}
}
// =============================================================================
// OperationalState
// =============================================================================
/// Operational state of a component: On, Off, or Bypass.
#[pyclass(name = "OperationalState", module = "entropyk")]
#[derive(Clone)]
pub struct PyOperationalState {
pub(crate) inner: entropyk_components::OperationalState,
}
#[pymethods]
impl PyOperationalState {
/// Create an OperationalState. Valid values: "on", "off", "bypass".
#[new]
fn new(state: &str) -> PyResult<Self> {
let inner = match state.to_lowercase().as_str() {
"on" => entropyk_components::OperationalState::On,
"off" => entropyk_components::OperationalState::Off,
"bypass" => entropyk_components::OperationalState::Bypass,
_ => {
return Err(PyValueError::new_err(
"state must be one of: 'on', 'off', 'bypass'",
))
}
};
Ok(PyOperationalState { inner })
}
fn __repr__(&self) -> String {
format!("OperationalState({:?})", self.inner)
}
fn __str__(&self) -> String {
format!("{:?}", self.inner)
}
fn __eq__(&self, other: &PyOperationalState) -> bool {
self.inner == other.inner
}
}
// =============================================================================
// Refrigerant Boundary Conditions
// =============================================================================
/// A boundary condition representing a refrigerant mass flow source.
#[pyclass(name = "RefrigerantSource", module = "entropyk")]
#[derive(Clone)]
pub struct PyRefrigerantSource {
pub(crate) fluid: String,
pub(crate) p_set_pa: f64,
pub(crate) quality: f64,
}
#[pymethods]
impl PyRefrigerantSource {
#[new]
#[pyo3(signature = (fluid="R410A", pressure_pa=101325.0, quality=1.0))]
fn new(fluid: &str, pressure_pa: f64, quality: f64) -> PyResult<Self> {
if pressure_pa <= 0.0 {
return Err(PyValueError::new_err("pressure_pa must be positive"));
}
if !(0.0..=1.0).contains(&quality) {
return Err(PyValueError::new_err("quality must be between 0.0 and 1.0"));
}
Ok(PyRefrigerantSource {
fluid: fluid.to_string(),
p_set_pa: pressure_pa,
quality,
})
}
fn __repr__(&self) -> String {
format!(
"RefrigerantSource(fluid={}, P={:.0} Pa, q={:.2})",
self.fluid, self.p_set_pa, self.quality
)
}
}
impl PyRefrigerantSource {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyRefrigerantSourceReal::new(&self.fluid, self.p_set_pa, self.quality))
}
}
/// A boundary condition representing a refrigerant mass flow sink.
#[pyclass(name = "RefrigerantSink", module = "entropyk")]
#[derive(Clone)]
pub struct PyRefrigerantSink {
pub(crate) fluid: String,
pub(crate) p_back_pa: f64,
pub(crate) quality_opt: Option<f64>,
}
#[pymethods]
impl PyRefrigerantSink {
#[new]
#[pyo3(signature = (fluid="R410A", p_back_pa=101325.0, quality=None))]
fn new(fluid: &str, p_back_pa: f64, quality: Option<f64>) -> PyResult<Self> {
if p_back_pa <= 0.0 {
return Err(PyValueError::new_err("p_back_pa must be positive"));
}
if let Some(q) = quality {
if !(0.0..=1.0).contains(&q) {
return Err(PyValueError::new_err("quality must be between 0.0 and 1.0"));
}
}
Ok(PyRefrigerantSink {
fluid: fluid.to_string(),
p_back_pa,
quality_opt: quality,
})
}
fn __repr__(&self) -> String {
format!(
"RefrigerantSink(fluid={}, P_back={:.0} Pa, q={:?})",
self.fluid, self.p_back_pa, self.quality_opt
)
}
}
impl PyRefrigerantSink {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyRefrigerantSinkReal::new(&self.fluid, self.p_back_pa, self.quality_opt))
}
}
// =============================================================================
// Brine Boundary Conditions
// =============================================================================
/// A boundary condition representing a brine mass flow source.
#[pyclass(name = "BrineSource", module = "entropyk")]
#[derive(Clone)]
pub struct PyBrineSource {
pub(crate) fluid: String,
pub(crate) concentration: f64,
pub(crate) temperature_k: f64,
pub(crate) pressure_pa: f64,
}
#[pymethods]
impl PyBrineSource {
#[new]
#[pyo3(signature = (fluid="Water", concentration=0.0, temperature_k=300.0, pressure_pa=101325.0))]
fn new(fluid: &str, concentration: f64, temperature_k: f64, pressure_pa: f64) -> PyResult<Self> {
if pressure_pa <= 0.0 {
return Err(PyValueError::new_err("pressure_pa must be positive"));
}
if temperature_k <= 0.0 {
return Err(PyValueError::new_err("temperature_k must be positive"));
}
if !(0.0..=1.0).contains(&concentration) {
return Err(PyValueError::new_err("concentration must be between 0.0 and 1.0"));
}
Ok(PyBrineSource {
fluid: fluid.to_string(),
concentration,
temperature_k,
pressure_pa,
})
}
fn __repr__(&self) -> String {
format!(
"BrineSource(fluid={}, c={:.2}, T={:.1} K, P={:.0} Pa)",
self.fluid, self.concentration, self.temperature_k, self.pressure_pa
)
}
}
impl PyBrineSource {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyBrineSourceReal::new(&self.fluid, self.concentration, self.temperature_k, self.pressure_pa))
}
}
/// A boundary condition representing a brine mass flow sink.
#[pyclass(name = "BrineSink", module = "entropyk")]
#[derive(Clone)]
pub struct PyBrineSink {
pub(crate) p_back_pa: f64,
}
#[pymethods]
impl PyBrineSink {
#[new]
#[pyo3(signature = (p_back_pa=101325.0))]
fn new(p_back_pa: f64) -> PyResult<Self> {
if p_back_pa <= 0.0 {
return Err(PyValueError::new_err("p_back_pa must be positive"));
}
Ok(PyBrineSink { p_back_pa })
}
fn __repr__(&self) -> String {
format!("BrineSink(P_back={:.0} Pa)", self.p_back_pa)
}
}
impl PyBrineSink {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyBrineSinkReal::new(self.p_back_pa))
}
}
// =============================================================================
// Air Boundary Conditions
// =============================================================================
/// A boundary condition representing an air mass flow source.
#[pyclass(name = "AirSource", module = "entropyk")]
#[derive(Clone)]
pub struct PyAirSource {
pub(crate) temperature_k: f64,
pub(crate) relative_humidity: f64,
pub(crate) pressure_pa: f64,
}
#[pymethods]
impl PyAirSource {
#[new]
#[pyo3(signature = (temperature_k=300.0, relative_humidity=0.5, pressure_pa=101325.0))]
fn new(temperature_k: f64, relative_humidity: f64, pressure_pa: f64) -> PyResult<Self> {
if pressure_pa <= 0.0 {
return Err(PyValueError::new_err("pressure_pa must be positive"));
}
if temperature_k <= 0.0 {
return Err(PyValueError::new_err("temperature_k must be positive"));
}
if !(0.0..=1.0).contains(&relative_humidity) {
return Err(PyValueError::new_err("relative_humidity must be between 0.0 and 1.0"));
}
Ok(PyAirSource {
temperature_k,
relative_humidity,
pressure_pa,
})
}
fn __repr__(&self) -> String {
format!(
"AirSource(T={:.1} K, RH={:.2}, P={:.0} Pa)",
self.temperature_k, self.relative_humidity, self.pressure_pa
)
}
}
impl PyAirSource {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyAirSourceReal::new(self.temperature_k, self.relative_humidity, self.pressure_pa))
}
}
/// A boundary condition representing an air mass flow sink.
#[pyclass(name = "AirSink", module = "entropyk")]
#[derive(Clone)]
pub struct PyAirSink {
pub(crate) p_back_pa: f64,
}
#[pymethods]
impl PyAirSink {
#[new]
#[pyo3(signature = (p_back_pa=101325.0))]
fn new(p_back_pa: f64) -> PyResult<Self> {
if p_back_pa <= 0.0 {
return Err(PyValueError::new_err("p_back_pa must be positive"));
}
Ok(PyAirSink { p_back_pa })
}
fn __repr__(&self) -> String {
format!("AirSink(P_back={:.0} Pa)", self.p_back_pa)
}
}
impl PyAirSink {
pub(crate) fn build(&self) -> Box<dyn Component> {
Box::new(entropyk_components::PyAirSinkReal::new(self.p_back_pa))
}
}
// =============================================================================
// Component enum for type-erasure
// =============================================================================
/// Internal enum to hold any Python component wrapper.
#[derive(Clone)]
pub(crate) enum AnyPyComponent {
Compressor(PyCompressor),
Condenser(PyCondenser),
Evaporator(PyEvaporator),
Economizer(PyEconomizer),
ExpansionValve(PyExpansionValve),
Pipe(PyPipe),
Pump(PyPump),
Fan(PyFan),
FlowSplitter(PyFlowSplitter),
FlowMerger(PyFlowMerger),
FlowSource(PyFlowSource),
FlowSink(PyFlowSink),
RefrigerantSource(PyRefrigerantSource),
RefrigerantSink(PyRefrigerantSink),
BrineSource(PyBrineSource),
BrineSink(PyBrineSink),
AirSource(PyAirSource),
AirSink(PyAirSink),
}
impl AnyPyComponent {
/// Build the Rust component to insert into a System.
pub(crate) fn build(&self) -> Box<dyn Component> {
match self {
AnyPyComponent::Compressor(c) => c.build(),
AnyPyComponent::Condenser(c) => c.build(),
AnyPyComponent::Evaporator(c) => c.build(),
AnyPyComponent::Economizer(c) => c.build(),
AnyPyComponent::ExpansionValve(c) => c.build(),
AnyPyComponent::Pipe(c) => c.build(),
AnyPyComponent::Pump(c) => c.build(),
AnyPyComponent::Fan(c) => c.build(),
AnyPyComponent::FlowSplitter(c) => c.build(),
AnyPyComponent::FlowMerger(c) => c.build(),
AnyPyComponent::FlowSource(c) => c.build(),
AnyPyComponent::FlowSink(c) => c.build(),
AnyPyComponent::RefrigerantSource(c) => c.build(),
AnyPyComponent::RefrigerantSink(c) => c.build(),
AnyPyComponent::BrineSource(c) => c.build(),
AnyPyComponent::BrineSink(c) => c.build(),
AnyPyComponent::AirSource(c) => c.build(),
AnyPyComponent::AirSink(c) => c.build(),
}
}
}