use feos_core::{FeosError, FeosResult, StateHD};
use nalgebra::DVector;
use num_dual::DualNum;
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;
use crate::epcsaft::eos::ElectrolytePcSaftVariants;
use crate::epcsaft::parameters::ElectrolytePcSaftPars;
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum PermittivityRecord {
ExperimentalData {
data: Vec<(f64, f64)>,
},
PerturbationTheory {
dipole_scaling: f64,
polarizability_scaling: f64,
correlation_integral_parameter: f64,
},
}
#[derive(Clone)]
pub struct Permittivity<D: DualNum<f64>> {
pub permittivity: D,
}
impl<D: DualNum<f64> + Copy> Permittivity<D> {
pub fn new(
state: &StateHD<D>,
parameters: &ElectrolytePcSaftPars,
epcsaft_variant: &ElectrolytePcSaftVariants,
) -> FeosResult<Self> {
if parameters.nionic == 0 {
return Ok(Self {
permittivity: D::one() * 1.,
});
}
let all_comp: Vec<_> = (0..parameters.m.len()).collect();
if let ElectrolytePcSaftVariants::Advanced = epcsaft_variant {
if parameters
.permittivity
.iter()
.any(|record| record.is_none())
{
return Err(FeosError::IncompatibleParameters(
"Provide permittivities for all components for ePC-SAFT advanced.".to_string(),
));
}
let mut mu_scaling: Vec<&f64> = vec![];
let mut alpha_scaling: Vec<&f64> = vec![];
let mut ci_param: Vec<&f64> = vec![];
let mut datas: Vec<Vec<(f64, f64)>> = vec![];
parameters
.permittivity
.iter()
.for_each(|record| match record.as_ref().unwrap() {
PermittivityRecord::PerturbationTheory {
dipole_scaling,
polarizability_scaling,
correlation_integral_parameter,
} => {
mu_scaling.push(dipole_scaling);
alpha_scaling.push(polarizability_scaling);
ci_param.push(correlation_integral_parameter);
}
PermittivityRecord::ExperimentalData { data } => {
datas.push(data.clone());
}
});
if let PermittivityRecord::ExperimentalData { .. } =
parameters.permittivity[0].as_ref().unwrap()
{
let permittivity =
Self::from_experimental_data(&datas, state.temperature, &state.molefracs)
.permittivity;
return Ok(Self { permittivity });
}
if let PermittivityRecord::PerturbationTheory { .. } =
parameters.permittivity[0].as_ref().unwrap()
{
let permittivity = Self::from_perturbation_theory(
state,
&mu_scaling,
&alpha_scaling,
&ci_param,
&all_comp,
)
.permittivity;
return Ok(Self { permittivity });
}
}
if let ElectrolytePcSaftVariants::Revised = epcsaft_variant {
if parameters.nsolvent > 1 {
return Err(FeosError::IncompatibleParameters(
"ePC-SAFT revised cannot be used for more than 1 solvent.".to_string(),
));
};
let permittivity = match parameters.permittivity[parameters.solvent_comp[0]]
.as_ref()
.unwrap()
{
PermittivityRecord::ExperimentalData { data } => {
Self::pure_from_experimental_data(data, state.temperature).permittivity
}
PermittivityRecord::PerturbationTheory {
dipole_scaling,
polarizability_scaling,
correlation_integral_parameter,
} => {
Self::pure_from_perturbation_theory(
state,
*dipole_scaling,
*polarizability_scaling,
*correlation_integral_parameter,
)
.permittivity
}
};
return Ok(Self { permittivity });
};
Err(FeosError::IncompatibleParameters(
"Permittivity computation failed".to_string(),
))
}
pub fn pure_from_experimental_data(data: &[(f64, f64)], temperature: D) -> Self {
let permittivity_pure = Self::interpolate(data, temperature).permittivity;
Self {
permittivity: permittivity_pure,
}
}
pub fn pure_from_perturbation_theory(
state: &StateHD<D>,
dipole_scaling: f64,
polarizability_scaling: f64,
correlation_integral_parameter: f64,
) -> Self {
let boltzmann = 1.380649e-23;
let beta = (state.temperature * boltzmann).recip();
let density = state.partial_density.sum();
let y_star =
density * (beta * dipole_scaling * 1e-19 + polarizability_scaling * 3.) * 4. / 9. * PI;
let correlation_integral = ((-y_star).exp() - 1.0) * correlation_integral_parameter + 1.0;
let permittivity_pure = y_star
* 3.0
* (y_star.powi(2) * (correlation_integral * (17. / 16.) - 1.0) + y_star + 1.0)
+ 1.0;
Self {
permittivity: permittivity_pure,
}
}
pub fn from_experimental_data(
data: &[Vec<(f64, f64)>],
temperature: D,
molefracs: &DVector<D>,
) -> Self {
let permittivity = data
.iter()
.enumerate()
.map(|(i, d)| Self::interpolate(d, temperature).permittivity * molefracs[i])
.sum();
Self { permittivity }
}
pub fn from_perturbation_theory(
state: &StateHD<D>,
dipole_scaling: &[&f64],
polarizability_scaling: &[&f64],
correlation_integral_parameter: &[&f64],
comp: &[usize],
) -> Self {
let boltzmann = 1.380649e-23;
let beta = (state.temperature * boltzmann).recip();
let mut y_star = D::zero();
let mut correlation_integral_parameter_mixture = D::zero();
for i in comp.iter() {
let rho_i = state.partial_density[*i];
let x_i = state.molefracs[*i];
y_star +=
rho_i * (beta * *dipole_scaling[*i] * 1e-19 + polarizability_scaling[*i] * 3.) * 4.
/ 9.
* PI;
correlation_integral_parameter_mixture += x_i * *correlation_integral_parameter[*i];
}
let correlation_integral =
((-y_star).exp() - 1.0) * correlation_integral_parameter_mixture + 1.0;
let permittivity = y_star
* 3.0
* (y_star.powi(2) * (correlation_integral * (17. / 16.) - 1.0) + y_star + 1.0)
+ 1.0;
Self { permittivity }
}
pub fn interpolate(interpolation_points: &[(f64, f64)], temperature: D) -> Self {
if interpolation_points.len() == 1 {
return Self {
permittivity: D::one() * interpolation_points[0].1,
};
}
let i = interpolation_points.binary_search_by(|&(ti, _)| {
ti.partial_cmp(&temperature.re())
.expect("Unexpected value for temperature in interpolation points.")
});
let i = i.unwrap_or_else(|i| i);
let n = interpolation_points.len();
let (l, u) = match i {
0 => (interpolation_points[0], interpolation_points[1]),
i if i >= n => (interpolation_points[n - 2], interpolation_points[n - 1]),
_ => (interpolation_points[i - 1], interpolation_points[i]),
};
let permittivity_pure = (temperature - l.0) / (u.0 - l.0) * (u.1 - l.1) + l.1;
Self {
permittivity: permittivity_pure,
}
}
}