use super::FlashError;
use super::init::wilson_k_values;
use super::system::{SystemSpec, k_values};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum Point {
Bubble,
Dew,
}
pub(crate) fn incipient_sum(
spec: &SystemSpec,
t: f64,
p: f64,
known: &[f64],
point: Point,
k: &mut Vec<f64>,
inner_max: usize,
) -> Result<(f64, Vec<f64>), FlashError> {
let n = spec.n();
let mut incipient = vec![0.0; n];
let mut s = 0.0;
for _ in 0..inner_max.max(1) {
s = 0.0;
for i in 0..n {
let un = match point {
Point::Bubble => k[i] * known[i], Point::Dew => known[i] / k[i], };
incipient[i] = un;
s += un;
}
for v in incipient.iter_mut() {
*v /= s;
}
let (x, y) = match point {
Point::Bubble => (known, incipient.as_slice()),
Point::Dew => (incipient.as_slice(), known),
};
let k_new = k_values(spec, t, p, x, y)?;
let mut max_step = 0.0_f64;
for i in 0..n {
max_step = max_step.max((k_new[i] / k[i]).ln().abs());
}
*k = k_new;
if max_step < 1e-10 {
break;
}
}
Ok((s, incipient))
}
pub(crate) fn wilson_pressure_guess(spec: &SystemSpec, t: f64, known: &[f64], point: Point) -> f64 {
let mut acc = 0.0;
for (i, c) in spec.components.iter().enumerate() {
let e = (5.373 * (1.0 + c.omega) * (1.0 - c.tc / t)).exp();
match point {
Point::Bubble => acc += known[i] * c.pc * e,
Point::Dew => acc += known[i] / (c.pc * e),
}
}
match point {
Point::Bubble => acc,
Point::Dew => 1.0 / acc,
}
}
pub(crate) fn wilson_k_init(spec: &SystemSpec, t: f64, p: f64) -> Vec<f64> {
wilson_k_values(spec.components, t, p)
}
pub(crate) struct SatPoint {
pub var: f64,
pub incipient: Vec<f64>,
pub k: Vec<f64>,
}
pub(crate) fn solve_pressure(
spec: &SystemSpec,
t: f64,
known: &[f64],
point: Point,
tol: f64,
max_iter: usize,
) -> Result<SatPoint, FlashError> {
let mut p = wilson_pressure_guess(spec, t, known, point).max(1e-6);
let mut k = wilson_k_init(spec, t, p);
for iter in 0..max_iter {
let (s, incipient) = incipient_sum(spec, t, p, known, point, &mut k, 40)?;
if (s - 1.0).abs() < tol {
return Ok(SatPoint {
var: p,
incipient,
k,
});
}
p = match point {
Point::Bubble => p * s,
Point::Dew => p / s,
}
.max(1e-9);
if iter + 1 == max_iter {
return Err(FlashError::NoConvergence {
what: "saturation pressure",
iters: max_iter,
residual: (s - 1.0).abs(),
});
}
}
unreachable!("loop returns via convergence or NoConvergence")
}
pub(crate) fn solve_temperature(
spec: &SystemSpec,
p: f64,
known: &[f64],
point: Point,
tol: f64,
max_iter: usize,
) -> Result<SatPoint, FlashError> {
let h = |t: f64| -> Option<f64> {
solve_pressure(spec, t, known, point, tol, max_iter)
.ok()
.map(|sp| sp.var)
.filter(|pt| pt.is_finite() && *pt > 0.0)
.map(|pt| (pt / p).ln())
};
let (t_start, t_end, steps) = (50.0, 1000.0, 96);
let mut prev: Option<(f64, f64)> = None;
let mut bracket: Option<(f64, f64, f64)> = None; for i in 0..=steps {
let t = t_start + (t_end - t_start) * i as f64 / steps as f64;
let ht = match h(t) {
Some(v) => v,
None => {
prev = None;
continue;
}
};
if let Some((tp, hp)) = prev {
if hp * ht <= 0.0 {
bracket = Some((tp, t, hp));
break;
}
}
prev = Some((t, ht));
}
let (mut lo, mut hi, mut h_lo) = bracket.ok_or(FlashError::NoConvergence {
what: "saturation temperature bracket",
iters: steps,
residual: f64::NAN,
})?;
let mut t_star = 0.5 * (lo + hi);
for iter in 0..max_iter {
t_star = 0.5 * (lo + hi);
let ht = h(t_star).ok_or(FlashError::Thermo("P_sat(mid) failed".into()))?;
if ht.abs() < tol || (hi - lo) < 1e-9 {
break;
}
if ht * h_lo > 0.0 {
lo = t_star;
h_lo = ht;
} else {
hi = t_star;
}
if iter + 1 == max_iter {
return Err(FlashError::NoConvergence {
what: "saturation temperature",
iters: max_iter,
residual: ht.abs(),
});
}
}
let mut k = wilson_k_init(spec, t_star, p);
let (_, incipient) = incipient_sum(spec, t_star, p, known, point, &mut k, 60)?;
Ok(SatPoint {
var: t_star,
incipient,
k,
})
}