Skip to content

Commit f7ffb5d

Browse files
authored
Include feos-ad in the feos workspace (#283)
* Include feos-ad in the feos workspace * update symbolic links * fix parameter fit * introduce features for models * introduce features for models correctly * fix workflow * introduce features for models actually correctly * no weird stuff here * bring apache license into correct template form
1 parent 819bf6a commit f7ffb5d

File tree

28 files changed

+4014
-20
lines changed

28 files changed

+4014
-20
lines changed

.github/workflows/test.yml

Lines changed: 13 additions & 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-ad, feos-dft]
1919
steps:
2020
- uses: actions/checkout@v4
2121
- name: Build
@@ -50,3 +50,15 @@ jobs:
5050
run: cargo build --release --features "${{ matrix.model }} dft"
5151
- name: Run tests
5252
run: cargo test --release --features "${{ matrix.model }} dft"
53+
54+
test_models_ad:
55+
runs-on: ubuntu-latest
56+
strategy:
57+
fail-fast: false
58+
matrix:
59+
model: [pcsaft, gc_pcsaft]
60+
61+
steps:
62+
- uses: actions/checkout@v4
63+
- name: Run tests
64+
run: cargo test --release -p feos-ad --features ${{ matrix.model }}

crates/feos-ad/CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# Changelog
2+
All notable changes to this project will be documented in this file.
3+
4+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6+
7+
## [Unreleased]
8+
9+
## [0.2.3] - 2025-05-28
10+
### Changed
11+
- Simplify the `Eigen` trait for better readable trait bounds. [#5](https://github.com/feos-org/feos-ad/pull/5)
12+
13+
## [0.2.2] - 2025-05-28
14+
### Fixed
15+
- Export `Eigen` to be able to calculate critical points for pure components and binary mixtures generically. [#4](https://github.com/feos-org/feos-ad/pull/4)
16+
17+
## [0.2.1] - 2025-04-14
18+
### Added
19+
- Added `StateAD::molar_isochoric_heat_capacity` and `StateAD::molar_isobaric_heat_capacity`. [#3](https://github.com/feos-org/feos-ad/pull/3)
20+
21+
## [0.2.0] - 2025-01-27
22+
### Changed
23+
- Made `PcSaftBinary` generic for associating/non-associating systems. [#2](https://github.com/feos-org/feos-ad/pull/2)
24+
25+
### Removed
26+
- Removed `ChemicalRecord`. [#2](https://github.com/feos-org/feos-ad/pull/2)
27+
28+
## [0.1.1] - 2025-01-21
29+
### Added
30+
- Implemented PC-SAFT for a binary mixture. [#1](https://github.com/feos-org/feos-ad/pull/1)
31+
32+
## [0.1.0] - 2025-01-08
33+
### Added
34+
- Initial release

crates/feos-ad/Cargo.toml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
[package]
2+
name = "feos-ad"
3+
edition.workspace = true
4+
version = "0.2.3"
5+
authors = ["Philipp Rehner <prehner@ethz.ch"]
6+
homepage.workspace = true
7+
license.workspace = true
8+
repository.workspace = true
9+
keywords.workspace = true
10+
categories.workspace = true
11+
description = "FeOs-AD - Implicit automatic differentiation of equations of state and phase equilibria."
12+
13+
[dependencies]
14+
num-dual = { workspace = true }
15+
quantity = { workspace = true, features = ["num-dual"] }
16+
nalgebra = { workspace = true }
17+
feos-core = { workspace = true }
18+
ndarray = { workspace = true }
19+
20+
[dev-dependencies]
21+
feos = { workspace = true }
22+
approx = { workspace = true }
23+
quantity = { workspace = true, features = ["num-dual", "approx"] }
24+
25+
[features]
26+
default = []
27+
pcsaft = ["feos/pcsaft"]
28+
gc_pcsaft = ["feos/gc_pcsaft"]
29+
parameter_fit = []

crates/feos-ad/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# FeOs-AD - Implicit automatic differentiation of equations of state and phase equilibria
2+
3+
[![crate](https://img.shields.io/crates/v/feos-ad.svg)](https://crates.io/crates/feos-ad)
4+
[![documentation](https://docs.rs/feos-ad/badge.svg)](https://docs.rs/feos-ad)
5+
6+
The `FeOs-AD` crate builds on the implementation of phase equilibrium calculations in `FeOs` to provide implicit automatic differentiation of properties and phase equilibria based on Helmholtz energy equations of state. Derivatives can be determined for any inputs, like temperature or pressure, but also model parameters.
7+
8+
Derivatives are calculated using forward automatic differentiation with generalized (hyper-) dual numbers from the [`num-dual`](https://github.com/itt-ustutt/num-dual) crate.
9+
10+
## Contents
11+
For now, the most important properties and phase equilibria are implemented:
12+
- **State construction**
13+
- from temperature and pressure
14+
- from pressure and entropy
15+
- from pressure and enthalpy
16+
- critical points
17+
- **Phase equilibria**
18+
- bubble points
19+
- dew points
20+
- tp flash
21+
- **Properties**
22+
- pressure, molar entropy, molar enthalpy
23+
24+
The following **models** are implemented:
25+
- **PC-SAFT** for pure components and binary mixtures
26+
- heterosegmented **gc-PC-SAFT** for pure components and mixtures
27+
- The **Joback & Reid** GC method for ideal gas heat capacities
28+
29+
## Installation
30+
Just add the dependency to your `Cargo.toml`
31+
```toml
32+
feos-ad = "0.2"
33+
```

crates/feos-ad/license-apache

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

crates/feos-ad/license-mit

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

crates/feos-ad/src/core/mod.rs

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
use feos_core::{Components, IdealGas, Residual, StateHD};
2+
use nalgebra::{Const, SVector, U1};
3+
use ndarray::{arr1, Array1, ScalarOperand};
4+
use num_dual::{Derivative, DualNum, DualVec};
5+
use std::sync::Arc;
6+
7+
mod phase_equilibria;
8+
mod residual;
9+
mod state;
10+
mod total;
11+
pub use phase_equilibria::PhaseEquilibriumAD;
12+
pub use residual::{ParametersAD, ResidualHelmholtzEnergy};
13+
pub use state::{Eigen, StateAD};
14+
pub use total::{EquationOfStateAD, IdealGasAD, TotalHelmholtzEnergy};
15+
16+
/// Used internally to implement the [Residual] and [IdealGas] traits from FeOs.
17+
pub struct FeOsWrapper<E, const N: usize>(E);
18+
19+
impl<R: ParametersAD, const N: usize> Components for FeOsWrapper<R, N> {
20+
fn components(&self) -> usize {
21+
N
22+
}
23+
24+
fn subset(&self, _: &[usize]) -> Self {
25+
panic!("Calculating properties of subsets of models is not possible with AD.")
26+
}
27+
}
28+
29+
impl<R: ResidualHelmholtzEnergy<N>, const N: usize> Residual for FeOsWrapper<R, N> {
30+
fn compute_max_density(&self, moles: &Array1<f64>) -> f64 {
31+
let total_moles = moles.sum();
32+
let molefracs = SVector::from_fn(|i, _| moles[i] / total_moles);
33+
self.0.compute_max_density(&molefracs)
34+
}
35+
36+
fn residual_helmholtz_energy_contributions<D: DualNum<f64> + Copy + ScalarOperand>(
37+
&self,
38+
state: &StateHD<D>,
39+
) -> Vec<(String, D)> {
40+
let temperature = state.temperature;
41+
let volume = state.volume;
42+
let density = SVector::from_column_slice(state.partial_density.as_slice().unwrap());
43+
let parameters = self.0.params();
44+
let a = R::residual_helmholtz_energy_density(&parameters, temperature, &density) * volume
45+
/ temperature;
46+
vec![(R::RESIDUAL.into(), a)]
47+
}
48+
}
49+
50+
impl<E: TotalHelmholtzEnergy<N>, const N: usize> IdealGas for FeOsWrapper<E, N> {
51+
fn ln_lambda3<D: DualNum<f64> + Copy>(&self, temperature: D) -> Array1<D> {
52+
let parameters = self.0.params();
53+
arr1(&E::ln_lambda3(&parameters, temperature).data.0[0])
54+
}
55+
56+
fn ideal_gas_model(&self) -> String {
57+
E::IDEAL_GAS.into()
58+
}
59+
}
60+
61+
/// Struct that stores the reference to the equation of state together with the (possibly) dual parameters.
62+
pub struct HelmholtzEnergyWrapper<E: ParametersAD, D: DualNum<f64> + Copy, const N: usize> {
63+
pub eos: Arc<FeOsWrapper<E, N>>,
64+
pub parameters: E::Parameters<D>,
65+
}
66+
67+
impl<E: ParametersAD, const N: usize> HelmholtzEnergyWrapper<E, f64, N> {
68+
/// Manually set the parameters and their derivatives.
69+
pub fn derivatives<D: DualNum<f64> + Copy>(
70+
&self,
71+
parameters: E::Parameters<D>,
72+
) -> HelmholtzEnergyWrapper<E, D, N> {
73+
HelmholtzEnergyWrapper {
74+
eos: self.eos.clone(),
75+
parameters,
76+
}
77+
}
78+
}
79+
80+
/// Models for which derivatives with respect to individual parameters can be calculated.
81+
pub trait NamedParameters: ParametersAD {
82+
/// Return a mutable reference to the parameter named by `index` from the parameter set.
83+
fn index_parameters_mut<'a, D: DualNum<f64> + Copy>(
84+
parameters: &'a mut Self::Parameters<D>,
85+
index: &str,
86+
) -> &'a mut D;
87+
}
88+
89+
impl<E: NamedParameters, const N: usize> HelmholtzEnergyWrapper<E, f64, N> {
90+
/// Initialize the parameters to calculate their derivatives.
91+
pub fn named_derivatives<const P: usize>(
92+
&self,
93+
parameters: [&str; P],
94+
) -> HelmholtzEnergyWrapper<E, DualVec<f64, f64, Const<P>>, N> {
95+
let mut params: E::Parameters<DualVec<f64, f64, Const<P>>> = self.eos.0.params();
96+
for (i, p) in parameters.into_iter().enumerate() {
97+
E::index_parameters_mut(&mut params, p).eps =
98+
Derivative::derivative_generic(Const::<P>, U1, i)
99+
}
100+
self.derivatives(params)
101+
}
102+
}

0 commit comments

Comments
 (0)