use crate::errors::{FeosError, FeosResult};
use crate::phase_equilibria::PhaseEquilibrium;
use crate::state::{
Contributions,
DensityInitialization::{InitialDensity, Liquid, Vapor},
};
use crate::{ReferenceSystem, Residual, SolverOptions, State, Verbosity};
use nalgebra::allocator::Allocator;
use nalgebra::{DMatrix, DVector, DefaultAllocator, Dim, Dyn, OVector, U1};
#[cfg(feature = "ndarray")]
use ndarray::Array1;
use num_dual::linalg::LU;
use num_dual::{DualNum, DualStruct, Gradients};
use quantity::{Density, Dimensionless, Moles, Pressure, Quantity, RGAS, SIUnit, Temperature};
use typenum::{N1, N2, P1, Z0};
const MAX_ITER_INNER: usize = 5;
const TOL_INNER: f64 = 1e-9;
const MAX_ITER_OUTER: usize = 400;
const TOL_OUTER: f64 = 1e-10;
const MAX_TSTEP: f64 = 20.0;
const MAX_LNPSTEP: f64 = 0.1;
const NEWTON_TOL: f64 = 1e-3;
pub trait TemperatureOrPressure<D: DualNum<f64> + Copy = f64>: Copy {
type Other: Copy;
const IDENTIFIER: &'static str;
fn temperature(&self) -> Option<Temperature<D>>;
fn pressure(&self) -> Option<Pressure<D>>;
fn temperature_pressure(
&self,
tp_init: Option<Self::Other>,
) -> (Option<Temperature<D>>, Option<Pressure<D>>, bool);
fn from_state<E: Residual<N, D>, N: Gradients>(state: &State<E, N, D>) -> Self::Other
where
DefaultAllocator: Allocator<N>;
#[cfg(feature = "ndarray")]
fn linspace(
&self,
start: Self::Other,
end: Self::Other,
n: usize,
) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>);
}
impl<D: DualNum<f64> + Copy> TemperatureOrPressure<D> for Temperature<D> {
type Other = Pressure<D>;
const IDENTIFIER: &'static str = "temperature";
fn temperature(&self) -> Option<Temperature<D>> {
Some(*self)
}
fn pressure(&self) -> Option<Pressure<D>> {
None
}
fn temperature_pressure(
&self,
tp_init: Option<Self::Other>,
) -> (Option<Temperature<D>>, Option<Pressure<D>>, bool) {
(Some(*self), tp_init, true)
}
fn from_state<E: Residual<N, D>, N: Gradients>(state: &State<E, N, D>) -> Self::Other
where
DefaultAllocator: Allocator<N>,
{
state.pressure(Contributions::Total)
}
#[cfg(feature = "ndarray")]
fn linspace(
&self,
start: Pressure<D>,
end: Pressure<D>,
n: usize,
) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>) {
(
Temperature::linspace(self.re(), self.re(), n),
Pressure::linspace(start.re(), end.re(), n),
)
}
}
impl<D: DualNum<f64> + Copy> TemperatureOrPressure<D>
for Quantity<D, SIUnit<N2, N1, P1, Z0, Z0, Z0, Z0>>
{
type Other = Temperature<D>;
const IDENTIFIER: &'static str = "pressure";
fn temperature(&self) -> Option<Temperature<D>> {
None
}
fn pressure(&self) -> Option<Pressure<D>> {
Some(*self)
}
fn temperature_pressure(
&self,
tp_init: Option<Self::Other>,
) -> (Option<Temperature<D>>, Option<Pressure<D>>, bool) {
(tp_init, Some(*self), false)
}
fn from_state<E: Residual<N, D>, N: Dim>(state: &State<E, N, D>) -> Self::Other
where
DefaultAllocator: Allocator<N>,
{
state.temperature
}
#[cfg(feature = "ndarray")]
fn linspace(
&self,
start: Temperature<D>,
end: Temperature<D>,
n: usize,
) -> (Temperature<Array1<f64>>, Pressure<Array1<f64>>) {
(
Temperature::linspace(start.re(), end.re(), n),
Pressure::linspace(self.re(), self.re(), n),
)
}
}
impl<E: Residual<N, D>, N: Gradients, D: DualNum<f64> + Copy> PhaseEquilibrium<E, 2, N, D>
where
DefaultAllocator: Allocator<N> + Allocator<N, N> + Allocator<U1, N>,
{
pub fn bubble_point<TP: TemperatureOrPressure<D>>(
eos: &E,
temperature_or_pressure: TP,
liquid_molefracs: &OVector<D, N>,
tp_init: Option<TP::Other>,
vapor_molefracs: Option<&OVector<f64, N>>,
options: (SolverOptions, SolverOptions),
) -> FeosResult<Self> {
Self::bubble_dew_point(
eos,
temperature_or_pressure,
liquid_molefracs,
tp_init,
vapor_molefracs,
true,
options,
)
}
pub fn dew_point<TP: TemperatureOrPressure<D>>(
eos: &E,
temperature_or_pressure: TP,
vapor_molefracs: &OVector<D, N>,
tp_init: Option<TP::Other>,
liquid_molefracs: Option<&OVector<f64, N>>,
options: (SolverOptions, SolverOptions),
) -> FeosResult<Self> {
Self::bubble_dew_point(
eos,
temperature_or_pressure,
vapor_molefracs,
tp_init,
liquid_molefracs,
false,
options,
)
}
pub(super) fn bubble_dew_point<TP: TemperatureOrPressure<D>>(
eos: &E,
temperature_or_pressure: TP,
vapor_molefracs: &OVector<D, N>,
tp_init: Option<TP::Other>,
liquid_molefracs: Option<&OVector<f64, N>>,
bubble: bool,
options: (SolverOptions, SolverOptions),
) -> FeosResult<Self> {
let (temperature, pressure, iterate_p) =
temperature_or_pressure.temperature_pressure(tp_init);
Self::bubble_dew_point_tp(
eos,
temperature,
pressure,
vapor_molefracs,
liquid_molefracs,
bubble,
iterate_p,
options,
)
}
#[expect(clippy::too_many_arguments)]
fn bubble_dew_point_tp(
eos: &E,
temperature: Option<Temperature<D>>,
pressure: Option<Pressure<D>>,
molefracs_spec: &OVector<D, N>,
molefracs_init: Option<&OVector<f64, N>>,
bubble: bool,
iterate_p: bool,
options: (SolverOptions, SolverOptions),
) -> FeosResult<Self> {
let eos_re = eos.re();
let mut temperature_re = temperature.map(|t| t.re());
let mut pressure_re = pressure.map(|p| p.re());
let molefracs_spec_re = molefracs_spec.map(|x| x.re());
let (v1, rho2) = if iterate_p {
let temperature_re = temperature_re.as_mut().unwrap();
if let Some(p) = pressure_re.as_mut() {
PhaseEquilibrium::iterate_bubble_dew(
&eos_re,
temperature_re,
p,
&molefracs_spec_re,
molefracs_init,
bubble,
iterate_p,
options,
)?
} else {
let x2 = PhaseEquilibrium::starting_pressure_ideal_gas(
&eos_re,
*temperature_re,
&molefracs_spec_re,
bubble,
)
.and_then(|(p, x)| {
pressure_re = Some(p);
PhaseEquilibrium::iterate_bubble_dew(
&eos_re,
temperature_re,
pressure_re.as_mut().unwrap(),
&molefracs_spec_re,
molefracs_init.or(Some(&x)),
bubble,
iterate_p,
options,
)
});
x2.or_else(|_| {
PhaseEquilibrium::starting_pressure_spinodal(
&eos_re,
*temperature_re,
&molefracs_spec_re,
)
.and_then(|p| {
pressure_re = Some(p);
PhaseEquilibrium::iterate_bubble_dew(
&eos_re,
temperature_re,
pressure_re.as_mut().unwrap(),
&molefracs_spec_re,
molefracs_init,
bubble,
iterate_p,
options,
)
})
})?
}
} else {
let pressure_re = pressure_re.as_mut().unwrap();
let temperature_re = temperature_re.as_mut().expect("An initial temperature is required for the calculation of bubble/dew points at given pressure!");
PhaseEquilibrium::iterate_bubble_dew(
&eos.re(),
temperature_re,
pressure_re,
&molefracs_spec_re,
molefracs_init,
bubble,
iterate_p,
options,
)?
};
let (mut t, mut p) = if iterate_p {
(
temperature.unwrap().into_reduced(),
D::from(pressure_re.unwrap().into_reduced()),
)
} else {
(
D::from(temperature_re.unwrap().into_reduced()),
pressure.unwrap().into_reduced(),
)
};
let mut molar_volume = D::from(v1);
let mut rho2 = rho2.map(D::from);
for _ in 0..D::NDERIV {
if iterate_p {
Self::newton_step_t(
eos,
t,
molefracs_spec,
&mut p,
&mut molar_volume,
&mut rho2,
Verbosity::None,
)
} else {
Self::newton_step_p(
eos,
&mut t,
molefracs_spec,
p,
&mut molar_volume,
&mut rho2,
Verbosity::None,
)
};
}
let state1 = State::new_intensive(
eos,
Temperature::from_reduced(t),
Density::from_reduced(molar_volume.recip()),
molefracs_spec,
)?;
let rho2_total = rho2.sum();
let x2 = rho2 / rho2_total;
let state2 = State::new_intensive(
eos,
Temperature::from_reduced(t),
Density::from_reduced(rho2_total),
&x2,
)?;
Ok(PhaseEquilibrium(if bubble {
[state2, state1]
} else {
[state1, state2]
}))
}
fn newton_step_t(
eos: &E,
temperature: D,
molefracs: &OVector<D, N>,
pressure: &mut D,
molar_volume: &mut D,
partial_density_other_phase: &mut OVector<D, N>,
verbosity: Verbosity,
) -> f64 {
let (p_1, mu_res_1, dp_1, dmu_1) = eos.dmu_drho(temperature, partial_density_other_phase);
let (p_2, mu_res_2, dp_2, dmu_2) = eos.dmu_dv(temperature, *molar_volume, molefracs);
let n = molefracs.len();
let f = DVector::from_fn(n + 2, |i, _| {
if i == n {
p_1 - *pressure
} else if i == n + 1 {
p_2 - *pressure
} else {
mu_res_1[i] - mu_res_2[i]
+ (partial_density_other_phase[i] * *molar_volume / molefracs[i]).ln()
* temperature
}
});
let jac = DMatrix::from_fn(n + 2, n + 2, |i, j| {
if i < n && j < n {
dmu_1[(i, j)]
} else if i < n && j == n {
-dmu_2[i]
} else if i == n && j < n {
dp_1[j]
} else if i == n + 1 && j == n {
dp_2
} else if i >= n && j == n + 1 {
-D::one()
} else {
D::zero()
}
});
let dx = LU::<_, _, Dyn>::new(jac).unwrap().solve(&f);
for i in 0..n {
partial_density_other_phase[i] -= dx[i];
}
*molar_volume -= dx[n];
*pressure -= dx[n + 1];
let error = f.map(|r| r.re()).norm();
let x = partial_density_other_phase.map(|r| r.re());
let x = &x / x.sum();
log_iteration(
verbosity,
Some(error),
Temperature::from_reduced(temperature.re()),
Pressure::from_reduced(pressure.re()),
x.as_slice(),
true,
);
error
}
fn newton_step_p(
eos: &E,
temperature: &mut D,
molefracs: &OVector<D, N>,
pressure: D,
molar_volume: &mut D,
partial_density_other_phase: &mut OVector<D, N>,
verbosity: Verbosity,
) -> f64 {
let (p_1, mu_res_1, dp_1, dmu_1) = eos.dmu_drho(*temperature, partial_density_other_phase);
let (p_2, mu_res_2, dp_2, dmu_2) = eos.dmu_dv(*temperature, *molar_volume, molefracs);
let (dp_dt_1, dmu_res_dt_1) = eos.dmu_dt(*temperature, partial_density_other_phase);
let (dp_dt_2, dmu_res_dt_2) = eos.dmu_dt(*temperature, &(molefracs / *molar_volume));
let n = molefracs.len();
let f = DVector::from_fn(n + 2, |i, _| {
if i == n {
p_1 - pressure
} else if i == n + 1 {
p_2 - pressure
} else {
mu_res_1[i] - mu_res_2[i]
+ (partial_density_other_phase[i] * *molar_volume / molefracs[i]).ln()
* *temperature
}
});
let jac = DMatrix::from_fn(n + 2, n + 2, |i, j| {
if i < n && j < n {
dmu_1[(i, j)]
} else if i < n && j == n {
-dmu_2[i]
} else if i < n && j == n + 1 {
dmu_res_dt_1[i] - dmu_res_dt_2[i]
+ (partial_density_other_phase[i] * *molar_volume / molefracs[i]).ln()
} else if i == n && j < n {
dp_1[j]
} else if i == n && j == n + 1 {
dp_dt_1
} else if i == n + 1 && j == n {
dp_2
} else if i == n + 1 && j == n + 1 {
dp_dt_2
} else {
D::zero()
}
});
let dx = LU::<_, _, Dyn>::new(jac).unwrap().solve(&f);
for i in 0..n {
partial_density_other_phase[i] -= dx[i];
}
*molar_volume -= dx[n];
*temperature -= dx[n + 1];
let error = f.map(|r| r.re()).norm();
let x = partial_density_other_phase.map(|r| r.re());
let x = &x / x.sum();
log_iteration(
verbosity,
Some(error),
Temperature::from_reduced(temperature.re()),
Pressure::from_reduced(pressure.re()),
x.as_slice(),
true,
);
error
}
}
impl<E: Residual<N>, N: Gradients> PhaseEquilibrium<E, 2, N>
where
DefaultAllocator: Allocator<N> + Allocator<N, N> + Allocator<U1, N>,
{
#[expect(clippy::too_many_arguments)]
fn iterate_bubble_dew(
eos: &E,
temperature: &mut Temperature,
pressure: &mut Pressure,
molefracs_spec: &OVector<f64, N>,
molefracs_init: Option<&OVector<f64, N>>,
bubble: bool,
iterate_p: bool,
options: (SolverOptions, SolverOptions),
) -> FeosResult<(f64, OVector<f64, N>)> {
let [mut state1, mut state2] = if bubble {
Self::starting_x2_bubble(eos, *temperature, *pressure, molefracs_spec, molefracs_init)
} else {
Self::starting_x2_dew(eos, *temperature, *pressure, molefracs_spec, molefracs_init)
}?;
let (options_inner, options_outer) = options;
let mut err_out = 1.0;
let mut k_out = 0;
if PhaseEquilibrium::is_trivial_solution(&state1, &state2) {
log_iter!(options_outer.verbosity, "Trivial solution encountered!");
return Err(FeosError::TrivialSolution);
}
log_iter!(
options_outer.verbosity,
"res outer loop | res inner loop | temperature | pressure | molefracs second phase",
);
log_iter!(options_outer.verbosity, "{:-<104}", "");
log_iteration(
options_outer.verbosity,
None,
*temperature,
*pressure,
state2.molefracs.as_slice(),
false,
);
for ko in 0..options_outer.max_iter.unwrap_or(MAX_ITER_OUTER) {
err_out = if err_out > NEWTON_TOL {
for _ in 0..options_inner.max_iter.unwrap_or(MAX_ITER_INNER) {
let res = if iterate_p {
Self::adjust_p(
*temperature,
pressure,
&mut state1,
&mut state2,
options_inner.verbosity,
)?
} else {
Self::adjust_t(
temperature,
*pressure,
&mut state1,
&mut state2,
options_inner.verbosity,
)?
};
if res < options_inner.tol.unwrap_or(TOL_INNER) {
break;
}
}
Self::adjust_x2(&state1, &mut state2, options_outer.verbosity)
} else {
let mut t = temperature.into_reduced();
let mut p = pressure.into_reduced();
let mut molar_volume = state1.density.into_reduced().recip();
let mut rho2 = state2.partial_density.to_reduced();
let err = if iterate_p {
Self::newton_step_t(
&state1.eos,
t,
&state1.molefracs,
&mut p,
&mut molar_volume,
&mut rho2,
options_outer.verbosity,
)
} else {
Self::newton_step_p(
&state1.eos,
&mut t,
&state1.molefracs,
p,
&mut molar_volume,
&mut rho2,
options_outer.verbosity,
)
};
*temperature = Temperature::from_reduced(t);
*pressure = Pressure::from_reduced(p);
state1.density = Density::from_reduced(molar_volume.recip());
state2.partial_density = Density::from_reduced(rho2);
Ok(err)
}?;
if Self::is_trivial_solution(&state1, &state2) {
log_iter!(options_outer.verbosity, "Trivial solution encountered!");
return Err(FeosError::TrivialSolution);
}
if err_out < options_outer.tol.unwrap_or(TOL_OUTER) {
k_out = ko + 1;
break;
}
}
if err_out < options_outer.tol.unwrap_or(TOL_OUTER) {
log_result!(
options_outer.verbosity,
"Bubble/dew point: calculation converged in {} step(s)\n",
k_out
);
Ok((
state1.density.into_reduced().recip(),
state2.partial_density.to_reduced(),
))
} else {
Err(FeosError::NotConverged(String::from(
"bubble-dew-iteration",
)))
}
}
fn adjust_p(
temperature: Temperature,
pressure: &mut Pressure,
state1: &mut State<E, N>,
state2: &mut State<E, N>,
verbosity: Verbosity,
) -> FeosResult<f64> {
let ln_phi_1 = state1.ln_phi();
let ln_phi_2 = state2.ln_phi();
let k = (&ln_phi_1 - &ln_phi_2).map(f64::exp);
let xk = state1.molefracs.component_mul(&k);
let f = xk.sum() - 1.0;
let ln_phi_1_dp = state1.dln_phi_dp();
let ln_phi_2_dp = state2.dln_phi_dp();
let df = ((ln_phi_1_dp - ln_phi_2_dp) * *pressure)
.into_value()
.component_mul(&xk)
.sum();
let mut lnpstep = -f / df;
lnpstep = lnpstep.clamp(-MAX_LNPSTEP, MAX_LNPSTEP);
*pressure *= lnpstep.exp();
Self::adjust_states(temperature, *pressure, state1, state2, None)?;
log_iteration(
verbosity,
Some(f),
temperature,
*pressure,
state2.molefracs.as_slice(),
false,
);
Ok(f.abs())
}
fn adjust_t(
temperature: &mut Temperature,
pressure: Pressure,
state1: &mut State<E, N>,
state2: &mut State<E, N>,
verbosity: Verbosity,
) -> FeosResult<f64> {
let ln_phi_1 = state1.ln_phi();
let ln_phi_2 = state2.ln_phi();
let k = (&ln_phi_1 - &ln_phi_2).map(f64::exp);
let f = state1.molefracs.dot(&k) - 1.0;
let ln_phi_1_dt = state1.dln_phi_dt();
let ln_phi_2_dt = state2.dln_phi_dt();
let df = ((ln_phi_1_dt - ln_phi_2_dt)
.component_mul(&Dimensionless::new(state1.molefracs.component_mul(&k))))
.sum();
let mut tstep = -f / df;
if tstep < -Temperature::from_reduced(MAX_TSTEP) {
tstep = -Temperature::from_reduced(MAX_TSTEP);
} else if tstep > Temperature::from_reduced(MAX_TSTEP) {
tstep = Temperature::from_reduced(MAX_TSTEP);
}
*temperature += tstep;
Self::adjust_states(*temperature, pressure, state1, state2, None)?;
log_iteration(
verbosity,
Some(f),
*temperature,
pressure,
state2.molefracs.as_slice(),
false,
);
Ok(f.abs())
}
fn starting_pressure_ideal_gas(
eos: &E,
temperature: Temperature,
molefracs_spec: &OVector<f64, N>,
bubble: bool,
) -> FeosResult<(Pressure, OVector<f64, N>)> {
if bubble {
Self::starting_pressure_ideal_gas_bubble(eos, temperature, molefracs_spec)
} else {
Self::starting_pressure_ideal_gas_dew(eos, temperature, molefracs_spec)
}
}
pub(super) fn starting_pressure_ideal_gas_bubble(
eos: &E,
temperature: Temperature,
liquid_molefracs: &OVector<f64, N>,
) -> FeosResult<(Pressure, OVector<f64, N>)> {
let density = 0.75 * Density::from_reduced(eos.compute_max_density(liquid_molefracs));
let liquid = State::new_intensive(eos, temperature, density, liquid_molefracs)?;
let v_l = liquid.partial_molar_volume();
let p_l = liquid.pressure(Contributions::Total);
let mu_l = liquid.residual_chemical_potential();
let k_i = liquid_molefracs.component_mul(
&((mu_l - v_l * p_l) / (RGAS * temperature))
.into_value()
.map(f64::exp),
);
let p = k_i.sum() * RGAS * temperature * density;
let y = &k_i / k_i.sum();
Ok((p, y))
}
fn starting_pressure_ideal_gas_dew(
eos: &E,
temperature: Temperature,
vapor_molefracs: &OVector<f64, N>,
) -> FeosResult<(Pressure, OVector<f64, N>)> {
let mut p: Option<Pressure> = None;
let mut x = vapor_molefracs.clone();
for _ in 0..5 {
let density = Density::from_reduced(0.75 * eos.compute_max_density(&x));
let liquid = State::new_intensive(eos, temperature, density, &x)?;
let v_l = liquid.partial_molar_volume();
let p_l = liquid.pressure(Contributions::Total);
let mu_l = liquid.residual_chemical_potential();
let k = vapor_molefracs.clone().component_div(
&((mu_l - v_l * p_l) / (RGAS * temperature))
.into_value()
.map(f64::exp),
);
let k_sum = k.sum();
let p_new = RGAS * temperature * density / k_sum;
x = k / k_sum;
if let Some(p_old) = p
&& ((p_new - p_old) / p_old).into_value().abs() < 1e-5
{
p = Some(p_new);
break;
}
p = Some(p_new);
}
Ok((p.unwrap(), x))
}
pub(super) fn starting_pressure_spinodal(
eos: &E,
temperature: Temperature,
molefracs: &OVector<f64, N>,
) -> FeosResult<Pressure> {
let [sp_v, sp_l] = State::spinodal(eos, temperature, Some(molefracs), Default::default())?;
let pv = sp_v.pressure(Contributions::Total);
let pl = sp_l.pressure(Contributions::Total);
Ok(0.5 * (Pressure::from_reduced(0.0).max(pl) + pv))
}
fn starting_x2_bubble(
eos: &E,
temperature: Temperature,
pressure: Pressure,
liquid_molefracs: &OVector<f64, N>,
vapor_molefracs: Option<&OVector<f64, N>>,
) -> FeosResult<[State<E, N>; 2]> {
let liquid_state =
State::new_xpt(eos, temperature, pressure, liquid_molefracs, Some(Liquid))?;
let xv = match vapor_molefracs {
Some(xv) => xv.clone(),
None => liquid_state
.ln_phi()
.map(f64::exp)
.component_mul(liquid_molefracs),
};
let vapor_state = State::new_xpt(eos, temperature, pressure, &xv, Some(Vapor))?;
Ok([liquid_state, vapor_state])
}
fn starting_x2_dew(
eos: &E,
temperature: Temperature,
pressure: Pressure,
vapor_molefracs: &OVector<f64, N>,
liquid_molefracs: Option<&OVector<f64, N>>,
) -> FeosResult<[State<E, N>; 2]> {
let vapor_state = State::new_npt(
eos,
temperature,
pressure,
&Moles::from_reduced(vapor_molefracs.clone()),
Some(Vapor),
)?;
let xl = match liquid_molefracs {
Some(xl) => xl.clone(),
None => {
let xl = vapor_state
.ln_phi()
.map(f64::exp)
.component_mul(vapor_molefracs);
let liquid_state = State::new_xpt(eos, temperature, pressure, &xl, Some(Liquid))?;
(vapor_state.ln_phi() - liquid_state.ln_phi())
.map(f64::exp)
.component_mul(vapor_molefracs)
}
};
let liquid_state = State::new_xpt(eos, temperature, pressure, &xl, Some(Liquid))?;
Ok([vapor_state, liquid_state])
}
fn adjust_states(
temperature: Temperature,
pressure: Pressure,
state1: &mut State<E, N>,
state2: &mut State<E, N>,
moles_state2: Option<&Moles<OVector<f64, N>>>,
) -> FeosResult<()> {
*state1 = State::new_npt(
&state1.eos,
temperature,
pressure,
&state1.moles,
Some(InitialDensity(state1.density)),
)?;
*state2 = State::new_npt(
&state2.eos,
temperature,
pressure,
moles_state2.unwrap_or(&state2.moles),
Some(InitialDensity(state2.density)),
)?;
Ok(())
}
fn adjust_x2(
state1: &State<E, N>,
state2: &mut State<E, N>,
verbosity: Verbosity,
) -> FeosResult<f64> {
let x1 = &state1.molefracs;
let ln_phi_1 = state1.ln_phi();
let ln_phi_2 = state2.ln_phi();
let k = (ln_phi_1 - ln_phi_2).map(f64::exp);
let kx1 = k.component_mul(x1);
let err_out = kx1
.component_div(&state2.molefracs)
.map(|e| (e - 1.0).abs())
.sum();
let x2 = &kx1 / kx1.sum();
log_iter!(
verbosity,
"{:<14.8e} | {:14} | {:14} | {:16} |",
err_out,
"",
"",
""
);
*state2 = State::new_xpt(
&state2.eos,
state2.temperature,
state2.pressure(Contributions::Total),
&x2,
Some(InitialDensity(state2.density)),
)?;
Ok(err_out)
}
}
fn log_iteration(
verbosity: Verbosity,
error: Option<f64>,
temperature: Temperature,
pressure: Pressure,
x2: &[f64],
newton: bool,
) {
let error = error.map_or_else(|| format!("{:14}", ""), |e| format!("{:<14.8e}", e.abs()));
log_iter!(
verbosity,
"{:14} | {} | {:12.8} | {:12.8} | {:.8?} {}",
"",
error,
temperature,
pressure,
x2,
if newton { "NEWTON" } else { "" }
);
}