Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
70c3e74
Merge feos-ad functionalities into feos
prehner Aug 18, 2025
81b045e
rebase
prehner Aug 18, 2025
9ab473a
use first_derivative from quantity for virial coefficients
prehner Aug 18, 2025
3be6e39
Minor optimizations for the ideal gas models
prehner Aug 18, 2025
afaced3
Fix robustness issue in vle_pure_t and revive Estimator interface wit…
prehner Aug 18, 2025
58a038f
fix import
prehner Aug 18, 2025
f0fb586
fix Cargo.toml
prehner Aug 19, 2025
8e9757a
moved GcPcSaftAD and therefore concluded the feos-ad integration
prehner Aug 19, 2025
25c789e
update workflow
prehner Aug 19, 2025
81fc7f8
fix python for gc_pcsaft
prehner Aug 19, 2025
6640601
fixed for gc-pcsaft
prehner Aug 25, 2025
982f421
newest lints
prehner Aug 25, 2025
ba7d6ec
use derivatives with units for state properties
prehner Aug 25, 2025
6112b2a
remove nshare dependency
prehner Aug 25, 2025
3dd4d15
fix dft for PC-SAFT
prehner Aug 29, 2025
71eb3ac
Updated SAFT-VR Mie model
g-bauer Aug 27, 2025
15ff86e
Updated PeTS
g-bauer Aug 27, 2025
23b9348
fix test
prehner Aug 29, 2025
2826fa4
finalized adaption of PC-SAFT family
prehner Aug 30, 2025
24aeafe
update to pyo3 0.26
prehner Aug 30, 2025
9cfafbc
python bindings for the libraries that are already updated
prehner Aug 30, 2025
f80b34e
make ndarray optional (a bit of a mess in the polar terms though :/)
prehner Sep 3, 2025
38147a9
fix dft
prehner Sep 3, 2025
79c76d9
fix sign in hc term
prehner Sep 3, 2025
5d9dc3d
implement AD for all state constructors
prehner Sep 3, 2025
5664a09
updated saftvrqmie and UV
prehner Sep 4, 2025
c2fca34
bikeshedding for the IdealGas traits
prehner Sep 5, 2025
424b778
more ideal gas bikeshedding
prehner Sep 5, 2025
687212a
final(?) ideal gas bikeshed
prehner Sep 5, 2025
4738d5c
fixed stability_analysis
prehner Sep 9, 2025
b6dcf3f
fix markdown and String representations of states
prehner Sep 9, 2025
043875b
fix some regression in the parameter handling in python
prehner Sep 9, 2025
e182cc9
add getters for Identifier
prehner Sep 9, 2025
3d631bc
remove some string allocations
prehner Sep 9, 2025
987bd7f
rebase
prehner Sep 12, 2025
01e98ab
pretty significant bug, no idea why it did not show earlier
prehner Sep 12, 2025
0277e02
fixes for num_dual change
prehner Sep 17, 2025
e575ae6
Fix bubble Newton step for bubble points at given pressure
prehner Sep 17, 2025
b03222a
Add tests for newton methods within bubble and dew point iterations
prehner Sep 17, 2025
f6be6aa
reactivate (and fix) PC-SAFT tests
prehner Sep 17, 2025
d352ed7
update num-dual and quantity dependencies
prehner Sep 28, 2025
bb8f5e5
get rid of most Arcs
prehner Sep 28, 2025
935b651
remove the ResidualConst trait
prehner Sep 29, 2025
6472647
replace PureProperty and BinaryProperty with new Estimator struct
prehner Sep 29, 2025
d51c8d9
rebase
prehner Oct 10, 2025
898c917
adding final docstrings to feos-core
prehner Oct 10, 2025
ba952f5
removed stuff that accidentally ended up here
prehner Oct 10, 2025
3ed1307
make python package usable without the "ad" feature
prehner Oct 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
finalized adaption of PC-SAFT family
  • Loading branch information
