Skip to main content

twine_models/support/hx/
stream.rs

1use crate::support::units::TemperatureDifference;
2use uom::si::f64::ThermodynamicTemperature;
3
4use super::{CapacitanceRate, HeatFlow};
5
6/// Inlet state for a stream entering the heat exchanger.
7///
8/// Assumes the fluid's specific heat remains constant through the exchanger.
9#[derive(Debug, Clone, Copy)]
10pub struct StreamInlet {
11    pub(crate) capacitance_rate: CapacitanceRate,
12    pub(crate) temperature: ThermodynamicTemperature,
13}
14
15impl StreamInlet {
16    /// Capture the inlet capacitance rate and temperature.
17    #[must_use]
18    pub fn new(capacitance_rate: CapacitanceRate, temperature: ThermodynamicTemperature) -> Self {
19        Self {
20            capacitance_rate,
21            temperature,
22        }
23    }
24
25    pub(crate) fn with_heat_flow(self, heat_flow: HeatFlow) -> Stream {
26        Stream::new_from_heat_flow(self.capacitance_rate, self.temperature, heat_flow)
27    }
28}
29
30/// A fully-resolved heat exchanger stream.
31#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct Stream {
33    /// Effective capacitance rate for the stream.
34    pub capacitance_rate: CapacitanceRate,
35    /// Temperature at the exchanger inlet.
36    pub inlet_temperature: ThermodynamicTemperature,
37    /// Temperature after the stream leaves the exchanger.
38    ///
39    /// When the capacitance rate tends to infinity this matches the inlet
40    /// temperature.
41    pub outlet_temperature: ThermodynamicTemperature,
42    /// Net heat flow direction and magnitude for the stream.
43    pub heat_flow: HeatFlow,
44}
45
46impl Stream {
47    /// Construct a fully-resolved stream from a known heat flow.
48    ///
49    /// Given the stream's inlet temperature, capacitance rate, and heat flow,
50    /// this calculates the outlet temperature using the energy balance:
51    /// `Q = C * (T_out - T_in)`.
52    ///
53    /// This constructor is useful when you know the heat transfer rate for a stream
54    /// (for example, from measurements or system specifications) and need to determine
55    /// the resulting outlet temperature.
56    #[must_use]
57    pub fn new_from_heat_flow(
58        capacitance_rate: CapacitanceRate,
59        inlet_temperature: ThermodynamicTemperature,
60        heat_flow: HeatFlow,
61    ) -> Self {
62        Self {
63            capacitance_rate,
64            inlet_temperature,
65            outlet_temperature: match heat_flow {
66                HeatFlow::In(heat_rate) => {
67                    inlet_temperature + heat_rate.into_inner() / *capacitance_rate
68                }
69                HeatFlow::Out(heat_rate) => {
70                    inlet_temperature - heat_rate.into_inner() / *capacitance_rate
71                }
72                HeatFlow::None => inlet_temperature,
73            },
74            heat_flow,
75        }
76    }
77
78    /// Construct a fully-resolved stream from known inlet and outlet temperatures.
79    ///
80    /// Given the stream's inlet and outlet temperatures along with its capacitance rate,
81    /// this calculates the heat flow using the energy balance: `Q = C * (T_out - T_in)`.
82    ///
83    /// The heat flow direction is automatically determined from the temperature change:
84    /// - If outlet > inlet, the heat flow is [`HeatFlow::In`]
85    /// - If outlet < inlet, the heat flow is [`HeatFlow::Out`]
86    /// - If outlet = inlet, the heat flow is [`HeatFlow::None`]
87    ///
88    /// This constructor is useful when you know both inlet and outlet temperatures for
89    /// a stream (for example, from measurements) and need to determine the heat transfer
90    /// rate.
91    ///
92    /// # Panics
93    ///
94    /// Panics if the temperatures cannot be compared (e.g., contain NaN values) or if
95    /// the calculated heat rate magnitude is invalid (which should not occur in normal use).
96    #[must_use]
97    pub fn new_from_outlet_temperature(
98        capacitance_rate: CapacitanceRate,
99        inlet_temperature: ThermodynamicTemperature,
100        outlet_temperature: ThermodynamicTemperature,
101    ) -> Self {
102        let heat_rate_magnitude =
103            *capacitance_rate * inlet_temperature.minus(outlet_temperature).abs();
104
105        Self {
106            capacitance_rate,
107            inlet_temperature,
108            outlet_temperature,
109            heat_flow: match inlet_temperature
110                .partial_cmp(&outlet_temperature)
111                .expect("temperatures to be comparable")
112            {
113                std::cmp::Ordering::Less => HeatFlow::incoming(heat_rate_magnitude)
114                    .expect("heat rate magnitude should always be positive"),
115                std::cmp::Ordering::Equal => HeatFlow::None,
116                std::cmp::Ordering::Greater => HeatFlow::outgoing(heat_rate_magnitude)
117                    .expect("heat rate magnitude should always be positive"),
118            },
119        }
120    }
121}
122
123impl From<Stream> for StreamInlet {
124    fn from(stream: Stream) -> Self {
125        Self {
126            capacitance_rate: stream.capacitance_rate,
127            temperature: stream.inlet_temperature,
128        }
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use crate::support::constraint::ConstraintResult;
135    use uom::si::{
136        f64::Power, power::watt, thermal_conductance::watt_per_kelvin,
137        thermodynamic_temperature::kelvin,
138    };
139
140    use super::*;
141
142    #[test]
143    fn stream_inlet_with_heat_flow() -> ConstraintResult<()> {
144        let capacitance_rate = CapacitanceRate::new::<watt_per_kelvin>(10.)?;
145        let inlet_temperature = ThermodynamicTemperature::new::<kelvin>(300.);
146        let heat_rate = Power::new::<watt>(20.);
147
148        let inlet = StreamInlet::new(capacitance_rate, inlet_temperature);
149
150        let no_heat = inlet.with_heat_flow(HeatFlow::None);
151        let incoming = inlet.with_heat_flow(HeatFlow::incoming(heat_rate)?);
152        let outgoing = inlet.with_heat_flow(HeatFlow::outgoing(heat_rate)?);
153
154        assert_eq!(
155            no_heat,
156            Stream {
157                capacitance_rate,
158                inlet_temperature,
159                outlet_temperature: inlet_temperature,
160                heat_flow: HeatFlow::None
161            }
162        );
163        assert_eq!(
164            incoming,
165            Stream {
166                capacitance_rate,
167                inlet_temperature,
168                outlet_temperature: ThermodynamicTemperature::new::<kelvin>(302.),
169                heat_flow: HeatFlow::incoming(heat_rate)?
170            }
171        );
172        assert_eq!(
173            outgoing,
174            Stream {
175                capacitance_rate,
176                inlet_temperature,
177                outlet_temperature: ThermodynamicTemperature::new::<kelvin>(298.),
178                heat_flow: HeatFlow::outgoing(heat_rate)?
179            }
180        );
181
182        Ok(())
183    }
184
185    #[test]
186    fn stream_new_from_heat_rate() -> ConstraintResult<()> {
187        let capacitance_rate = CapacitanceRate::new::<watt_per_kelvin>(10.)?;
188        let inlet_temperature = ThermodynamicTemperature::new::<kelvin>(300.);
189        let heat_flow = HeatFlow::incoming(Power::new::<watt>(20.))?;
190
191        let stream = Stream::new_from_heat_flow(capacitance_rate, inlet_temperature, heat_flow);
192
193        assert_eq!(
194            stream,
195            Stream {
196                capacitance_rate,
197                inlet_temperature,
198                outlet_temperature: ThermodynamicTemperature::new::<kelvin>(302.),
199                heat_flow
200            }
201        );
202
203        Ok(())
204    }
205
206    #[test]
207    fn stream_new_from_outlet_temperature() -> ConstraintResult<()> {
208        let capacitance_rate = CapacitanceRate::new::<watt_per_kelvin>(10.)?;
209        let inlet_temperature = ThermodynamicTemperature::new::<kelvin>(300.);
210        let outlet_temperature = ThermodynamicTemperature::new::<kelvin>(302.);
211
212        let stream = Stream::new_from_outlet_temperature(
213            capacitance_rate,
214            inlet_temperature,
215            outlet_temperature,
216        );
217
218        assert_eq!(
219            stream,
220            Stream {
221                capacitance_rate,
222                inlet_temperature,
223                outlet_temperature,
224                heat_flow: HeatFlow::incoming(Power::new::<watt>(20.))?
225            }
226        );
227
228        Ok(())
229    }
230}