Skip to content

Commit bf6b7e6

Browse files
authored
New workspace crate for reusable implementations of SAFT (feos-org#27)
* New workspace crate for reusable implementations of SAFT * Use a single generic implementation for association across models * cleanup * remove obsolete files * add missing files, update CI, and add tests from previously deleted files * remove default features * fix pets and python bindings * small but very important fix * update changelog and fix bug in wheels
1 parent ad5981c commit bf6b7e6

54 files changed

Lines changed: 1501 additions & 1800 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/release_saft.yml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
name: Release feos-saft
2+
3+
on:
4+
push:
5+
tags: ["feos-saft-v*"]
6+
7+
jobs:
8+
release-crates-io:
9+
name: Release crates.io
10+
runs-on: ubuntu-latest
11+
defaults:
12+
run:
13+
working-directory: ./feos-saft
14+
steps:
15+
- uses: actions/checkout@v2
16+
- uses: actions-rs/toolchain@v1
17+
with:
18+
profile: minimal
19+
toolchain: stable
20+
override: true
21+
- uses: katyo/publish-crates@v1
22+
with:
23+
registry-token: ${{ secrets.CRATES_IO_TOKEN }}
24+
path: './feos-saft'

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
fail-fast: false
1717
matrix:
18-
crate: [feos-core, feos-dft]
18+
crate: [feos-core, feos-dft, feos-saft]
1919
steps:
2020
- uses: actions/checkout@v3
2121
- name: Build

Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ categories = ["science"]
1616
features = ["all_models"]
1717

1818
[workspace]
19-
members = ["feos-core", "feos-dft"]
19+
members = ["feos-core", "feos-dft", "feos-saft"]
2020

2121
[lib]
2222
crate-type = ["rlib", "cdylib"]
@@ -26,7 +26,7 @@ quantity = "0.5"
2626
num-dual = "0.5"
2727
feos-core = { version = "0.3", path = "feos-core" }
2828
feos-dft = { version = "0.2", path = "feos-dft", optional = true }
29-
#feos-pets = { version = "0.1", features = ["python"] }
29+
feos-saft = { version = "0.1", path = "feos-saft", optional = true }
3030
numpy = { version = "0.16", optional = true }
3131
ndarray = { version = "0.15", features = ["approx"] }
3232
petgraph = { version = "0.6", optional = true }
@@ -48,11 +48,11 @@ approx = "0.4"
4848

4949
[features]
5050
default = []
51-
dft = ["feos-dft", "petgraph"]
51+
dft = ["feos-dft", "petgraph", "feos-saft?/dft"]
5252
estimator = []
53-
pcsaft = []
54-
gc_pcsaft = []
53+
pcsaft = ["feos-saft/association"]
54+
gc_pcsaft = ["feos-saft/association"]
5555
uvtheory = ["lazy_static"]
56-
pets = []
57-
python = ["pyo3", "numpy", "feos-core/python", "feos-dft?/python"]
56+
pets = ["feos-saft"]
57+
python = ["pyo3", "numpy", "feos-core/python", "feos-dft?/python", "feos-saft?/python"]
5858
all_models = ["dft", "estimator", "pcsaft", "gc_pcsaft", "uvtheory", "pets"]

feos-dft/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
- Made FMT functional more flexible w.r.t. the shape of the weight functions. [#31](https://github.com/feos-org/feos-dft/pull/31)
1414
- Changed interface of `PairCorrelationFunction` to facilitate the calculation of pair correlation functions in mixtures. [#29](https://github.com/feos-org/feos-dft/pull/29)
1515

16+
### Removed
17+
- Moved the implementation of fundamental measure theory to the new `feos-saft` crate. [#27](https://github.com/feos-org/feos/pull/27)
18+
1619
## [0.2.0] - 2022-04-12
1720
### Added
1821
- Added `grand_potential_density` getter for DFT profiles in Python. [#22](https://github.com/feos-org/feos-dft/pull/22)

feos-dft/src/lib.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ pub mod adsorption;
77
mod convolver;
88
mod functional;
99
mod functional_contribution;
10-
pub mod fundamental_measure_theory;
1110
mod geometry;
1211
mod ideal_chain_contribution;
1312
pub mod interface;

feos-saft/Cargo.toml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
[package]
2+
name = "feos-saft"
3+
version = "0.1.0"
4+
authors = ["Gernot Bauer <bauer@itt.uni-stuttgart.de>",
5+
"Philipp Rehner <prehner@ethz.ch"]
6+
edition = "2021"
7+
license = "MIT OR Apache-2.0"
8+
description = "Core traits and functionalities for the `feos` project."
9+
homepage = "https://github.com/feos-org"
10+
readme = "README.md"
11+
repository = "https://github.com/feos-org/feos"
12+
keywords = ["physics", "thermodynamics", "equations_of_state", "phase_equilibria"]
13+
categories = ["science"]
14+
workspace = ".."
15+
16+
[package.metadata.docs.rs]
17+
rustdoc-args = [ "--html-in-header", "./docs-header.html" ]
18+
19+
[dependencies]
20+
num-dual = { version = "0.5", features = ["linalg"] }
21+
feos-core = { version = "0.3", path = "../feos-core" }
22+
feos-dft = { version = "0.2", path = "../feos-dft", optional = true }
23+
ndarray = { version = "0.15", features = ["serde"] }
24+
pyo3 = { version = "0.16", optional = true }
25+
serde = "1.0"
26+
serde_json = "1.0"
27+
28+
[features]
29+
default = []
30+
association = []
31+
dft = ["feos-dft"]
32+
python = ["pyo3"]

feos-saft/docs-header.html

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.css" integrity="sha384-9eLZqc9ds8eNjO3TmqPeYcDj8n+Qfa4nuSiGYa6DjLNcv9BtN69ZIulL9+8CqC9Y" crossorigin="anonymous">
2+
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/katex.min.js" integrity="sha384-K3vbOmF2BtaVai+Qk37uypf7VrgBubhQreNQe9aGsz9lB63dIFiQVlJbr92dw2Lx" crossorigin="anonymous"></script>
3+
<script src="https://cdn.jsdelivr.net/npm/katex@0.10.0/dist/contrib/auto-render.min.js" integrity="sha384-kmZOZB5ObwgQnS/DuDg6TScgOiWWBiVt0plIRkZCmE6rDZGrEOQeHM5PcHi+nyqe" crossorigin="anonymous"></script>
4+
<script>
5+
document.addEventListener("DOMContentLoaded", function() {
6+
renderMathInElement(document.body, {
7+
delimiters: [
8+
{left: "$$", right: "$$", display: true},
9+
{left: "\\(", right: "\\)", display: false},
10+
{left: "$", right: "$", display: false},
11+
{left: "\\[", right: "\\]", display: true}
12+
]
13+
});
14+
});
15+
</script>

feos-saft/license-apache

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../license-apache

feos-saft/license-mit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../license-mit
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,40 @@
1-
use super::parameter::GcPcSaftFunctionalParameters;
2-
use crate::gc_pcsaft::eos::association::{
3-
assoc_site_frac_a, assoc_site_frac_ab, helmholtz_energy_density_cross_association,
4-
};
5-
use feos_core::EosError;
1+
use super::*;
2+
use crate::HardSphereProperties;
3+
use feos_core::EosResult;
64
use feos_dft::{
75
FunctionalContributionDual, WeightFunction, WeightFunctionInfo, WeightFunctionShape,
86
};
9-
use ndarray::*;
107
use num_dual::DualNum;
118
use std::f64::consts::PI;
12-
use std::fmt;
139
use std::ops::MulAssign;
14-
use std::rc::Rc;
1510

1611
pub const N0_CUTOFF: f64 = 1e-9;
1712

18-
#[derive(Clone)]
19-
pub struct AssociationFunctional {
20-
parameters: Rc<GcPcSaftFunctionalParameters>,
21-
max_iter: usize,
22-
tol: f64,
23-
}
24-
25-
impl AssociationFunctional {
26-
pub fn new(parameters: &Rc<GcPcSaftFunctionalParameters>, max_iter: usize, tol: f64) -> Self {
27-
Self {
28-
parameters: parameters.clone(),
29-
max_iter,
30-
tol,
31-
}
32-
}
33-
}
34-
35-
impl<N> FunctionalContributionDual<N> for AssociationFunctional
13+
impl<N, P> FunctionalContributionDual<N> for Association<P>
3614
where
3715
N: DualNum<f64> + ScalarOperand,
16+
P: HardSphereProperties,
3817
{
3918
fn weight_functions(&self, temperature: N) -> WeightFunctionInfo<N> {
4019
let p = &self.parameters;
4120
let r = p.hs_diameter(temperature) * 0.5;
42-
WeightFunctionInfo::new(p.component_index.clone(), false)
21+
let [_, _, _, c3] = p.geometry_coefficients(temperature);
22+
WeightFunctionInfo::new(p.component_index().into_owned(), false)
4323
.add(
4424
WeightFunction::new_scaled(r.clone(), WeightFunctionShape::Delta),
4525
false,
4626
)
4727
.add(
4828
WeightFunction {
49-
prefactor: p.m.mapv(N::from),
29+
prefactor: c3.clone(),
5030
kernel_radius: r.clone(),
5131
shape: WeightFunctionShape::DeltaVec,
5232
},
5333
false,
5434
)
5535
.add(
5636
WeightFunction {
57-
prefactor: p.m.mapv(N::from),
37+
prefactor: c3,
5838
kernel_radius: r,
5939
shape: WeightFunctionShape::Theta,
6040
},
@@ -66,43 +46,40 @@ where
6646
&self,
6747
temperature: N,
6848
weighted_densities: ArrayView2<N>,
69-
) -> Result<Array1<N>, EosError> {
49+
) -> EosResult<Array1<N>> {
7050
let p = &self.parameters;
7151

7252
// number of segments
73-
let segments = p.m.len();
53+
let n = self.association_parameters.component_index.len();
7454

7555
// number of associating segments
76-
let nassoc = p.assoc_segment.len();
56+
let nassoc = self.association_parameters.assoc_comp.len();
7757

7858
// number of dimensions
79-
let dim = (weighted_densities.shape()[0] - 1) / segments - 1;
59+
let dim = (weighted_densities.shape()[0] - 1) / n - 1;
8060

8161
// weighted densities
82-
let n0i = weighted_densities.slice_axis(Axis(0), Slice::new(0, Some(segments as isize), 1));
62+
let n0i = weighted_densities.slice_axis(Axis(0), Slice::new(0, Some(n as isize), 1));
8363
let n2vi: Vec<_> = (0..dim)
8464
.map(|i| {
8565
weighted_densities.slice_axis(
8666
Axis(0),
87-
Slice::new(
88-
(segments * (i + 1)) as isize,
89-
Some((segments * (i + 2)) as isize),
90-
1,
91-
),
67+
Slice::new((n * (i + 1)) as isize, Some((n * (i + 2)) as isize), 1),
9268
)
9369
})
9470
.collect();
95-
let n3 = weighted_densities.index_axis(Axis(0), segments * (dim + 1));
71+
let n3 = weighted_densities.index_axis(Axis(0), n * (dim + 1));
9672

9773
// calculate rho0 (only associating segments)
74+
let [_, _, c2, _] = p.geometry_coefficients(temperature);
9875
let diameter = p.hs_diameter(temperature);
9976
let mut n2i = n0i.to_owned();
10077
for (i, mut n2i) in n2i.outer_iter_mut().enumerate() {
101-
n2i.mul_assign(diameter[i].powi(2) * p.m[i] * PI);
78+
n2i.mul_assign(diameter[i].powi(2) * c2[i] * PI);
10279
}
10380
let mut rho0: Array2<N> = (n2vi
10481
.iter()
105-
.fold(Array2::zeros(n2i.raw_dim()), |acc, n2vi| acc + n2vi * n2vi)
82+
.fold(Array::zeros(n0i.raw_dim()), |acc, n2vi| acc + n2vi * n2vi)
10683
/ -(&n2i * &n2i)
10784
+ 1.0)
10885
* n0i;
@@ -111,8 +88,9 @@ where
11188
*rho0 = n0i;
11289
}
11390
});
114-
let rho0 =
115-
Array2::from_shape_fn((nassoc, n3.len()), |(i, j)| rho0[(p.assoc_segment[i], j)]);
91+
let rho0 = Array2::from_shape_fn((nassoc, n3.len()), |(i, j)| {
92+
rho0[(self.association_parameters.assoc_comp[i], j)]
93+
});
11694

11795
// calculate xi
11896
let n2v: Vec<_> = n2vi.iter().map(|n2vi| n2vi.sum_axis(Axis(0))).collect();
@@ -136,23 +114,24 @@ where
136114
// only one associating component
137115
if nassoc == 1 {
138116
// association strength
139-
let k = &n2 * &n3i * diameter[p.assoc_segment[0]] * 0.5;
117+
let k = &n2 * &n3i * diameter[self.association_parameters.assoc_comp[0]] * 0.5;
140118
let deltarho = (((&k / 18.0 + 0.5) * &k * xi + 1.0) * n3i)
141-
* ((temperature.recip() * p.epsilon_k_aibj[(0, 0)]).exp_m1()
142-
* p.sigma3_kappa_aibj[(0, 0)])
119+
* ((temperature.recip() * self.association_parameters.epsilon_k_aibj[(0, 0)])
120+
.exp_m1()
121+
* self.association_parameters.sigma3_kappa_aibj[(0, 0)])
143122
* rho0.index_axis(Axis(0), 0);
144123

145-
let na = p.na[0];
146-
let nb = p.nb[0];
124+
let na = self.association_parameters.na[0];
125+
let nb = self.association_parameters.nb[0];
147126
let f = |x: N| x.ln() - x * 0.5 + 0.5;
148127
if nb > 0.0 {
149128
// no cross association, two association sites
150-
let xa = deltarho.mapv(|d| assoc_site_frac_ab(d, na, nb));
129+
let xa = deltarho.mapv(|d| Self::assoc_site_frac_ab(d, na, nb));
151130
let xb = (&xa - 1.0) * (na / nb) + 1.0;
152131
Ok((xa.mapv(f) * na + xb.mapv(f) * nb) * rho0.index_axis(Axis(0), 0))
153132
} else {
154133
// no cross association, one association site
155-
let xa = deltarho.mapv(|d| assoc_site_frac_a(d, na));
134+
let xa = deltarho.mapv(|d| Self::assoc_site_frac_a(d, na));
156135

157136
Ok(xa.mapv(f) * na * rho0.index_axis(Axis(0), 0))
158137
}
@@ -167,12 +146,7 @@ where
167146
.zip(n3i.iter())
168147
.zip(xi.iter())
169148
.map(|(((rho0, &n2), &n3i), &xi)| {
170-
helmholtz_energy_density_cross_association(
171-
&p.assoc_segment,
172-
&p.sigma3_kappa_aibj,
173-
&p.epsilon_k_aibj,
174-
&p.na,
175-
&p.nb,
149+
self.helmholtz_energy_density_cross_association(
176150
temperature,
177151
&rho0,
178152
&diameter,
@@ -190,9 +164,3 @@ where
190164
}
191165
}
192166
}
193-
194-
impl fmt::Display for AssociationFunctional {
195-
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196-
write!(f, "Association functional")
197-
}
198-
}

0 commit comments

Comments
 (0)