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