1use super::{FeOsWrapper, HelmholtzEnergyWrapper};
2use nalgebra::{SMatrix, SVector};
3use num_dual::{
4 first_derivative, gradient, hessian, partial_hessian, second_derivative, Dual, Dual2, Dual2Vec,
5 DualNum, DualVec, HyperDualVec,
6};
7use std::sync::Arc;
8
9pub trait ParametersAD: Send + Sync + Sized {
11 type Parameters<D: DualNum<f64> + Copy>: Clone;
13
14 fn params<D: DualNum<f64> + Copy>(&self) -> Self::Parameters<D>;
16
17 fn params_from_inner<D: DualNum<f64> + Copy, D2: DualNum<f64, Inner = D> + Copy>(
19 parameters: &Self::Parameters<D>,
20 ) -> Self::Parameters<D2>;
21
22 fn wrap<const N: usize>(self) -> HelmholtzEnergyWrapper<Self, f64, N> {
25 let parameters = self.params();
26 HelmholtzEnergyWrapper {
27 eos: Arc::new(FeOsWrapper(self)),
28 parameters,
29 }
30 }
31}
32
33pub trait ResidualHelmholtzEnergy<const N: usize>: ParametersAD {
35 const RESIDUAL: &str;
37
38 fn compute_max_density(&self, molefracs: &SVector<f64, N>) -> f64;
40
41 fn residual_helmholtz_energy_density<D: DualNum<f64> + Copy>(
42 parameters: &Self::Parameters<D>,
43 temperature: D,
44 partial_density: &SVector<D, N>,
45 ) -> D;
46
47 fn residual_molar_helmholtz_energy<D: DualNum<f64> + Copy>(
48 parameters: &Self::Parameters<D>,
49 temperature: D,
50 molar_volume: D,
51 molefracs: &SVector<D, N>,
52 ) -> D {
53 let partial_density = molefracs / molar_volume;
54 Self::residual_helmholtz_energy_density(parameters, temperature, &partial_density)
55 * molar_volume
56 }
57
58 fn residual_chemical_potential<D: DualNum<f64> + Copy>(
59 parameters: &Self::Parameters<D>,
60 temperature: D,
61 molar_volume: D,
62 molefracs: &SVector<D, N>,
63 ) -> SVector<D, N> {
64 let params = Self::params_from_inner(parameters);
65 let temperature = DualVec::from_re(temperature);
66 let molar_volume = DualVec::from_re(molar_volume);
67 let (_, mu) = gradient(
68 |molefracs| {
69 Self::residual_molar_helmholtz_energy(
70 ¶ms,
71 temperature,
72 molar_volume,
73 &molefracs,
74 )
75 },
76 *molefracs,
77 );
78 mu
79 }
80
81 fn pressure<D: DualNum<f64> + Copy>(
82 parameters: &Self::Parameters<D>,
83 temperature: D,
84 molar_volume: D,
85 molefracs: &SVector<D, N>,
86 ) -> D {
87 let params = Self::params_from_inner(parameters);
88 let t = Dual::from_re(temperature);
89 let molefracs = molefracs.map(Dual::from_re);
90 let (_, dadv) = first_derivative(
91 |v| Self::residual_molar_helmholtz_energy(¶ms, t, v, &molefracs),
92 molar_volume,
93 );
94 -dadv + temperature / molar_volume
95 }
96
97 fn residual_molar_entropy<D: DualNum<f64> + Copy>(
98 parameters: &Self::Parameters<D>,
99 temperature: D,
100 molar_volume: D,
101 molefracs: &SVector<D, N>,
102 ) -> D {
103 let params = Self::params_from_inner(parameters);
104 let molar_volume = Dual::from_re(molar_volume);
105 let molefracs = molefracs.map(Dual::from_re);
106 let (_, da_dt) = first_derivative(
107 |temperature| {
108 Self::residual_molar_helmholtz_energy(
109 ¶ms,
110 temperature,
111 molar_volume,
112 &molefracs,
113 )
114 },
115 temperature,
116 );
117 -da_dt
118 }
119
120 fn residual_molar_enthalpy<D: DualNum<f64> + Copy>(
121 parameters: &Self::Parameters<D>,
122 temperature: D,
123 molar_volume: D,
124 molefracs: &SVector<D, N>,
125 ) -> D {
126 let params = Self::params_from_inner(parameters);
127 let molefracs = molefracs.map(DualVec::from_re);
128 let (a, da) = gradient(
129 |x| {
130 let [temperature, molar_volume] = x.data.0[0];
131 Self::residual_molar_helmholtz_energy(
132 ¶ms,
133 temperature,
134 molar_volume,
135 &molefracs,
136 )
137 },
138 SVector::from([temperature, molar_volume]),
139 );
140 let [da_dt, da_dv] = da.data.0[0];
141 a - temperature * da_dt - molar_volume * da_dv
142 }
143
144 fn dp_drho<D: DualNum<f64> + Copy>(
145 parameters: &Self::Parameters<D>,
146 temperature: D,
147 molar_volume: D,
148 molefracs: &SVector<D, N>,
149 ) -> (D, D, D) {
150 let params = Self::params_from_inner(parameters);
151 let t = Dual2::from_re(temperature);
152 let x = molefracs.map(Dual2::from_re);
153 let (a, da, d2a) = second_derivative(
154 |molar_volume| Self::residual_molar_helmholtz_energy(¶ms, t, molar_volume, &x),
155 molar_volume,
156 );
157 let density = molar_volume.recip();
158 (
159 a * density,
160 -da + temperature * density,
161 molar_volume * molar_volume * d2a + temperature,
162 )
163 }
164
165 fn dmu_drho<D: DualNum<f64> + Copy>(
167 parameters: &Self::Parameters<D>,
168 temperature: D,
169 partial_density: &SVector<D, N>,
170 ) -> (D, SVector<D, N>, SVector<D, N>, SMatrix<D, N, N>) {
171 let params = Self::params_from_inner(parameters);
172 let t = Dual2Vec::from_re(temperature);
173 let (f_res, mu_res, dmu_res) = hessian(
174 |rho| Self::residual_helmholtz_energy_density(¶ms, t, &rho),
175 *partial_density,
176 );
177 let p = mu_res.dot(partial_density) - f_res + temperature * partial_density.sum();
178 let dmu = dmu_res + SMatrix::from_diagonal(&partial_density.map(|d| temperature / d));
179 let dp = dmu * partial_density;
180 (p, mu_res, dp, dmu)
181 }
182
183 fn dmu_dv<D: DualNum<f64> + Copy>(
185 parameters: &Self::Parameters<D>,
186 temperature: D,
187 molar_volume: D,
188 molefracs: &SVector<D, N>,
189 ) -> (D, SVector<D, N>, D, SVector<D, N>) {
190 let params = Self::params_from_inner(parameters);
191 let t = HyperDualVec::from_re(temperature);
192 let (_, mu_res, a_res_v, mu_res_v) = partial_hessian(
193 |x, v| Self::residual_molar_helmholtz_energy(¶ms, t, v[0], &x),
194 *molefracs,
195 SVector::from([molar_volume]),
196 );
197 let p = (-a_res_v)[0] + temperature / molar_volume;
198 let mu_v = mu_res_v.map(|m| m - temperature / molar_volume);
199 let p_v = mu_v.dot(molefracs) / molar_volume;
200 (p, mu_res, p_v, mu_v)
201 }
202}