Skip to content

Commit 2d821f3

Browse files
g-bauerprehner
andauthored
Add benchmarks and Cargo profile using link-time optimization (#89)
* add benchmarks * Add benchmarks for binary mixture of methane/co2 * Renamed and restructured benchmarks, added README for benchmarks, added Cargo profile for LTO * Changed profile in workflow for publishing wheels to use LTO * added more systems (methane and water) for dual number evaluations, smaller description for README * Apply suggestions from code review concerning changes to the text and typos. Co-authored-by: Philipp Rehner <69816385+prehner@users.noreply.github.com> * added utilities to better extend benchmarks reusing the deriveX-methods from `State` Co-authored-by: Philipp Rehner <69816385+prehner@users.noreply.github.com>
1 parent c6a1bf7 commit 2d821f3

File tree

9 files changed

+265
-17
lines changed

9 files changed

+265
-17
lines changed

.github/workflows/release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ jobs:
1717
with:
1818
manylinux: auto
1919
command: build
20-
args: --release --out dist
20+
args: --profile release-lto --out dist
2121
- name: Upload wheels
2222
uses: actions/upload-artifact@v2
2323
with:
@@ -42,11 +42,11 @@ jobs:
4242
uses: messense/maturin-action@v1
4343
with:
4444
target: x86_64
45-
args: --release --out dist
45+
args: --profile release-lto --out dist
4646
- name: Build wheels - universal2
4747
uses: messense/maturin-action@v1
4848
with:
49-
args: --release --universal2 --out dist
49+
args: --profile release-lto --universal2 --out dist
5050
- name: Upload wheels
5151
uses: actions/upload-artifact@v2
5252
with:
@@ -74,7 +74,7 @@ jobs:
7474
uses: messense/maturin-action@v1
7575
with:
7676
target: ${{ matrix.target }}
77-
args: --release --out dist
77+
args: --profile release-lto --out dist
7878
- name: Upload wheels
7979
uses: actions/upload-artifact@v2
8080
with:

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
- Added SAFT-VRQ Mie equation of state and Helmholtz energy functional for first order Feynman-Hibbs corrected Mie fluids. [#79](https://github.com/feos-org/feos/pull/79)
1010
- Added `estimator` module to documentation. [#86](https://github.com/feos-org/feos/pull/86)
11+
- Added benchmarks for the evaluation of the Helmholtz energy and some properties of the `State` object for PC-SAFT. [#89](https://github.com/feos-org/feos/pull/89)
1112

1213
### Changed
1314
- Export `EosVariant` and `FunctionalVariant` directly in the crate root instead of their own modules. [#62](https://github.com/feos-org/feos/pull/62)

Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ optional = true
4646

4747
[dev-dependencies]
4848
approx = "0.4"
49+
criterion = "0.4"
50+
51+
[[bench]]
52+
name = "state_properties"
53+
harness = false
54+
55+
[[bench]]
56+
name = "dual_numbers"
57+
harness = false
58+
59+
[profile.release-lto]
60+
inherits = "release"
61+
lto = true
4962

5063
[features]
5164
default = []

README.md

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -96,37 +96,52 @@ If there is no compiled package for your system available from PyPI and you have
9696
pip install git+https://github.com/feos-org/feos
9797
```
9898

99+
This command builds the package without link-time optimization (LTO) that can be used to increase the performance further.
100+
See the *Building from source* section for information about building the wheel including LTO.
101+
99102
### Building from source
100103

101104
To compile the code you need the Rust compiler and `maturin` (>=0.13,<0.14) installed.
102-
To install the package directly into the active environment, use
105+
To install the package directly into the active environment (virtualenv or conda), use
103106

104107
```
105-
maturin develop --release --features python
108+
maturin develop --release
106109
```
107110

108-
and specify the models that you want to include in the python package as additional features, e.g.
111+
which uses the `python` and `all_models` feature as specified in the `pyproject.toml` file.
112+
113+
Alternatively, you can specify the models or features that you want to include in the python package explicitly, e.g.
109114

110115
```
111116
maturin develop --release --features "python pcsaft dft"
112117
```
113118

114-
for the PC-SAFT equation of state and Helmholtz energy functional. If you want to include all available models, use
119+
for the PC-SAFT equation of state and Helmholtz energy functional.
120+
121+
To build wheels including link-time optimization (LTO), use
115122

116123
```
117-
maturin develop --release --features "python all_models"
124+
maturin build --profile="release-lto"
118125
```
119126

120-
To build wheels, use
127+
which will use the `python` and `all_models` features specified in the `pyproject.toml` file.
128+
Use the following command to build a wheel with specific features:
121129

122130
```
123-
maturin build --release --out dist --features "python ..."
131+
maturin build --profile="release-lto" --features "python ..."
124132
```
125133

134+
LTO increases compile times measurably but the resulting wheel is more performant and has a smaller size.
135+
For development however, we recommend using the `--release` flag.
136+
126137
## Documentation
127138

128139
For a documentation of the Python API, Python examples, and a guide to the underlying Rust framework check out the [documentation](https://feos-org.github.io/feos/).
129140

141+
## Benchmarks
142+
143+
Check out the [benches](https://github.com/feos-org/feos/tree/main/benches) directory for information about provided Rust benchmarks and how to run them.
144+
130145
## Developers
131146

132147
This software is currently maintained by members of the groups of

benches/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Benchmarks
2+
3+
This directory contains different benchmarks.
4+
For best performance, use the `release-lto` profile.
5+
Depending on the benchmark, you might have to consider different Cargo `features` as denoted in the table below.
6+
7+
For example, to run the benchmarks in `dual_numbers`, which uses PC-SAFT, use
8+
9+
```
10+
cargo bench --profile=release-lto --features=pcsaft --bench=dual_numbers
11+
```
12+
13+
|Name|Description|Cargo features|
14+
|--|--|--|
15+
|`dual_numbers`|Helmholtz energy function evaluated using `StateHD` with different dual number types.|`pcsaft`|
16+
|`state_properties`|Properties of `State`. Including state creation using the natural variables of the Helmholtz energy (no density iteration).|`pcsaft`|

benches/dual_numbers.rs

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
//! Benchmarks for the evaluation of the Helmholtz energy function
2+
//! for a given `StateHD` for different types of dual numbers.
3+
//! These should give an idea about the expected slow-down depending
4+
//! on the dual number type used without the overhead of the `State`
5+
//! creation.
6+
use criterion::{criterion_group, criterion_main, Criterion};
7+
use feos::pcsaft::{PcSaft, PcSaftParameters};
8+
use feos_core::{
9+
parameter::{IdentifierOption, Parameter},
10+
Derivative, EquationOfState, HelmholtzEnergy, HelmholtzEnergyDual, State, StateHD,
11+
};
12+
use ndarray::{arr1, Array};
13+
use num_dual::DualNum;
14+
use quantity::si::*;
15+
use std::sync::Arc;
16+
17+
/// Helper function to create a state for given parameters.
18+
/// - temperature is 80% of critical temperature,
19+
/// - volume is critical volume,
20+
/// - molefracs (or moles) for equimolar mixture.
21+
fn state_pcsaft(parameters: PcSaftParameters) -> State<SIUnit, PcSaft> {
22+
let n = parameters.pure_records.len();
23+
let eos = Arc::new(PcSaft::new(Arc::new(parameters)));
24+
let moles = Array::from_elem(n, 1.0 / n as f64) * 10.0 * MOL;
25+
let cp = State::critical_point(&eos, Some(&moles), None, Default::default()).unwrap();
26+
let temperature = 0.8 * cp.temperature;
27+
State::new_nvt(&eos, temperature, cp.volume, &moles).unwrap()
28+
}
29+
30+
/// Residual Helmholtz energy given an equation of state and a StateHD.
31+
fn a_res<D: DualNum<f64>, E: EquationOfState>(inp: (&Arc<E>, &StateHD<D>)) -> D
32+
where
33+
(dyn HelmholtzEnergy + 'static): HelmholtzEnergyDual<D>,
34+
{
35+
inp.0.evaluate_residual(inp.1)
36+
}
37+
38+
/// Benchmark for evaluation of the Helmholtz energy for different dual number types.
39+
fn bench_dual_numbers<E: EquationOfState>(
40+
c: &mut Criterion,
41+
group_name: &str,
42+
state: State<SIUnit, E>,
43+
) {
44+
let mut group = c.benchmark_group(group_name);
45+
group.bench_function("a_f64", |b| {
46+
b.iter(|| a_res((&state.eos, &state.derive0())))
47+
});
48+
group.bench_function("a_dual", |b| {
49+
b.iter(|| a_res((&state.eos, &state.derive1(Derivative::DV))))
50+
});
51+
group.bench_function("a_hyperdual", |b| {
52+
b.iter(|| a_res((&state.eos, &state.derive2(Derivative::DV, Derivative::DV))))
53+
});
54+
group.bench_function("a_dual3", |b| {
55+
b.iter(|| a_res((&state.eos, &state.derive3(Derivative::DV))))
56+
});
57+
}
58+
59+
/// Benchmark for the PC-SAFT equation of state
60+
fn pcsaft(c: &mut Criterion) {
61+
// methane
62+
let parameters = PcSaftParameters::from_json(
63+
vec!["methane"],
64+
"./parameters/pcsaft/gross2001.json",
65+
None,
66+
IdentifierOption::Name,
67+
)
68+
.unwrap();
69+
bench_dual_numbers(c, "methane", state_pcsaft(parameters));
70+
71+
// water (4C, polar)
72+
let parameters = PcSaftParameters::from_json(
73+
vec!["water_4C_polar"],
74+
"./parameters/pcsaft/rehner2020.json",
75+
None,
76+
IdentifierOption::Name,
77+
)
78+
.unwrap();
79+
bench_dual_numbers(c, "water_4c_polar", state_pcsaft(parameters));
80+
81+
// methane, ethane, propane
82+
let parameters = PcSaftParameters::from_json(
83+
vec!["methane", "ethane", "propane"],
84+
"./parameters/pcsaft/gross2001.json",
85+
None,
86+
IdentifierOption::Name,
87+
)
88+
.unwrap();
89+
bench_dual_numbers(c, "methane_ethane_propane", state_pcsaft(parameters));
90+
}
91+
92+
/// Benchmark for the PC-SAFT equation of state.
93+
/// Binary system of methane and co2 used to model biogas.
94+
fn methane_co2_pcsaft(c: &mut Criterion) {
95+
let parameters = PcSaftParameters::from_multiple_json(
96+
&[
97+
(vec!["methane"], "./parameters/pcsaft/gross2001.json"),
98+
(
99+
vec!["carbon dioxide"],
100+
"./parameters/pcsaft/gross2005_fit.json",
101+
),
102+
],
103+
None,
104+
IdentifierOption::Name,
105+
)
106+
.unwrap();
107+
let k_ij = -0.0192211646;
108+
let parameters =
109+
PcSaftParameters::new_binary(parameters.pure_records.clone(), Some(k_ij.into()));
110+
let eos = Arc::new(PcSaft::new(Arc::new(parameters)));
111+
112+
// 230 K, 50 bar, x0 = 0.15
113+
let temperature = 230.0 * KELVIN;
114+
let density = 24.16896 * KILO * MOL / METER.powi(3);
115+
let volume = 10.0 * MOL / density;
116+
let x = arr1(&[0.15, 0.85]);
117+
let moles = &x * 10.0 * MOL;
118+
let state = State::new_nvt(&eos, temperature, volume, &moles).unwrap();
119+
bench_dual_numbers(c, "methane_co2", state);
120+
}
121+
122+
criterion_group!(bench, pcsaft, methane_co2_pcsaft);
123+
criterion_main!(bench);

benches/state_properties.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use criterion::{criterion_group, criterion_main, Criterion};
2+
use feos::pcsaft::{PcSaft, PcSaftParameters};
3+
use feos_core::{
4+
parameter::{IdentifierOption, Parameter},
5+
Contributions, EquationOfState, State,
6+
};
7+
use ndarray::arr1;
8+
use quantity::si::*;
9+
use std::sync::Arc;
10+
11+
type S = State<SIUnit, PcSaft>;
12+
13+
/// Evaluate a property of a state given the EoS, the property to compute,
14+
/// temperature, volume, moles, and the contributions to consider.
15+
fn property<E: EquationOfState, T, F: Fn(&State<SIUnit, E>, Contributions) -> T>(
16+
(eos, property, t, v, n, contributions): (
17+
&Arc<E>,
18+
F,
19+
SINumber,
20+
SINumber,
21+
&SIArray1,
22+
Contributions,
23+
),
24+
) -> T {
25+
let state = State::new_nvt(eos, t, v, n).unwrap();
26+
property(&state, contributions)
27+
}
28+
29+
/// Evaluate a property with of a state given the EoS, the property to compute,
30+
/// temperature, volume, moles.
31+
fn property_no_contributions<E: EquationOfState, T, F: Fn(&State<SIUnit, E>) -> T>(
32+
(eos, property, t, v, n): (&Arc<E>, F, SINumber, SINumber, &SIArray1),
33+
) -> T {
34+
let state = State::new_nvt(eos, t, v, n).unwrap();
35+
property(&state)
36+
}
37+
38+
fn properties_pcsaft(c: &mut Criterion) {
39+
let parameters = PcSaftParameters::from_json(
40+
vec!["methane", "ethane", "propane"],
41+
"./parameters/pcsaft/gross2001.json",
42+
None,
43+
IdentifierOption::Name,
44+
)
45+
.unwrap();
46+
let eos = Arc::new(PcSaft::new(Arc::new(parameters)));
47+
let t = 300.0 * KELVIN;
48+
let density = 71.18 * KILO * MOL / METER.powi(3);
49+
let v = 100.0 * MOL / density;
50+
let x = arr1(&[1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0]);
51+
let m = &x * 100.0 * MOL;
52+
53+
let mut group = c.benchmark_group("methane_ethane_propane");
54+
group.bench_function("a", |b| {
55+
b.iter(|| property((&eos, S::helmholtz_energy, t, v, &m, Contributions::Total)))
56+
});
57+
group.bench_function("compressibility", |b| {
58+
b.iter(|| property((&eos, S::compressibility, t, v, &m, Contributions::Total)))
59+
});
60+
group.bench_function("ln_phi", |b| {
61+
b.iter(|| property_no_contributions((&eos, S::ln_phi, t, v, &m)))
62+
});
63+
group.bench_function("c_v", |b| {
64+
b.iter(|| property((&eos, S::c_v, t, v, &m, Contributions::ResidualNvt)))
65+
});
66+
group.bench_function("molar_volume", |b| {
67+
b.iter(|| property((&eos, S::molar_volume, t, v, &m, Contributions::ResidualNvt)))
68+
});
69+
}
70+
71+
criterion_group!(bench, properties_pcsaft);
72+
criterion_main!(bench);

feos-core/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub use errors::{EosError, EosResult};
4242
pub use phase_equilibria::{
4343
PhaseDiagram, PhaseDiagramHetero, PhaseEquilibrium, SolverOptions, Verbosity,
4444
};
45-
pub use state::{Contributions, DensityInitialization, State, StateBuilder, StateHD, StateVec};
45+
pub use state::{Contributions, DensityInitialization, State, StateBuilder, StateHD, StateVec, Derivative};
4646

4747
#[cfg(feature = "python")]
4848
pub mod python;

feos-core/src/state/mod.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ where
188188
/// Derivatives of the helmholtz energy.
189189
#[derive(Clone, Copy, Eq, Hash, PartialEq, Debug, PartialOrd, Ord)]
190190
#[allow(non_camel_case_types)]
191-
pub(crate) enum Derivative {
191+
pub enum Derivative {
192192
/// Derivative with respect to system volume.
193193
DV,
194194
/// Derivative with respect to temperature.
@@ -670,15 +670,17 @@ impl<U: EosUnit, E: EquationOfState> State<U, E> {
670670
Err(EosError::NotConverged("State::update_gibbs_energy".into()))
671671
}
672672

673-
fn derive0(&self) -> StateHD<f64> {
673+
/// Creates a [StateHD] cloning temperature, volume and moles.
674+
pub fn derive0(&self) -> StateHD<f64> {
674675
StateHD::new(
675676
self.reduced_temperature,
676677
self.reduced_volume,
677678
self.reduced_moles.clone(),
678679
)
679680
}
680681

681-
fn derive1(&self, derivative: Derivative) -> StateHD<Dual64> {
682+
/// Creates a [StateHD] taking the first derivative.
683+
pub fn derive1(&self, derivative: Derivative) -> StateHD<Dual64> {
682684
let mut t = Dual64::from(self.reduced_temperature);
683685
let mut v = Dual64::from(self.reduced_volume);
684686
let mut n = self.reduced_moles.mapv(Dual64::from);
@@ -690,7 +692,12 @@ impl<U: EosUnit, E: EquationOfState> State<U, E> {
690692
StateHD::new(t, v, n)
691693
}
692694

693-
fn derive2(&self, derivative1: Derivative, derivative2: Derivative) -> StateHD<HyperDual64> {
695+
/// Creates a [StateHD] taking the first and second (partial) derivatives.
696+
pub fn derive2(
697+
&self,
698+
derivative1: Derivative,
699+
derivative2: Derivative,
700+
) -> StateHD<HyperDual64> {
694701
let mut t = HyperDual64::from(self.reduced_temperature);
695702
let mut v = HyperDual64::from(self.reduced_volume);
696703
let mut n = self.reduced_moles.mapv(HyperDual64::from);
@@ -707,7 +714,8 @@ impl<U: EosUnit, E: EquationOfState> State<U, E> {
707714
StateHD::new(t, v, n)
708715
}
709716

710-
fn derive3(&self, derivative: Derivative) -> StateHD<Dual3_64> {
717+
/// Creates a [StateHD] taking the first, second, and third derivative with respect to a single property.
718+
pub fn derive3(&self, derivative: Derivative) -> StateHD<Dual3_64> {
711719
let mut t = Dual3_64::from(self.reduced_temperature);
712720
let mut v = Dual3_64::from(self.reduced_volume);
713721
let mut n = self.reduced_moles.mapv(Dual3_64::from);

0 commit comments

Comments
 (0)