Skip to content
This repository was archived by the owner on Dec 12, 2025. It is now read-only.

Commit 68db198

Browse files
committed
Add functions for bubble and dew point pressures
1 parent 7268689 commit 68db198

File tree

2 files changed

+167
-14
lines changed

2 files changed

+167
-14
lines changed

src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ pub use core::{
99
#[cfg(feature = "parameter_fit")]
1010
mod parameter_fit;
1111
#[cfg(feature = "parameter_fit")]
12-
pub use parameter_fit::{equilibrium_liquid_density, liquid_density, vapor_pressure};
12+
pub use parameter_fit::{
13+
bubble_point_pressure, dew_point_pressure, equilibrium_liquid_density, liquid_density,
14+
vapor_pressure,
15+
};

src/parameter_fit/mod.rs

Lines changed: 163 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@ pub fn vapor_pressure<R: ResidualHelmholtzEnergy<1>, const P: usize>(
1919
let v2 = 1.0 / vle.vapor().density.to_reduced();
2020
let t = temperature.into_reduced();
2121
let (a1, a2) = {
22-
let t = DualVec::from(t);
23-
let v1 = DualVec::from(v1);
24-
let v2 = DualVec::from(v2);
25-
let x = SVector::from([DualVec::from(1.0)]);
22+
let t = Gradient::from(t);
23+
let v1 = Gradient::from(v1);
24+
let v2 = Gradient::from(v2);
25+
let x = SVector::from([Gradient::from(1.0)]);
2626

2727
let a1 = R::residual_molar_helmholtz_energy(&eos.parameters, t, v1, &x);
2828
let a2 = R::residual_molar_helmholtz_energy(&eos.parameters, t, v2, &x);
@@ -43,10 +43,10 @@ pub fn equilibrium_liquid_density<R: ResidualHelmholtzEnergy<1>, const P: usize>
4343
let v_v = 1.0 / vle.vapor().density.to_reduced();
4444
let t = temperature.into_reduced();
4545
let (f_l, p_l, dp_l, a_v) = {
46-
let t = DualVec::from(temperature.into_reduced());
47-
let v_l = DualVec::from(v_l);
48-
let v_v = DualVec::from(v_v);
49-
let x = SVector::from([DualVec::from(1.0)]);
46+
let t = Gradient::from(temperature.into_reduced());
47+
let v_l = Gradient::from(v_l);
48+
let v_v = Gradient::from(v_v);
49+
let x = SVector::from([Gradient::from(1.0)]);
5050

5151
let (f_l, p_l, dp_l) = R::dp_drho(&eos.parameters, t, v_l, &x);
5252
let a_v = R::residual_molar_helmholtz_energy(&eos.parameters, t, v_v, &x);
@@ -70,9 +70,9 @@ pub fn liquid_density<R: ResidualHelmholtzEnergy<1>, const P: usize>(
7070
let v = 1.0 / state.density.to_reduced();
7171
let p0 = pressure.into_reduced();
7272
let (p, dp) = {
73-
let t = DualVec::from(t);
74-
let v = DualVec::from(v);
75-
let x = SVector::from([DualVec::from(1.0)]);
73+
let t = Gradient::from(t);
74+
let v = Gradient::from(v);
75+
let x = SVector::from([Gradient::from(1.0)]);
7676
let (_, p, dp) = R::dp_drho(&eos.parameters, t, v, &x);
7777

7878
(p, dp)
@@ -82,11 +82,101 @@ pub fn liquid_density<R: ResidualHelmholtzEnergy<1>, const P: usize>(
8282
Ok(Density::from_reduced(rho))
8383
}
8484

85+
pub fn bubble_point_pressure<R: ResidualHelmholtzEnergy<2>, const P: usize>(
86+
eos: &HelmholtzEnergyWrapper<R, Gradient<P>, 2>,
87+
temperature: Temperature,
88+
pressure: Option<Pressure>,
89+
liquid_molefracs: SVector<f64, 2>,
90+
) -> EosResult<Pressure<Gradient<P>>> {
91+
let x = arr1(liquid_molefracs.as_slice());
92+
let vle = PhaseEquilibrium::bubble_point(
93+
&eos.eos,
94+
temperature,
95+
&x,
96+
pressure,
97+
None,
98+
Default::default(),
99+
)?;
100+
101+
let v_l = 1.0 / vle.liquid().density.to_reduced();
102+
let v_v = 1.0 / vle.vapor().density.to_reduced();
103+
let y = &vle.vapor().molefracs;
104+
let y: SVector<_, 2> = SVector::from_fn(|i, _| y[i]);
105+
let t = temperature.into_reduced();
106+
let (a_l, a_v, v_l, v_v) = {
107+
let t = Gradient::from(t);
108+
let v_l = Gradient::from(v_l);
109+
let v_v = Gradient::from(v_v);
110+
let y = y.map(Gradient::from);
111+
let x = liquid_molefracs.map(Gradient::from);
112+
113+
let a_v = R::residual_molar_helmholtz_energy(&eos.parameters, t, v_v, &y);
114+
let (p_l, mu_res_l, dp_l, dmu_l) = R::dmu_dv(&eos.parameters, t, v_l, &x);
115+
let vi_l = dmu_l / dp_l;
116+
let v_l = vi_l.dot(&y);
117+
let a_l = (mu_res_l - vi_l * p_l).dot(&y);
118+
(a_l, a_v, v_l, v_v)
119+
};
120+
let rho_l = vle.liquid().partial_density.to_reduced();
121+
let rho_l = [rho_l[0], rho_l[1]];
122+
let rho_v = vle.vapor().partial_density.to_reduced();
123+
let rho_v = [rho_v[0], rho_v[1]];
124+
let p = -(a_v - a_l
125+
+ t * (y[0] * (rho_v[0] / rho_l[0]).ln() + y[1] * (rho_v[1] / rho_l[1]).ln() - 1.0))
126+
/ (v_v - v_l);
127+
Ok(Pressure::from_reduced(p))
128+
}
129+
130+
pub fn dew_point_pressure<R: ResidualHelmholtzEnergy<2>, const P: usize>(
131+
eos: &HelmholtzEnergyWrapper<R, Gradient<P>, 2>,
132+
temperature: Temperature,
133+
pressure: Option<Pressure>,
134+
vapor_molefracs: SVector<f64, 2>,
135+
) -> EosResult<Pressure<Gradient<P>>> {
136+
let y = arr1(vapor_molefracs.as_slice());
137+
let vle = PhaseEquilibrium::dew_point(
138+
&eos.eos,
139+
temperature,
140+
&y,
141+
pressure,
142+
None,
143+
Default::default(),
144+
)?;
145+
146+
let v_l = 1.0 / vle.liquid().density.to_reduced();
147+
let v_v = 1.0 / vle.vapor().density.to_reduced();
148+
let x = &vle.liquid().molefracs;
149+
let x: SVector<_, 2> = SVector::from_fn(|i, _| x[i]);
150+
let t = temperature.into_reduced();
151+
let (a_l, a_v, v_l, v_v) = {
152+
let t = Gradient::from(t);
153+
let v_l = Gradient::from(v_l);
154+
let v_v = Gradient::from(v_v);
155+
let x = x.map(Gradient::from);
156+
let y = vapor_molefracs.map(Gradient::from);
157+
158+
let a_l = R::residual_molar_helmholtz_energy(&eos.parameters, t, v_l, &x);
159+
let (p_v, mu_res_v, dp_v, dmu_v) = R::dmu_dv(&eos.parameters, t, v_v, &y);
160+
let vi_v = dmu_v / dp_v;
161+
let v_v = vi_v.dot(&x);
162+
let a_v = (mu_res_v - vi_v * p_v).dot(&x);
163+
(a_l, a_v, v_l, v_v)
164+
};
165+
let rho_l = vle.liquid().partial_density.to_reduced();
166+
let rho_l = [rho_l[0], rho_l[1]];
167+
let rho_v = vle.vapor().partial_density.to_reduced();
168+
let rho_v = [rho_v[0], rho_v[1]];
169+
let p = -(a_l - a_v
170+
+ t * (x[0] * (rho_l[0] / rho_v[0]).ln() + x[1] * (rho_l[1] / rho_v[1]).ln() - 1.0))
171+
/ (v_l - v_v);
172+
Ok(Pressure::from_reduced(p))
173+
}
174+
85175
#[cfg(test)]
86176
mod test {
87177
use super::*;
88-
use crate::eos::pcsaft::test::{pcsaft, pcsaft_non_assoc};
89-
use crate::eos::PcSaftPure;
178+
use crate::eos::pcsaft::test::{pcsaft, pcsaft_binary, pcsaft_non_assoc};
179+
use crate::eos::{PcSaftBinary, PcSaftPure};
90180
use crate::{ParametersAD, PhaseEquilibriumAD, StateAD};
91181
use approx::assert_relative_eq;
92182
use nalgebra::U1;
@@ -246,4 +336,64 @@ mod test {
246336
}
247337
Ok(())
248338
}
339+
340+
#[test]
341+
fn test_bubble_point_pressure() -> EosResult<()> {
342+
let (pcsaft, _) = pcsaft_binary()?;
343+
let pcsaft = pcsaft.wrap();
344+
let pcsaft_ad = pcsaft.named_derivatives(["k_ij"]);
345+
let temperature = 500.0 * KELVIN;
346+
let x = SVector::from([0.5, 0.5]);
347+
let p = bubble_point_pressure(&pcsaft_ad, temperature, None, x)?;
348+
let p = p.convert_into(BAR);
349+
let (p, [[grad]]) = (p.re, p.eps.unwrap_generic(U1, U1).data.0);
350+
351+
println!("{:.5}", p);
352+
println!("{:.5?}", grad);
353+
354+
let (params, mut kij) = pcsaft.parameters;
355+
let h = 1e-7;
356+
kij += h;
357+
let pcsaft_h = PcSaftBinary::new(params, kij).wrap();
358+
let (_, p_h) = PhaseEquilibriumAD::bubble_point(&pcsaft_h, temperature, x)?;
359+
let dp_h = (p_h.convert_into(BAR) - p) / h;
360+
println!(
361+
"k_ij: {:11.5} {:11.5} {:.3e}",
362+
dp_h,
363+
grad,
364+
((dp_h - grad) / grad).abs()
365+
);
366+
assert_relative_eq!(grad, dp_h, max_relative = 1e-6);
367+
Ok(())
368+
}
369+
370+
#[test]
371+
fn test_dew_point_pressure() -> EosResult<()> {
372+
let (pcsaft, _) = pcsaft_binary()?;
373+
let pcsaft = pcsaft.wrap();
374+
let pcsaft_ad = pcsaft.named_derivatives(["k_ij"]);
375+
let temperature = 500.0 * KELVIN;
376+
let y = SVector::from([0.5, 0.5]);
377+
let p = dew_point_pressure(&pcsaft_ad, temperature, None, y)?;
378+
let p = p.convert_into(BAR);
379+
let (p, [[grad]]) = (p.re, p.eps.unwrap_generic(U1, U1).data.0);
380+
381+
println!("{:.5}", p);
382+
println!("{:.5?}", grad);
383+
384+
let (params, mut kij) = pcsaft.parameters;
385+
let h = 1e-7;
386+
kij += h;
387+
let pcsaft_h = PcSaftBinary::new(params, kij).wrap();
388+
let (_, p_h) = PhaseEquilibriumAD::dew_point(&pcsaft_h, temperature, y)?;
389+
let dp_h = (p_h.convert_into(BAR) - p) / h;
390+
println!(
391+
"k_ij: {:11.5} {:11.5} {:.3e}",
392+
dp_h,
393+
grad,
394+
((dp_h - grad) / grad).abs()
395+
);
396+
assert_relative_eq!(grad, dp_h, max_relative = 1e-6);
397+
Ok(())
398+
}
249399
}

0 commit comments

Comments
 (0)