prehner committed Sep 12, 2025
commit 2826fa434f3d90c1985aef879770980a966cab07
2 changes: 1 addition & 1 deletion crates/feos-core/src/state/residual_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,7 @@ where
/// Pressure $p$ evaluated for each contribution of the equation of state.
pub fn pressure_contributions(&self) -> Vec<(String, Pressure<D>)> {
let t = Dual::from_re(self.temperature.into_reduced());
let v = Dual::from_re(self.temperature.into_reduced()).derivative();
let v = Dual::from_re(self.density.into_reduced().recip()).derivative();
let x = self.molefracs.map(Dual::from_re);
let contributions = self
.eos
Expand Down
18 changes: 10 additions & 8 deletions crates/feos-dft/src/functional.rs
Original file line number Diff line number Diff line change
Expand Up @@ -105,21 +105,23 @@ pub trait HelmholtzEnergyFunctional: Residual {
.collect()
}

fn m(&self) -> Cow<'_, DVector<f64>> {
fn m(&self) -> Cow<'_, [f64]> {
match self.molecule_shape() {
MoleculeShape::Spherical(n) => Cow::Owned(DVector::from_element(n, 1.0)),
MoleculeShape::NonSpherical(m) => Cow::Borrowed(m),
MoleculeShape::Spherical(n) => Cow::Owned(vec![1.0; n]),
MoleculeShape::NonSpherical(m) => Cow::Borrowed(m.as_slice()),
MoleculeShape::Heterosegmented(component_index) => {
Cow::Owned(DVector::from_element(component_index.len(), 1.0))
Cow::Owned(vec![1.0; component_index.len()])
}
}
}

fn component_index(&self) -> Cow<'_, DVector<usize>> {
fn component_index(&self) -> Cow<'_, [usize]> {
match self.molecule_shape() {
MoleculeShape::Spherical(n) => Cow::Owned(DVector::from_fn(n, |i, _| i)),
MoleculeShape::NonSpherical(m) => Cow::Owned(DVector::from_fn(m.len(), |i, _| i)),
MoleculeShape::Heterosegmented(component_index) => Cow::Borrowed(component_index),
MoleculeShape::Spherical(n) => Cow::Owned((0..n).collect()),
MoleculeShape::NonSpherical(m) => Cow::Owned((0..m.len()).collect()),
MoleculeShape::Heterosegmented(component_index) => {
Cow::Borrowed(component_index.as_slice())
}
}
}

Expand Down
6 changes: 3 additions & 3 deletions crates/feos-dft/src/ideal_chain_contribution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ pub struct IdealChainContribution {
}

