1use super::{DataSet, EstimatorError, Phase};
2use feos_core::{
3 Contributions, DensityInitialization, PhaseDiagram, PhaseEquilibrium, ReferenceSystem,
4 Residual, State, TemperatureOrPressure,
5};
6use itertools::izip;
7use ndarray::{arr1, s, Array1, ArrayView1, Axis};
8use quantity::{MolarEnergy, Moles, Pressure, Quantity, Temperature, _Dimensionless, PASCAL, RGAS};
9use std::fmt;
10use std::iter::FromIterator;
11use std::ops::Sub;
12use std::sync::Arc;
13
14#[derive(Clone)]
16pub struct BinaryVleChemicalPotential {
17 temperature: Temperature<Array1<f64>>,
18 pressure: Pressure<Array1<f64>>,
19 liquid_molefracs: Array1<f64>,
20 vapor_molefracs: Array1<f64>,
21 target: Array1<f64>,
22}
23
24impl BinaryVleChemicalPotential {
25 pub fn new(
26 temperature: Temperature<Array1<f64>>,
27 pressure: Pressure<Array1<f64>>,
28 liquid_molefracs: Array1<f64>,
29 vapor_molefracs: Array1<f64>,
30 ) -> Self {
31 let target = Array1::ones(temperature.len() * 2);
32 Self {
33 temperature,
34 pressure,
35 liquid_molefracs,
36 vapor_molefracs,
37 target,
38 }
39 }
40}
41
42impl<E: Residual> DataSet<E> for BinaryVleChemicalPotential {
43 fn target(&self) -> &Array1<f64> {
44 &self.target
45 }
46
47 fn target_str(&self) -> &str {
48 "chemical potential"
49 }
50
51 fn input_str(&self) -> Vec<&str> {
52 vec![
53 "temperature",
54 "pressure",
55 "liquid molefracs",
56 "vapor molefracs",
57 ]
58 }
59
60 fn predict(&self, eos: &Arc<E>) -> Result<Array1<f64>, EstimatorError> {
61 let mut prediction = Vec::new();
62 for (&xi, &yi, t, p) in izip!(
63 &self.liquid_molefracs,
64 &self.vapor_molefracs,
65 &self.temperature,
66 &self.pressure
67 ) {
68 let liquid_moles = Moles::from_reduced(arr1(&[xi, 1.0 - xi]));
69 let liquid = State::new_npt(eos, t, p, &liquid_moles, DensityInitialization::Liquid)?;
70 let mu_res_liquid = liquid.residual_chemical_potential();
71 let vapor_moles = Moles::from_reduced(arr1(&[yi, 1.0 - yi]));
72 let vapor = State::new_npt(eos, t, p, &vapor_moles, DensityInitialization::Vapor)?;
73 let mu_res_vapor = vapor.residual_chemical_potential();
74
75 let kt = RGAS * t;
76 let rho_frac = (&liquid.partial_density / &vapor.partial_density).into_value();
77 prediction.push(mu_res_liquid.get(0) - mu_res_vapor.get(0) + kt * rho_frac[0].ln());
78 prediction.push(mu_res_liquid.get(1) - mu_res_vapor.get(1) + kt * rho_frac[1].ln());
79 }
80 let prediction = (MolarEnergy::from_vec(prediction) / MolarEnergy::from_reduced(500.0))
81 .into_value()
82 + 1.0;
83 Ok(prediction)
84 }
85
86 }
101
102#[derive(Clone)]
104pub struct BinaryVlePressure {
105 target: Array1<f64>,
106 temperature: Temperature<Array1<f64>>,
107 pressure: Pressure<Array1<f64>>,
108 unit: Pressure,
109 molefracs: Array1<f64>,
110 phase: Phase,
111}
112
113impl BinaryVlePressure {
114 pub fn new(
115 temperature: Temperature<Array1<f64>>,
116 pressure: Pressure<Array1<f64>>,
117 molefracs: Array1<f64>,
118 phase: Phase,
119 ) -> Self {
120 let unit = PASCAL;
121 Self {
122 target: (&pressure / unit).into_value(),
123 temperature,
124 pressure,
125 unit,
126 molefracs,
127 phase,
128 }
129 }
130}
131
132impl<E: Residual> DataSet<E> for BinaryVlePressure {
133 fn target(&self) -> &Array1<f64> {
134 &self.target
135 }
136
137 fn target_str(&self) -> &str {
138 "pressure"
139 }
140
141 fn input_str(&self) -> Vec<&str> {
142 let mut vec = vec!["temperature", "pressure"];
143 vec.push(match self.phase {
144 Phase::Vapor => "vapor molefracs",
145 Phase::Liquid => "liquid molefracs",
146 });
147 vec
148 }
149
150 fn predict(&self, eos: &Arc<E>) -> Result<Array1<f64>, EstimatorError> {
151 let options = Default::default();
152 self.molefracs
153 .iter()
154 .enumerate()
155 .map(|(i, &xi)| {
156 let vle = (match self.phase {
157 Phase::Vapor => PhaseEquilibrium::dew_point(
158 eos,
159 self.temperature.get(i),
160 &arr1(&[xi, 1.0 - xi]),
161 Some(self.pressure.get(i)),
162 None,
163 options,
164 ),
165 Phase::Liquid => PhaseEquilibrium::bubble_point(
166 eos,
167 self.temperature.get(i),
168 &arr1(&[xi, 1.0 - xi]),
169 Some(self.pressure.get(i)),
170 None,
171 options,
172 ),
173 })?;
174
175 Ok(vle
176 .vapor()
177 .pressure(Contributions::Total)
178 .convert_to(self.unit))
179 })
180 .collect()
181 }
182
183 }
198
199#[derive(Clone)]
201pub struct BinaryPhaseDiagram<TP: TemperatureOrPressure, U> {
202 specification: TP,
203 temperature_or_pressure: Quantity<Array1<f64>, U>,
204 liquid_molefracs: Option<Array1<f64>>,
205 vapor_molefracs: Option<Array1<f64>>,
206 npoints: Option<usize>,
207 target: Array1<f64>,
208}
209
210impl<TP: TemperatureOrPressure, U> BinaryPhaseDiagram<TP, U> {
211 pub fn new(
212 specification: TP,
213 temperature_or_pressure: Quantity<Array1<f64>, U>,
214 liquid_molefracs: Option<Array1<f64>>,
215 vapor_molefracs: Option<Array1<f64>>,
216 npoints: Option<usize>,
217 ) -> Self {
218 let count = liquid_molefracs.as_ref().map_or(0, |x| 2 * x.len())
219 + vapor_molefracs.as_ref().map_or(0, |x| 2 * x.len());
220 let target = Array1::ones(count);
221 Self {
222 specification,
223 temperature_or_pressure,
224 liquid_molefracs,
225 vapor_molefracs,
226 npoints,
227 target,
228 }
229 }
230}
231
232impl<
233 TP: TemperatureOrPressure + Sync + Send + fmt::Display,
234 U: Copy + Sync + Send,
235 E: Residual,
236 > DataSet<E> for BinaryPhaseDiagram<TP, U>
237where
238 Quantity<Array1<f64>, U>: FromIterator<TP::Other>,
239 U: Sub<U, Output = _Dimensionless>,
240{
241 fn target(&self) -> &Array1<f64> {
242 &self.target
243 }
244
245 fn target_str(&self) -> &str {
246 "distance"
247 }
248
249 fn input_str(&self) -> Vec<&str> {
250 let mut vec = vec![TP::IDENTIFIER, TP::Other::IDENTIFIER];
251 if self.liquid_molefracs.is_some() {
252 vec.push("liquid molefracs")
253 }
254 if self.vapor_molefracs.is_some() {
255 vec.push("vapor molefracs")
256 }
257 vec
258 }
259
260 fn predict(&self, eos: &Arc<E>) -> Result<Array1<f64>, EstimatorError> {
261 let mut res = Vec::new();
262
263 let dia = PhaseDiagram::binary_vle(
264 eos,
265 self.specification,
266 self.npoints,
267 None,
268 Default::default(),
269 )?;
270 let x_liq = dia.liquid().molefracs();
271 let x_vec_liq = x_liq.index_axis(Axis(1), 0);
272 let x_vap = dia.vapor().molefracs();
273 let x_vec_vap = x_vap.index_axis(Axis(1), 0);
274 let tp_vec = dia.vapor().iter().map(|s| TP::from_state(s)).collect();
275 for (x_exp, x_vec) in [
276 (&self.liquid_molefracs, x_vec_liq),
277 (&self.vapor_molefracs, x_vec_vap),
278 ] {
279 if let Some(x_exp) = x_exp {
280 res.extend(predict_distance(
281 x_vec,
282 &tp_vec,
283 x_exp,
284 &self.temperature_or_pressure,
285 ));
286 }
287 }
288 Ok(Array1::from_vec(res))
289 }
290
291 }
327
328fn predict_distance<U: Copy + Sub<U, Output = _Dimensionless>>(
329 x_vec: ArrayView1<f64>,
330 tp_vec: &Quantity<Array1<f64>, U>,
331 x_exp: &Array1<f64>,
332 tp_exp: &Quantity<Array1<f64>, U>,
333) -> Vec<f64> {
334 let mut res = Vec::new();
335 for (tp, &x) in tp_exp.into_iter().zip(x_exp.into_iter()) {
336 let y = 1.0;
337 let y_vec = (tp_vec / tp).into_value();
338 let dx = &x_vec.slice(s![1..]) - &x_vec.slice(s![..-1]);
339 let dy = &y_vec.slice(s![1..]) - &y_vec.slice(s![..-1]);
340 let x_vec = x_vec.slice(s![..-1]);
341 let y_vec = y_vec.slice(s![..-1]);
342
343 let t = ((x - &x_vec) * &dx + (y - &y_vec) * &dy) / (&dx * &dx + &dy * &dy);
344 let x0 = &t * dx + x_vec;
345 let y0 = &t * dy + y_vec;
346
347 let k = x0
348 .iter()
349 .zip(y0.iter())
350 .enumerate()
351 .filter(|(i, _)| 0.0 < t[*i] && t[*i] < 1.0)
352 .map(|(i, (xx, yy))| (i, (xx - x) * (xx - x) + (yy - y) * (yy - y)))
353 .reduce(|(k1, d1), (k2, d2)| if d1 < d2 { (k1, d1) } else { (k2, d2) });
354
355 let (x0, y0) = match k {
356 None => {
357 let point = t
358 .iter()
359 .zip(t.iter().skip(1))
360 .enumerate()
361 .find(|&(_, (t1, t2))| *t1 > 1.0 && *t2 < 0.0);
362 if let Some((point, _)) = point {
363 (x_vec[point + 1], y_vec[point + 1])
364 } else {
365 let n = t.len();
366 if t[0].abs() < t[n - 1].abs() {
367 (x_vec[0], y_vec[0])
368 } else {
369 (x_vec[n - 1], y_vec[n - 1])
370 }
371 }
372 }
373 Some((k, _)) => (x0[k], y0[k]),
374 };
375 res.push(x0 - x + 1.0);
376 res.push(y0);
377 }
378 res
379}