Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
strategy:
fail-fast: false
matrix:
model: [pcsaft, epcsaft, gc_pcsaft, pets, uvtheory, saftvrqmie, saftvrmie]
model: [pcsaft, epcsaft, gc_pcsaft, pets, uvtheory, saftvrqmie, saftvrmie, multiparameter]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 1 addition & 1 deletion crates/feos-core/src/equation_of_state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ impl<I> EquationOfState<Vec<I>, NoResidual> {
}
}

impl<I: Clone, R: ResidualDyn> ResidualDyn for EquationOfState<Vec<I>, R> {
impl<I, R: ResidualDyn> ResidualDyn for EquationOfState<Vec<I>, R> {
fn components(&self) -> usize {
self.residual.components()
}
Expand Down
2 changes: 1 addition & 1 deletion crates/feos-core/src/state/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ where
}
}

impl<E: Residual, N: Dim, D: DualNum<f64> + Copy> fmt::Display for State<E, N, D>
impl<E: Residual<N, D>, N: Dim, D: DualNum<f64> + Copy> fmt::Display for State<E, N, D>
where
DefaultAllocator: Allocator<N>,
{
Expand Down
4 changes: 3 additions & 1 deletion crates/feos/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ petgraph = { workspace = true, optional = true }
thiserror = { workspace = true }
num-traits = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
indexmap = { workspace = true }
rayon = { workspace = true, optional = true }
itertools = { workspace = true }
Expand All @@ -32,7 +33,6 @@ feos-dft = { workspace = true, optional = true }
approx = { workspace = true }
quantity = { workspace = true, features = ["approx"] }
criterion = { workspace = true }
serde_json = { workspace = true }

[features]
default = []
Expand All @@ -45,6 +45,7 @@ uvtheory = []
pets = []
saftvrqmie = []
saftvrmie = ["association"]
multiparameter = []
rayon = ["dep:rayon", "ndarray/rayon", "feos-core/rayon", "feos-dft?/rayon"]
all_models = [
"dft",
Expand All @@ -55,6 +56,7 @@ all_models = [
"pets",
"saftvrqmie",
"saftvrmie",
"multiparameter"
]

[[bench]]
Expand Down
2 changes: 2 additions & 0 deletions crates/feos/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub mod hard_sphere;
pub mod epcsaft;
#[cfg(feature = "gc_pcsaft")]
pub mod gc_pcsaft;
#[cfg(feature = "multiparameter")]
pub mod multiparameter;
#[cfg(feature = "pcsaft")]
pub mod pcsaft;
#[cfg(feature = "pets")]
Expand Down
140 changes: 140 additions & 0 deletions crates/feos/src/multiparameter/ideal_gas_function.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
use num_dual::DualNum;
use serde::Deserialize;
use serde_json::Value;
use std::collections::HashMap;

#[derive(Clone, Deserialize)]
pub struct IdealGasFunctionJson {
#[serde(rename = "type")]
pub ty: String,
#[serde(flatten)]
parameters: HashMap<String, Value>,
}

pub struct IdealGasFunctionIterator {
inner: IdealGasFunctionJson,
count: usize,
index: usize,
}

impl Iterator for IdealGasFunctionIterator {
type Item = IdealGasFunction;

fn next(&mut self) -> Option<Self::Item> {
if self.index == self.count {
return None;
}
let mut parameters: HashMap<_, _> = self
.inner
.parameters
.iter()
.map(|(k, v)| {
(
k.clone(),
if self.inner.ty == "IdealGasHelmholtzCP0AlyLee" {
// AlyLee parameters are stored as list instead of named parameters
v.clone()
} else {
v.as_array().map_or(v, |v| &v[self.index]).clone()
},
)
})
.collect();
if self.inner.ty == "IdealGasHelmholtzCP0AlyLee" {
self.index += 5
} else {
self.index += 1;
}
parameters.insert("type".into(), serde_json::to_value(&self.inner.ty).unwrap());
Some(serde_json::from_value(serde_json::to_value(parameters).unwrap()).unwrap())
}
}

impl IntoIterator for IdealGasFunctionJson {
type Item = IdealGasFunction;
type IntoIter = IdealGasFunctionIterator;

fn into_iter(self) -> Self::IntoIter {
let count = self
.parameters
.values()
.map(|e| e.as_array().map_or(1, Vec::len))
.max()
.unwrap();
IdealGasFunctionIterator {
count,
inner: self,
index: 0,
}
}
}

#[derive(Clone, Copy, Deserialize)]
#[serde(tag = "type")]
#[expect(non_snake_case)]
pub enum IdealGasFunction {
IdealGasHelmholtzLead { a1: f64, a2: f64 },
IdealGasHelmholtzLogTau { a: f64 },
IdealGasHelmholtzPower { n: f64, t: f64 },
IdealGasHelmholtzPlanckEinstein { n: f64, t: f64 },
IdealGasHelmholtzPlanckEinsteinFunctionT { Tcrit: f64, n: f64, v: f64 },
IdealGasHelmholtzPlanckEinsteinGeneralized { c: f64, d: f64, n: f64, t: f64 },
IdealGasHelmholtzCP0AlyLee { T0: f64, Tc: f64, c: [f64; 5] },
IdealGasHelmholtzCP0Constant { T0: f64, Tc: f64, cp_over_R: f64 },
IdealGasHelmholtzCP0PolyT { T0: f64, Tc: f64, c: f64, t: f64 },
IdealGasHelmholtzEnthalpyEntropyOffset { a1: f64, a2: f64 },
}

impl IdealGasFunction {
pub fn evaluate<D: DualNum<f64> + Copy>(&self, delta: D, tau: D) -> D {
match *self {
IdealGasFunction::IdealGasHelmholtzLead { a1, a2 } => delta.ln() + a1 + tau * a2,
IdealGasFunction::IdealGasHelmholtzLogTau { a } => tau.ln() * a,
IdealGasFunction::IdealGasHelmholtzPower { n, t } => tau.powf(t) * n,
IdealGasFunction::IdealGasHelmholtzPlanckEinstein { n, t } => {
(-(-tau * t).exp()).ln_1p() * n
}
IdealGasFunction::IdealGasHelmholtzPlanckEinsteinFunctionT { Tcrit, n, v } => {
(-(-tau * v / Tcrit).exp()).ln_1p() * n
}
IdealGasFunction::IdealGasHelmholtzPlanckEinsteinGeneralized { c, d, n, t } => {
((tau * t).exp() * d + c).ln() * n
}
IdealGasFunction::IdealGasHelmholtzCP0AlyLee { T0, Tc, c } => {
let [a, b, c, d, e] = c;
let mut res = D::zero();
if a > 0.0 {
let tau0 = Tc / T0;
res += (-tau / tau0 + 1.0 + (tau / tau0).ln()) * a;
}
if b > 0.0 {
res += (-(-tau * 2.0 * c / Tc).exp()).ln_1p() * b;
}
if d > 0.0 {
res -= ((-tau * 2.0 * e / Tc).exp()).ln_1p() * d;
}
res
}
IdealGasFunction::IdealGasHelmholtzCP0Constant { T0, Tc, cp_over_R } => {
let tau0 = Tc / T0;
(-tau / tau0 + 1.0 + (tau / tau0).ln()) * cp_over_R
}
IdealGasFunction::IdealGasHelmholtzCP0PolyT { T0, Tc, c, t } => {
// unfortunately some models use floats and other models use 0 or -1 which needs to be treated separately...
if t.abs() < 10.0 * f64::EPSILON {
let tau0 = Tc / T0;
(-tau / tau0 + 1.0 + (tau / tau0).ln()) * c
} else if (t + 1.0).abs() < 10.0 * f64::EPSILON {
let tau0 = Tc / T0;
(-tau / Tc * (tau / tau0).ln() + (tau - tau0) / Tc) * c
} else {
(-tau.powf(-t) * Tc.powf(t) / (t * (t + 1.0))
- tau * T0.powf(t + 1.0) / (Tc * (t + 1.0))
+ T0.powf(t) / t)
* c
}
}
IdealGasFunction::IdealGasHelmholtzEnthalpyEntropyOffset { a1, a2 } => tau * a2 + a1,
}
}
}
Loading