use std::f64;
use nalgebra::{DMatrix, SVD};
#[derive(Debug, Clone)]
pub struct ReactionBasis {
pub rank: usize,
pub num_reactions: usize,
pub reactions: DMatrix<f64>,
}
pub fn compute_reaction_basis(a: &DMatrix<f64>, tol: f64) -> ReactionBasis {
let m = a.nrows(); let _e = a.ncols();
let at = a.transpose();
let svd = SVD::new(at.clone(), true, true);
let _u = svd.u.expect("SVD failed: U");
let v_t = svd.v_t.expect("SVD failed: Vt");
let singular_values = svd.singular_values;
let rank = singular_values.iter().filter(|&&s| s > tol).count();
let num_reactions = if rank <= m { m - rank } else { 0 };
let mut reactions = DMatrix::<f64>::zeros(m, num_reactions);
if num_reactions > 0 && rank < v_t.nrows() {
let mut col = 0;
for i in rank..std::cmp::min(rank + num_reactions, v_t.nrows()) {
let v_col = v_t.row(i).transpose();
if col < reactions.ncols() {
reactions.set_column(col, &v_col);
col += 1;
}
}
}
ReactionBasis {
rank,
num_reactions,
reactions,
}
}
pub fn mole_numbers(xi: &[f64], n0: &[f64], reactions: &DMatrix<f64>) -> Vec<f64> {
let m = n0.len();
let r = xi.len();
let mut n = n0.to_vec();
for i in 0..m {
for j in 0..r {
n[i] += reactions[(i, j)] * xi[j];
}
}
n
}
pub fn reaction_delta_g(reactions: &DMatrix<f64>, gibbs: &[GibbsFn]) -> Vec<GibbsFn> {
let m = reactions.nrows();
let r = reactions.ncols();
assert_eq!(gibbs.len(), m);
(0..r)
.map(|j| {
let nu = reactions.column(j).clone_owned();
let g = gibbs.to_vec();
Arc::new(move |t: f64| {
let mut dg = 0.0;
for i in 0..m {
dg += nu[i] * g[i](t);
}
dg
}) as GibbsFn
})
.collect()
}
use std::sync::Arc;
pub type GibbsFn = Arc<dyn Fn(f64) -> f64 + Send + Sync>;
#[allow(non_upper_case_globals)]
const R_g: f64 = 8.314;
pub fn equilibrium_residual_generator(
n0: Vec<f64>,
reactions: DMatrix<f64>,
delta_g: Vec<GibbsFn>,
temperature: f64,
pressure: f64,
) -> impl Fn(&[f64]) -> Vec<f64> {
let r = reactions.ncols();
let m = reactions.nrows();
move |xi: &[f64]| {
let n = mole_numbers(xi, &n0, &reactions);
let N_tot: f64 = n.iter().sum();
let mut f = vec![0.0; r];
for j in 0..r {
let mut sum = 0.0;
let mut dN = 0.0;
for i in 0..m {
sum += reactions[(i, j)] * n[i].ln();
dN += reactions[(i, j)];
}
let ln_Kp = -delta_g[j](temperature) / (R_g * temperature);
f[j] = sum - dN * N_tot.ln() + dN * (pressure / 101325.0).ln() - ln_Kp;
}
f
}
}
#[cfg(test)]
mod tests {
use super::*;
use nalgebra::DMatrix;
const TOL: f64 = 1e-10;
#[test]
fn diatomic_dissociation_o2_o() {
let a = DMatrix::from_row_slice(
2,
1,
&[
2.0, 1.0, ],
);
let basis = compute_reaction_basis(&a, TOL);
println!("basis: {:?}", basis);
assert_eq!(basis.rank, 1);
assert_eq!(basis.num_reactions, 1);
let residual = a.transpose() * &basis.reactions;
assert!(residual.norm() < 1e-12);
}
#[test]
fn nitrogen_oxygen_system() {
let a = DMatrix::from_row_slice(
4,
2,
&[
2.0, 0.0, 0.0, 2.0, 1.0, 1.0, 1.0, 2.0, ],
);
let basis = compute_reaction_basis(&a, TOL);
assert_eq!(basis.rank, 2);
assert_eq!(basis.num_reactions, 2);
let residual = a.transpose() * &basis.reactions;
assert!(residual.norm() < 1e-12);
}
#[test]
fn methane_combustion_species() {
let a = DMatrix::from_row_slice(
4,
3,
&[
1.0, 4.0, 0.0, 0.0, 0.0, 2.0, 1.0, 0.0, 2.0, 0.0, 2.0, 1.0, ],
);
let basis = compute_reaction_basis(&a, TOL);
assert_eq!(basis.rank, 3);
assert_eq!(basis.num_reactions, 1);
let residual = a.transpose() * &basis.reactions;
assert!(residual.norm() < 1e-12);
}
}