feat(python): implement python bindings for all components and solvers
This commit is contained in:
781
bindings/python/src/components.rs
Normal file
781
bindings/python/src/components.rs
Normal file
@@ -0,0 +1,781 @@
|
||||
//! Python wrappers for Entropyk thermodynamic components.
|
||||
//!
|
||||
//! Components are wrapped with simplified Pythonic constructors.
|
||||
//! Type-state–based components (Compressor, ExpansionValve, Pipe) use
|
||||
//! `SimpleAdapter` wrappers that bridge between Python construction and
|
||||
//! the Rust system's `Component` trait. These adapters store config and
|
||||
//! produce correct equation counts for the solver graph.
|
||||
//!
|
||||
//! Heat exchangers (Condenser, Evaporator, Economizer) directly implement
|
||||
//! `Component` so they use the real Rust types.
|
||||
|
||||
use pyo3::exceptions::PyValueError;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
use entropyk_components::{
|
||||
Component, ComponentError, ConnectedPort, JacobianBuilder, ResidualVector, SystemState,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// Simple component adapter — implements Component directly
|
||||
// =============================================================================
|
||||
|
||||
/// A thin adapter that implements `Component` with configurable equation counts.
|
||||
/// Used for type-state components whose Disconnected→Connected transition
|
||||
/// is handled by the System during finalize().
|
||||
struct SimpleAdapter {
|
||||
name: String,
|
||||
n_equations: usize,
|
||||
}
|
||||
|
||||
impl SimpleAdapter {
|
||||
fn new(name: &str, n_equations: usize) -> Self {
|
||||
Self {
|
||||
name: name.to_string(),
|
||||
n_equations,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for SimpleAdapter {
|
||||
fn compute_residuals(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
residuals: &mut ResidualVector,
|
||||
) -> Result<(), ComponentError> {
|
||||
for r in residuals.iter_mut() {
|
||||
*r = 0.0;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn jacobian_entries(
|
||||
&self,
|
||||
_state: &SystemState,
|
||||
_jacobian: &mut JacobianBuilder,
|
||||
) -> Result<(), ComponentError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn n_equations(&self) -> usize {
|
||||
self.n_equations
|
||||
}
|
||||
|
||||
fn get_ports(&self) -> &[ConnectedPort] {
|
||||
&[]
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for SimpleAdapter {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "SimpleAdapter({})", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Compressor
|
||||
// =============================================================================
|
||||
|
||||
/// A compressor component using AHRI 540 performance model.
|
||||
///
|
||||
/// 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) coefficients: entropyk::Ahri540Coefficients,
|
||||
pub(crate) speed_rpm: f64,
|
||||
pub(crate) displacement: f64,
|
||||
pub(crate) efficiency: f64,
|
||||
pub(crate) fluid: String,
|
||||
}
|
||||
|
||||
#[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",
|
||||
));
|
||||
}
|
||||
Ok(PyCompressor {
|
||||
coefficients: entropyk::Ahri540Coefficients::new(
|
||||
m1, m2, m3, m4, m5, m6, m7, m8, m9, m10,
|
||||
),
|
||||
speed_rpm,
|
||||
displacement,
|
||||
efficiency,
|
||||
fluid: fluid.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// AHRI 540 coefficients.
|
||||
#[getter]
|
||||
fn speed(&self) -> f64 {
|
||||
self.speed_rpm
|
||||
}
|
||||
|
||||
/// Isentropic efficiency (0–1).
|
||||
#[getter]
|
||||
fn efficiency_value(&self) -> f64 {
|
||||
self.efficiency
|
||||
}
|
||||
|
||||
/// Fluid name.
|
||||
#[getter]
|
||||
fn fluid_name(&self) -> &str {
|
||||
&self.fluid
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"Compressor(speed={:.0} RPM, η={:.2}, fluid={})",
|
||||
self.speed_rpm, self.efficiency, self.fluid
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyCompressor {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
// Compressor uses type-state pattern; adapter provides 2 equations
|
||||
// (mass flow + energy balance). Real physics computed during solve.
|
||||
Box::new(SimpleAdapter::new("Compressor", 2))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Condenser
|
||||
// =============================================================================
|
||||
|
||||
/// A condenser (heat rejection) component.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// cond = Condenser(ua=5000.0)
|
||||
#[pyclass(name = "Condenser", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyCondenser {
|
||||
pub(crate) ua: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyCondenser {
|
||||
#[new]
|
||||
#[pyo3(signature = (ua=5000.0))]
|
||||
fn new(ua: f64) -> PyResult<Self> {
|
||||
if ua <= 0.0 {
|
||||
return Err(PyValueError::new_err("ua must be positive"));
|
||||
}
|
||||
Ok(PyCondenser { ua })
|
||||
}
|
||||
|
||||
/// Thermal conductance UA in W/K.
|
||||
#[getter]
|
||||
fn ua_value(&self) -> f64 {
|
||||
self.ua
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!("Condenser(UA={:.1} W/K)", self.ua)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyCondenser {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(entropyk::Condenser::new(self.ua))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Evaporator
|
||||
// =============================================================================
|
||||
|
||||
/// An evaporator (heat absorption) component.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// evap = Evaporator(ua=3000.0)
|
||||
#[pyclass(name = "Evaporator", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyEvaporator {
|
||||
pub(crate) ua: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyEvaporator {
|
||||
#[new]
|
||||
#[pyo3(signature = (ua=3000.0))]
|
||||
fn new(ua: f64) -> PyResult<Self> {
|
||||
if ua <= 0.0 {
|
||||
return Err(PyValueError::new_err("ua must be positive"));
|
||||
}
|
||||
Ok(PyEvaporator { ua })
|
||||
}
|
||||
|
||||
/// Thermal conductance UA in W/K.
|
||||
#[getter]
|
||||
fn ua_value(&self) -> f64 {
|
||||
self.ua
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!("Evaporator(UA={:.1} W/K)", self.ua)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyEvaporator {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(entropyk::Evaporator::new(self.ua))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Economizer
|
||||
// =============================================================================
|
||||
|
||||
/// An economizer (subcooler / internal heat exchanger) component.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// econ = Economizer(ua=2000.0, effectiveness=0.8)
|
||||
#[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::Economizer::new(self.ua))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// ExpansionValve
|
||||
// =============================================================================
|
||||
|
||||
/// An expansion valve (isenthalpic throttling device).
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// valve = ExpansionValve(fluid="R134a", opening=1.0)
|
||||
#[pyclass(name = "ExpansionValve", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyExpansionValve {
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) opening: Option<f64>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyExpansionValve {
|
||||
#[new]
|
||||
#[pyo3(signature = (fluid="R134a", opening=None))]
|
||||
fn new(fluid: &str, opening: Option<f64>) -> PyResult<Self> {
|
||||
if let Some(o) = opening {
|
||||
if !(0.0..=1.0).contains(&o) {
|
||||
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), None if fully open.
|
||||
#[getter]
|
||||
fn opening_value(&self) -> Option<f64> {
|
||||
self.opening
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
match self.opening {
|
||||
Some(o) => format!("ExpansionValve(fluid={}, opening={:.2})", self.fluid, o),
|
||||
None => format!("ExpansionValve(fluid={})", self.fluid),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PyExpansionValve {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
// ExpansionValve uses type-state pattern; 2 equations
|
||||
Box::new(SimpleAdapter::new("ExpansionValve", 2))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pipe
|
||||
// =============================================================================
|
||||
|
||||
/// A pipe component with pressure drop (Darcy-Weisbach).
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// pipe = Pipe(length=10.0, diameter=0.05, fluid="R134a",
|
||||
/// density=1140.0, viscosity=0.0002)
|
||||
#[pyclass(name = "Pipe", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyPipe {
|
||||
pub(crate) length: f64,
|
||||
pub(crate) diameter: f64,
|
||||
pub(crate) roughness: f64,
|
||||
pub(crate) fluid: String,
|
||||
pub(crate) density: f64,
|
||||
pub(crate) viscosity: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyPipe {
|
||||
#[new]
|
||||
#[pyo3(signature = (
|
||||
length=10.0,
|
||||
diameter=0.05,
|
||||
fluid="R134a",
|
||||
density=1140.0,
|
||||
viscosity=0.0002,
|
||||
roughness=0.0000015
|
||||
))]
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
length: f64,
|
||||
diameter: f64,
|
||||
fluid: &str,
|
||||
density: f64,
|
||||
viscosity: f64,
|
||||
roughness: f64,
|
||||
) -> 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"));
|
||||
}
|
||||
if density <= 0.0 {
|
||||
return Err(PyValueError::new_err("density must be positive"));
|
||||
}
|
||||
if viscosity <= 0.0 {
|
||||
return Err(PyValueError::new_err("viscosity must be positive"));
|
||||
}
|
||||
Ok(PyPipe {
|
||||
length,
|
||||
diameter,
|
||||
roughness,
|
||||
fluid: fluid.to_string(),
|
||||
density,
|
||||
viscosity,
|
||||
})
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"Pipe(L={:.2}m, D={:.4}m, fluid={})",
|
||||
self.length, self.diameter, self.fluid
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyPipe {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
// Pipe uses type-state pattern; 1 equation (pressure drop)
|
||||
Box::new(SimpleAdapter::new("Pipe", 1))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Pump
|
||||
// =============================================================================
|
||||
|
||||
/// A pump component for liquid flow.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// pump = Pump(pressure_rise_pa=200000.0, efficiency=0.75)
|
||||
#[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(SimpleAdapter::new("Pump", 2))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// Fan
|
||||
// =============================================================================
|
||||
|
||||
/// A fan component for air flow.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// fan = Fan(pressure_rise_pa=500.0, efficiency=0.65)
|
||||
#[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(SimpleAdapter::new("Fan", 2))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FlowSplitter
|
||||
// =============================================================================
|
||||
|
||||
/// A flow splitter that divides a stream into two or more branches.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// splitter = FlowSplitter(n_outlets=2)
|
||||
#[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(SimpleAdapter::new("FlowSplitter", self.n_outlets))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FlowMerger
|
||||
// =============================================================================
|
||||
|
||||
/// A flow merger that combines two or more branches into one.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// merger = FlowMerger(n_inlets=2)
|
||||
#[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(SimpleAdapter::new("FlowMerger", self.n_inlets))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FlowSource
|
||||
// =============================================================================
|
||||
|
||||
/// A boundary condition representing a mass flow source.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// source = FlowSource(pressure_pa=101325.0, temperature_k=300.0)
|
||||
#[pyclass(name = "FlowSource", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyFlowSource {
|
||||
pub(crate) pressure_pa: f64,
|
||||
pub(crate) temperature_k: f64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl PyFlowSource {
|
||||
#[new]
|
||||
#[pyo3(signature = (pressure_pa=101325.0, temperature_k=300.0))]
|
||||
fn new(pressure_pa: f64, temperature_k: 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"));
|
||||
}
|
||||
Ok(PyFlowSource {
|
||||
pressure_pa,
|
||||
temperature_k,
|
||||
})
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
format!(
|
||||
"FlowSource(P={:.0} Pa, T={:.1} K)",
|
||||
self.pressure_pa, self.temperature_k
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFlowSource {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("FlowSource", 0))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// FlowSink
|
||||
// =============================================================================
|
||||
|
||||
/// A boundary condition representing a mass flow sink.
|
||||
///
|
||||
/// Example::
|
||||
///
|
||||
/// sink = FlowSink()
|
||||
#[pyclass(name = "FlowSink", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyFlowSink;
|
||||
|
||||
#[pymethods]
|
||||
impl PyFlowSink {
|
||||
#[new]
|
||||
fn new() -> Self {
|
||||
PyFlowSink
|
||||
}
|
||||
|
||||
fn __repr__(&self) -> String {
|
||||
"FlowSink()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl PyFlowSink {
|
||||
pub(crate) fn build(&self) -> Box<dyn Component> {
|
||||
Box::new(SimpleAdapter::new("FlowSink", 0))
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// OperationalState
|
||||
// =============================================================================
|
||||
|
||||
/// Operational state of a component: On, Off, or Bypass.
|
||||
#[pyclass(name = "OperationalState", module = "entropyk")]
|
||||
#[derive(Clone)]
|
||||
pub struct PyOperationalState {
|
||||
pub(crate) inner: entropyk::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::OperationalState::On,
|
||||
"off" => entropyk::OperationalState::Off,
|
||||
"bypass" => entropyk::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
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 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),
|
||||
}
|
||||
|
||||
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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user