use std::{array, ops::RangeInclusive};
use uom::si::{
f64::{ThermodynamicTemperature, Volume},
temperature_interval, thermodynamic_temperature,
};
pub(super) fn stabilize<const N: usize>(
temp: &mut [ThermodynamicTemperature; N],
vol: &[Volume; N],
) {
if temp.windows(2).all(|w| w[0] <= w[1]) {
return;
}
let mut stack: [Block; N] = array::from_fn(|_| Block::default());
let mut stack_len = 0_usize;
for i in 0..N {
let mut block = Block::new(i, temp[i], vol[i]);
while stack_len > 0 && stack[stack_len - 1].temp > block.temp {
block = merge(&stack[stack_len - 1], &block);
stack_len -= 1;
}
stack[stack_len] = block;
stack_len += 1;
}
for block in &stack[..stack_len] {
for i in block.range.clone() {
temp[i] = block.temp;
}
}
}
#[derive(Debug, Clone)]
struct Block {
range: RangeInclusive<usize>,
temp: ThermodynamicTemperature,
vol: Volume,
}
impl Block {
fn new(index: usize, temp: ThermodynamicTemperature, vol: Volume) -> Self {
Self {
range: index..=index,
temp,
vol,
}
}
}
impl Default for Block {
fn default() -> Self {
Self {
range: 0..=0,
temp: ThermodynamicTemperature::default(),
vol: Volume::default(),
}
}
}
fn merge(below: &Block, above: &Block) -> Block {
let total_vol = below.vol + above.vol;
let t_mix = (below.vol * below.temp + above.vol * above.temp) / total_vol;
let t_k = t_mix.get::<temperature_interval::kelvin>();
let mixed_temp = ThermodynamicTemperature::new::<thermodynamic_temperature::kelvin>(t_k);
Block {
range: *below.range.start()..=*above.range.end(),
temp: mixed_temp,
vol: total_vol,
}
}
#[cfg(test)]
mod tests {
use super::*;
use uom::si::{thermodynamic_temperature::degree_celsius, volume::cubic_meter};
fn ts<const N: usize>(temps_c: [f64; N]) -> [ThermodynamicTemperature; N] {
temps_c.map(ThermodynamicTemperature::new::<degree_celsius>)
}
fn vs<const N: usize>(vols_m3: [f64; N]) -> [Volume; N] {
vols_m3.map(Volume::new::<cubic_meter>)
}
#[test]
fn no_mixing_needed() {
let vol = vs([1.0; 3]);
let mut temp = ts([30.0, 40.0, 50.0]);
stabilize(&mut temp, &vol);
assert_eq!(temp, ts([30.0, 40.0, 50.0]));
}
#[test]
fn fully_inverted_all_mixed() {
let vol = vs([1.0; 3]);
let mut temp = ts([50.0, 40.0, 30.0]);
stabilize(&mut temp, &vol);
assert_eq!(temp, ts([40.0, 40.0, 40.0]));
}
#[test]
fn partial_inversion_mixed_from_unstable_region() {
let vol = vs([1.0; 5]);
let mut temp = ts([20.0, 30.0, 50.0, 40.0, 42.0]);
stabilize(&mut temp, &vol);
assert_eq!(temp, ts([20.0, 30.0, 44.0, 44.0, 44.0]));
}
#[test]
fn uneven_volumes_weighted_average() {
let vol = vs([1.0, 4.0, 2.0]);
let mut temp = ts([2.0, 10.0, 4.0]);
stabilize(&mut temp, &vol);
assert_eq!(temp, ts([2.0, 8.0, 8.0]));
}
#[test]
fn equal_temperatures_stable() {
let vol = vs([1.0; 4]);
let mut temp = ts([25.0; 4]);
let before = temp;
stabilize(&mut temp, &vol);
assert_eq!(temp, before);
}
#[test]
fn single_node_stable() {
let vol = vs([1.0]);
let mut temp = ts([50.0]);
stabilize(&mut temp, &vol);
assert_eq!(temp, ts([50.0]));
}
}