use crate::types::{Component, R_GAS};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(i32)]
#[cfg_attr(feature = "python", pyo3::pyclass(eq, eq_int))]
pub enum VolumeModel {
Rackett = 1,
Thomson = 2,
}
const A: f64 = -1.528_16;
const B: f64 = 1.439_07;
const C: f64 = -0.814_46;
const D: f64 = 0.190_454;
const E: f64 = -0.296_123;
const F: f64 = 0.386_914;
const G: f64 = -0.042_527_258;
const H: f64 = -0.048_064_5;
pub fn liquid_molar_volume(model: VolumeModel, comp: &Component, t: f64) -> f64 {
let tr = t / comp.tc;
match model {
VolumeModel::Rackett => {
let toh = if tr <= 0.75 {
1.0 + (1.0 - tr).powf(2.0 / 7.0)
} else {
1.6 + 0.006_930_26 / (tr - 0.655)
};
1000.0 * R_GAS * comp.tc / comp.pc * comp.zra.powf(toh)
}
VolumeModel::Thomson => {
let om = 1.0 - tr;
let vr0 = 1.0
+ A * om.powf(1.0 / 3.0)
+ B * om.powf(2.0 / 3.0)
+ C * om
+ D * om.powf(4.0 / 3.0);
let vrs = (E + F * tr + G * tr * tr + H * tr * tr * tr) / (tr - 1.000_01);
comp.liquid_volume * vr0 * (1.0 - comp.omega_srk * vrs)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn water() -> Component {
Component {
tc: 647.3,
pc: 22_120.0,
zra: 0.235,
..Default::default()
}
}
#[test]
fn rackett_water_is_about_18_cm3_per_mol() {
let v = liquid_molar_volume(VolumeModel::Rackett, &water(), 298.15);
assert!((15.0..20.0).contains(&v), "got {v} cm³/mol");
}
#[test]
fn rackett_volume_increases_with_temperature() {
let w = water();
let lo = liquid_molar_volume(VolumeModel::Rackett, &w, 298.15);
let hi = liquid_molar_volume(VolumeModel::Rackett, &w, 330.0);
assert!(hi > lo);
}
#[test]
fn thomson_recovers_characteristic_volume_scale() {
let comp = Component {
tc: 500.0,
pc: 4000.0,
liquid_volume: 100.0,
omega_srk: 0.0,
..Default::default()
};
let v = liquid_molar_volume(VolumeModel::Thomson, &comp, 300.0);
assert!((30.0..45.0).contains(&v), "got {v} cm³/mol");
}
#[test]
fn high_tr_branch_is_finite() {
let w = water();
let v = liquid_molar_volume(VolumeModel::Rackett, &w, 0.97 * w.tc);
assert!(v.is_finite() && v > 0.0, "got {v}");
}
}