use crate::support::{
constraint::{ConstraintError, ConstraintResult},
units::TemperatureDifference,
};
use uom::{
ConstZero,
si::f64::{Power, ThermalConductance},
};
use super::{
Effectiveness, HeatFlow, Ntu, StreamInlet,
effectiveness_ntu::{EffectivenessRelation, NtuRelation},
stream::Stream,
};
pub fn known_conductance_and_inlets(
arrangement: &impl EffectivenessRelation,
ua: ThermalConductance,
inlets: [StreamInlet; 2],
) -> ConstraintResult<KnownConductanceResult> {
let streams_with_max_heat = calculate_max_heat_flow(inlets)?;
let capacitance_rates = [inlets[0].capacitance_rate, inlets[1].capacitance_rate];
let effectiveness = arrangement.effectiveness(
Ntu::from_conductance_and_capacitance_rates(ua, capacitance_rates)?,
capacitance_rates,
);
Ok(KnownConductanceResult {
streams: [
inlets[0].with_heat_flow(HeatFlow::from_signed(
*effectiveness * streams_with_max_heat[0].heat_flow.signed(),
)?),
inlets[1].with_heat_flow(HeatFlow::from_signed(
*effectiveness * streams_with_max_heat[1].heat_flow.signed(),
)?),
],
effectiveness,
})
}
#[derive(Debug, Clone, Copy)]
pub struct KnownConductanceResult {
pub streams: [Stream; 2],
pub effectiveness: Effectiveness,
}
pub fn known_conditions_and_inlets(
arrangement: &impl NtuRelation,
streams: (StreamInlet, Stream),
) -> ConstraintResult<KnownConditionsResult> {
let streams_with_max_heat = calculate_max_heat_flow([streams.0, streams.1.into()])?;
let capacitance_rates = [streams.0.capacitance_rate, streams.1.capacitance_rate];
let max_heat_flow = streams_with_max_heat[0].heat_flow.signed().abs();
let actual_heat_flow = streams.1.heat_flow.signed().abs();
if max_heat_flow == Power::ZERO {
if actual_heat_flow != Power::ZERO {
return Err(ConstraintError::AboveMaximum);
}
let ntu = Ntu::new(0.0)?;
return Ok(KnownConditionsResult {
streams: [streams.0.with_heat_flow(HeatFlow::None), streams.1],
ua: ThermalConductance::ZERO,
ntu,
});
}
let effectiveness = Effectiveness::from_quantity(actual_heat_flow / max_heat_flow)?;
let ntu = arrangement.ntu(effectiveness, capacitance_rates);
Ok(KnownConditionsResult {
streams: [
streams.0.with_heat_flow(HeatFlow::from_signed(
*effectiveness * streams_with_max_heat[0].heat_flow.signed(),
)?),
streams.1,
],
ua: *ntu * capacitance_rates[0].min(*capacitance_rates[1]),
ntu,
})
}
#[derive(Debug, Clone, Copy)]
pub struct KnownConditionsResult {
pub streams: [Stream; 2],
pub ua: ThermalConductance,
pub ntu: Ntu,
}
fn calculate_max_heat_flow(inlets: [StreamInlet; 2]) -> ConstraintResult<[Stream; 2]> {
let min_capacitance_rate = inlets[0].capacitance_rate.min(*inlets[1].capacitance_rate);
let max_heat_flow = min_capacitance_rate * inlets[0].temperature.minus(inlets[1].temperature);
Ok(
match max_heat_flow
.partial_cmp(&Power::ZERO)
.expect("heat flow should not be NaN")
{
std::cmp::Ordering::Less => [
inlets[0].with_heat_flow(HeatFlow::incoming(max_heat_flow.abs())?),
inlets[1].with_heat_flow(HeatFlow::outgoing(max_heat_flow.abs())?),
],
std::cmp::Ordering::Equal => [
inlets[0].with_heat_flow(HeatFlow::None),
inlets[1].with_heat_flow(HeatFlow::None),
],
std::cmp::Ordering::Greater => [
inlets[0].with_heat_flow(HeatFlow::outgoing(max_heat_flow)?),
inlets[1].with_heat_flow(HeatFlow::incoming(max_heat_flow)?),
],
},
)
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use uom::si::{
f64::ThermodynamicTemperature, power::kilowatt, ratio::ratio,
thermal_conductance::kilowatt_per_kelvin, thermodynamic_temperature::degree_celsius,
};
use super::super::CapacitanceRate;
use super::super::arrangement::CounterFlow;
use super::*;
#[test]
fn test_known_conductance_and_inlets() -> ConstraintResult<()> {
let result = known_conductance_and_inlets(
&CounterFlow,
ThermalConductance::new::<kilowatt_per_kelvin>(3. * 4.0_f64.ln()),
[
StreamInlet::new(
CapacitanceRate::new::<kilowatt_per_kelvin>(3.)?,
ThermodynamicTemperature::new::<degree_celsius>(50.),
),
StreamInlet::new(
CapacitanceRate::new::<kilowatt_per_kelvin>(6.)?,
ThermodynamicTemperature::new::<degree_celsius>(80.),
),
],
)?;
let KnownConductanceResult {
streams,
effectiveness,
} = result;
assert_relative_eq!(effectiveness.get::<ratio>(), 2. / 3.);
assert!(matches!(streams[0].heat_flow, HeatFlow::In(_)));
assert!(matches!(streams[1].heat_flow, HeatFlow::Out(_)));
for stream in streams {
assert_relative_eq!(
stream.heat_flow.signed().get::<kilowatt>().abs(),
60.,
max_relative = 1e-15
);
assert_relative_eq!(stream.outlet_temperature.get::<degree_celsius>(), 70.);
}
Ok(())
}
#[test]
fn test_known_conditions_and_inlets() -> ConstraintResult<()> {
let result = known_conditions_and_inlets(
&CounterFlow,
(
StreamInlet::new(
CapacitanceRate::new::<kilowatt_per_kelvin>(3.)?,
ThermodynamicTemperature::new::<degree_celsius>(50.),
),
Stream::new_from_heat_flow(
CapacitanceRate::new::<kilowatt_per_kelvin>(6.)?,
ThermodynamicTemperature::new::<degree_celsius>(80.),
HeatFlow::outgoing(Power::new::<kilowatt>(60.))?,
),
),
)?;
let KnownConditionsResult { streams, ua, ntu } = result;
assert_relative_eq!(ua.get::<kilowatt_per_kelvin>(), 3. * 4.0_f64.ln());
assert_relative_eq!(ntu.get::<ratio>(), 4.0_f64.ln());
assert!(matches!(streams[0].heat_flow, HeatFlow::In(_)));
assert!(matches!(streams[1].heat_flow, HeatFlow::Out(_)));
for stream in streams {
assert_relative_eq!(
stream.heat_flow.signed().get::<kilowatt>().abs(),
60.,
max_relative = 1e-15
);
assert_relative_eq!(stream.outlet_temperature.get::<degree_celsius>(), 70.);
}
Ok(())
}
}