1use crate::errors::{FeosError, FeosResult};
2use crate::phase_equilibria::PhaseEquilibrium;
3use crate::state::{
4 Contributions,
5 DensityInitialization::{InitialDensity, Liquid, Vapor},
6};
7use crate::{Composition, ReferenceSystem, Residual, SolverOptions, State, Verbosity};
8use nalgebra::allocator::Allocator;
9use nalgebra::{DMatrix, DVector, DefaultAllocator, Dim, Dyn, OVector, U1};
10#[cfg(feature = "ndarray")]
11use ndarray::Array1;
12use num_dual::linalg::LU;
13use num_dual::{DualNum, DualStruct, Gradients};
14use quantity::{Density, Dimensionless, Pressure, Quantity, RGAS, SIUnit, Temperature};
15
16const MAX_ITER_INNER: usize = 5;
17const TOL_INNER: f64 = 1e-9;
18const MAX_ITER_OUTER: usize = 400;
19const TOL_OUTER: f64 = 1e-10;
20
21const MAX_TSTEP: f64 = 20.0;
22const MAX_LNPSTEP: f64 = 0.1;
23const NEWTON_TOL: f64 = 1e-3;
24
25pub trait TemperatureOrPressure<D: DualNum<f64> + Copy = f64>: Copy {
27 type Other: Copy;
28
29 const IDENTIFIER: &'static str;
30
31 fn temperature(&self) -> Option<Temperature<D>>;
32 fn pressure(&self) -> Option<Pressure<D>>;
33
34 fn temperature_pressure(
35 &self,
36 tp_init: Option<Self::Other>,
37 ) -> (Option<Temperature<D>>, Option<Pressure<D>>, bool);
38
39 fn from_state<E: Residual<N, D>, N: Gradients>(state: &State<E, N, D>) -> Self::Other
40 where
41 DefaultAllocator: Allocator<N>;
42
43 #[cfg(feature = "ndarray")]
44 fn linspace(
45 &self,
46 start: Self::Other,
47 end: Self::Other,
48 n: usize,
49 ) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>);
50}
51
52impl<D: DualNum<f64> + Copy> TemperatureOrPressure<D> for Temperature<D> {
53 type Other = Pressure<D>;
54 const IDENTIFIER: &'static str = "temperature";
55
56 fn temperature(&self) -> Option<Temperature<D>> {
57 Some(*self)
58 }
59
60 fn pressure(&self) -> Option<Pressure<D>> {
61 None
62 }
63
64 fn temperature_pressure(
65 &self,
66 tp_init: Option<Self::Other>,
67 ) -> (Option<Temperature<D>>, Option<Pressure<D>>, bool) {
68 (Some(*self), tp_init, true)
69 }
70
71 fn from_state<E: Residual<N, D>, N: Gradients>(state: &State<E, N, D>) -> Self::Other
72 where
73 DefaultAllocator: Allocator<N>,
74 {
75 state.pressure(Contributions::Total)
76 }
77
78 #[cfg(feature = "ndarray")]
79 fn linspace(
80 &self,
81 start: Pressure<D>,
82 end: Pressure<D>,
83 n: usize,
84 ) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>) {
85 (
86 Temperature::linspace(self.re(), self.re(), n),
87 Pressure::linspace(start.re(), end.re(), n),
88 )
89 }
90}
91
92impl<D: DualNum<f64> + Copy> TemperatureOrPressure<D>
96 for Quantity<D, SIUnit<-2, -1, 1, 0, 0, 0, 0>>
97{
98 type Other = Temperature<D>;
99 const IDENTIFIER: &'static str = "pressure";
100
101 fn temperature(&self) -> Option<Temperature<D>> {
102 None
103 }
104
105 fn pressure(&self) -> Option<Pressure<D>> {
106 Some(*self)
107 }
108
109 fn temperature_pressure(
110 &self,
111 tp_init: Option<Self::Other>,
112 ) -> (Option<Temperature<D>>, Option<Pressure<D>>, bool) {
113 (tp_init, Some(*self), false)
114 }
115
116 fn from_state<E: Residual<N, D>, N: Dim>(state: &State<E, N, D>) -> Self::Other
117 where
118 DefaultAllocator: Allocator<N>,
119 {
120 state.temperature
121 }
122
123 #[cfg(feature = "ndarray")]
124 fn linspace(
125 &self,
126 start: Temperature<D>,
127 end: Temperature<D>,
128 n: usize,
129 ) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>) {
130 (
131 Temperature::linspace(start.re(), end.re(), n),
132 Pressure::linspace(self.re(), self.re(), n),
133 )
134 }
135}
136
137impl<E: Residual<N, D>, N: Gradients, D: DualNum<f64> + Copy> PhaseEquilibrium<E, 2, N, D>
139where
140 DefaultAllocator: Allocator<N> + Allocator<N, N> + Allocator<U1, N>,
141{
142 pub fn bubble_point<TP: TemperatureOrPressure<D>, X: Composition<D, N>>(
145 eos: &E,
146 temperature_or_pressure: TP,
147 liquid_molefracs: X,
148 tp_init: Option<TP::Other>,
149 vapor_molefracs: Option<&OVector<f64, N>>,
150 options: (SolverOptions, SolverOptions),
151 ) -> FeosResult<Self> {
152 Self::bubble_dew_point(
153 eos,
154 temperature_or_pressure,
155 liquid_molefracs,
156 tp_init,
157 vapor_molefracs,
158 true,
159 options,
160 )
161 }
162
163 pub fn dew_point<TP: TemperatureOrPressure<D>, X: Composition<D, N>>(
166 eos: &E,
167 temperature_or_pressure: TP,
168 vapor_molefracs: X,
169 tp_init: Option<TP::Other>,
170 liquid_molefracs: Option<&OVector<f64, N>>,
171 options: (SolverOptions, SolverOptions),
172 ) -> FeosResult<Self> {
173 Self::bubble_dew_point(
174 eos,
175 temperature_or_pressure,
176 vapor_molefracs,
177 tp_init,
178 liquid_molefracs,
179 false,
180 options,
181 )
182 }
183
184 pub(super) fn bubble_dew_point<TP: TemperatureOrPressure<D>, X: Composition<D, N>>(
185 eos: &E,
186 temperature_or_pressure: TP,
187 vapor_molefracs: X,
188 tp_init: Option<TP::Other>,
189 liquid_molefracs: Option<&OVector<f64, N>>,
190 bubble: bool,
191 options: (SolverOptions, SolverOptions),
192 ) -> FeosResult<Self> {
193 if eos.components() == 1 {
194 let mut vle = Self::pure(eos, temperature_or_pressure, None, options.1)?;
195 if bubble {
196 vle.phase_fractions = [D::from(0.0), D::from(1.0)];
197 }
198 Ok(vle)
199 } else {
200 let (temperature, pressure, iterate_p) =
201 temperature_or_pressure.temperature_pressure(tp_init);
202 Self::bubble_dew_point_tp(
203 eos,
204 temperature,
205 pressure,
206 vapor_molefracs,
207 liquid_molefracs,
208 bubble,
209 iterate_p,
210 options,
211 )
212 }
213 }
214
215 #[expect(clippy::too_many_arguments)]
216 fn bubble_dew_point_tp<X: Composition<D, N>>(
217 eos: &E,
218 temperature: Option<Temperature<D>>,
219 pressure: Option<Pressure<D>>,
220 composition: X,
221 molefracs_init: Option<&OVector<f64, N>>,
222 bubble: bool,
223 iterate_p: bool,
224 options: (SolverOptions, SolverOptions),
225 ) -> FeosResult<Self> {
226 let eos_re = eos.re();
227 let mut temperature_re = temperature.map(|t| t.re());
228 let mut pressure_re = pressure.map(|p| p.re());
229 let (molefracs_spec, total_moles) = composition.into_molefracs(eos)?;
230 let molefracs_spec_re = molefracs_spec.map(|x| x.re());
231 let (v1, rho2) = if iterate_p {
232 let temperature_re = temperature_re.as_mut().ok_or(FeosError::Error(
234 "Temperature information is expected for bubble/dew calculation.".to_string(),
235 ))?;
236
237 if let Some(p) = pressure_re.as_mut() {
239 PhaseEquilibrium::iterate_bubble_dew(
240 &eos_re,
241 temperature_re,
242 p,
243 &molefracs_spec_re,
244 molefracs_init,
245 bubble,
246 iterate_p,
247 options,
248 )?
249 } else {
250 let x2 = PhaseEquilibrium::starting_pressure_ideal_gas(
252 &eos_re,
253 *temperature_re,
254 &molefracs_spec_re,
255 bubble,
256 )
257 .and_then(|(p, x)| {
258 let p = pressure_re.insert(p);
259 PhaseEquilibrium::iterate_bubble_dew(
260 &eos_re,
261 temperature_re,
262 p,
263 &molefracs_spec_re,
264 molefracs_init.or(Some(&x)),
265 bubble,
266 iterate_p,
267 options,
268 )
269 });
270
271 x2.or_else(|_| {
273 PhaseEquilibrium::starting_pressure_spinodal(
274 &eos_re,
275 *temperature_re,
276 &molefracs_spec_re,
277 )
278 .and_then(|p| {
279 let p = pressure_re.insert(p);
280 PhaseEquilibrium::iterate_bubble_dew(
281 &eos_re,
282 temperature_re,
283 p,
284 &molefracs_spec_re,
285 molefracs_init,
286 bubble,
287 iterate_p,
288 options,
289 )
290 })
291 })?
292 }
293 } else {
294 let pressure_re = pressure_re.as_mut().ok_or(FeosError::Error(
296 "Pressure information is expected for bubble/dew calculation.".to_string(),
297 ))?;
298
299 let temperature_re = temperature_re
300 .as_mut()
301 .ok_or(FeosError::Error(
302 "An initial temperature is required for the calculation of bubble/dew points at given pressure.".to_string()))?;
303 PhaseEquilibrium::iterate_bubble_dew(
304 &eos.re(),
305 temperature_re,
306 pressure_re,
307 &molefracs_spec_re,
308 molefracs_init,
309 bubble,
310 iterate_p,
311 options,
312 )?
313 };
314
315 let (mut t, mut p) = if iterate_p {
318 (
319 temperature.unwrap().into_reduced(),
320 D::from(pressure_re.unwrap().into_reduced()),
321 )
322 } else {
323 (
324 D::from(temperature_re.unwrap().into_reduced()),
325 pressure.unwrap().into_reduced(),
326 )
327 };
328 let mut molar_volume = D::from(v1);
329 let mut rho2 = rho2.map(D::from);
330 for _ in 0..D::NDERIV {
331 if iterate_p {
332 Self::newton_step_t(
333 eos,
334 t,
335 &molefracs_spec,
336 &mut p,
337 &mut molar_volume,
338 &mut rho2,
339 Verbosity::None,
340 )?
341 } else {
342 Self::newton_step_p(
343 eos,
344 &mut t,
345 &molefracs_spec,
346 p,
347 &mut molar_volume,
348 &mut rho2,
349 Verbosity::None,
350 )?
351 };
352 }
353 let state1 = State::new(
354 eos,
355 Temperature::from_reduced(t),
356 Density::from_reduced(molar_volume.recip()),
357 molefracs_spec,
358 )?;
359 let rho2_total = rho2.sum();
360 let x2 = rho2 / rho2_total;
361 let state2 = State::new(
362 eos,
363 Temperature::from_reduced(t),
364 Density::from_reduced(rho2_total),
365 x2,
366 )?;
367
368 Ok(if bubble {
369 PhaseEquilibrium::with_vapor_phase_fraction(state2, state1, D::from(0.0), total_moles)
370 } else {
371 PhaseEquilibrium::with_vapor_phase_fraction(state1, state2, D::from(1.0), total_moles)
372 })
373 }
374
375 fn newton_step_t(
376 eos: &E,
377 temperature: D,
378 molefracs: &OVector<D, N>,
379 pressure: &mut D,
380 molar_volume: &mut D,
381 partial_density_other_phase: &mut OVector<D, N>,
382 verbosity: Verbosity,
383 ) -> FeosResult<f64> {
384 let (p_1, mu_res_1, dp_1, dmu_1) = eos.dmu_drho(temperature, partial_density_other_phase);
386 let (p_2, mu_res_2, dp_2, dmu_2) = eos.dmu_dv(temperature, *molar_volume, molefracs);
387
388 let n = molefracs.len();
390 let f = DVector::from_fn(n + 2, |i, _| {
391 if i == n {
392 p_1 - *pressure
393 } else if i == n + 1 {
394 p_2 - *pressure
395 } else {
396 mu_res_1[i] - mu_res_2[i]
397 + (partial_density_other_phase[i] * *molar_volume / molefracs[i]).ln()
398 * temperature
399 }
400 });
401
402 let jac = DMatrix::from_fn(n + 2, n + 2, |i, j| {
404 if i < n && j < n {
405 dmu_1[(i, j)]
406 } else if i < n && j == n {
407 -dmu_2[i]
408 } else if i == n && j < n {
409 dp_1[j]
410 } else if i == n + 1 && j == n {
411 dp_2
412 } else if i >= n && j == n + 1 {
413 -D::one()
414 } else {
415 D::zero()
416 }
417 });
418
419 let dx = LU::<_, _, Dyn>::new(jac)?.solve(&f);
421
422 for i in 0..n {
424 partial_density_other_phase[i] -= dx[i];
425 }
426 *molar_volume -= dx[n];
427 *pressure -= dx[n + 1];
428
429 let error = f.map(|r| r.re()).norm();
430
431 let x = partial_density_other_phase.map(|r| r.re());
432 let x = &x / x.sum();
433 log_iteration(
434 verbosity,
435 Some(error),
436 Temperature::from_reduced(temperature.re()),
437 Pressure::from_reduced(pressure.re()),
438 x.as_slice(),
439 true,
440 );
441 Ok(error)
442 }
443
444 fn newton_step_p(
445 eos: &E,
446 temperature: &mut D,
447 molefracs: &OVector<D, N>,
448 pressure: D,
449 molar_volume: &mut D,
450 partial_density_other_phase: &mut OVector<D, N>,
451 verbosity: Verbosity,
452 ) -> FeosResult<f64> {
453 let (p_1, mu_res_1, dp_1, dmu_1) = eos.dmu_drho(*temperature, partial_density_other_phase);
455 let (p_2, mu_res_2, dp_2, dmu_2) = eos.dmu_dv(*temperature, *molar_volume, molefracs);
456 let (dp_dt_1, dmu_res_dt_1) = eos.dmu_dt(*temperature, partial_density_other_phase);
457 let (dp_dt_2, dmu_res_dt_2) = eos.dmu_dt(*temperature, &(molefracs / *molar_volume));
458
459 let n = molefracs.len();
461 let f = DVector::from_fn(n + 2, |i, _| {
462 if i == n {
463 p_1 - pressure
464 } else if i == n + 1 {
465 p_2 - pressure
466 } else {
467 mu_res_1[i] - mu_res_2[i]
468 + (partial_density_other_phase[i] * *molar_volume / molefracs[i]).ln()
469 * *temperature
470 }
471 });
472
473 let jac = DMatrix::from_fn(n + 2, n + 2, |i, j| {
475 if i < n && j < n {
476 dmu_1[(i, j)]
477 } else if i < n && j == n {
478 -dmu_2[i]
479 } else if i < n && j == n + 1 {
480 dmu_res_dt_1[i] - dmu_res_dt_2[i]
481 + (partial_density_other_phase[i] * *molar_volume / molefracs[i]).ln()
482 } else if i == n && j < n {
483 dp_1[j]
484 } else if i == n && j == n + 1 {
485 dp_dt_1
486 } else if i == n + 1 && j == n {
487 dp_2
488 } else if i == n + 1 && j == n + 1 {
489 dp_dt_2
490 } else {
491 D::zero()
492 }
493 });
494
495 let dx = LU::<_, _, Dyn>::new(jac)?.solve(&f);
497
498 for i in 0..n {
500 partial_density_other_phase[i] -= dx[i];
501 }
502 *molar_volume -= dx[n];
503 *temperature -= dx[n + 1];
504
505 let error = f.map(|r| r.re()).norm();
506
507 let x = partial_density_other_phase.map(|r| r.re());
508 let x = &x / x.sum();
509 log_iteration(
510 verbosity,
511 Some(error),
512 Temperature::from_reduced(temperature.re()),
513 Pressure::from_reduced(pressure.re()),
514 x.as_slice(),
515 true,
516 );
517 Ok(error)
518 }
519}
520
521impl<E: Residual<N>, N: Gradients> PhaseEquilibrium<E, 2, N>
523where
524 DefaultAllocator: Allocator<N> + Allocator<N, N> + Allocator<U1, N>,
525{
526 #[expect(clippy::too_many_arguments)]
527 fn iterate_bubble_dew(
528 eos: &E,
529 temperature: &mut Temperature,
530 pressure: &mut Pressure,
531 molefracs_spec: &OVector<f64, N>,
532 molefracs_init: Option<&OVector<f64, N>>,
533 bubble: bool,
534 iterate_p: bool,
535 options: (SolverOptions, SolverOptions),
536 ) -> FeosResult<(f64, OVector<f64, N>)> {
537 let [mut state1, mut state2] = if bubble {
538 Self::starting_x2_bubble(eos, *temperature, *pressure, molefracs_spec, molefracs_init)
539 } else {
540 Self::starting_x2_dew(eos, *temperature, *pressure, molefracs_spec, molefracs_init)
541 }?;
542 let (options_inner, options_outer) = options;
543
544 let mut err_out = 1.0;
546 let mut k_out = 0;
547
548 if PhaseEquilibrium::is_trivial_solution(&state1, &state2) {
549 log_iter!(options_outer.verbosity, "Trivial solution encountered!");
550 return Err(FeosError::TrivialSolution);
551 }
552
553 log_iter!(
554 options_outer.verbosity,
555 "res outer loop | res inner loop | temperature | pressure | molefracs second phase",
556 );
557 log_iter!(options_outer.verbosity, "{:-<104}", "");
558 log_iteration(
559 options_outer.verbosity,
560 None,
561 *temperature,
562 *pressure,
563 state2.molefracs.as_slice(),
564 false,
565 );
566
567 for ko in 0..options_outer.max_iter.unwrap_or(MAX_ITER_OUTER) {
569 err_out = if err_out > NEWTON_TOL {
571 for _ in 0..options_inner.max_iter.unwrap_or(MAX_ITER_INNER) {
573 let res = if iterate_p {
574 Self::adjust_p(
575 *temperature,
576 pressure,
577 &mut state1,
578 &mut state2,
579 options_inner.verbosity,
580 )?
581 } else {
582 Self::adjust_t(
583 temperature,
584 *pressure,
585 &mut state1,
586 &mut state2,
587 options_inner.verbosity,
588 )?
589 };
590 if res < options_inner.tol.unwrap_or(TOL_INNER) {
591 break;
592 }
593 }
594 Self::adjust_x2(&state1, &mut state2, options_outer.verbosity)
595 } else {
596 let mut t = temperature.into_reduced();
597 let mut p = pressure.into_reduced();
598 let mut molar_volume = state1.molar_volume.into_reduced();
599 let mut rho2 = state2.partial_density().to_reduced();
600 let err = if iterate_p {
601 Self::newton_step_t(
602 &state1.eos,
603 t,
604 &state1.molefracs,
605 &mut p,
606 &mut molar_volume,
607 &mut rho2,
608 options_outer.verbosity,
609 )?
610 } else {
611 Self::newton_step_p(
612 &state1.eos,
613 &mut t,
614 &state1.molefracs,
615 p,
616 &mut molar_volume,
617 &mut rho2,
618 options_outer.verbosity,
619 )?
620 };
621 *temperature = Temperature::from_reduced(t);
622 *pressure = Pressure::from_reduced(p);
623 state1 = State::new(
624 &state1.eos,
625 *temperature,
626 Density::from_reduced(molar_volume.recip()),
627 molefracs_spec,
628 )?;
629 let density = rho2.sum();
630 state2 = State::new(
631 &state2.eos,
632 *temperature,
633 Density::from_reduced(density),
634 rho2 / density,
635 )?;
636 Ok(err)
637 }?;
638
639 if Self::is_trivial_solution(&state1, &state2) {
640 log_iter!(options_outer.verbosity, "Trivial solution encountered!");
641 return Err(FeosError::TrivialSolution);
642 }
643
644 if err_out < options_outer.tol.unwrap_or(TOL_OUTER) {
645 k_out = ko + 1;
646 break;
647 }
648 }
649
650 if err_out < options_outer.tol.unwrap_or(TOL_OUTER) {
651 log_result!(
652 options_outer.verbosity,
653 "Bubble/dew point: calculation converged in {} step(s)\n",
654 k_out
655 );
656 Ok((
657 state1.density.into_reduced().recip(),
658 state2.partial_density().to_reduced(),
659 ))
660 } else {
661 Err(FeosError::NotConverged(String::from(
663 "bubble-dew-iteration",
664 )))
665 }
666 }
667
668 fn adjust_p(
669 temperature: Temperature,
670 pressure: &mut Pressure,
671 state1: &mut State<E, N>,
672 state2: &mut State<E, N>,
673 verbosity: Verbosity,
674 ) -> FeosResult<f64> {
675 let ln_phi_1 = state1.ln_phi();
677 let ln_phi_2 = state2.ln_phi();
678 let k = (&ln_phi_1 - &ln_phi_2).map(f64::exp);
679
680 let xk = state1.molefracs.component_mul(&k);
682 let f = xk.sum() - 1.0;
683
684 let ln_phi_1_dp = state1.dln_phi_dp();
686 let ln_phi_2_dp = state2.dln_phi_dp();
687 let df = ((ln_phi_1_dp - ln_phi_2_dp) * *pressure)
688 .into_value()
689 .component_mul(&xk)
690 .sum();
691 let mut lnpstep = -f / df;
692
693 lnpstep = lnpstep.clamp(-MAX_LNPSTEP, MAX_LNPSTEP);
695
696 *pressure *= lnpstep.exp();
698
699 Self::adjust_states(temperature, *pressure, state1, state2, None)?;
701
702 log_iteration(
704 verbosity,
705 Some(f),
706 temperature,
707 *pressure,
708 state2.molefracs.as_slice(),
709 false,
710 );
711
712 Ok(f.abs())
713 }
714
715 fn adjust_t(
716 temperature: &mut Temperature,
717 pressure: Pressure,
718 state1: &mut State<E, N>,
719 state2: &mut State<E, N>,
720 verbosity: Verbosity,
721 ) -> FeosResult<f64> {
722 let ln_phi_1 = state1.ln_phi();
724 let ln_phi_2 = state2.ln_phi();
725 let k = (&ln_phi_1 - &ln_phi_2).map(f64::exp);
726
727 let f = state1.molefracs.dot(&k) - 1.0;
729
730 let ln_phi_1_dt = state1.dln_phi_dt();
732 let ln_phi_2_dt = state2.dln_phi_dt();
733 let df = ((ln_phi_1_dt - ln_phi_2_dt)
734 .component_mul(&Dimensionless::new(state1.molefracs.component_mul(&k))))
735 .sum();
736 let mut tstep = -f / df;
737
738 if tstep < -Temperature::from_reduced(MAX_TSTEP) {
740 tstep = -Temperature::from_reduced(MAX_TSTEP);
741 } else if tstep > Temperature::from_reduced(MAX_TSTEP) {
742 tstep = Temperature::from_reduced(MAX_TSTEP);
743 }
744
745 *temperature += tstep;
747
748 Self::adjust_states(*temperature, pressure, state1, state2, None)?;
750
751 log_iteration(
753 verbosity,
754 Some(f),
755 *temperature,
756 pressure,
757 state2.molefracs.as_slice(),
758 false,
759 );
760
761 Ok(f.abs())
762 }
763
764 fn starting_pressure_ideal_gas(
765 eos: &E,
766 temperature: Temperature,
767 molefracs_spec: &OVector<f64, N>,
768 bubble: bool,
769 ) -> FeosResult<(Pressure, OVector<f64, N>)> {
770 if bubble {
771 Self::starting_pressure_ideal_gas_bubble(eos, temperature, molefracs_spec)
772 } else {
773 Self::starting_pressure_ideal_gas_dew(eos, temperature, molefracs_spec)
774 }
775 }
776
777 pub(super) fn starting_pressure_ideal_gas_bubble(
778 eos: &E,
779 temperature: Temperature,
780 liquid_molefracs: &OVector<f64, N>,
781 ) -> FeosResult<(Pressure, OVector<f64, N>)> {
782 let density = 0.75 * Density::from_reduced(eos.compute_max_density(liquid_molefracs));
783 let liquid = State::new(eos, temperature, density, liquid_molefracs)?;
784 let v_l = liquid.partial_molar_volume();
785 let p_l = liquid.pressure(Contributions::Total);
786 let mu_l = liquid.residual_chemical_potential();
787 let k_i = liquid_molefracs.component_mul(
788 &((mu_l - v_l * p_l) / (RGAS * temperature))
789 .into_value()
790 .map(f64::exp),
791 );
792 let p = k_i.sum() * RGAS * temperature * density;
793 let y = &k_i / k_i.sum();
794 Ok((p, y))
795 }
796
797 fn starting_pressure_ideal_gas_dew(
798 eos: &E,
799 temperature: Temperature,
800 vapor_molefracs: &OVector<f64, N>,
801 ) -> FeosResult<(Pressure, OVector<f64, N>)> {
802 let mut p: Option<Pressure> = None;
803
804 let mut x = vapor_molefracs.clone();
805 for _ in 0..5 {
806 let density = Density::from_reduced(0.75 * eos.compute_max_density(&x));
807 let liquid = State::new(eos, temperature, density, x)?;
808 let v_l = liquid.partial_molar_volume();
809 let p_l = liquid.pressure(Contributions::Total);
810 let mu_l = liquid.residual_chemical_potential();
811 let k = vapor_molefracs.clone().component_div(
812 &((mu_l - v_l * p_l) / (RGAS * temperature))
813 .into_value()
814 .map(f64::exp),
815 );
816 let k_sum = k.sum();
817 let p_new = RGAS * temperature * density / k_sum;
818 x = k / k_sum;
819 if let Some(p_old) = p
820 && ((p_new - p_old) / p_old).into_value().abs() < 1e-5
821 {
822 p = Some(p_new);
823 break;
824 }
825 p = Some(p_new);
826 }
827 Ok((p.unwrap(), x))
828 }
829
830 pub(super) fn starting_pressure_spinodal(
831 eos: &E,
832 temperature: Temperature,
833 molefracs: &OVector<f64, N>,
834 ) -> FeosResult<Pressure> {
835 let [sp_v, sp_l] = State::spinodal(eos, temperature, molefracs, Default::default())?;
836 let pv = sp_v.pressure(Contributions::Total);
837 let pl = sp_l.pressure(Contributions::Total);
838 Ok(0.5 * (Pressure::from_reduced(0.0).max(pl) + pv))
839 }
840
841 fn starting_x2_bubble(
842 eos: &E,
843 temperature: Temperature,
844 pressure: Pressure,
845 liquid_molefracs: &OVector<f64, N>,
846 vapor_molefracs: Option<&OVector<f64, N>>,
847 ) -> FeosResult<[State<E, N>; 2]> {
848 let liquid_state =
849 State::new_npt(eos, temperature, pressure, liquid_molefracs, Some(Liquid))?;
850 let xv = match vapor_molefracs {
851 Some(xv) => xv.clone(),
852 None => liquid_state
853 .ln_phi()
854 .map(f64::exp)
855 .component_mul(liquid_molefracs),
856 };
857 let vapor_state = State::new_npt(eos, temperature, pressure, xv, Some(Vapor))?;
858 Ok([liquid_state, vapor_state])
859 }
860
861 fn starting_x2_dew(
862 eos: &E,
863 temperature: Temperature,
864 pressure: Pressure,
865 vapor_molefracs: &OVector<f64, N>,
866 liquid_molefracs: Option<&OVector<f64, N>>,
867 ) -> FeosResult<[State<E, N>; 2]> {
868 let vapor_state = State::new_npt(eos, temperature, pressure, vapor_molefracs, Some(Vapor))?;
869 let xl = match liquid_molefracs {
870 Some(xl) => xl.clone(),
871 None => {
872 let xl = vapor_state
873 .ln_phi()
874 .map(f64::exp)
875 .component_mul(vapor_molefracs);
876 let liquid_state = State::new_npt(eos, temperature, pressure, xl, Some(Liquid))?;
877 (vapor_state.ln_phi() - liquid_state.ln_phi())
878 .map(f64::exp)
879 .component_mul(vapor_molefracs)
880 }
881 };
882 let liquid_state = State::new_npt(eos, temperature, pressure, xl, Some(Liquid))?;
883 Ok([vapor_state, liquid_state])
884 }
885
886 fn adjust_states(
887 temperature: Temperature,
888 pressure: Pressure,
889 state1: &mut State<E, N>,
890 state2: &mut State<E, N>,
891 molefracs_state2: Option<&OVector<f64, N>>,
892 ) -> FeosResult<()> {
893 *state1 = State::new_npt(
894 &state1.eos,
895 temperature,
896 pressure,
897 &state1.molefracs,
898 Some(InitialDensity(state1.density)),
899 )?;
900 *state2 = State::new_npt(
901 &state2.eos,
902 temperature,
903 pressure,
904 molefracs_state2.unwrap_or(&state2.molefracs),
905 Some(InitialDensity(state2.density)),
906 )?;
907 Ok(())
908 }
909
910 fn adjust_x2(
911 state1: &State<E, N>,
912 state2: &mut State<E, N>,
913 verbosity: Verbosity,
914 ) -> FeosResult<f64> {
915 let x1 = &state1.molefracs;
916 let ln_phi_1 = state1.ln_phi();
917 let ln_phi_2 = state2.ln_phi();
918 let k = (ln_phi_1 - ln_phi_2).map(f64::exp);
919 let kx1 = k.component_mul(x1);
920 let err_out = kx1
921 .component_div(&state2.molefracs)
922 .map(|e| (e - 1.0).abs())
923 .sum();
924 let x2 = &kx1 / kx1.sum();
925 log_iter!(
926 verbosity,
927 "{:<14.8e} | {:14} | {:14} | {:16} |",
928 err_out,
929 "",
930 "",
931 ""
932 );
933 *state2 = State::new_npt(
934 &state2.eos,
935 state2.temperature,
936 state2.pressure(Contributions::Total),
937 x2,
938 Some(InitialDensity(state2.density)),
939 )?;
940 Ok(err_out)
941 }
942}
943
944fn log_iteration(
945 verbosity: Verbosity,
946 error: Option<f64>,
947 temperature: Temperature,
948 pressure: Pressure,
949 x2: &[f64],
950 newton: bool,
951) {
952 let error = error.map_or_else(|| format!("{:14}", ""), |e| format!("{:<14.8e}", e.abs()));
953 log_iter!(
954 verbosity,
955 "{:14} | {} | {:12.8} | {:12.8} | {:.8?} {}",
956 "",
957 error,
958 temperature,
959 pressure,
960 x2,
961 if newton { "NEWTON" } else { "" }
962 );
963}