use super::{DataSet, FeosError, Phase};
use feos_core::{
Contributions, DensityInitialization, PhaseDiagram, PhaseEquilibrium, ReferenceSystem,
Residual, State, TemperatureOrPressure,
};
use itertools::izip;
use ndarray::{Array1, ArrayView1, Axis, arr1, s};
use quantity::{_Dimensionless, MolarEnergy, Moles, PASCAL, Pressure, Quantity, RGAS, Temperature};
use std::fmt;
use std::iter::FromIterator;
use std::ops::Sub;
#[derive(Clone)]
pub struct BinaryVleChemicalPotential {
temperature: Temperature<Array1<f64>>,
pressure: Pressure<Array1<f64>>,
liquid_molefracs: Array1<f64>,
vapor_molefracs: Array1<f64>,
target: Array1<f64>,
}
impl BinaryVleChemicalPotential {
pub fn new(
temperature: Temperature<Array1<f64>>,
pressure: Pressure<Array1<f64>>,
liquid_molefracs: Array1<f64>,
vapor_molefracs: Array1<f64>,
) -> Self {
let target = Array1::ones(temperature.len() * 2);
Self {
temperature,
pressure,
liquid_molefracs,
vapor_molefracs,
target,
}
}
}
impl<E: Residual> DataSet<E> for BinaryVleChemicalPotential {
fn target(&self) -> &Array1<f64> {
&self.target
}
fn target_str(&self) -> &str {
"chemical potential"
}
fn input_str(&self) -> Vec<&str> {
vec![
"temperature",
"pressure",
"liquid molefracs",
"vapor molefracs",
]
}
fn predict(&self, eos: &Arc<E>) -> Result<Array1<f64>, FeosError> {
let mut prediction = Vec::new();
for (&xi, &yi, t, p) in izip!(
&self.liquid_molefracs,
&self.vapor_molefracs,
&self.temperature,
&self.pressure
) {
let liquid_moles = Moles::from_reduced(arr1(&[xi, 1.0 - xi]));
let liquid = State::new_npt(eos, t, p, &liquid_moles, DensityInitialization::Liquid)?;
let mu_res_liquid = liquid.residual_chemical_potential();
let vapor_moles = Moles::from_reduced(arr1(&[yi, 1.0 - yi]));
let vapor = State::new_npt(eos, t, p, &vapor_moles, DensityInitialization::Vapor)?;
let mu_res_vapor = vapor.residual_chemical_potential();
let kt = RGAS * t;
let rho_frac = (&liquid.partial_density / &vapor.partial_density).into_value();
prediction.push(mu_res_liquid.get(0) - mu_res_vapor.get(0) + kt * rho_frac[0].ln());
prediction.push(mu_res_liquid.get(1) - mu_res_vapor.get(1) + kt * rho_frac[1].ln());
}
let prediction = (MolarEnergy::from_vec(prediction) / MolarEnergy::from_reduced(500.0))
.into_value()
+ 1.0;
Ok(prediction)
}
}
#[derive(Clone)]
pub struct BinaryVlePressure {
target: Array1<f64>,
temperature: Temperature<Array1<f64>>,
pressure: Pressure<Array1<f64>>,
unit: Pressure,
molefracs: Array1<f64>,
phase: Phase,
}
impl BinaryVlePressure {
pub fn new(
temperature: Temperature<Array1<f64>>,
pressure: Pressure<Array1<f64>>,
molefracs: Array1<f64>,
phase: Phase,
) -> Self {
let unit = PASCAL;
Self {
target: (&pressure / unit).into_value(),
temperature,
pressure,
unit,
molefracs,
phase,
}
}
}
impl<E: Residual> DataSet<E> for BinaryVlePressure {
fn target(&self) -> &Array1<f64> {
&self.target
}
fn target_str(&self) -> &str {
"pressure"
}
fn input_str(&self) -> Vec<&str> {
let mut vec = vec!["temperature", "pressure"];
vec.push(match self.phase {
Phase::Vapor => "vapor molefracs",
Phase::Liquid => "liquid molefracs",
});
vec
}
fn predict(&self, eos: &Arc<E>) -> Result<Array1<f64>, FeosError> {
let options = Default::default();
self.molefracs
.iter()
.enumerate()
.map(|(i, &xi)| {
let vle = (match self.phase {
Phase::Vapor => PhaseEquilibrium::dew_point(
eos,
self.temperature.get(i),
&arr1(&[xi, 1.0 - xi]),
Some(self.pressure.get(i)),
None,
options,
),
Phase::Liquid => PhaseEquilibrium::bubble_point(
eos,
self.temperature.get(i),
&arr1(&[xi, 1.0 - xi]),
Some(self.pressure.get(i)),
None,
options,
),
})?;
Ok(vle
.vapor()
.pressure(Contributions::Total)
.convert_to(self.unit))
})
.collect()
}
}
#[derive(Clone)]
pub struct BinaryPhaseDiagram<TP: TemperatureOrPressure, U> {
specification: TP,
temperature_or_pressure: Quantity<Array1<f64>, U>,
liquid_molefracs: Option<Array1<f64>>,
vapor_molefracs: Option<Array1<f64>>,
npoints: Option<usize>,
target: Array1<f64>,
}
impl<TP: TemperatureOrPressure, U> BinaryPhaseDiagram<TP, U> {
pub fn new(
specification: TP,
temperature_or_pressure: Quantity<Array1<f64>, U>,
liquid_molefracs: Option<Array1<f64>>,
vapor_molefracs: Option<Array1<f64>>,
npoints: Option<usize>,
) -> Self {
let count = liquid_molefracs.as_ref().map_or(0, |x| 2 * x.len())
+ vapor_molefracs.as_ref().map_or(0, |x| 2 * x.len());
let target = Array1::ones(count);
Self {
specification,
temperature_or_pressure,
liquid_molefracs,
vapor_molefracs,
npoints,
target,
}
}
}
impl<TP: TemperatureOrPressure + Sync + Send + fmt::Display, U: Copy + Sync + Send, E: Residual>
DataSet<E> for BinaryPhaseDiagram<TP, U>
where
Quantity<Array1<f64>, U>: FromIterator<TP::Other>,
U: Sub<U, Output = _Dimensionless>,
{
fn target(&self) -> &Array1<f64> {
&self.target
}
fn target_str(&self) -> &str {
"distance"
}
fn input_str(&self) -> Vec<&str> {
let mut vec = vec![TP::IDENTIFIER, TP::Other::IDENTIFIER];
if self.liquid_molefracs.is_some() {
vec.push("liquid molefracs")
}
if self.vapor_molefracs.is_some() {
vec.push("vapor molefracs")
}
vec
}
fn predict(&self, eos: &Arc<E>) -> Result<Array1<f64>, FeosError> {
let mut res = Vec::new();
let dia = PhaseDiagram::binary_vle(
eos,
self.specification,
self.npoints,
None,
Default::default(),
)?;
let x_liq = dia.liquid().molefracs();
let x_vec_liq = x_liq.index_axis(Axis(1), 0);
let x_vap = dia.vapor().molefracs();
let x_vec_vap = x_vap.index_axis(Axis(1), 0);
let tp_vec = dia.vapor().iter().map(|s| TP::from_state(s)).collect();
for (x_exp, x_vec) in [
(&self.liquid_molefracs, x_vec_liq),
(&self.vapor_molefracs, x_vec_vap),
] {
if let Some(x_exp) = x_exp {
res.extend(predict_distance(
x_vec,
&tp_vec,
x_exp,
&self.temperature_or_pressure,
));
}
}
Ok(Array1::from_vec(res))
}
}
fn predict_distance<U: Copy + Sub<U, Output = _Dimensionless>>(
x_vec: ArrayView1<f64>,
tp_vec: &Quantity<Array1<f64>, U>,
x_exp: &Array1<f64>,
tp_exp: &Quantity<Array1<f64>, U>,
) -> Vec<f64> {
let mut res = Vec::new();
for (tp, &x) in tp_exp.into_iter().zip(x_exp.into_iter()) {
let y = 1.0;
let y_vec = (tp_vec / tp).into_value();
let dx = &x_vec.slice(s![1..]) - &x_vec.slice(s![..-1]);
let dy = &y_vec.slice(s![1..]) - &y_vec.slice(s![..-1]);
let x_vec = x_vec.slice(s![..-1]);
let y_vec = y_vec.slice(s![..-1]);
let t = ((x - &x_vec) * &dx + (y - &y_vec) * &dy) / (&dx * &dx + &dy * &dy);
let x0 = &t * dx + x_vec;
let y0 = &t * dy + y_vec;
let k = x0
.iter()
.zip(y0.iter())
.enumerate()
.filter(|(i, _)| 0.0 < t[*i] && t[*i] < 1.0)
.map(|(i, (xx, yy))| (i, (xx - x) * (xx - x) + (yy - y) * (yy - y)))
.reduce(|(k1, d1), (k2, d2)| if d1 < d2 { (k1, d1) } else { (k2, d2) });
let (x0, y0) = match k {
None => {
let point = t
.iter()
.zip(t.iter().skip(1))
.enumerate()
.find(|&(_, (t1, t2))| *t1 > 1.0 && *t2 < 0.0);
if let Some((point, _)) = point {
(x_vec[point + 1], y_vec[point + 1])
} else {
let n = t.len();
if t[0].abs() < t[n - 1].abs() {
(x_vec[0], y_vec[0])
} else {
(x_vec[n - 1], y_vec[n - 1])
}
}
}
Some((k, _)) => (x0[k], y0[k]),
};
res.push(x0 - x + 1.0);
res.push(y0);
}
res
}