Skip to content

Commit 8479e0f

Browse files
authored
Add modules for EosVariant and FunctionalVariant (feos-org#46)
* add modules for EosVariant and FunctionalVariant so that they can be used outside of Python feature * put new dft module behind feature, removed python stuff from modules. * fixed bugs in features * started proc macro for equation of state * Proc macro for EquationOfState and HelmholtzEnergyFunctional for the variant enums * Add symlinks to licenses * Add symlink to readme, added missing fields to Cargo.toml * Add own README, add short description for crate docs, ran formatter * fixed typos in README and added workflow for crates.io release
1 parent 315b2aa commit 8479e0f

14 files changed

Lines changed: 650 additions & 342 deletions

File tree

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

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ features = ["all_models"]
1717
rustdoc-args = [ "--html-in-header", "./docs-header.html" ]
1818

1919
[workspace]
20-
members = ["feos-core", "feos-dft"]
20+
members = ["feos-core", "feos-dft", "feos-derive"]
2121

2222
[lib]
2323
crate-type = ["rlib", "cdylib"]
@@ -27,6 +27,7 @@ quantity = "0.5"
2727
num-dual = "0.5"
2828
feos-core = { version = "0.3", path = "feos-core" }
2929
feos-dft = { version = "0.3", path = "feos-dft", optional = true }
30+
feos-derive = { path = "feos-derive" }
3031
numpy = { version = "0.16", optional = true }
3132
ndarray = { version = "0.15", features = ["approx"] }
3233
petgraph = { version = "0.6", optional = true }

feos-derive/Cargo.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
[package]
2+
name = "feos-derive"
3+
version = "0.1.0"
4+
authors = ["Gernot Bauer <bauer@itt.uni-stuttgart.de>", "Philipp Rehner <prehner@ethz.ch>"]
5+
edition = "2021"
6+
readme = "README.md"
7+
description = "Macros for the EquationOfState and HelmholtzEnergyFunctional traits in FeOs"
8+
license = "MIT OR Apache-2.0"
9+
homepage = "https://github.com/feos-org"
10+
repository = "https://github.com/feos-org/feos"
11+
keywords = ["physics", "thermodynamics", "equations_of_state", "phase_equilibria", "density_functional_theory"]
12+
13+
[lib]
14+
proc-macro = true
15+
16+
[dependencies]
17+
syn = { version = "1.0", features = ["extra-traits", "derive"] }
18+
quote = "1.0"
19+
proc-macro2 = "1.0"

feos-derive/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# FeOs-derive
2+
3+
[![crate](https://img.shields.io/crates/v/feos.svg)](https://crates.io/crates/feos-derive)
4+
[![documentation](https://docs.rs/feos/badge.svg)](https://docs.rs/feos-derive)
5+
[![documentation](https://img.shields.io/badge/docs-github--pages-blue)](https://feos-org.github.io/feos/)
6+
7+
`feos-derive` is part of the [FeOs project](https://github.com/feos-org/feos).
8+
9+
> **FeOs - A Framework for Equations of State and Classical Density Functional Theory**
10+
11+
## What it does
12+
13+
The `feos-derive` crate contains two macros that provide boilerplate for the implementation of the `EquationOfState` and the `HelmholtzEnergyFunctional` trait for the `EosVariant` and `FunctionalVariant` enums of the `feos` crate. With these macros, new equations of state and functionals can be added to the variants without any further implementations needed.
14+
15+

feos-derive/license-apache

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

feos-derive/license-mit

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

feos-derive/src/dft.rs

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
use quote::quote;
2+
use syn::DeriveInput;
3+
4+
use crate::implement;
5+
6+
const OPT_IMPLS: [&'static str; 4] = [
7+
"bond_lengths",
8+
"molar_weight",
9+
"fluid_parameters",
10+
"pair_potential",
11+
];
12+
13+
pub(crate) fn expand_helmholtz_energy_functional(
14+
input: DeriveInput,
15+
) -> syn::Result<proc_macro2::TokenStream> {
16+
let variants = match input.data {
17+
syn::Data::Enum(syn::DataEnum { ref variants, .. }) => variants,
18+
_ => panic!("this derive macro only works on enums"),
19+
};
20+
21+
let from = impl_from(variants)?;
22+
let functional = impl_helmholtz_energy_functional(variants)?;
23+
let molar_weight = impl_molar_weight(variants)?;
24+
let fluid_parameters = impl_fluid_parameters(variants)?;
25+
let pair_potential = impl_pair_potential(variants)?;
26+
Ok(quote! {
27+
#from
28+
#functional
29+
#molar_weight
30+
#fluid_parameters
31+
#pair_potential
32+
})
33+
}
34+
35+
// extract the variant name and the name of the functional,
36+
// i.e. PcSaft(PcSaftFunctional) will return (PcSaft, PcSaftFunctional)
37+
fn extract_names<'a>(variant: &'a syn::Variant) -> syn::Result<(&'a syn::Ident, &'a syn::Ident)> {
38+
let name = &variant.ident;
39+
let field = if let syn::Fields::Unnamed(syn::FieldsUnnamed { ref unnamed, .. }) = variant.fields
40+
{
41+
if unnamed.len() != 1 {
42+
return Err(syn::Error::new_spanned(
43+
unnamed,
44+
"expected tuple struct with single HelmholtzFunctional as variant",
45+
));
46+
}
47+
&unnamed[0]
48+
} else {
49+
return Err(syn::Error::new_spanned(
50+
name,
51+
"expected variant with a HelmholtzFunctional as data",
52+
));
53+
};
54+
55+
let inner = if let syn::Type::Path(syn::TypePath { ref path, .. }) = &field.ty {
56+
path.get_ident()
57+
} else {
58+
None
59+
}
60+
.ok_or(syn::Error::new_spanned(
61+
field,
62+
"expected HelmholtzFunctional",
63+
))?;
64+
Ok((name, inner))
65+
}
66+
67+
fn impl_from(
68+
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
69+
) -> syn::Result<proc_macro2::TokenStream> {
70+
variants
71+
.iter()
72+
.map(|v| {
73+
let (variant_name, functional_name) = extract_names(v)?;
74+
Ok(quote! {
75+
impl From<#functional_name> for FunctionalVariant {
76+
fn from(f: #functional_name) -> Self {
77+
Self::#variant_name(f)
78+
}
79+
}
80+
})
81+
})
82+
.collect()
83+
}
84+
85+
fn impl_helmholtz_energy_functional(
86+
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
87+
) -> syn::Result<proc_macro2::TokenStream> {
88+
let subset = variants.iter().map(|v| {
89+
let name = &v.ident;
90+
quote! {
91+
Self::#name(functional) => functional.subset(component_list).into()
92+
}
93+
});
94+
let molecule_shape = variants.iter().map(|v| {
95+
let name = &v.ident;
96+
quote! {
97+
Self::#name(functional) => functional.molecule_shape()
98+
}
99+
});
100+
let compute_max_density = variants.iter().map(|v| {
101+
let name = &v.ident;
102+
quote! {
103+
Self::#name(functional) => functional.compute_max_density(moles)
104+
}
105+
});
106+
let contributions = variants.iter().map(|v| {
107+
let name = &v.ident;
108+
quote! {
109+
Self::#name(functional) => functional.contributions()
110+
}
111+
});
112+
let ideal_gas = variants.iter().map(|v| {
113+
let name = &v.ident;
114+
quote! {
115+
Self::#name(functional) => functional.ideal_gas()
116+
}
117+
});
118+
119+
let mut bond_lengths = Vec::new();
120+
for v in variants.iter() {
121+
if implement("bond_lengths", v, &OPT_IMPLS)? {
122+
let name = &v.ident;
123+
bond_lengths.push(quote! {
124+
Self::#name(functional) => functional.bond_lengths(temperature)
125+
});
126+
}
127+
}
128+
129+
Ok(quote! {
130+
impl HelmholtzEnergyFunctional for FunctionalVariant {
131+
fn subset(&self, component_list: &[usize]) -> DFT<Self> {
132+
match self {
133+
#(#subset,)*
134+
}
135+
}
136+
fn molecule_shape(&self) -> MoleculeShape {
137+
match self {
138+
#(#molecule_shape,)*
139+
}
140+
}
141+
fn compute_max_density(&self, moles: &Array1<f64>) -> f64 {
142+
match self {
143+
#(#compute_max_density,)*
144+
}
145+
}
146+
fn contributions(&self) -> &[Box<dyn FunctionalContribution>] {
147+
match self {
148+
#(#contributions,)*
149+
}
150+
}
151+
fn ideal_gas(&self) -> &dyn IdealGasContribution {
152+
match self {
153+
#(#ideal_gas,)*
154+
}
155+
}
156+
fn bond_lengths(&self, temperature: f64) -> UnGraph<(), f64> {
157+
match self {
158+
#(#bond_lengths,)*
159+
_ => Graph::with_capacity(0, 0),
160+
}
161+
}
162+
}
163+
})
164+
}
165+
166+
fn impl_molar_weight(
167+
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
168+
) -> syn::Result<proc_macro2::TokenStream> {
169+
let mut molar_weight = Vec::new();
170+
171+
for v in variants.iter() {
172+
if implement("molar_weight", v, &OPT_IMPLS)? {
173+
let name = &v.ident;
174+
molar_weight.push(quote! {
175+
Self::#name(functional) => functional.molar_weight()
176+
});
177+
}
178+
}
179+
Ok(quote! {
180+
impl MolarWeight<SIUnit> for FunctionalVariant {
181+
fn molar_weight(&self) -> SIArray1 {
182+
match self {
183+
#(#molar_weight,)*
184+
_ => unimplemented!()
185+
}
186+
}
187+
}
188+
})
189+
}
190+
191+
fn impl_fluid_parameters(
192+
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
193+
) -> syn::Result<proc_macro2::TokenStream> {
194+
let mut epsilon_k_ff = Vec::new();
195+
let mut sigma_ff = Vec::new();
196+
197+
for v in variants.iter() {
198+
if implement("fluid_parameters", v, &OPT_IMPLS)? {
199+
let name = &v.ident;
200+
epsilon_k_ff.push(quote! {
201+
Self::#name(functional) => functional.epsilon_k_ff()
202+
});
203+
sigma_ff.push(quote! {
204+
Self::#name(functional) => functional.sigma_ff()
205+
});
206+
}
207+
}
208+
Ok(quote! {
209+
impl FluidParameters for FunctionalVariant {
210+
fn epsilon_k_ff(&self) -> Array1<f64> {
211+
match self {
212+
#(#epsilon_k_ff,)*
213+
_ => unimplemented!()
214+
}
215+
}
216+
217+
fn sigma_ff(&self) -> &Array1<f64> {
218+
match self {
219+
#(#sigma_ff,)*
220+
_ => unimplemented!()
221+
}
222+
}
223+
}
224+
})
225+
}
226+
227+
fn impl_pair_potential(
228+
variants: &syn::punctuated::Punctuated<syn::Variant, syn::token::Comma>,
229+
) -> syn::Result<proc_macro2::TokenStream> {
230+
let mut pair_potential = Vec::new();
231+
232+
for v in variants.iter() {
233+
if implement("pair_potential", v, &OPT_IMPLS)? {
234+
let name = &v.ident;
235+
pair_potential.push(quote! {
236+
Self::#name(functional) => functional.pair_potential(i, r, temperature)
237+
});
238+
}
239+
}
240+
Ok(quote! {
241+
impl PairPotential for FunctionalVariant {
242+
fn pair_potential(&self, i: usize, r: &Array1<f64>, temperature: f64) -> Array2<f64> {
243+
match self {
244+
#(#pair_potential,)*
245+
_ => unimplemented!()
246+
}
247+
}
248+
}
249+
})
250+
}

0 commit comments

Comments
 (0)