feos_core/equation_of_state/
mod.rs1use 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#[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 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 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
115pub trait IdealGas<D = f64> {
117 fn ln_lambda3<D2: DualNum<f64, Inner = D> + Copy>(&self, temperature: D2) -> D2;
121
122 fn ideal_gas_model(&self) -> &'static str;
124}
125
126pub 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}