use crate::saftvrqmie::parameters::SaftVRQMieParameters;
use feos_core::{HelmholtzEnergyDual, StateHD};
use ndarray::{Array1, Array2};
use num_dual::DualNum;
use std::f64::consts::FRAC_PI_6;
use std::fmt;
use std::sync::Arc;
const LAM_COEFF: [[f64; 4]; 4] = [
[0.81096, 1.7888, -37.578, 92.284],
[1.0205, -19.341, 151.26, -463.50],
[-1.9057, 22.845, -228.14, 973.92],
[1.0885, -6.1962, 106.98, -677.64],
];
const PHI: [[f64; 7]; 6] = [
[
7.5365557, -37.60463, 71.745953, -46.83552, -2.467982, -0.50272, 8.0956883,
],
[-359.44, 1825.6, -3168.0, 1884.2, -0.82376, -3.1935, 3.709],
[1550.9, -5070.1, 6534.6, -3288.7, -2.7171, 2.0883, 0.0],
[
-1.19932, 9.063632, -17.9482, 11.34027, 20.52142, -56.6377, 40.53683,
],
[
-1911.28, 21390.175, -51320.7, 37064.54, 1103.742, -3264.61, 2556.181,
],
[
9236.9, -129430.0, 357230.0, -315530.0, 1390.2, -4518.2, 4241.6,
],
];
pub struct Alpha<D: DualNum<f64>> {
alpha_ij: Array2<D>,
}
impl<D: DualNum<f64>> Alpha<D> {
pub fn new(
parameters: &SaftVRQMieParameters,
sigma_eff_ij: &Array2<D>,
epsilon_k_eff_ij: &Array2<D>,
temperature: D,
) -> Self {
let p = parameters;
let nc = sigma_eff_ij.shape()[0];
let mut alpha_ij: Array2<D> = Array2::zeros((nc, nc));
for i in 0..nc {
for j in 0..nc {
let sigma_ratio = D::one() * p.sigma_ij[[i, j]] / sigma_eff_ij[[i, j]];
let eps_ratio = D::one() * p.epsilon_k_ij[[i, j]] / epsilon_k_eff_ij[[i, j]];
let la = p.lambda_a_ij[[i, j]];
let lr = p.lambda_r_ij[[i, j]];
let sigma_ratio_a = sigma_ratio.powf(la);
let sigma_ratio_r = sigma_ratio.powf(lr);
let dmt = p.quantum_d_ij(i, j, temperature) / p.sigma_ij[[i, j]].powi(2);
let ma = sigma_ratio_a / (la - 3.0);
let mr = sigma_ratio_r / (lr - 3.0);
let q1a = sigma_ratio_a * sigma_ratio.powi(2) * la * (la - 1.0) / (la - 1.0);
let q1r = sigma_ratio_r * sigma_ratio.powi(2) * lr * (lr - 1.0) / (lr - 1.0);
alpha_ij[[i, j]] = (dmt * (q1a - q1r) + ma - mr) * p.c_ij[[i, j]] * eps_ratio;
}
}
Self { alpha_ij }
}
fn f(&self, k: usize, i: usize, j: usize) -> D {
let a_ij = self.alpha_ij[[i, j]];
let phi = PHI[k];
(a_ij * phi[1] + a_ij.powi(2) * phi[2] + a_ij.powi(3) * phi[3] + phi[0])
/ (a_ij * phi[4] + a_ij.powi(2) * phi[5] + a_ij.powi(3) * phi[6] + 1.0)
}
}
pub struct Dispersion {
pub parameters: Arc<SaftVRQMieParameters>,
}
impl<D: DualNum<f64>> HelmholtzEnergyDual<D> for Dispersion {
fn helmholtz_energy(&self, state: &StateHD<D>) -> D {
let n = self.parameters.m.len();
let p = &self.parameters;
let rho = &state.partial_density;
let s_eff_ij = Array2::from_shape_fn((n, n), |(i, j)| -> D {
p.calc_sigma_eff_ij(i, j, state.temperature)
});
let d_hs_ij = Array2::from_shape_fn((n, n), |(i, j)| -> D {
p.hs_diameter_ij(i, j, state.temperature, s_eff_ij[[i, j]])
});
let epsilon_k_eff_ij = Array2::from_shape_fn((n, n), |(i, j)| -> D {
p.calc_epsilon_k_eff_ij(i, j, state.temperature)
});
let dq_ij = Array2::from_shape_fn((n, n), |(i, j)| -> D {
p.quantum_d_ij(i, j, state.temperature)
});
let mut x_s = Array1::from_shape_fn(n, |i| -> D { state.molefracs[i] * p.m[i] });
let inv_x_s_sum = x_s.sum().recip();
for i in 0..n {
x_s[i] *= inv_x_s_sum;
}
let mut rho_s = D::zero();
for i in 0..n {
rho_s += rho[i] * p.m[i];
}
let zeta = zeta_saft_vrq_mie(&p.m, &x_s, &d_hs_ij, rho_s);
let zeta_bar = zeta_saft_vrq_mie(&p.m, &x_s, &s_eff_ij, rho_s);
let alpha = Alpha::new(p, &s_eff_ij, &epsilon_k_eff_ij, state.temperature);
let a1 = first_order_perturbation(p, &x_s, zeta, rho_s, &d_hs_ij, &s_eff_ij, &dq_ij);
let a2 = second_order_perturbation(
p, &alpha, &x_s, zeta, zeta_bar, rho_s, &d_hs_ij, &s_eff_ij, &dq_ij,
);
let a3 = third_order_perturbation(p, &alpha, &x_s, zeta_bar, &epsilon_k_eff_ij);
let mut n_s = D::zero();
for i in 0..n {
n_s += state.moles[i] * p.m[i];
}
let inv_t = state.temperature.recip();
n_s * (a1 * inv_t + a2 * inv_t.powi(2) + a3 * inv_t.powi(3))
}
}
#[cfg(feature = "dft")]
pub fn dispersion_energy_density<D: DualNum<f64>>(
parameters: &SaftVRQMieParameters,
d_hs_ij: &Array2<D>,
s_eff_ij: &Array2<D>,
epsilon_k_eff_ij: &Array2<D>,
dq_ij: &Array2<D>,
alpha: &Alpha<D>,
rho: &Array1<D>,
temperature: D,
) -> D {
let n = rho.len();
let p = ¶meters;
let mut rho_s = D::zero();
for i in 0..n {
rho_s += rho[i] * p.m[i];
}
let x_s = Array1::from_shape_fn(n, |i| -> D { rho[i] * p.m[i] * rho_s.recip() });
let zeta = zeta_saft_vrq_mie(&p.m, &x_s, d_hs_ij, rho_s);
let zeta_bar = zeta_saft_vrq_mie(&p.m, &x_s, s_eff_ij, rho_s);
let a1 = first_order_perturbation(p, &x_s, zeta, rho_s, d_hs_ij, s_eff_ij, dq_ij);
let a2 = second_order_perturbation(
p, alpha, &x_s, zeta, zeta_bar, rho_s, d_hs_ij, s_eff_ij, dq_ij,
);
let a3 = third_order_perturbation(p, alpha, &x_s, zeta_bar, epsilon_k_eff_ij);
let inv_t = temperature.recip();
rho_s * (a1 * inv_t + a2 * inv_t.powi(2) + a3 * inv_t.powi(3))
}
fn zeta_saft_vrq_mie<D: DualNum<f64>>(
m: &Array1<f64>,
x_s: &Array1<D>,
diameter: &Array2<D>,
rho_s: D,
) -> D {
let mut zeta = D::zero();
for i in 0..m.len() {
for j in 0..m.len() {
zeta += x_s[i] * x_s[j] * diameter[[i, j]].powi(3);
}
}
zeta * FRAC_PI_6 * rho_s
}
fn first_order_perturbation<D: DualNum<f64>>(
parameters: &SaftVRQMieParameters,
x_s: &Array1<D>,
zeta: D,
rho_s: D,
d_hs_ij: &Array2<D>,
s_eff_ij: &Array2<D>,
dq_ij: &Array2<D>,
) -> D {
let n = parameters.sigma.len();
let mut a1 = D::zero();
for i in 0..n {
for j in 0..n {
let x0 = d_hs_ij[[i, j]].recip() * parameters.sigma_ij[[i, j]];
let x0_eff = s_eff_ij[[i, j]] / d_hs_ij[[i, j]];
let dq_div_sigma_2 = dq_ij[[i, j]] / parameters.sigma_ij[[i, j]].powi(2);
a1 += x_s[i]
* x_s[j]
* FRAC_PI_6
* rho_s
* d_hs_ij[[i, j]].powi(3)
* first_order_perturbation_ij(
parameters.lambda_a_ij[[i, j]],
parameters.lambda_r_ij[[i, j]],
parameters.epsilon_k_ij[[i, j]],
zeta,
x0,
x0_eff,
parameters.c_ij[[i, j]],
dq_div_sigma_2,
)
}
}
a1
}
fn first_order_perturbation_ij<D: DualNum<f64>>(
lambda_a: f64,
lambda_r: f64,
epsilon_k: f64,
zeta: D,
x0: D,
x0_eff: D,
c: f64,
dq_div_sigma_2: D,
) -> D {
let int_a = combine_sutherland_and_b(lambda_a, epsilon_k, zeta, x0, x0_eff);
let int_r = combine_sutherland_and_b(lambda_r, epsilon_k, zeta, x0, x0_eff);
let int_qa = combine_sutherland_and_b(lambda_a + 2.0, epsilon_k, zeta, x0, x0_eff);
let int_qr = combine_sutherland_and_b(lambda_r + 2.0, epsilon_k, zeta, x0, x0_eff);
let qa1 = dq_div_sigma_2 * quantum_prefactor(lambda_a);
let qr1 = dq_div_sigma_2 * quantum_prefactor(lambda_r);
(int_qa * qa1 - int_qr * qr1 + int_a - int_r) * c
}
fn eta_eff<D: DualNum<f64>>(lambda: f64, zeta: D) -> D {
let inv_lambda = Array1::from(vec![
1.0,
1.0 / lambda,
1.0 / lambda.powi(2),
1.0 / lambda.powi(3),
]);
let c = Array1::from_shape_fn(4, |i| {
inv_lambda[0] * LAM_COEFF[i][0]
+ inv_lambda[1] * LAM_COEFF[i][1]
+ inv_lambda[2] * LAM_COEFF[i][2]
+ inv_lambda[3] * LAM_COEFF[i][3]
});
zeta * (zeta * (zeta * (zeta * c[3] + c[2]) + c[1]) + c[0])
}
fn sutherland<D: DualNum<f64>>(lambda: f64, epsilon_k: f64, zeta: D, x0: D) -> D {
let ef = eta_eff(lambda, zeta);
(-ef * 0.5 + 1.0) * -12.0 * x0.powf(lambda) * epsilon_k / (lambda - 3.0) / (-ef + 1.0).powi(3)
}
fn ilambda<D: DualNum<f64>>(lambda: f64, x0: D) -> D {
-(x0.powf(3.0 - lambda) - 1.0) / (lambda - 3.0)
}
fn jlambda<D: DualNum<f64>>(lambda: f64, x0: D) -> D {
-(x0.powf(4.0 - lambda) * (lambda - 3.0) - x0.powf(3.0 - lambda) * (lambda - 4.0) - 1.0)
/ ((lambda - 3.0) * (lambda - 4.0))
}
fn b<D: DualNum<f64>>(lambda: f64, epsilon_k: f64, zeta: D, x0: D, x0_eff: D) -> D {
let ilambda = ilambda(lambda, x0_eff);
let jlambda = jlambda(lambda, x0_eff);
let denum = (-zeta + 1.0).powi(3);
x0.powf(lambda)
* ((-zeta + 2.0) / denum * ilambda + -zeta * 9.0 * (zeta + 1.0) / denum * jlambda)
* 6.0
* epsilon_k
}
#[inline]
fn combine_sutherland_and_b<D: DualNum<f64>>(
lambda: f64,
epsilon_k: f64,
zeta: D,
x0: D,
x0_eff: D,
) -> D {
let int_as = sutherland(lambda, epsilon_k, zeta, x0);
let int_b = b(lambda, epsilon_k, zeta, x0, x0_eff);
int_as + int_b
}
fn second_order_perturbation<D: DualNum<f64>>(
parameters: &SaftVRQMieParameters,
alpha: &Alpha<D>,
x_s: &Array1<D>,
zeta: D,
zeta_bar: D,
rho_s: D,
d_hs_ij: &Array2<D>,
s_eff_ij: &Array2<D>,
dq_ij: &Array2<D>,
) -> D {
let n = parameters.sigma.len();
let mut a2 = D::zero();
let k = (-zeta + 1.0).powi(4)
/ (zeta * 4.0 + zeta.powi(2) * 4.0 - zeta.powi(3) * 4.0 + zeta.powi(4) + 1.0);
for i in 0..n {
for j in 0..n {
let chi = alpha.f(0, i, j) * zeta_bar
+ alpha.f(1, i, j) * zeta_bar.powi(5)
+ alpha.f(2, i, j) * zeta_bar.powi(8);
let x0 = d_hs_ij[[i, j]].recip() * parameters.sigma_ij[[i, j]];
let x0_eff = s_eff_ij[[i, j]] / d_hs_ij[[i, j]];
let dq_div_sigma_2 = dq_ij[[i, j]] / parameters.sigma_ij[[i, j]].powi(2);
a2 += x_s[i]
* x_s[j]
* FRAC_PI_6
* rho_s
* d_hs_ij[[i, j]].powi(3)
* (chi + 1.0)
* second_order_perturbation_ij(
parameters.lambda_a_ij[[i, j]],
parameters.lambda_r_ij[[i, j]],
parameters.epsilon_k_ij[[i, j]],
zeta,
x0,
x0_eff,
parameters.c_ij[[i, j]],
dq_div_sigma_2,
)
}
}
a2 * k
}
#[inline]
fn quantum_prefactor(lambda: f64) -> f64 {
lambda * (lambda - 1.0)
}
fn second_order_perturbation_ij<D: DualNum<f64>>(
lambda_a: f64,
lambda_r: f64,
epsilon_k: f64,
zeta: D,
x0: D,
x0_eff: D,
c: f64,
dq_div_sigma_2: D,
) -> D {
let lambda_2r = 2.0 * lambda_r;
let lambda_2a = 2.0 * lambda_a;
let lambda_ar = lambda_a + lambda_r;
let qa1 = dq_div_sigma_2 * quantum_prefactor(lambda_a);
let qr1 = dq_div_sigma_2 * quantum_prefactor(lambda_r);
let mut a2_ij = D::zero();
let mut afac = D::one();
let mut rfac = D::one();
let mut arfac = -D::one() * 2.0;
for q in 0..3 {
let int_a =
combine_sutherland_and_b(lambda_2a + 2.0 * q as f64, epsilon_k, zeta, x0, x0_eff);
let int_r =
combine_sutherland_and_b(lambda_2r + 2.0 * q as f64, epsilon_k, zeta, x0, x0_eff);
let int_ar =
combine_sutherland_and_b(lambda_ar + 2.0 * q as f64, epsilon_k, zeta, x0, x0_eff);
if q == 1 {
rfac = qr1 * 2.0;
afac = qa1 * 2.0;
arfac = -(rfac + afac);
} else if q == 2 {
rfac = qr1 * qr1;
afac = qa1 * qa1;
arfac = -qa1 * qr1 * 2.0;
}
a2_ij += int_a * afac + int_ar * arfac + int_r * rfac;
}
a2_ij * 0.5 * epsilon_k * c.powi(2)
}
fn third_order_perturbation<D: DualNum<f64>>(
parameters: &SaftVRQMieParameters,
alpha: &Alpha<D>,
x_s: &Array1<D>,
zeta_bar: D,
epsilon_k_eff_ij: &Array2<D>,
) -> D {
let n = parameters.sigma.len();
let mut a3 = D::zero();
for i in 0..n {
for j in 0..n {
a3 += x_s[i]
* x_s[j]
* third_order_perturbation_ij(i, j, epsilon_k_eff_ij[[i, j]], alpha, zeta_bar)
}
}
a3
}
fn third_order_perturbation_ij<D: DualNum<f64>>(
i: usize,
j: usize,
epsilon_k_eff: D,
alpha: &Alpha<D>,
zeta_bar: D,
) -> D {
-epsilon_k_eff.powi(3)
* alpha.f(3, i, j)
* zeta_bar
* (alpha.f(4, i, j) * zeta_bar + alpha.f(5, i, j) * zeta_bar.powi(2)).exp()
}
impl fmt::Display for Dispersion {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "Dispersion")
}
}
#[allow(clippy::excessive_precision)]
#[cfg(test)]
mod tests {
use super::*;
use crate::saftvrqmie::parameters::utils::h2_ne_fh1;
use crate::saftvrqmie::parameters::utils::hydrogen_fh1;
use approx::assert_relative_eq;
use ndarray::arr1;
use num_dual::Dual2;
use num_dual::DualNum;
use num_traits::Zero;
#[test]
fn test_eta_eff() {
let lambda_6 = 6.0;
let lambda_12 = 12.0;
let mut zeta = 0.45;
let e_eff_6_045 = eta_eff(lambda_6, zeta);
let e_eff_12_045 = eta_eff(lambda_12, zeta);
zeta = 0.1;
let e_eff_6_01 = eta_eff(lambda_6, zeta);
let e_eff_12_01 = eta_eff(lambda_12, zeta);
assert_relative_eq!(e_eff_6_045, 0.19401806958333320, epsilon = 1e-7);
assert_relative_eq!(e_eff_12_045, 0.32193464807291666, epsilon = 1e-7);
assert_relative_eq!(e_eff_6_01, 4.7840898518518506E-002, epsilon = 1e-7);
assert_relative_eq!(e_eff_12_01, 7.6226364537037058E-002, epsilon = 1e-7);
}
#[test]
fn test_alpha() {
let temperature = Dual2::from_re(26.7060).derive();
let parameters = hydrogen_fh1();
let n = 1;
let s_eff_ij = Array2::from_shape_fn((n, n), |(i, j)| {
parameters.calc_sigma_eff_ij(i, j, temperature)
});
let epsilon_k_eff_ij = Array2::from_shape_fn((n, n), |(i, j)| {
parameters.calc_epsilon_k_eff_ij(i, j, temperature)
});
let alpha = Alpha::new(¶meters, &s_eff_ij, &epsilon_k_eff_ij, temperature);
assert_relative_eq!(
alpha.alpha_ij[[0, 0]].re(),
1.0239374984636636,
epsilon = 5e-8
);
}
#[test]
fn test_sutherland() {
let x0 = Dual2::from_re(1.1).derive();
let zeta = Dual2::from_re(0.333).derive();
let lambda = 13.77;
let eps_div_k = 13.88;
let asa = sutherland(lambda, eps_div_k, zeta, x0);
assert_relative_eq!(asa.re(), -122.12017536923423, epsilon = 1e-12);
}
#[test]
fn test_b() {
let x0 = Dual2::from_re(1.1).derive();
let zeta = Dual2::from_re(0.333).derive();
let lambda = 13.77;
let eps_div_k = 13.88;
let ba = b(lambda, eps_div_k, zeta, x0, x0);
assert_relative_eq!(ba.re(), 93.436438943866293, epsilon = 1e-12);
}
#[test]
fn test_quantum_d_ij() {
let p = hydrogen_fh1();
let temperature = Dual2::from_re(26.7060).derive();
let dq_ij = p.quantum_d_ij(0, 0, temperature);
assert_relative_eq!(dq_ij.re(), 7.5092605940987542e-2, epsilon = 5e-8);
}
#[test]
fn test_first_order_perturbation_ij() {
let p = hydrogen_fh1();
let temperature = Dual2::from_re(26.7060).derive();
let zeta = Dual2::from_re(0.333).derive();
let dq_div_s2 = p.quantum_d_ij(0, 0, temperature) / p.sigma_ij[[0, 0]].powi(2);
let s_eff = p.calc_sigma_eff_ij(0, 0, temperature);
let d_hs = p.hs_diameter_ij(0, 0, temperature, s_eff);
let x0 = d_hs.recip() * p.sigma_ij[[0, 0]];
let x0_eff = s_eff / d_hs;
let a1_ij = first_order_perturbation_ij(
p.lambda_a_ij[[0, 0]],
p.lambda_r_ij[[0, 0]],
p.epsilon_k_ij[[0, 0]],
zeta,
x0,
x0_eff,
p.c_ij[[0, 0]],
dq_div_s2,
);
let rel_err = (a1_ij.re() + 332.00915966785539) / 332.00915966785539;
assert_relative_eq!(rel_err, 0.0, epsilon = 1e-7);
}
#[test]
fn test_second_order_perturbation_ij() {
let p = hydrogen_fh1();
let temperature = Dual2::from_re(26.7060).derive();
let zeta = Dual2::from_re(0.333).derive();
let dq_div_s2 = p.quantum_d_ij(0, 0, temperature) / p.sigma_ij[[0, 0]].powi(2);
let s_eff = p.calc_sigma_eff_ij(0, 0, temperature);
let d_hs = p.hs_diameter_ij(0, 0, temperature, s_eff);
let x0 = d_hs.recip() * p.sigma_ij[[0, 0]];
let x0_eff = s_eff / d_hs;
let a2_ij = second_order_perturbation_ij(
p.lambda_a_ij[[0, 0]],
p.lambda_r_ij[[0, 0]],
p.epsilon_k_ij[[0, 0]],
zeta,
x0,
x0_eff,
p.c_ij[[0, 0]],
dq_div_s2,
);
let rel_err = (a2_ij.re() + 1907.5055256805874) / 1907.5055256805874;
assert_relative_eq!(rel_err, 0.0, epsilon = 1e-7);
}
#[test]
fn test_third_order_perturbation_ij() {
let p = hydrogen_fh1();
let temperature = Dual2::from_re(26.7060).derive();
let zeta_bar = Dual2::from_re(0.333).derive();
let n = 1;
let s_eff_ij =
Array2::from_shape_fn((n, n), |(i, j)| p.calc_sigma_eff_ij(i, j, temperature));
let epsilon_k_eff_ij =
Array2::from_shape_fn((n, n), |(i, j)| p.calc_epsilon_k_eff_ij(i, j, temperature));
let alpha = Alpha::new(&p, &s_eff_ij, &epsilon_k_eff_ij, temperature);
let a3_ij = third_order_perturbation_ij(0, 0, epsilon_k_eff_ij[[0, 0]], &alpha, zeta_bar);
let rel_err = (a3_ij.re() + 25.807966819127916) / 25.807966819127916;
assert_relative_eq!(rel_err, 0.0, epsilon = 5e-7);
}
#[test]
fn test_zeta_saft_vrq_mie() {
let p = hydrogen_fh1();
let t = Dual2::from_re(26.7060).derive();
let v = Dual2::from_re(1.0e26).derive();
let n = Dual2::from_re(6.02214076e23).derive();
let state = StateHD::new(t, v, arr1(&[n]));
let nc = 1;
let s_eff_ij = Array2::from_shape_fn((nc, nc), |(i, j)| {
p.calc_sigma_eff_ij(i, j, state.temperature)
});
let d_hs_ij = Array2::from_shape_fn((nc, nc), |(i, j)| {
p.hs_diameter_ij(i, j, state.temperature, s_eff_ij[[i, j]])
});
let mut x_s = Array1::from_shape_fn(nc, |i| state.molefracs[i] * p.m[i]);
let inv_x_s_sum = x_s.sum().recip();
for i in 0..nc {
x_s[i] *= inv_x_s_sum;
}
let mut rho_s = Dual2::zero();
for i in 0..nc {
rho_s += state.partial_density[i] * p.m[i];
}
let zeta = zeta_saft_vrq_mie(&p.m, &x_s, &d_hs_ij, rho_s);
let zeta_bar = zeta_saft_vrq_mie(&p.m, &x_s, &s_eff_ij, rho_s);
assert_relative_eq!(zeta.re(), 9.7717457994590765E-002, epsilon = 5e-9);
assert_relative_eq!(zeta_bar.re(), 0.10864364645845238, epsilon = 5e-9);
}
#[test]
fn test_perturbation_terms() {
let p = hydrogen_fh1();
let t = Dual2::from_re(26.7060).derive();
let v = Dual2::from_re(1.0e26).derive();
let n = Dual2::from_re(6.02214076e23).derive();
let state = StateHD::new(t, v, arr1(&[n]));
let nc = 1;
let s_eff_ij = Array2::from_shape_fn((nc, nc), |(i, j)| {
p.calc_sigma_eff_ij(i, j, state.temperature)
});
let d_hs_ij = Array2::from_shape_fn((nc, nc), |(i, j)| {
p.hs_diameter_ij(i, j, state.temperature, s_eff_ij[[i, j]])
});
let mut x_s = Array1::from_shape_fn(nc, |i| state.molefracs[i] * p.m[i]);
let inv_x_s_sum = x_s.sum().recip();
for i in 0..nc {
x_s[i] *= inv_x_s_sum;
}
let mut rho_s = Dual2::zero();
for i in 0..nc {
rho_s += state.partial_density[i] * p.m[i];
}
let zeta = zeta_saft_vrq_mie(&p.m, &x_s, &d_hs_ij, rho_s);
let zeta_bar = zeta_saft_vrq_mie(&p.m, &x_s, &s_eff_ij, rho_s);
let epsilon_k_eff_ij = Array2::from_shape_fn((nc, nc), |(i, j)| {
p.calc_epsilon_k_eff_ij(i, j, state.temperature)
});
let alpha = Alpha::new(&p, &s_eff_ij, &epsilon_k_eff_ij, state.temperature);
let dq_ij =
Array2::from_shape_fn((nc, nc), |(i, j)| p.quantum_d_ij(i, j, state.temperature));
let a1 = first_order_perturbation(&p, &x_s, zeta, rho_s, &d_hs_ij, &s_eff_ij, &dq_ij);
let a2 = second_order_perturbation(
&p, &alpha, &x_s, zeta, zeta_bar, rho_s, &d_hs_ij, &s_eff_ij, &dq_ij,
);
let a3 = third_order_perturbation(&p, &alpha, &x_s, zeta_bar, &epsilon_k_eff_ij);
let rel_err_a1 = (a1.re() + 30.702499892515764) / 30.702499892515764;
let rel_err_a2 = (a2.re() + 67.046957636607587) / 67.046957636607587;
let rel_err_a3 = (a3.re() + 470.96241656623727) / 470.96241656623727;
assert_relative_eq!(rel_err_a1.re(), 0.0, epsilon = 5e-7);
assert_relative_eq!(rel_err_a2.re(), 0.0, epsilon = 5e-7);
assert_relative_eq!(rel_err_a3.re(), 0.0, epsilon = 5e-7);
}
#[test]
fn test_dispersion() {
let disp = Dispersion {
parameters: hydrogen_fh1(),
};
let a_ref = [
-1.2683816065838103,
-0.61628364979962436,
-0.40740884837300861,
-0.30420171646199534,
-0.24264152651314486,
];
let na = 6.02214076e23;
for (it, &a) in a_ref.iter().enumerate() {
let t = Dual2::from_re(26.7060 * (it + 1) as f64).derive();
let v = Dual2::from_re(1.0e26).derive();
let n = Dual2::from_re(na).derive();
let state = StateHD::new(t, v, arr1(&[n]));
let a_disp = disp.helmholtz_energy(&state) / na;
assert_relative_eq!(a_disp.re(), a, epsilon = 1e-7);
}
let t = Dual2::from_re(26.7060).derive();
let v = Dual2::from_re(1.0e26 * 2.0).derive();
let n = Dual2::from_re(na * 2.0).derive();
let state = StateHD::new(t, v, arr1(&[n]));
let a_disp = disp.helmholtz_energy(&state) / na;
assert_relative_eq!(a_disp.re(), a_ref[0] * 2.0, epsilon = 1e-7);
}
#[test]
fn test_parameters_mix() {
let disp = Dispersion {
parameters: h2_ne_fh1(),
};
let p = disp.parameters;
assert_relative_eq!(p.c_ij[[0, 1]], 4.7303195840057679, epsilon = 1e-7);
assert_relative_eq!(p.epsilon_k_ij[[0, 1]], 28.246978839971383, epsilon = 1e-7);
assert_relative_eq!(p.lambda_a_ij[[0, 1]], 6.0, epsilon = 1e-7);
assert_relative_eq!(p.lambda_r_ij[[0, 1]], 10.745966692414834, epsilon = 1e-7);
}
#[test]
fn test_dispersion_mix() {
let disp = Dispersion {
parameters: h2_ne_fh1(),
};
let a_ref = [
-4.4340438372333235,
-2.1563424617699911,
-1.4211021562556054,
-1.0581654195146963,
-0.84210863940206726,
];
let na = 6.02214076e23;
let n = [Dual2::from_re(1.1 * na), Dual2::from_re(1.0 * na)];
let v = Dual2::from_re(1.0e26).derive();
for (it, &a) in a_ref.iter().enumerate() {
let t = Dual2::from_re(30.0 * (it + 1) as f64).derive();
let state = StateHD::new(t, v, arr1(&n));
let a_disp = disp.helmholtz_energy(&state) / na;
dbg!(it);
assert_relative_eq!(a_disp.re(), a, epsilon = 1e-7);
}
let t = Dual2::from_re(30.0).derive();
let v = Dual2::from_re(1.0e26 * 2.0).derive();
let n = [Dual2::from_re(2.2 * na), Dual2::from_re(2.0 * na)];
let state = StateHD::new(t, v, arr1(&n));
let a_disp = disp.helmholtz_energy(&state) / na;
assert_relative_eq!(a_disp.re(), a_ref[0] * 2.0, epsilon = 1e-7);
}
#[cfg(feature = "dft")]
#[test]
fn test_dispersion_energy_density() {
let disp = Dispersion {
parameters: hydrogen_fh1(),
};
let p = &disp.parameters;
let n = p.m.len();
let rho = Array1::from_shape_fn(n, |_i| Dual2::from_re(0.01));
let t = Dual2::from_re(25.0).derive();
let s_eff_ij = Array2::from_shape_fn((n, n), |(i, j)| p.calc_sigma_eff_ij(i, j, t));
let d_hs_ij =
Array2::from_shape_fn((n, n), |(i, j)| p.hs_diameter_ij(i, j, t, s_eff_ij[[i, j]]));
let epsilon_k_eff_ij =
Array2::from_shape_fn((n, n), |(i, j)| p.calc_epsilon_k_eff_ij(i, j, t));
let dq_ij = Array2::from_shape_fn((n, n), |(i, j)| p.quantum_d_ij(i, j, t));
let alpha = Alpha::new(p, &s_eff_ij, &epsilon_k_eff_ij, t);
let a_disp = dispersion_energy_density(
p,
&d_hs_ij,
&s_eff_ij,
&epsilon_k_eff_ij,
&dq_ij,
&alpha,
&rho,
t,
);
dbg!(rho);
dbg!(a_disp.re());
assert_relative_eq!(a_disp.re(), -0.022349175545184223, epsilon = 1e-7);
}
}