use crate::Thermodynamics::DBhandlers::TRANSPORTdata::{
TransportData, TransportInput, omega_11_calc, omega_11_calc_sym,
};
use crate::Thermodynamics::DBhandlers::transport_api::TransportError;
use crate::Thermodynamics::thermo_lib_api::ThermoData;
use RustedSciThe::symbolic::symbolic_engine::Expr;
use std::collections::HashMap;
use std::fmt;
#[derive(Debug)]
pub enum PairDiffusionError {
InvalidTemperatureRange,
SymbolicError(String),
InvalidUnit(String),
SubstanceNotFound(String),
}
impl fmt::Display for PairDiffusionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PairDiffusionError::InvalidTemperatureRange => {
write!(f, "Invalid temperature range specified.")
}
PairDiffusionError::SymbolicError(msg) => {
write!(f, "Symbolic computation error: {}", msg)
}
PairDiffusionError::InvalidUnit(unit) => write!(f, "Invalid unit specified: {}", unit),
PairDiffusionError::SubstanceNotFound(name) => {
write!(f, "Substance not found: {}", name)
}
}
}
}
pub struct PairDiffusion {
pub substance_A_input: TransportInput,
pub substance_B_input: TransportInput,
pub T_interval: Option<(f64, f64)>,
pub T: f64,
pub P: f64,
pub M_A: f64,
pub M_B: f64,
pub e_k: f64,
pub sigma: f64,
pub mu: f64,
pub D: f64,
pub D_unit: Option<String>,
pub unit_multiplier: f64,
pub D_closure: Box<dyn Fn(f64) -> f64 + Send + Sync>,
pub D_symbolic: Expr,
}
impl PairDiffusion {
pub fn default() -> Self {
PairDiffusion {
substance_A_input: TransportInput::default(),
substance_B_input: TransportInput::default(),
T_interval: None,
T: 300.0,
P: 101325.0,
M_A: 0.0,
M_B: 0.0,
e_k: 0.0,
sigma: 0.0,
mu: 0.0,
D: 0.0,
D_unit: None,
unit_multiplier: 1.0,
D_closure: Box::new(|_T| 0.0),
D_symbolic: Expr::Const(0.0),
}
}
pub fn new(
substance_A_input: TransportInput,
substance_B_input: TransportInput,
T: f64,
P: f64,
M_A: f64,
M_B: f64,
) -> Self {
PairDiffusion {
substance_A_input,
substance_B_input,
T_interval: None,
T,
P,
M_A,
M_B,
e_k: 0.0,
sigma: 0.0,
mu: 0.0,
D: 0.0,
D_unit: None,
unit_multiplier: 1.0,
D_closure: Box::new(|_T| 0.0),
D_symbolic: Expr::Const(0.0),
}
}
pub fn set_T_interval(&mut self, T_min: f64, T_max: f64) {
self.T_interval = Some((T_min, T_max));
}
pub fn set_D_unit(&mut self, unit: Option<String>) -> Result<(), PairDiffusionError> {
if let Some(unit) = unit {
self.D_unit = Some(unit.clone());
match unit.as_str() {
"m2/s" => self.unit_multiplier = 1.0,
"cm2/s" => self.unit_multiplier = 1e4,
_ => return Err(PairDiffusionError::InvalidUnit(unit)),
}
}
Ok(())
}
pub fn calc_coefficients(&mut self) {
let TransportInput {
Altname: _,
polar: alpha_A,
Form: _,
diam: sigma_k_A,
well_depth: e_k_A,
rot_relax: _,
dipole: mu_A,
} = self.substance_A_input;
let TransportInput {
Altname: _,
polar: alpha_B,
Form: _,
diam: sigma_k_B,
well_depth: e_k_B,
rot_relax: _,
dipole: mu_B,
} = self.substance_B_input;
const K_B: f64 = 1.38e-23;
let (e_k, sigma, mu) = if (mu_A == 0.0 && mu_B == 0.0) || (mu_A != 0.0 && mu_B != 0.0) {
let e_k = (e_k_A * e_k_B).powf(0.5);
let sigma = (sigma_k_A + sigma_k_B) / 2.0;
let mu = (mu_A * mu_B).powf(0.5);
(e_k, sigma, mu)
} else {
let (mu, e_k_p, sigma_p, e_k_n, sigma_n, alpha_n) = if mu_A != 0.0 {
(mu_A, e_k_A, sigma_k_A, e_k_B, sigma_k_B, alpha_B)
} else {
(mu_B, e_k_B, sigma_k_B, e_k_A, sigma_k_A, alpha_A)
};
let alpha_1 = alpha_n / sigma_n.powi(3);
let mu_1 = 3.162e-10 * mu / (e_k_p * K_B * sigma_p.powi(3)).powf(0.5);
let xi = 1.0 + (1.0 / 4.0) * alpha_1 * mu_1 * (e_k_p / e_k_n).powf(0.5);
let sigma = xi.powf(-1.0 / 6.0) * (sigma_n + sigma_p) / 2.0;
let e_k = xi.powi(2) * (e_k_n * e_k_p).powf(0.5);
(e_k, sigma, 0.0)
};
self.e_k = e_k;
self.sigma = sigma;
self.mu = mu;
}
pub fn calculate_D(&mut self) {
let P = self.P;
let T = self.T;
let e_k = self.e_k;
let sigma = self.sigma;
let mu = self.mu;
let M_a = self.M_A;
let M_b = self.M_B;
let um = self.unit_multiplier;
let omega = omega_11_calc(T, e_k, mu, sigma);
let m1 = 1.0 / M_a + 1.0 / M_b;
let D = um * 1.858e-3 * T.powf(1.5) * m1.powf(0.5) / (P * sigma.powi(2) * omega);
self.D = D;
}
pub fn calculate_D_closure(&mut self) {
let P = self.P;
let e_k = self.e_k.clone();
let sigma = self.sigma.clone();
let mu = self.mu.clone();
let M_a = self.M_A;
let M_b = self.M_B;
let um = self.unit_multiplier;
let m1 = 1.0 / M_a + 1.0 / M_b;
let D_closure = move |T| {
let omega = omega_11_calc(T, e_k, mu, sigma);
um * 1.858e-3 * T.powf(1.5) * m1.powf(0.5) / (P * sigma.powi(2) * omega)
};
self.D_closure = Box::new(D_closure);
}
pub fn calculate_D_symbolic(&mut self) {
let P = self.P;
let e_k = self.e_k.clone();
let sigma = self.sigma.clone();
let mu = self.mu.clone();
let M_a = self.M_A;
let M_b = self.M_B;
let um = self.unit_multiplier;
let m1 = 1.0 / M_a + 1.0 / M_b;
let omega = omega_11_calc_sym(e_k, mu, sigma);
let T = Expr::Var("T".to_string());
let P = Expr::Const(P);
let k = Expr::Const(um * 1.858e-3);
let sigma = Expr::Const(sigma);
let m1 = Expr::Const(m1);
let D_symbolic = k * T.pow(Expr::Const(1.5)) * m1.pow(Expr::Const(0.5))
/ (P * sigma.pow(Expr::Const(2.0)) * omega);
self.D_symbolic = D_symbolic;
}
pub fn taylor_series(&mut self, T0: f64, n: usize) {
let D_sym = self.D_symbolic.clone();
let series = D_sym.taylor_series1D("T", T0, n);
self.D_symbolic = series;
}
pub fn integr_mean(&mut self) -> Result<(), PairDiffusionError> {
use RustedSciThe::symbolic::symbolic_integration::QuadMethod;
let (T_min, T_max) = self
.T_interval
.ok_or(PairDiffusionError::InvalidTemperatureRange)?;
if T_min >= T_max {
return Err(PairDiffusionError::InvalidTemperatureRange);
}
let T_range = T_max - T_min;
let inv_T_range = 1.0 / T_range;
let D_sym = self.D_symbolic.clone();
let I = inv_T_range
* D_sym
.quad(QuadMethod::GaussLegendre, 3, T_min, T_max, None)
.map_err(|e| {
PairDiffusionError::SymbolicError(format!("Failed to integrate entropy: {}", e))
})?;
self.D = I;
Ok(())
}
}
impl Clone for PairDiffusion {
fn clone(&self) -> Self {
PairDiffusion {
substance_A_input: self.substance_A_input.clone(),
substance_B_input: self.substance_B_input.clone(),
T_interval: self.T_interval,
T: self.T,
P: self.P,
M_A: self.M_A,
M_B: self.M_B,
e_k: self.e_k,
sigma: self.sigma,
mu: self.mu,
D: self.D,
D_unit: self.D_unit.clone(),
unit_multiplier: self.unit_multiplier,
D_closure: Box::new(|_T| 0.0),
D_symbolic: self.D_symbolic.clone(),
}
}
}
use std::fmt::Debug;
impl Debug for PairDiffusion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PairDiffusion")
.field("substance_A_input", &self.substance_A_input)
.field("substance_B_input", &self.substance_B_input)
.field("T_interval", &self.T_interval)
.field("T", &self.T)
.field("P", &self.P)
.field("M_A", &self.M_A)
.field("M_B", &self.M_B)
.field("e_k", &self.e_k)
.field("sigma", &self.sigma)
.field("mu", &self.mu)
.field("D", &self.D)
.field("D_unit", &self.D_unit)
.field("unit_multiplier", &self.unit_multiplier)
.field("D_symbolic", &self.D_symbolic)
.finish()
}
}
#[derive(Debug)]
#[derive(Clone)]
pub struct MultiSubstanceDiffusion {
pub substances: HashMap<String, TransportData>,
pub substance_names: Vec<String>,
pub pair_diffusions: HashMap<(String, String), PairDiffusion>,
pub diffusion_matrix: Option<Vec<Vec<f64>>>,
pub T: f64,
pub P: f64,
pub D_unit: Option<String>,
}
impl MultiSubstanceDiffusion {
pub fn default() -> Self {
Self {
substances: HashMap::new(),
substance_names: Vec::new(),
pair_diffusions: HashMap::new(),
diffusion_matrix: None,
T: 300.0,
P: 101325.0,
D_unit: None,
}
}
pub fn new(T: f64, P: f64) -> Self {
Self {
substances: HashMap::new(),
substance_names: Vec::new(),
pair_diffusions: HashMap::new(),
diffusion_matrix: None,
T,
P,
D_unit: None,
}
}
pub fn add_substance(&mut self, name: String, mut transport_data: TransportData) {
transport_data.P = self.P;
self.substances.insert(name.clone(), transport_data);
if !self.substance_names.contains(&name) {
self.substance_names.push(name);
}
}
pub fn add_substances_from_library(
&mut self,
substance_names: Vec<String>,
library_name: &str,
) -> Result<(), TransportError> {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get(library_name).ok_or_else(|| {
TransportError::MissingData(format!("Library {} not found", library_name))
})?;
for name in substance_names {
let data = sublib.get(&name).ok_or_else(|| {
TransportError::MissingData(format!("Substance {} not found", name.to_string()))
})?;
let mut transport_data = TransportData::new();
transport_data.from_serde(data.clone())?;
transport_data.P = self.P;
self.add_substance(name, transport_data);
}
Ok(())
}
pub fn calculate_all_pairs(&mut self) {
self.pair_diffusions.clear();
for (i, name_a) in self.substance_names.iter().enumerate() {
for (j, name_b) in self.substance_names.iter().enumerate() {
if i <= j {
let transport_a = &self.substances[name_a];
let transport_b = &self.substances[name_b];
let mut pair_diff = PairDiffusion::new(
transport_a.input.clone(),
transport_b.input.clone(),
self.T,
self.P,
transport_a.M,
transport_b.M,
);
if let Some(ref unit) = self.D_unit {
let _ = pair_diff.set_D_unit(Some(unit.clone()));
}
pair_diff.calc_coefficients();
pair_diff.calculate_D();
self.pair_diffusions
.insert((name_a.clone(), name_b.clone()), pair_diff);
}
}
}
}
pub fn calculate_all_closures(&mut self) {
for pair_diff in self.pair_diffusions.values_mut() {
pair_diff.calculate_D_closure();
}
}
pub fn calculate_all_symbolic(&mut self) {
for pair_diff in self.pair_diffusions.values_mut() {
pair_diff.calculate_D_symbolic();
}
}
pub fn create_diffusion_matrix(&mut self) -> Vec<Vec<f64>> {
let n = self.substance_names.len();
let mut matrix = vec![vec![0.0; n]; n];
for (i, name_a) in self.substance_names.iter().enumerate() {
for (j, name_b) in self.substance_names.iter().enumerate() {
let key = if i <= j {
(name_a.clone(), name_b.clone())
} else {
(name_b.clone(), name_a.clone())
};
if let Some(pair_diff) = self.pair_diffusions.get(&key) {
matrix[i][j] = pair_diff.D;
matrix[j][i] = pair_diff.D;
}
}
}
self.diffusion_matrix = Some(matrix.clone());
matrix
}
pub fn get_pair_diffusion(&self, sub_a: &str, sub_b: &str) -> Option<&PairDiffusion> {
let key1 = (sub_a.to_string(), sub_b.to_string());
let key2 = (sub_b.to_string(), sub_a.to_string());
self.pair_diffusions
.get(&key1)
.or_else(|| self.pair_diffusions.get(&key2))
}
pub fn get_diffusion_coefficient(&self, sub_a: &str, sub_b: &str) -> Option<f64> {
self.get_pair_diffusion(sub_a, sub_b).map(|pd| pd.D)
}
pub fn set_common_D_unit(&mut self, unit: String) {
self.D_unit = Some(unit);
}
pub fn print_diffusion_matrix(&self) {
if let Some(ref matrix) = self.diffusion_matrix {
println!("Diffusion Matrix:");
print!("\t");
for name in &self.substance_names {
print!("{:>12}", name);
}
println!();
for (i, name) in self.substance_names.iter().enumerate() {
print!("{:>8}", name);
for j in 0..self.substance_names.len() {
print!("{:>12.2e}", matrix[i][j]);
}
println!();
}
}
}
pub fn set_molar_masses(&mut self, masses: HashMap<String, f64>) {
for (name, mass) in masses {
if let Some(transport_data) = self.substances.get_mut(&name) {
transport_data.M = mass;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Thermodynamics::DBhandlers::TRANSPORTdata::TransportData;
use crate::Thermodynamics::thermo_lib_api::ThermoData;
use approx::assert_relative_eq;
#[test]
fn test_pair_diffusion() {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let CO_data = sublib.get("CO").unwrap();
let N2_data = sublib.get("N2").unwrap();
let mut tr1 = TransportData::new();
tr1.from_serde(CO_data.clone()).unwrap();
let CO_input = tr1.input;
let mut tr2 = TransportData::new();
tr2.from_serde(N2_data.clone()).unwrap();
let N2_input = tr2.input;
let mut pair_diff = PairDiffusion::new(
CO_input,
N2_input,
300.0,
101325.0,
28.01 / 1000.0,
30.01 / 1000.0,
);
let _ = pair_diff.set_D_unit(Some("m2/s".to_string()));
pair_diff.calc_coefficients();
pair_diff.calculate_D();
println!("D_CO_NO at 300K: {} m^2/s* 1e6", pair_diff.D * 1e6);
}
#[test]
fn test_closure() {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let CO_data = sublib.get("CO").unwrap();
let N2_data = sublib.get("N2").unwrap();
let mut tr1 = TransportData::new();
tr1.from_serde(CO_data.clone()).unwrap();
let CO_input = tr1.input;
let mut tr2 = TransportData::new();
tr2.from_serde(N2_data.clone()).unwrap();
let N2_input = tr2.input;
let mut pair_diff = PairDiffusion::new(
CO_input,
N2_input,
300.0,
101325.0,
28.01 / 1000.0,
30.01 / 1000.0,
);
pair_diff.calc_coefficients();
pair_diff.calculate_D_closure();
let D_300 = (pair_diff.D_closure)(300.0);
let D_400 = (pair_diff.D_closure)(400.0);
assert!(D_400 > D_300);
println!("D at 300K: {}, D at 400K: {}", D_300, D_400);
}
#[test]
fn test_symbolic() {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let CO_data = sublib.get("CO").unwrap();
let N2_data = sublib.get("N2").unwrap();
let mut tr1 = TransportData::new();
tr1.from_serde(CO_data.clone()).unwrap();
let CO_input = tr1.input;
let mut tr2 = TransportData::new();
tr2.from_serde(N2_data.clone()).unwrap();
let N2_input = tr2.input;
let mut pair_diff = PairDiffusion::new(
CO_input,
N2_input,
300.0,
101325.0,
28.01 / 1000.0,
30.01 / 1000.0,
);
pair_diff.calc_coefficients();
pair_diff.calculate_D_symbolic();
let D_sym_func = pair_diff.D_symbolic.lambdify1D();
let D_300 = D_sym_func(300.0);
let D_400 = D_sym_func(400.0);
assert!(D_400 > D_300);
println!("Symbolic D at 300K: {}, at 400K: {}", D_300, D_400);
}
#[test]
fn test_taylor_series() {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let CO_data = sublib.get("CO").unwrap();
let N2_data = sublib.get("N2").unwrap();
let mut tr1 = TransportData::new();
tr1.from_serde(CO_data.clone()).unwrap();
let CO_input = tr1.input;
let mut tr2 = TransportData::new();
tr2.from_serde(N2_data.clone()).unwrap();
let N2_input = tr2.input;
let mut pair_diff = PairDiffusion::new(
CO_input,
N2_input,
300.0,
101325.0,
28.01 / 1000.0,
30.01 / 1000.0,
);
pair_diff.calc_coefficients();
pair_diff.calculate_D_symbolic();
let original_func = pair_diff.D_symbolic.lambdify1D();
let original_300 = original_func(300.0);
pair_diff.taylor_series(300.0, 3);
let taylor_func = pair_diff.D_symbolic.lambdify1D();
let taylor_300 = taylor_func(300.0);
assert_relative_eq!(original_300, taylor_300, epsilon = 1e-6);
println!("Original: {}, Taylor: {}", original_300, taylor_300);
}
#[test]
fn test_integr_mean() {
let thermo_data = ThermoData::new();
let sublib = thermo_data.LibThermoData.get("Aramco_transport").unwrap();
let CO_data = sublib.get("CO").unwrap();
let N2_data = sublib.get("N2").unwrap();
let mut tr1 = TransportData::new();
tr1.from_serde(CO_data.clone()).unwrap();
let CO_input = tr1.input;
let mut tr2 = TransportData::new();
tr2.from_serde(N2_data.clone()).unwrap();
let N2_input = tr2.input;
let mut pair_diff = PairDiffusion::new(
CO_input,
N2_input,
300.0,
101325.0,
28.01 / 1000.0,
30.01 / 1000.0,
);
pair_diff.calc_coefficients();
pair_diff.calculate_D_symbolic();
pair_diff.set_T_interval(300.0, 400.0);
let result = pair_diff.integr_mean();
assert!(result.is_ok());
println!("Mean D over 300-400K: {}", pair_diff.D);
}
#[test]
fn test_unit_conversion() {
let mut pair_diff = PairDiffusion::default();
assert!(pair_diff.set_D_unit(Some("cm2/s".to_string())).is_ok());
assert_eq!(pair_diff.unit_multiplier, 1e4);
assert!(pair_diff.set_D_unit(Some("m2/s".to_string())).is_ok());
assert_eq!(pair_diff.unit_multiplier, 1.0);
assert!(pair_diff.set_D_unit(Some("invalid".to_string())).is_err());
}
#[test]
fn test_multi_substance_diffusion() {
let mut multi_diff = MultiSubstanceDiffusion::new(300.0, 101325.0);
let result = multi_diff.add_substances_from_library(
vec!["CO".to_string(), "N2".to_string(), "O2".to_string()],
"Aramco_transport",
);
assert!(result.is_ok());
let masses = [
("CO", 28.01 / 1000.0),
("N2", 28.014 / 1000.0),
("O2", 31.998 / 1000.0),
]
.iter()
.map(|(k, v)| (k.to_string(), *v))
.collect();
multi_diff.set_molar_masses(masses);
multi_diff.set_common_D_unit("cm2/s".to_string());
multi_diff.calculate_all_pairs();
let matrix = multi_diff.create_diffusion_matrix();
assert_eq!(matrix.len(), 3);
assert_eq!(matrix[0].len(), 3);
let d_co_n2 = multi_diff.get_diffusion_coefficient("CO", "N2");
assert!(d_co_n2.is_some());
multi_diff.print_diffusion_matrix();
println!("CO-N2 diffusion: {:?}", d_co_n2);
}
#[test]
fn test_multi_substance_closures() {
let mut multi_diff = MultiSubstanceDiffusion::new(300.0, 101325.0);
let _ = multi_diff.add_substances_from_library(
vec!["CO".to_string(), "N2".to_string()],
"Aramco_transport",
);
let masses = [("CO", 28.01 / 1000.0), ("N2", 28.014 / 1000.0)]
.iter()
.map(|(k, v)| (k.to_string(), *v))
.collect();
multi_diff.set_molar_masses(masses);
multi_diff.calculate_all_pairs();
multi_diff.calculate_all_closures();
let pair_diff = multi_diff.get_pair_diffusion("CO", "N2").unwrap();
let D_300 = (pair_diff.D_closure)(300.0);
let D_400 = (pair_diff.D_closure)(400.0);
assert!(D_400 > D_300);
}
#[test]
fn test_multi_substance_symbolic() {
let mut multi_diff = MultiSubstanceDiffusion::new(300.0, 101325.0);
let _ = multi_diff.add_substances_from_library(
vec!["CO".to_string(), "N2".to_string()],
"Aramco_transport",
);
let masses = [("CO", 28.01 / 1000.0), ("N2", 28.014 / 1000.0)]
.iter()
.map(|(k, v)| (k.to_string(), *v))
.collect();
multi_diff.set_molar_masses(masses);
multi_diff.calculate_all_pairs();
multi_diff.calculate_all_symbolic();
let pair_diff = multi_diff.get_pair_diffusion("CO", "N2").unwrap();
let D_sym_func = pair_diff.D_symbolic.lambdify1D();
let D_300 = D_sym_func(300.0);
let D_400 = D_sym_func(400.0);
assert!(D_400 > D_300);
}
#[test]
fn test_matrix_symmetry() {
let mut multi_diff = MultiSubstanceDiffusion::new(300.0, 101325.0);
let _ = multi_diff.add_substances_from_library(
vec!["CO".to_string(), "N2".to_string(), "O2".to_string()],
"Aramco_transport",
);
let masses = [
("CO", 28.01 / 1000.0),
("N2", 28.014 / 1000.0),
("O2", 31.998 / 1000.0),
]
.iter()
.map(|(k, v)| (k.to_string(), *v))
.collect();
multi_diff.set_molar_masses(masses);
multi_diff.calculate_all_pairs();
let matrix = multi_diff.create_diffusion_matrix();
for i in 0..matrix.len() {
for j in 0..matrix[i].len() {
assert_relative_eq!(matrix[i][j], matrix[j][i], epsilon = 1e-10);
}
}
}
#[test]
fn test_error_handling() {
let mut multi_diff = MultiSubstanceDiffusion::new(300.0, 101325.0);
let result = multi_diff
.add_substances_from_library(vec!["INVALID_SUBSTANCE".to_string()], "Aramco_transport");
assert!(result.is_err());
let result =
multi_diff.add_substances_from_library(vec!["CO".to_string()], "INVALID_LIBRARY");
assert!(result.is_err());
}
}