975 lines
28 KiB
Rust
975 lines
28 KiB
Rust
//! 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 (0–1).
|
||
#[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 (0–1).
|
||
#[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(),
|
||
}
|
||
}
|
||
}
|