feos_core/equation_of_state/
mod.rs

1use crate::{ReferenceSystem, StateHD};
2use nalgebra::{
3    Const, DVector, DefaultAllocator, Dim, Dyn, OVector, SVector, U1, allocator::Allocator,
4};
5use num_dual::DualNum;
6use quantity::{Energy, MolarEnergy, Moles, Temperature, Volume};
7use std::ops::Deref;
8
9mod residual;
10pub use residual::{EntropyScaling, Molarweight, NoResidual, Residual, ResidualDyn, Subset};
11
12/// An equation of state consisting of an ideal gas model
13/// and a residual Helmholtz energy model.
14#[derive(Clone)]
15pub struct EquationOfState<I, R> {
16    pub ideal_gas: I,
17    pub residual: R,
18}
19
20impl<I, R> Deref for EquationOfState<I, R> {
21    type Target = R;
22    fn deref(&self) -> &R {
23        &self.residual
24    }
25}
26
27impl<I, R> EquationOfState<I, R> {
28    /// Return a new [EquationOfState] with the given ideal gas
29    /// and residual models.
30    pub fn new(ideal_gas: I, residual: R) -> Self {
31        Self {
32            ideal_gas,
33            residual,
34        }
35    }
36}
37
38impl<I> EquationOfState<Vec<I>, NoResidual> {
39    /// Return a new [EquationOfState] that only consists of
40    /// an ideal gas models.
41    pub fn ideal_gas(ideal_gas: Vec<I>) -> Self {
42        let residual = NoResidual(ideal_gas.len());
43        Self {
44            ideal_gas,
45            residual,
46        }
47    }
48}
49
50impl<I, R: ResidualDyn> ResidualDyn for EquationOfState<Vec<I>, R> {
51    fn components(&self) -> usize {
52        self.residual.components()
53    }
54
55    fn compute_max_density<D: DualNum<f64> + Copy>(&self, molefracs: &DVector<D>) -> D {
56        self.residual.compute_max_density(molefracs)
57    }
58
59    fn reduced_helmholtz_energy_density_contributions<D: DualNum<f64> + Copy>(
60        &self,
61        state: &StateHD<D>,
62    ) -> Vec<(&'static str, D)> {
63        self.residual
64            .reduced_helmholtz_energy_density_contributions(state)
65    }
66}
67
68impl<I: Clone, R: Subset> Subset for EquationOfState<Vec<I>, R> {
69    fn subset(&self, component_list: &[usize]) -> Self {
70        let ideal_gas = component_list
71            .iter()
72            .map(|&i| self.ideal_gas[i].clone())
73            .collect();
74        EquationOfState {
75            ideal_gas,
76            residual: self.residual.subset(component_list),
77        }
78    }
79}
80
81impl<I: Clone, R: Residual<Const<N>, D>, D: DualNum<f64> + Copy, const N: usize>
82    Residual<Const<N>, D> for EquationOfState<[I; N], R>
83{
84    fn components(&self) -> usize {
85        N
86    }
87
88    type Real = EquationOfState<[I; N], R::Real>;
89    type Lifted<D2: DualNum<f64, Inner = D> + Copy> = EquationOfState<[I; N], R::Lifted<D2>>;
90    fn re(&self) -> Self::Real {
91        EquationOfState::new(self.ideal_gas.clone(), self.residual.re())
92    }
93    fn lift<D2: DualNum<f64, Inner = D> + Copy>(&self) -> Self::Lifted<D2> {
94        EquationOfState::new(self.ideal_gas.clone(), self.residual.lift())
95    }
96
97    fn compute_max_density(&self, molefracs: &SVector<D, N>) -> D {
98        self.residual.compute_max_density(molefracs)
99    }
100
101    fn reduced_helmholtz_energy_density_contributions(
102        &self,
103        state: &StateHD<D, Const<N>>,
104    ) -> Vec<(&'static str, D)> {
105        self.residual
106            .reduced_helmholtz_energy_density_contributions(state)
107    }
108
109    fn reduced_residual_helmholtz_energy_density(&self, state: &StateHD<D, Const<N>>) -> D {
110        self.residual
111            .reduced_residual_helmholtz_energy_density(state)
112    }
113}
114
115/// Ideal gas Helmholtz energy contribution.
116pub trait IdealGas<D = f64> {
117    /// Implementation of an ideal gas model in terms of the
118    /// logarithm of the cubic thermal de Broglie wavelength
119    /// in units ln(A³) for each component in the system.
120    fn ln_lambda3<D2: DualNum<f64, Inner = D> + Copy>(&self, temperature: D2) -> D2;
121
122    /// The name of the ideal gas model.
123    fn ideal_gas_model(&self) -> &'static str;
124}
125
126/// A total Helmholtz energy model consisting of a [Residual] model and an [IdealGas] part.
127pub trait Total<N: Dim = Dyn, D: DualNum<f64> + Copy = f64>: Residual<N, D>
128where
129    DefaultAllocator: Allocator<N>,
130{
131    type IdealGas: IdealGas<D>;
132
133    fn ideal_gas_model(&self) -> &'static str;
134
135    fn ideal_gas(&self) -> impl Iterator<Item = &Self::IdealGas>;
136
137    fn ln_lambda3<D2: DualNum<f64, Inner = D> + Copy>(&self, temperature: D2) -> OVector<D2, N> {
138        OVector::from_iterator_generic(
139            N::from_usize(self.components()),
140            U1,
141            self.ideal_gas().map(|i| i.ln_lambda3(temperature)),
142        )
143    }
144
145    fn ideal_gas_molar_helmholtz_energy<D2: DualNum<f64, Inner = D> + Copy>(
146        &self,
147        temperature: D2,
148        molar_volume: D2,
149        molefracs: &OVector<D2, N>,
150    ) -> D2 {
151        let partial_density = molefracs / molar_volume;
152        let mut res = D2::from(0.0);
153        for (i, &r) in self.ideal_gas().zip(partial_density.iter()) {
154            let ln_rho_m1 = if r.re() == 0.0 {
155                D2::from(0.0)
156            } else {
157                r.ln() - 1.0
158            };
159            res += r * (i.ln_lambda3(temperature) + ln_rho_m1)
160        }
161        res * molar_volume * temperature
162    }
163
164    fn ideal_gas_helmholtz_energy<D2: DualNum<f64, Inner = D> + Copy>(
165        &self,
166        temperature: Temperature<D2>,
167        volume: Volume<D2>,
168        moles: &Moles<OVector<D2, N>>,
169    ) -> Energy<D2> {
170        let total_moles = moles.sum();
171        let molefracs = moles / total_moles;
172        let molar_volume = volume / total_moles;
173        MolarEnergy::from_reduced(self.ideal_gas_molar_helmholtz_energy(
174            temperature.into_reduced(),
175            molar_volume.into_reduced(),
176            &molefracs,
177        )) * total_moles
178    }
179}
180
181impl<
182    I: IdealGas + Clone + 'static,
183    C: Deref<Target = EquationOfState<Vec<I>, R>> + Clone,
184    R: ResidualDyn + 'static,
185> Total<Dyn, f64> for C
186{
187    type IdealGas = I;
188
189    fn ideal_gas_model(&self) -> &'static str {
190        self.ideal_gas[0].ideal_gas_model()
191    }
192
193    fn ideal_gas(&self) -> impl Iterator<Item = &Self::IdealGas> {
194        self.ideal_gas.iter()
195    }
196}
197
198impl<I: IdealGas<D> + Clone, R: Residual<Const<N>, D>, D: DualNum<f64> + Copy, const N: usize>
199    Total<Const<N>, D> for EquationOfState<[I; N], R>
200{
201    type IdealGas = I;
202
203    fn ideal_gas_model(&self) -> &'static str {
204        self.ideal_gas[0].ideal_gas_model()
205    }
206
207    fn ideal_gas(&self) -> impl Iterator<Item = &Self::IdealGas> {
208        self.ideal_gas.iter()
209    }
210}