1use super::PhaseEquilibrium;
2use crate::equation_of_state::Residual;
3use crate::errors::{EosError, EosResult};
4use crate::state::{
5 Contributions,
6 DensityInitialization::{InitialDensity, Liquid, Vapor},
7 State, StateBuilder, TPSpec,
8};
9use crate::{ReferenceSystem, SolverOptions, Verbosity};
10use ndarray::*;
11use num_dual::linalg::{norm, LU};
12use quantity::{Density, Dimensionless, Moles, Pressure, Quantity, SIUnit, Temperature, RGAS};
13use std::fmt;
14use std::sync::Arc;
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: Copy + Into<TPSpec> {
28 type Other: fmt::Display + TemperatureOrPressure;
29
30 const IDENTIFIER: &'static str;
31
32 fn temperature_pressure(&self, tp_init: Self::Other) -> (Temperature, Pressure);
33
34 fn from_state<E: Residual>(state: &State<E>) -> Self::Other;
35
36 fn linspace(
37 &self,
38 start: Self::Other,
39 end: Self::Other,
40 n: usize,
41 ) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>);
42
43 fn bubble_dew_point<E: Residual>(
44 eos: &Arc<E>,
45 tp_spec: Self,
46 tp_init: Option<Self::Other>,
47 molefracs_spec: &Array1<f64>,
48 molefracs_init: Option<&Array1<f64>>,
49 bubble: bool,
50 options: (SolverOptions, SolverOptions),
51 ) -> EosResult<PhaseEquilibrium<E, 2>>;
52
53 fn adjust_t_p<E: Residual>(
54 tp_spec: Self,
55 var: &mut Self::Other,
56 state1: &mut State<E>,
57 state2: &mut State<E>,
58 verbosity: Verbosity,
59 ) -> EosResult<f64>;
60
61 fn newton_step<E: Residual>(
62 tp_spec: Self,
63 var: &mut Self::Other,
64 state1: &mut State<E>,
65 state2: &mut State<E>,
66 verbosity: Verbosity,
67 ) -> EosResult<f64>;
68}
69
70impl TemperatureOrPressure for Temperature {
71 type Other = Pressure;
72 const IDENTIFIER: &'static str = "temperature";
73
74 fn temperature_pressure(&self, tp_init: Self::Other) -> (Temperature, Pressure) {
75 (*self, tp_init)
76 }
77
78 fn from_state<E: Residual>(state: &State<E>) -> Self::Other {
79 state.pressure(Contributions::Total)
80 }
81
82 fn linspace(
83 &self,
84 start: Pressure,
85 end: Pressure,
86 n: usize,
87 ) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>) {
88 (
89 Temperature::linspace(*self, *self, n),
90 Pressure::linspace(start, end, n),
91 )
92 }
93
94 fn bubble_dew_point<E: Residual>(
95 eos: &Arc<E>,
96 temperature: Self,
97 p_init: Option<Pressure>,
98 molefracs_spec: &Array1<f64>,
99 molefracs_init: Option<&Array1<f64>>,
100 bubble: bool,
101 options: (SolverOptions, SolverOptions),
102 ) -> EosResult<PhaseEquilibrium<E, 2>> {
103 if let Some(p) = p_init {
105 return PhaseEquilibrium::iterate_bubble_dew(
106 eos,
107 temperature,
108 p,
109 molefracs_spec,
110 molefracs_init,
111 bubble,
112 options,
113 );
114 }
115
116 let vle =
118 PhaseEquilibrium::starting_pressure_ideal_gas(eos, temperature, molefracs_spec, bubble)
119 .and_then(|(p, x)| {
120 PhaseEquilibrium::iterate_bubble_dew(
121 eos,
122 temperature,
123 p,
124 molefracs_spec,
125 molefracs_init.or(Some(&x)),
126 bubble,
127 options,
128 )
129 });
130
131 vle.or_else(|_| {
133 PhaseEquilibrium::iterate_bubble_dew(
134 eos,
135 temperature,
136 PhaseEquilibrium::starting_pressure_spinodal(eos, temperature, molefracs_spec)?,
137 molefracs_spec,
138 molefracs_init,
139 bubble,
140 options,
141 )
142 })
143 }
144
145 fn adjust_t_p<E: Residual>(
146 temperature: Temperature,
147 pressure: &mut Pressure,
148 state1: &mut State<E>,
149 state2: &mut State<E>,
150 verbosity: Verbosity,
151 ) -> EosResult<f64> {
152 let ln_phi_1 = state1.ln_phi();
154 let ln_phi_2 = state2.ln_phi();
155 let k = (&ln_phi_1 - &ln_phi_2).mapv(f64::exp);
156
157 let f = (&state1.molefracs * &k).sum() - 1.0;
159
160 let ln_phi_1_dp = state1.dln_phi_dp();
162 let ln_phi_2_dp = state2.dln_phi_dp();
163 let df =
164 (((ln_phi_1_dp - ln_phi_2_dp) * *pressure).into_value() * &state1.molefracs * &k).sum();
165 let mut lnpstep = -f / df;
166
167 lnpstep = lnpstep.clamp(-MAX_LNPSTEP, MAX_LNPSTEP);
169
170 *pressure *= lnpstep.exp();
172
173 adjust_states(temperature, *pressure, state1, state2, None)?;
175
176 log_iter!(
178 verbosity,
179 "{:14} | {:<14.8e} | {:12.8} | {:.8}",
180 "",
181 f.abs(),
182 pressure,
183 state2.molefracs
184 );
185
186 Ok(f.abs())
187 }
188
189 fn newton_step<E: Residual>(
190 _: Temperature,
191 pressure: &mut Pressure,
192 state1: &mut State<E>,
193 state2: &mut State<E>,
194 verbosity: Verbosity,
195 ) -> EosResult<f64> {
196 let dmu_drho_1 = (state1.dmu_dni(Contributions::Total) * state1.volume)
197 .to_reduced()
198 .dot(&state1.molefracs);
199 let dmu_drho_2 = (state2.dmu_dni(Contributions::Total) * state2.volume).to_reduced();
200 let dp_drho_1 = (state1.dp_dni(Contributions::Total) * state1.volume)
201 .to_reduced()
202 .dot(&state1.molefracs);
203 let dp_drho_2 = (state2.dp_dni(Contributions::Total) * state2.volume).to_reduced();
204 let mu_1_res = state1.residual_chemical_potential().to_reduced();
205 let mu_2_res = state2.residual_chemical_potential().to_reduced();
206 let p_1 = state1.pressure(Contributions::Total).to_reduced();
207 let p_2 = state2.pressure(Contributions::Total).to_reduced();
208
209 let dmu_ig = (RGAS * state1.temperature).to_reduced()
211 * (&state1.partial_density / &state2.partial_density)
212 .into_value()
213 .mapv(f64::ln);
214 let res = concatenate![Axis(0), mu_1_res - mu_2_res + dmu_ig, arr1(&[p_1 - p_2])];
215 let error = norm(&res);
216
217 let jacobian = concatenate![
219 Axis(1),
220 concatenate![Axis(0), -dmu_drho_2, -dp_drho_2.insert_axis(Axis(0))],
221 concatenate![
222 Axis(0),
223 dmu_drho_1.insert_axis(Axis(1)),
224 arr2(&[[dp_drho_1]])
225 ]
226 ];
227
228 let dx = LU::new(jacobian)?.solve(&res);
230
231 let rho_l1 = state1.density - Density::from_reduced(dx[dx.len() - 1]);
233 let rho_l2 =
234 state2.partial_density.clone() - Density::from_reduced(dx.slice(s![0..-1]).to_owned());
235
236 *state1 = StateBuilder::new(&state1.eos)
238 .temperature(state1.temperature)
239 .density(rho_l1)
240 .molefracs(&state1.molefracs)
241 .build()?;
242 *state2 = StateBuilder::new(&state2.eos)
243 .temperature(state2.temperature)
244 .partial_density(&rho_l2)
245 .build()?;
246 *pressure = state1.pressure(Contributions::Total);
247 log_iter!(
248 verbosity,
249 "{:<14.8e} | {:14} | {:12.8} | {:.8} NEWTON",
250 error,
251 "",
252 pressure,
253 state2.molefracs
254 );
255 Ok(error)
256 }
257}
258
259impl TemperatureOrPressure for Quantity<f64, SIUnit<N2, N1, P1, Z0, Z0, Z0, Z0>> {
263 type Other = Temperature;
264 const IDENTIFIER: &'static str = "pressure";
265
266 fn temperature_pressure(&self, tp_init: Self::Other) -> (Temperature, Pressure) {
267 (tp_init, *self)
268 }
269
270 fn from_state<E: Residual>(state: &State<E>) -> Self::Other {
271 state.temperature
272 }
273
274 fn linspace(
275 &self,
276 start: Temperature,
277 end: Temperature,
278 n: usize,
279 ) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>) {
280 (
281 Temperature::linspace(start, end, n),
282 Pressure::linspace(*self, *self, n),
283 )
284 }
285
286 fn bubble_dew_point<E: Residual>(
287 eos: &Arc<E>,
288 pressure: Self,
289 t_init: Option<Temperature>,
290 molefracs_spec: &Array1<f64>,
291 molefracs_init: Option<&Array1<f64>>,
292 bubble: bool,
293 options: (SolverOptions, SolverOptions),
294 ) -> EosResult<PhaseEquilibrium<E, 2>> {
295 let temperature = t_init.expect("An initial temperature is required for the calculation of bubble/dew points at given pressure!");
296 PhaseEquilibrium::iterate_bubble_dew(
297 eos,
298 pressure,
299 temperature,
300 molefracs_spec,
301 molefracs_init,
302 bubble,
303 options,
304 )
305 }
306
307 fn adjust_t_p<E: Residual>(
308 pressure: Pressure,
309 temperature: &mut Temperature,
310 state1: &mut State<E>,
311 state2: &mut State<E>,
312 verbosity: Verbosity,
313 ) -> EosResult<f64> {
314 let ln_phi_1 = state1.ln_phi();
316 let ln_phi_2 = state2.ln_phi();
317 let k = (&ln_phi_1 - &ln_phi_2).mapv(f64::exp);
318
319 let f = (&state1.molefracs * &k).sum() - 1.0;
321
322 let ln_phi_1_dt = state1.dln_phi_dt();
324 let ln_phi_2_dt = state2.dln_phi_dt();
325 let df = ((ln_phi_1_dt - ln_phi_2_dt) * Dimensionless::new(&state1.molefracs * &k)).sum();
326 let mut tstep = -f / df;
327
328 if tstep < -Temperature::from_reduced(MAX_TSTEP) {
330 tstep = -Temperature::from_reduced(MAX_TSTEP);
331 } else if tstep > Temperature::from_reduced(MAX_TSTEP) {
332 tstep = Temperature::from_reduced(MAX_TSTEP);
333 }
334
335 *temperature += tstep;
337
338 adjust_states(*temperature, pressure, state1, state2, None)?;
340
341 log_iter!(
343 verbosity,
344 "{:14} | {:<14.8e} | {:12.8} | {:.8}",
345 "",
346 f.abs(),
347 temperature,
348 state2.molefracs
349 );
350
351 Ok(f.abs())
352 }
353
354 fn newton_step<E: Residual>(
355 pressure: Pressure,
356 temperature: &mut Temperature,
357 state1: &mut State<E>,
358 state2: &mut State<E>,
359 verbosity: Verbosity,
360 ) -> EosResult<f64> {
361 let dmu_drho_1 = (state1.dmu_dni(Contributions::Total) * state1.volume)
362 .to_reduced()
363 .dot(&state1.molefracs);
364 let dmu_drho_2 = (state2.dmu_dni(Contributions::Total) * state2.volume).to_reduced();
365 let dmu_res_dt_1 = state1.dmu_res_dt().to_reduced();
366 let dmu_res_dt_2 = state2.dmu_res_dt().to_reduced();
367 let dp_drho_1 = (state1.dp_dni(Contributions::Total) * state1.volume)
368 .to_reduced()
369 .dot(&state1.molefracs);
370 let dp_dt_1 = state1.dp_dt(Contributions::Total).to_reduced();
371 let dp_dt_2 = state2.dp_dt(Contributions::Total).to_reduced();
372 let dp_drho_2 = (state2.dp_dni(Contributions::Total) * state2.volume).to_reduced();
373 let mu_1_res = state1.residual_chemical_potential().to_reduced();
374 let mu_2_res = state2.residual_chemical_potential().to_reduced();
375 let p_1 = state1.pressure(Contributions::Total).to_reduced();
376 let p_2 = state2.pressure(Contributions::Total).to_reduced();
377 let p = pressure.to_reduced();
378
379 let delta_dmu_ig_dt = (&state1.partial_density / &state2.partial_density)
381 .into_value()
382 .mapv(f64::ln);
383 let delta_mu_ig = (RGAS * state1.temperature).to_reduced() * &delta_dmu_ig_dt;
384 let res = concatenate![
385 Axis(0),
386 mu_1_res - mu_2_res + delta_mu_ig,
387 arr1(&[p_1 - p]),
388 arr1(&[p_2 - p])
389 ];
390 let error = norm(&res);
391
392 let jacobian = concatenate![
394 Axis(1),
395 concatenate![
396 Axis(0),
397 -dmu_drho_2,
398 Array2::zeros((1, res.len() - 2)),
399 dp_drho_2.insert_axis(Axis(0))
400 ],
401 concatenate![
402 Axis(0),
403 dmu_drho_1.insert_axis(Axis(1)),
404 arr2(&[[dp_drho_1], [0.0]])
405 ],
406 concatenate![
407 Axis(0),
408 (dmu_res_dt_1 - dmu_res_dt_2 + delta_dmu_ig_dt).insert_axis(Axis(1)),
409 arr2(&[[dp_dt_1], [dp_dt_2]])
410 ]
411 ];
412
413 let dx = LU::new(jacobian)?.solve(&res);
415
416 let rho_l1 = state1.density - Density::from_reduced(dx[dx.len() - 2]);
418 let rho_l2 =
419 state2.partial_density.clone() - Density::from_reduced(dx.slice(s![0..-2]).to_owned());
420 let t = state1.temperature - Temperature::from_reduced(dx[dx.len() - 1]);
421
422 *state1 = StateBuilder::new(&state1.eos)
424 .temperature(t)
425 .density(rho_l1)
426 .molefracs(&state1.molefracs)
427 .build()?;
428 *state2 = StateBuilder::new(&state2.eos)
429 .temperature(t)
430 .partial_density(&rho_l2)
431 .build()?;
432 *temperature = t;
433 log_iter!(
434 verbosity,
435 "{:<14.8e} | {:14} | {:12.8} | {:.8} NEWTON",
436 error,
437 "",
438 temperature,
439 state2.molefracs
440 );
441 Ok(error)
442 }
443}
444
445impl<E: Residual> PhaseEquilibrium<E, 2> {
447 pub fn bubble_point<TP: TemperatureOrPressure>(
450 eos: &Arc<E>,
451 temperature_or_pressure: TP,
452 liquid_molefracs: &Array1<f64>,
453 tp_init: Option<TP::Other>,
454 vapor_molefracs: Option<&Array1<f64>>,
455 options: (SolverOptions, SolverOptions),
456 ) -> EosResult<Self> {
457 Self::bubble_dew_point(
458 eos,
459 temperature_or_pressure,
460 tp_init,
461 liquid_molefracs,
462 vapor_molefracs,
463 true,
464 options,
465 )
466 }
467
468 pub fn dew_point<TP: TemperatureOrPressure>(
471 eos: &Arc<E>,
472 temperature_or_pressure: TP,
473 vapor_molefracs: &Array1<f64>,
474 tp_init: Option<TP::Other>,
475 liquid_molefracs: Option<&Array1<f64>>,
476 options: (SolverOptions, SolverOptions),
477 ) -> EosResult<Self> {
478 Self::bubble_dew_point(
479 eos,
480 temperature_or_pressure,
481 tp_init,
482 vapor_molefracs,
483 liquid_molefracs,
484 false,
485 options,
486 )
487 }
488
489 pub(super) fn bubble_dew_point<TP: TemperatureOrPressure>(
490 eos: &Arc<E>,
491 tp_spec: TP,
492 tp_init: Option<TP::Other>,
493 molefracs_spec: &Array1<f64>,
494 molefracs_init: Option<&Array1<f64>>,
495 bubble: bool,
496 options: (SolverOptions, SolverOptions),
497 ) -> EosResult<Self> {
498 TP::bubble_dew_point(
499 eos,
500 tp_spec,
501 tp_init,
502 molefracs_spec,
503 molefracs_init,
504 bubble,
505 options,
506 )
507 }
508
509 fn iterate_bubble_dew<TP: TemperatureOrPressure>(
510 eos: &Arc<E>,
511 tp_spec: TP,
512 tp_init: TP::Other,
513 molefracs_spec: &Array1<f64>,
514 molefracs_init: Option<&Array1<f64>>,
515 bubble: bool,
516 options: (SolverOptions, SolverOptions),
517 ) -> EosResult<Self> {
518 let (t, p) = tp_spec.temperature_pressure(tp_init);
519 let [state1, state2] = if bubble {
520 starting_x2_bubble(eos, t, p, molefracs_spec, molefracs_init)
521 } else {
522 starting_x2_dew(eos, t, p, molefracs_spec, molefracs_init)
523 }?;
524 bubble_dew(tp_spec, tp_init, state1, state2, bubble, options)
525 }
526
527 fn starting_pressure_ideal_gas(
528 eos: &Arc<E>,
529 temperature: Temperature,
530 molefracs_spec: &Array1<f64>,
531 bubble: bool,
532 ) -> EosResult<(Pressure, Array1<f64>)> {
533 if bubble {
534 Self::starting_pressure_ideal_gas_bubble(eos, temperature, molefracs_spec)
535 } else {
536 Self::starting_pressure_ideal_gas_dew(eos, temperature, molefracs_spec)
537 }
538 }
539
540 pub(super) fn starting_pressure_ideal_gas_bubble(
541 eos: &Arc<E>,
542 temperature: Temperature,
543 liquid_molefracs: &Array1<f64>,
544 ) -> EosResult<(Pressure, Array1<f64>)> {
545 let m = Moles::from_reduced(liquid_molefracs.to_owned());
546 let density = 0.75 * eos.max_density(Some(&m))?;
547 let liquid = State::new_nvt(eos, temperature, m.sum() / density, &m)?;
548 let v_l = liquid.partial_molar_volume();
549 let p_l = liquid.pressure(Contributions::Total);
550 let mu_l = liquid.residual_chemical_potential();
551 let p_i = (liquid_molefracs * temperature * density * RGAS)
552 * Dimensionless::new(
553 ((mu_l - p_l * v_l) / (RGAS * temperature))
554 .into_value()
555 .mapv(f64::exp),
556 );
557 let p = p_i.sum();
558 let y = (p_i / p).into_value();
559 Ok((p, y))
560 }
561
562 fn starting_pressure_ideal_gas_dew(
563 eos: &Arc<E>,
564 temperature: Temperature,
565 vapor_molefracs: &Array1<f64>,
566 ) -> EosResult<(Pressure, Array1<f64>)> {
567 let mut p: Option<Pressure> = None;
568
569 let mut x = vapor_molefracs.clone();
570 for _ in 0..5 {
571 let m = Moles::from_reduced(x);
572 let density = 0.75 * eos.max_density(Some(&m))?;
573 let liquid = State::new_nvt(eos, temperature, m.sum() / density, &m)?;
574 let v_l = liquid.partial_molar_volume();
575 let p_l = liquid.pressure(Contributions::Total);
576 let mu_l = liquid.residual_chemical_potential();
577 let k = vapor_molefracs
578 / ((mu_l - p_l * v_l) / (RGAS * temperature))
579 .into_value()
580 .mapv(f64::exp);
581 let p_new = RGAS * temperature * density / k.sum();
582 x = &k / k.sum();
583 if let Some(p_old) = p {
584 if ((p_new - p_old) / p_old).into_value().abs() < 1e-5 {
585 p = Some(p_new);
586 break;
587 }
588 }
589 p = Some(p_new);
590 }
591 Ok((p.unwrap(), x))
592 }
593
594 pub(super) fn starting_pressure_spinodal(
595 eos: &Arc<E>,
596 temperature: Temperature,
597 molefracs: &Array1<f64>,
598 ) -> EosResult<Pressure> {
599 let moles = Moles::from_reduced(molefracs.clone());
600 let [sp_v, sp_l] = State::spinodal(eos, temperature, Some(&moles), Default::default())?;
601 let pv = sp_v.pressure(Contributions::Total);
602 let pl = sp_l.pressure(Contributions::Total);
603 Ok(0.5 * (Pressure::from_reduced(0.0).max(pl) + pv))
604 }
605}
606
607fn starting_x2_bubble<E: Residual>(
608 eos: &Arc<E>,
609 temperature: Temperature,
610 pressure: Pressure,
611 liquid_molefracs: &Array1<f64>,
612 vapor_molefracs: Option<&Array1<f64>>,
613) -> EosResult<[State<E>; 2]> {
614 let liquid_state = State::new_npt(
615 eos,
616 temperature,
617 pressure,
618 &Moles::from_reduced(liquid_molefracs.clone()),
619 Liquid,
620 )?;
621 let xv = match vapor_molefracs {
622 Some(xv) => xv.clone(),
623 None => liquid_state.ln_phi().mapv(f64::exp) * liquid_molefracs,
624 };
625 let vapor_state = State::new_npt(eos, temperature, pressure, &Moles::from_reduced(xv), Vapor)?;
626 Ok([liquid_state, vapor_state])
627}
628
629fn starting_x2_dew<E: Residual>(
630 eos: &Arc<E>,
631 temperature: Temperature,
632 pressure: Pressure,
633 vapor_molefracs: &Array1<f64>,
634 liquid_molefracs: Option<&Array1<f64>>,
635) -> EosResult<[State<E>; 2]> {
636 let vapor_state = State::new_npt(
637 eos,
638 temperature,
639 pressure,
640 &Moles::from_reduced(vapor_molefracs.clone()),
641 Vapor,
642 )?;
643 let xl = match liquid_molefracs {
644 Some(xl) => xl.clone(),
645 None => {
646 let xl = vapor_state.ln_phi().mapv(f64::exp) * vapor_molefracs;
647 let liquid_state =
648 State::new_npt(eos, temperature, pressure, &Moles::from_reduced(xl), Liquid)?;
649 (vapor_state.ln_phi() - liquid_state.ln_phi()).mapv(f64::exp) * vapor_molefracs
650 }
651 };
652 let liquid_state =
653 State::new_npt(eos, temperature, pressure, &Moles::from_reduced(xl), Liquid)?;
654 Ok([vapor_state, liquid_state])
655}
656
657fn bubble_dew<E: Residual, TP: TemperatureOrPressure>(
658 tp_spec: TP,
659 mut var_tp: TP::Other,
660 mut state1: State<E>,
661 mut state2: State<E>,
662 bubble: bool,
663 options: (SolverOptions, SolverOptions),
664) -> EosResult<PhaseEquilibrium<E, 2>> {
665 let (options_inner, options_outer) = options;
666
667 let mut err_out = 1.0;
669 let mut k_out = 0;
670
671 if PhaseEquilibrium::is_trivial_solution(&state1, &state2) {
672 log_iter!(options_outer.verbosity, "Trivial solution encountered!");
673 return Err(EosError::TrivialSolution);
674 }
675
676 log_iter!(
677 options_outer.verbosity,
678 "res outer loop | res inner loop | {:^16} | molefracs second phase",
679 TP::IDENTIFIER
680 );
681 log_iter!(options_outer.verbosity, "{:-<85}", "");
682 log_iter!(
683 options_outer.verbosity,
684 "{:14} | {:14} | {:12.8} | {:.8}",
685 "",
686 "",
687 var_tp,
688 state2.molefracs
689 );
690
691 for ko in 0..options_outer.max_iter.unwrap_or(MAX_ITER_OUTER) {
693 err_out = if err_out > NEWTON_TOL {
695 for _ in 0..options_inner.max_iter.unwrap_or(MAX_ITER_INNER) {
697 if TP::adjust_t_p(
698 tp_spec,
699 &mut var_tp,
700 &mut state1,
701 &mut state2,
702 options_inner.verbosity,
703 )? < options_inner.tol.unwrap_or(TOL_INNER)
704 {
705 break;
706 }
707 }
708 adjust_x2(&state1, &mut state2, options_outer.verbosity)
709 } else {
710 TP::newton_step(
711 tp_spec,
712 &mut var_tp,
713 &mut state1,
714 &mut state2,
715 options_outer.verbosity,
716 )
717 }?;
718
719 if PhaseEquilibrium::is_trivial_solution(&state1, &state2) {
720 log_iter!(options_outer.verbosity, "Trivial solution encountered!");
721 return Err(EosError::TrivialSolution);
722 }
723
724 if err_out < options_outer.tol.unwrap_or(TOL_OUTER) {
725 k_out = ko + 1;
726 break;
727 }
728 }
729
730 if err_out < options_outer.tol.unwrap_or(TOL_OUTER) {
731 log_result!(
732 options_outer.verbosity,
733 "Bubble/dew point: calculation converged in {} step(s)\n",
734 k_out
735 );
736 if bubble {
737 Ok(PhaseEquilibrium([state2, state1]))
738 } else {
739 Ok(PhaseEquilibrium([state1, state2]))
740 }
741 } else {
742 Err(EosError::NotConverged(String::from("bubble-dew-iteration")))
744 }
745}
746
747fn adjust_states<E: Residual>(
748 temperature: Temperature,
749 pressure: Pressure,
750 state1: &mut State<E>,
751 state2: &mut State<E>,
752 moles_state2: Option<&Moles<Array1<f64>>>,
753) -> EosResult<()> {
754 *state1 = State::new_npt(
755 &state1.eos,
756 temperature,
757 pressure,
758 &state1.moles,
759 InitialDensity(state1.density),
760 )?;
761 *state2 = State::new_npt(
762 &state2.eos,
763 temperature,
764 pressure,
765 moles_state2.unwrap_or(&state2.moles),
766 InitialDensity(state2.density),
767 )?;
768 Ok(())
769}
770
771fn adjust_x2<E: Residual>(
772 state1: &State<E>,
773 state2: &mut State<E>,
774 verbosity: Verbosity,
775) -> EosResult<f64> {
776 let x1 = &state1.molefracs;
777 let ln_phi_1 = state1.ln_phi();
778 let ln_phi_2 = state2.ln_phi();
779 let k = (ln_phi_1 - ln_phi_2).mapv(f64::exp);
780 let err_out = (&k * x1 / &state2.molefracs - 1.0).mapv(f64::abs).sum();
781 let x2 = (x1 * &k) / (&k * x1).sum();
782 log_iter!(verbosity, "{:<14.8e} | {:14} | {:16} |", err_out, "", "");
783 *state2 = State::new_npt(
784 &state2.eos,
785 state2.temperature,
786 state2.pressure(Contributions::Total),
787 &Moles::from_reduced(x2),
788 InitialDensity(state2.density),
789 )?;
790 Ok(err_out)
791}