//! 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { let warnings = py.import_bound("warnings")?; warnings.call_method1( "warn", ( "FlowSource is deprecated. Use RefrigerantSource, BrineSource, or AirSource instead.", py.get_type_bound::(), 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 { 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 { let warnings = py.import_bound("warnings")?; warnings.call_method1( "warn", ( "FlowSink is deprecated. Use RefrigerantSink, BrineSink, or AirSink instead.", py.get_type_bound::(), 2, ), )?; Ok(PyFlowSink) } fn __repr__(&self) -> String { "FlowSink()".to_string() } } impl PyFlowSink { pub(crate) fn build(&self) -> Box { 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 { 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 { 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 { 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, } #[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) -> PyResult { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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(), } } }