use crate::types::{Component, R_GAS};
use thiserror::Error;
#[derive(Debug, Error, PartialEq)]
pub enum VirialError {
#[error("invalid input to virial layer: {0}")]
InvalidInput(String),
}
pub fn pitzer_b0(tr: f64) -> f64 {
0.083 - 0.422 / tr.powf(1.6)
}
pub fn pitzer_b1(tr: f64) -> f64 {
0.139 - 0.172 / tr.powf(4.2)
}
pub fn pitzer_b(comp: &Component, t: f64) -> f64 {
let tr = t / comp.tc;
let b_reduced = pitzer_b0(tr) + comp.omega * pitzer_b1(tr);
1000.0 * R_GAS * comp.tc / comp.pc * b_reduced
}
pub fn pitzer_d_b_d_t(comp: &Component, t: f64) -> f64 {
let tr = t / comp.tc;
let db0_dtr = 0.422 * 1.6 * tr.powf(-2.6);
let db1_dtr = 0.172 * 4.2 * tr.powf(-5.2);
1000.0 * R_GAS / comp.pc * (db0_dtr + comp.omega * db1_dtr)
}
pub fn z_factor_virial(comp: &Component, t: f64, p: f64) -> f64 {
let b = pitzer_b(comp, t); 1.0 + b * p / (1000.0 * R_GAS * t)
}
pub fn ln_phi_pure_virial(comp: &Component, t: f64, p: f64) -> f64 {
let b = pitzer_b(comp, t);
b * p / (1000.0 * R_GAS * t)
}
pub fn h_departure_rt_virial(comp: &Component, t: f64, p: f64) -> f64 {
let b = pitzer_b(comp, t);
let db_dt = pitzer_d_b_d_t(comp, t);
(b - t * db_dt) * p / (1000.0 * R_GAS * t)
}
pub fn s_departure_r_virial(comp: &Component, t: f64, p: f64) -> f64 {
let db_dt = pitzer_d_b_d_t(comp, t);
-p * db_dt / (1000.0 * R_GAS)
}
pub fn b_mix_matrix(components: &[Component], t: f64) -> Vec<Vec<f64>> {
let n = components.len();
let mut mat = vec![vec![0.0_f64; n]; n];
for i in 0..n {
for j in i..n {
let bij = if i == j {
pitzer_b(&components[i], t)
} else {
let tc_ij = (components[i].tc * components[j].tc).sqrt();
let pc_ij = 0.5 * (components[i].pc + components[j].pc);
let omega_ij = 0.5 * (components[i].omega + components[j].omega);
let tr = t / tc_ij;
let b_reduced = pitzer_b0(tr) + omega_ij * pitzer_b1(tr);
1000.0 * R_GAS * tc_ij / pc_ij * b_reduced
};
mat[i][j] = bij;
mat[j][i] = bij;
}
}
mat
}
pub fn b_mix(components: &[Component], mole_fractions: &[f64], t: f64) -> Result<f64, VirialError> {
if components.len() != mole_fractions.len() {
return Err(VirialError::InvalidInput(format!(
"components.len()={} but mole_fractions.len()={}",
components.len(),
mole_fractions.len()
)));
}
let mat = b_mix_matrix(components, t);
let n = components.len();
let mut acc = 0.0_f64;
for i in 0..n {
for j in 0..n {
acc += mole_fractions[i] * mole_fractions[j] * mat[i][j];
}
}
Ok(acc)
}
pub fn ln_phi_mix_virial(
components: &[Component],
mole_fractions: &[f64],
t: f64,
p: f64,
) -> Result<Vec<f64>, VirialError> {
let n = components.len();
if n != mole_fractions.len() {
return Err(VirialError::InvalidInput(format!(
"components.len()={} but mole_fractions.len()={}",
n,
mole_fractions.len()
)));
}
let mat = b_mix_matrix(components, t);
let bmix = b_mix(components, mole_fractions, t)?;
let factor = p / (1000.0 * R_GAS * t);
let mut out = Vec::with_capacity(n);
for i in 0..n {
let sum_j: f64 = (0..n).map(|j| mole_fractions[j] * mat[i][j]).sum();
out.push(factor * (2.0 * sum_j - bmix));
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn dummy_methane() -> Component {
Component {
name: "methane".into(),
tc: 190.564,
pc: 4599.0, omega: 0.0115,
..Component::default()
}
}
#[test]
fn pitzer_b0_b1_at_critical() {
assert!((pitzer_b0(1.0) + 0.339).abs() < 1e-6);
assert!((pitzer_b1(1.0) + 0.033).abs() < 1e-6);
}
#[test]
fn virial_z_ideal_limit() {
let c = dummy_methane();
let z = z_factor_virial(&c, 300.0, 1.0); assert!((z - 1.0).abs() < 1e-3);
}
}