impl IdealChainContribution {
pub fn new(component_index: &DVector<usize>, m: &DVector<f64>) -> Self {
pub fn new(component_index: &[usize], m: &[f64]) -> Self {
Self {
component_index: component_index.clone(),
m: m.clone(),
component_index: DVector::from_column_slice(component_index),
m: DVector::from_column_slice(m),
}
}

Expand Down
4 changes: 2 additions & 2 deletions crates/feos-dft/src/weight_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub enum WeightFunctionShape {
/// Information about weight functions
pub struct WeightFunctionInfo<T> {
/// Index of the component that each individual segment belongs to.
pub(crate) component_index: Vec<usize>,
pub(crate) component_index: DVector<usize>,
/// Flag if local density is required in the functional
pub(crate) local_density: bool,
/// Container for scalar component-wise weighted densities
Expand Down Expand Up @@ -197,7 +197,7 @@ impl<T> WeightFunctionInfo<T> {

impl<T> WeightFunctionInfo<T> {
/// Initializing empty `WeightFunctionInfo`.
pub fn new(component_index: Vec<usize>, local_density: bool) -> Self {
pub fn new(component_index: DVector<usize>, local_density: bool) -> Self {
Self {
component_index,
local_density,
Expand Down
2 changes: 1 addition & 1 deletion crates/feos/src/association/dft.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ where
let p = self.model;
let r = p.hs_diameter(temperature) * N::from(0.5);
let [_, _, _, c3] = p.geometry_coefficients(temperature);
WeightFunctionInfo::new(p.component_index().into_owned(), false)
WeightFunctionInfo::new(p.component_index().into_owned().into(), false)
.add(
WeightFunction::new_scaled(r.clone(), WeightFunctionShape::Delta),
false,
Expand Down
24 changes: 12 additions & 12 deletions crates/feos/src/epcsaft/eos/born.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ use super::ElectrolytePcSaftVariants;
use crate::epcsaft::eos::permittivity::Permittivity;
use crate::epcsaft::parameters::ElectrolytePcSaftPars;
use feos_core::StateHD;
use ndarray::Array1;
use nalgebra::DVector;
use num_dual::DualNum;

pub struct Born;

impl Born {
pub fn helmholtz_energy<D: DualNum<f64> + Copy>(
pub fn helmholtz_energy_density<D: DualNum<f64> + Copy>(
&self,
parameters: &ElectrolytePcSaftPars,
state: &StateHD<D>,
diameter: &Array1<D>,
diameter: &DVector<D>,
) -> D {
// Parameters
let p = parameters;
Expand All @@ -25,14 +25,14 @@ impl Born {
.unwrap()
.permittivity;

// Calculate sum xi zi^2 / di
let mut sum_xi_zi_ai = D::zero();
// Calculate sum rhoi zi^2 / di
let mut sum_rhoi_zi_ai = D::zero();
for i in 0..state.molefracs.len() {
sum_xi_zi_ai += state.molefracs[i] * p.z[i].powi(2) / diameter[i];
sum_rhoi_zi_ai += state.partial_density[i] * p.z[i].powi(2) / diameter[i];
}

// Calculate born contribution
-lambda_b * (epsilon_r - 1.) * sum_xi_zi_ai * state.moles.sum()
-lambda_b * (epsilon_r - 1.) * sum_rhoi_zi_ai
}
}

Expand All @@ -42,17 +42,17 @@ mod tests {
use crate::epcsaft::parameters::utils::{water_nacl_parameters, water_nacl_parameters_perturb};
use crate::hard_sphere::HardSphereProperties;
use approx::assert_relative_eq;
use ndarray::arr1;
use nalgebra::dvector;

#[test]
fn helmholtz_energy_perturb() {
let p = ElectrolytePcSaftPars::new(&water_nacl_parameters_perturb()).unwrap();

let t = 298.0;
let v = 31.875;
let s = StateHD::new(t, v, arr1(&[0.9, 0.05, 0.05]));
let s = StateHD::new(t, v, &dvector![0.9, 0.05, 0.05]);
let d = p.hs_diameter(t);
let a_rust = Born.helmholtz_energy(&p, &s, &d);
let a_rust = Born.helmholtz_energy_density(&p, &s, &d) * v;

assert_relative_eq!(a_rust, -22.51064553710294, epsilon = 1e-10);
}
Expand All @@ -63,9 +63,9 @@ mod tests {

let t = 298.0;
let v = 31.875;
let s = StateHD::new(t, v, arr1(&[0.9, 0.05, 0.05]));
let s = StateHD::new(t, v, &dvector![0.9, 0.05, 0.05]);
let d = p.hs_diameter(t);
let a_rust = Born.helmholtz_energy(&p, &s, &d);
let a_rust = Born.helmholtz_energy_density(&p, &s, &d) * v;

assert_relative_eq!(a_rust, -22.525624511559244, epsilon = 1e-10);
}
Expand Down
55 changes: 26 additions & 29 deletions crates/feos/src/epcsaft/eos/dispersion.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::epcsaft::parameters::ElectrolytePcSaftPars;
use feos_core::StateHD;
use ndarray::{Array, Array1, Array2};
use nalgebra::{DMatrix, DVector};
use num_dual::DualNum;
use std::f64::consts::{FRAC_PI_3, PI};
use std::f64::consts::{FRAC_PI_6, PI};

pub const A0: [f64; 7] = [
0.91056314451539,
Expand Down Expand Up @@ -62,34 +62,34 @@ pub const B2: [f64; 7] = [
pub const T_REF: f64 = 298.15;

impl ElectrolytePcSaftPars {
pub fn k_ij_t<D: DualNum<f64>>(&self, temperature: D) -> Array2<f64> {
pub fn k_ij_t<D: DualNum<f64>>(&self, temperature: D) -> DMatrix<f64> {
let k_ij = &self.k_ij;
let n = self.m.len();

let mut k_ij_t = Array::zeros((n, n));
let mut k_ij_t = DMatrix::zeros(n, n);

for i in 0..n {
for j in 0..n {
// Calculate k_ij(T)
k_ij_t[[i, j]] = (temperature.re() - T_REF) * k_ij[[i, j]][1]
+ (temperature.re() - T_REF).powi(2) * k_ij[[i, j]][2]
+ (temperature.re() - T_REF).powi(3) * k_ij[[i, j]][3]
+ k_ij[[i, j]][0];
k_ij_t[(i, j)] = (temperature.re() - T_REF) * k_ij[(i, j)][1]
+ (temperature.re() - T_REF).powi(2) * k_ij[(i, j)][2]
+ (temperature.re() - T_REF).powi(3) * k_ij[(i, j)][3]
+ k_ij[(i, j)][0];
}
}
//println!("k_ij_t: {}", k_ij_t);
k_ij_t
}

pub fn epsilon_k_ij_t<D: DualNum<f64>>(&self, temperature: D) -> Array2<f64> {
pub fn epsilon_k_ij_t<D: DualNum<f64>>(&self, temperature: D) -> DMatrix<f64> {
let k_ij_t = self.k_ij_t(temperature);
let n = self.m.len();

let mut epsilon_k_ij_t = Array::zeros((n, n));
let mut epsilon_k_ij_t = DMatrix::zeros(n, n);

for i in 0..n {
for j in 0..n {
epsilon_k_ij_t[[i, j]] = (1.0 - k_ij_t[[i, j]]) * self.e_k_ij[[i, j]];
epsilon_k_ij_t[(i, j)] = (1.0 - k_ij_t[(i, j)]) * self.e_k_ij[(i, j)];
}
}
epsilon_k_ij_t
Expand All @@ -99,31 +99,28 @@ impl ElectrolytePcSaftPars {
pub struct Dispersion;

impl Dispersion {
pub fn helmholtz_energy<D: DualNum<f64> + Copy>(
pub fn helmholtz_energy_density<D: DualNum<f64> + Copy>(
&self,
parameters: &ElectrolytePcSaftPars,
state: &StateHD<D>,
diameter: &Array1<D>,
diameter: &DVector<D>,
) -> D {
// auxiliary variables
let n = parameters.m.len();
let p = parameters;
let rho = &state.partial_density;

// temperature dependent segment radius
let r = diameter * 0.5;

// convert sigma_ij
let sigma_ij_t = p.sigma_ij_t(state.temperature);

// convert epsilon_k_ij
let epsilon_k_ij_t = p.epsilon_k_ij_t(state.temperature);

// packing fraction
let eta = (rho * &p.m * &r * &r * &r).sum() * 4.0 * FRAC_PI_3;
let eta = rho.dot(&diameter.zip_map(&p.m, |d, m| d.powi(3) * m)) * FRAC_PI_6;

// mean segment number
let m = (&state.molefracs * &p.m).sum();
let m = state.molefracs.dot(&p.m.map(D::from));

// mixture densities, crosswise interactions of all segments on all chains
let mut rho1mix = D::zero();
Expand Down Expand Up @@ -154,7 +151,7 @@ impl Dispersion {
.recip();

// Helmholtz energy
(-rho1mix * i1 * 2.0 - rho2mix * m * c1 * i2) * PI * state.volume
(-rho1mix * i1 * 2.0 - rho2mix * m * c1 * i2) * PI
}
}

Expand All @@ -166,17 +163,17 @@ mod tests {
};
use crate::hard_sphere::HardSphereProperties;
use approx::assert_relative_eq;
use ndarray::arr1;
use nalgebra::dvector;

#[test]
fn helmholtz_energy() {
let p = ElectrolytePcSaftPars::new(&propane_parameters()).unwrap();
let t = 250.0;
let v = 1000.0;
let n = 1.0;
let s = StateHD::new(t, v, arr1(&[n]));
let s = StateHD::new(t, v, &dvector![n]);
let d = p.hs_diameter(t);
let a_rust = Dispersion.helmholtz_energy(&p, &s, &d);
let a_rust = Dispersion.helmholtz_energy_density(&p, &s, &d) * v;
assert_relative_eq!(a_rust, -1.0622531100351962, epsilon = 1e-10);
}

Expand All @@ -188,13 +185,13 @@ mod tests {
let t = 250.0;
let v = 2.5e28;
let n = 1.0;
let s = StateHD::new(t, v, arr1(&[n]));
let a1 = Dispersion.helmholtz_energy(&p1, &s, &p1.hs_diameter(t));
let a2 = Dispersion.helmholtz_energy(&p2, &s, &p2.hs_diameter(t));
let s1m = StateHD::new(t, v, arr1(&[n, 0.0]));
let a1m = Dispersion.helmholtz_energy(&p12, &s1m, &p12.hs_diameter(t));
let s2m = StateHD::new(t, v, arr1(&[0.0, n]));
let a2m = Dispersion.helmholtz_energy(&p12, &s2m, &p12.hs_diameter(t));
let s = StateHD::new(t, v, &dvector![n]);
let a1 = Dispersion.helmholtz_energy_density(&p1, &s, &p1.hs_diameter(t));
let a2 = Dispersion.helmholtz_energy_density(&p2, &s, &p2.hs_diameter(t));
let s1m = StateHD::new(t, v, &dvector![n, 0.0]);
let a1m = Dispersion.helmholtz_energy_density(&p12, &s1m, &p12.hs_diameter(t));
let s2m = StateHD::new(t, v, &dvector![0.0, n]);
let a2m = Dispersion.helmholtz_energy_density(&p12, &s2m, &p12.hs_diameter(t));
assert_relative_eq!(a1, a1m, epsilon = 1e-14);
assert_relative_eq!(a2, a2m, epsilon = 1e-14);
}
Expand Down
30 changes: 14 additions & 16 deletions crates/feos/src/epcsaft/eos/hard_chain.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use crate::epcsaft::parameters::ElectrolytePcSaftPars;
use crate::hard_sphere::HardSphereProperties;
use feos_core::StateHD;
use ndarray::Array;
use nalgebra::DVector;
use num_dual::*;

pub struct HardChain;

impl HardChain {
#[inline]
pub fn helmholtz_energy<D: DualNum<f64> + Copy>(
pub fn helmholtz_energy_density<D: DualNum<f64> + Copy>(
&self,
parameters: &ElectrolytePcSaftPars,
state: &StateHD<D>,
Expand All @@ -18,13 +18,11 @@ impl HardChain {
let [zeta2, zeta3] = p.zeta(state.temperature, &state.partial_density, [2, 3]);
let frac_1mz3 = -(zeta3 - 1.0).recip();
let c = zeta2 * frac_1mz3 * frac_1mz3;
let g_hs =
d.mapv(|d| frac_1mz3 + d * c * 1.5 - d.powi(2) * c.powi(2) * (zeta3 - 1.0) * 0.5);
Array::from_shape_fn(p.m.len(), |i| {
let g_hs = d.map(|d| frac_1mz3 + d * c * 1.5 - d.powi(2) * c.powi(2) * (zeta3 - 1.0) * 0.5);
DVector::from_fn(p.m.len(), |i, _| {
state.partial_density[i] * (1.0 - p.m[i]) * g_hs[i].ln()
})
.sum()
* state.volume
}
}

Expand All @@ -35,16 +33,16 @@ mod tests {
butane_parameters, propane_butane_parameters, propane_parameters,
};
use approx::assert_relative_eq;
use ndarray::arr1;
use nalgebra::dvector;

#[test]
fn helmholtz_energy() {
let p = ElectrolytePcSaftPars::new(&propane_parameters()).unwrap();
let t = 250.0;
let v = 1000.0;
let n = 1.0;
let s = StateHD::new(t, v, arr1(&[n]));
let a_rust = HardChain.helmholtz_energy(&p, &s);
let s = StateHD::new(t, v, &dvector![n]);
let a_rust = HardChain.helmholtz_energy_density(&p, &s) * v;
assert_relative_eq!(a_rust, -0.12402626171926148, epsilon = 1e-10);
}

Expand All @@ -56,13 +54,13 @@ mod tests {
let t = 250.0;
let v = 2.5e28;
let n = 1.0;
let s = StateHD::new(t, v, arr1(&[n]));
let a1 = HardChain.helmholtz_energy(&p1, &s);
let a2 = HardChain.helmholtz_energy(&p2, &s);
let s1m = StateHD::new(t, v, arr1(&[n, 0.0]));
let a1m = HardChain.helmholtz_energy(&p12, &s1m);
let s2m = StateHD::new(t, v, arr1(&[0.0, n]));
let a2m = HardChain.helmholtz_energy(&p12, &s2m);
let s = StateHD::new(t, v, &dvector![n]);
let a1 = HardChain.helmholtz_energy_density(&p1, &s);
let a2 = HardChain.helmholtz_energy_density(&p2, &s);
let s1m = StateHD::new(t, v, &dvector![n, 0.0]);
let a1m = HardChain.helmholtz_energy_density(&p12, &s1m);
let s2m = StateHD::new(t, v, &dvector![0.0, n]);
let a2m = HardChain.helmholtz_energy_density(&p12, &s2m);
assert_relative_eq!(a1, a1m, epsilon = 1e-14);
assert_relative_eq!(a2, a2m, epsilon = 1e-14);
}
Expand Down
Loading