Skip to main content

twine_models/support/hx/
effectiveness_ntu.rs

1use std::ops::Deref;
2
3use crate::support::constraint::{Constrained, ConstraintResult, NonNegative, UnitInterval};
4use uom::si::{
5    f64::{Ratio, ThermalConductance},
6    ratio::ratio,
7};
8
9use super::{CapacitanceRate, CapacityRatio};
10
11/// Trait for computing heat exchanger effectiveness from NTU.
12pub trait EffectivenessRelation {
13    /// Calculate the effectiveness for an arrangement given the [NTU](Ntu) and
14    /// [capacity ratio](CapacityRatio).
15    fn effectiveness(&self, ntu: Ntu, capacitance_rates: [CapacitanceRate; 2]) -> Effectiveness;
16}
17
18/// Trait for computing NTU from heat exchanger effectiveness.
19pub trait NtuRelation {
20    /// Calculate the [NTU](Ntu) for an arrangement given the
21    /// [effectiveness](Effectiveness) and [capacity ratio](CapacityRatio).
22    fn ntu(&self, effectiveness: Effectiveness, capacitance_rates: [CapacitanceRate; 2]) -> Ntu;
23}
24
25/// The effectiveness of a heat exchanger.
26///
27/// The effectiveness is the ratio of the actual amount of heat transferred to
28/// the maximum possible amount of heat transferred in the heat exchanger.
29///
30/// The effectiveness must be in the interval [0, 1].
31#[derive(Debug, Clone, Copy)]
32pub struct Effectiveness(Constrained<Ratio, UnitInterval>);
33
34impl Effectiveness {
35    /// Create an [`Effectiveness`] from a scalar value.
36    ///
37    /// # Errors
38    ///
39    /// Returns `Err` if the value lies outside the interval [0, 1].
40    pub fn new(value: f64) -> ConstraintResult<Self> {
41        let quantity = Ratio::new::<ratio>(value);
42        Self::from_quantity(quantity)
43    }
44
45    /// Create an [`Effectiveness`] from a ratio quantity.
46    ///
47    /// # Errors
48    ///
49    /// Returns `Err` if the quantity lies outside the interval [0, 1].
50    pub fn from_quantity(quantity: Ratio) -> ConstraintResult<Self> {
51        Ok(Self(UnitInterval::new(quantity)?))
52    }
53}
54
55impl Deref for Effectiveness {
56    type Target = Ratio;
57
58    fn deref(&self) -> &Self::Target {
59        self.0.as_ref()
60    }
61}
62
63/// The number of transfer units for a heat exchanger.
64///
65/// The number of transfer units represents the dimensionless size of a heat
66/// exchanger.
67///
68/// The number of transfer units must be >= 0.
69#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
70pub struct Ntu(Constrained<Ratio, NonNegative>);
71
72impl Ntu {
73    /// Create an [`Ntu`] from a scalar value.
74    ///
75    /// # Errors
76    ///
77    /// Returns `Err` if the value is negative.
78    pub fn new(value: f64) -> ConstraintResult<Self> {
79        let quantity = Ratio::new::<ratio>(value);
80        Self::from_quantity(quantity)
81    }
82
83    /// Create an [`Ntu`] from a ratio quantity.
84    ///
85    /// # Errors
86    ///
87    /// Returns `Err` if the quantity is negative.
88    pub fn from_quantity(quantity: Ratio) -> ConstraintResult<Self> {
89        Ok(Self(NonNegative::new(quantity)?))
90    }
91
92    /// Create an [`Ntu`] from a heat exchanger conductance and
93    /// [capacitance rates](CapacitanceRate).
94    ///
95    /// The [capacitance rates](CapacitanceRate) of both streams are required so
96    /// that the minimum of the two can be used in the calculation.
97    ///
98    /// # Errors
99    ///
100    /// Returns `Err` if the resulting NTU would be negative (for example, when
101    /// `ua` is negative).
102    pub fn from_conductance_and_capacitance_rates(
103        ua: ThermalConductance,
104        capacitance_rates: [CapacitanceRate; 2],
105    ) -> ConstraintResult<Self> {
106        Self::from_quantity(ua / capacitance_rates[0].min(*capacitance_rates[1]))
107    }
108}
109
110impl Deref for Ntu {
111    type Target = Ratio;
112
113    fn deref(&self) -> &Self::Target {
114        self.0.as_ref()
115    }
116}
117
118#[inline]
119pub(crate) fn effectiveness_via(
120    ntu: Ntu,
121    capacitance_rates: [CapacitanceRate; 2],
122    fn_raw: impl Fn(f64, f64) -> f64,
123) -> Effectiveness {
124    let cr = CapacityRatio::from_capacitance_rates(capacitance_rates).get::<ratio>();
125    let ntu = ntu.get::<ratio>();
126    if cr == 0.0 {
127        return {
128            Effectiveness::new(1. - (-ntu).exp())
129                .expect("ntu should always yield valid effectiveness")
130        };
131    }
132    Effectiveness::new(fn_raw(ntu, cr)).expect("ntu should always yield valid effectiveness")
133}
134
135#[inline]
136pub(crate) fn ntu_via(
137    effectiveness: Effectiveness,
138    capacitance_rates: [CapacitanceRate; 2],
139    fn_raw: impl Fn(f64, f64) -> f64,
140) -> Ntu {
141    let cr = CapacityRatio::from_capacitance_rates(capacitance_rates).get::<ratio>();
142    let eff = effectiveness.get::<ratio>();
143    if cr == 0.0 {
144        return {
145            Ntu::new(-(1. - eff).ln()).expect("effectiveness should always yield valid ntu")
146        };
147    }
148    Ntu::new(fn_raw(eff, cr)).expect("effectiveness should always yield valid ntu")
149}
150
151#[cfg(test)]
152mod tests {
153    use approx::assert_relative_eq;
154    use uom::si::thermal_conductance::watt_per_kelvin;
155
156    use super::*;
157
158    #[test]
159    fn ntu_from_conductance_and_capacitance_rates() -> ConstraintResult<()> {
160        let ua = ThermalConductance::new::<watt_per_kelvin>(10.);
161        let capacitance_rates = [
162            CapacitanceRate::new::<watt_per_kelvin>(10.)?,
163            CapacitanceRate::new::<watt_per_kelvin>(20.)?,
164        ];
165
166        let ntu = Ntu::from_conductance_and_capacitance_rates(ua, capacitance_rates)?;
167
168        assert_relative_eq!(ntu.get::<ratio>(), 1.);
169        Ok(())
170    }
171}