1use crate::state::StateHD;
2use crate::{Composition, FeosResult, ReferenceSystem};
3use nalgebra::allocator::Allocator;
4use nalgebra::{DVector, DefaultAllocator, Dim, Dyn, OMatrix, OVector, SVector, U1, U2};
5use num_dual::{
6 DualNum, Gradients, hessian, partial, partial2, second_derivative, third_derivative,
7};
8use quantity::ad::first_derivative;
9use quantity::*;
10use std::ops::{Deref, Div};
11use std::sync::Arc;
12
13type Quot<T1, T2> = <T1 as Div<T2>>::Output;
14
15pub trait Molarweight<N: Dim = Dyn, D: DualNum<f64> + Copy = f64>
19where
20 DefaultAllocator: Allocator<N>,
21{
22 fn molar_weight(&self) -> MolarWeight<OVector<D, N>>;
23}
24
25impl<C: Deref<Target = T>, T: Molarweight<N, D>, N: Dim, D: DualNum<f64> + Copy> Molarweight<N, D>
26 for C
27where
28 DefaultAllocator: Allocator<N>,
29{
30 fn molar_weight(&self) -> MolarWeight<OVector<D, N>> {
31 T::molar_weight(self)
32 }
33}
34
35pub trait Subset {
37 fn subset(&self, component_list: &[usize]) -> Self;
40}
41
42impl<T: Subset> Subset for Arc<T> {
43 fn subset(&self, component_list: &[usize]) -> Self {
44 Arc::new(T::subset(self, component_list))
45 }
46}
47
48pub trait ResidualDyn {
56 fn components(&self) -> usize;
58
59 fn compute_max_density<D: DualNum<f64> + Copy>(&self, molefracs: &DVector<D>) -> D;
66
67 fn reduced_helmholtz_energy_density_contributions<D: DualNum<f64> + Copy>(
70 &self,
71 state: &StateHD<D>,
72 ) -> Vec<(&'static str, D)>;
73}
74
75impl<C: Deref<Target = T> + Clone, T: ResidualDyn, D: DualNum<f64> + Copy> Residual<Dyn, D> for C {
76 type Real = Self;
77 type Lifted<D2: DualNum<f64, Inner = D> + Copy> = Self;
78 fn re(&self) -> Self::Real {
79 self.clone()
80 }
81 fn lift<D2: DualNum<f64, Inner = D> + Copy>(&self) -> Self::Lifted<D2> {
82 self.clone()
83 }
84 fn components(&self) -> usize {
85 ResidualDyn::components(self.deref())
86 }
87 fn compute_max_density(&self, molefracs: &DVector<D>) -> D {
88 ResidualDyn::compute_max_density(self.deref(), molefracs)
89 }
90 fn reduced_helmholtz_energy_density_contributions(
91 &self,
92 state: &StateHD<D, Dyn>,
93 ) -> Vec<(&'static str, D)> {
94 ResidualDyn::reduced_helmholtz_energy_density_contributions(self.deref(), state)
95 }
96}
97
98pub trait Residual<N: Dim = Dyn, D: DualNum<f64> + Copy = f64>: Clone
100where
101 DefaultAllocator: Allocator<N>,
102{
103 fn components(&self) -> usize;
105
106 fn pure_molefracs() -> OVector<D, N> {
110 OVector::from_element_generic(N::from_usize(1), U1, D::one())
111 }
112
113 type Real: Residual<N>;
115
116 type Lifted<D2: DualNum<f64, Inner = D> + Copy>: Residual<N, D2>;
118
119 fn re(&self) -> Self::Real;
121
122 fn lift<D2: DualNum<f64, Inner = D> + Copy>(&self) -> Self::Lifted<D2>;
124
125 fn compute_max_density(&self, molefracs: &OVector<D, N>) -> D;
132
133 fn reduced_helmholtz_energy_density_contributions(
136 &self,
137 state: &StateHD<D, N>,
138 ) -> Vec<(&'static str, D)>;
139
140 fn reduced_residual_helmholtz_energy_density(&self, state: &StateHD<D, N>) -> D {
142 self.reduced_helmholtz_energy_density_contributions(state)
143 .iter()
144 .fold(D::zero(), |acc, (_, a)| acc + a)
145 }
146
147 fn helmholtz_energy_contributions(
150 &self,
151 temperature: D,
152 volume: D,
153 moles: &OVector<D, N>,
154 ) -> Vec<(&'static str, D)> {
155 let state = StateHD::new(temperature, volume, moles);
156 self.reduced_helmholtz_energy_density_contributions(&state)
157 .into_iter()
158 .map(|(n, f)| (n, f * temperature * volume))
159 .collect()
160 }
161
162 fn residual_helmholtz_energy(&self, temperature: D, volume: D, moles: &OVector<D, N>) -> D {
164 let state = StateHD::new(temperature, volume, moles);
165 self.reduced_residual_helmholtz_energy_density(&state) * temperature * volume
166 }
167
168 fn residual_molar_helmholtz_energy(
173 &self,
174 temperature: Temperature<D>,
175 molar_volume: MolarVolume<D>,
176 molefracs: &OVector<D, N>,
177 ) -> MolarEnergy<D> {
178 MolarEnergy::from_reduced(self.residual_helmholtz_energy(
179 temperature.into_reduced(),
180 molar_volume.into_reduced(),
181 molefracs,
182 ))
183 }
184
185 fn max_density<X: Composition<D, N>>(&self, composition: X) -> FeosResult<Density<D>> {
192 let (x, _) = composition.into_molefracs(self)?;
193 Ok(Density::from_reduced(self.compute_max_density(&x)))
194 }
195
196 fn second_virial_coefficient<X: Composition<D, N>>(
198 &self,
199 temperature: Temperature<D>,
200 composition: X,
201 ) -> FeosResult<MolarVolume<D>> {
202 let (x, _) = composition.into_molefracs(self)?;
203 let (_, _, d2f) = second_derivative(
204 partial2(
205 |rho, &t, x| {
206 let state = StateHD::new_virial(t, rho, x);
207 self.lift()
208 .reduced_residual_helmholtz_energy_density(&state)
209 },
210 &temperature.into_reduced(),
211 &x,
212 ),
213 D::from(0.0),
214 );
215
216 Ok(Quantity::from_reduced(d2f * 0.5))
217 }
218
219 fn third_virial_coefficient<X: Composition<D, N>>(
221 &self,
222 temperature: Temperature<D>,
223 composition: X,
224 ) -> FeosResult<Quot<MolarVolume<D>, Density<D>>> {
225 let (x, _) = composition.into_molefracs(self)?;
226 let (_, _, _, d3f) = third_derivative(
227 partial2(
228 |rho, &t, x| {
229 let state = StateHD::new_virial(t, rho, x);
230 self.lift()
231 .reduced_residual_helmholtz_energy_density(&state)
232 },
233 &temperature.into_reduced(),
234 &x,
235 ),
236 D::from(0.0),
237 );
238
239 Ok(Quantity::from_reduced(d3f / 3.0))
240 }
241
242 fn second_virial_coefficient_temperature_derivative<X: Composition<D, N>>(
244 &self,
245 temperature: Temperature<D>,
246 composition: X,
247 ) -> FeosResult<Quot<MolarVolume<D>, Temperature<D>>> {
248 let (molefracs, _) = composition.into_molefracs(self)?;
249 let (_, db_dt) = first_derivative(
250 partial(
251 |t, x: &OVector<_, _>| self.lift().second_virial_coefficient(t, x).unwrap(),
252 &molefracs,
253 ),
254 temperature,
255 );
256 Ok(db_dt)
257 }
258
259 #[expect(clippy::type_complexity)]
261 fn third_virial_coefficient_temperature_derivative<X: Composition<D, N>>(
262 &self,
263 temperature: Temperature<D>,
264 composition: X,
265 ) -> FeosResult<Quot<Quot<MolarVolume<D>, Density<D>>, Temperature<D>>> {
266 let (molefracs, _) = composition.into_molefracs(self)?;
267 let (_, dc_dt) = first_derivative(
268 partial(
269 |t, x: &OVector<_, _>| self.lift().third_virial_coefficient(t, x).unwrap(),
271 &molefracs,
272 ),
273 temperature,
274 );
275 Ok(dc_dt)
276 }
277
278 fn p_dpdrho(&self, temperature: D, density: D, molefracs: &OVector<D, N>) -> (D, D, D) {
282 let molar_volume = density.recip();
283 let (a, da, d2a) = second_derivative(
284 partial2(
285 |molar_volume, &t, x| self.lift().residual_helmholtz_energy(t, molar_volume, x),
286 &temperature,
287 molefracs,
288 ),
289 molar_volume,
290 );
291 (
292 a,
293 -da + temperature * density,
294 molar_volume * molar_volume * d2a + temperature,
295 )
296 }
297
298 fn p_dpdrho_dpdt(
300 &self,
301 temperature: D,
302 density: D,
303 molefracs: &OVector<D, N>,
304 ) -> (D, D, D, D, D) {
305 let molar_volume = density.recip();
306 let (a, da, d2a) = hessian::<_, _, _, U2, _>(
307 partial(
308 |vt: SVector<_, 2>, x: &OVector<_, N>| {
309 let [[v, t]] = vt.data.0;
310 self.lift().residual_helmholtz_energy(t, v, x)
311 },
312 molefracs,
313 ),
314 &SVector::from([molar_volume, temperature]),
315 );
316 let [[da_dv, da_dt]] = da.data.0;
317 let [[d2a_dv2, d2a_dvdt], _] = d2a.data.0;
318 (
319 a,
320 -da_dv + temperature * density,
321 -da_dt,
322 molar_volume * molar_volume * d2a_dv2 + temperature,
323 -d2a_dvdt + density,
324 )
325 }
326
327 fn p_dpdrho_d2pdrho2(
329 &self,
330 temperature: D,
331 density: D,
332 molefracs: &OVector<D, N>,
333 ) -> (D, D, D) {
334 let molar_volume = density.recip();
335 let (_, da, d2a, d3a) = third_derivative(
336 partial2(
337 |molar_volume, &t, x| self.lift().residual_helmholtz_energy(t, molar_volume, x),
338 &temperature,
339 molefracs,
340 ),
341 molar_volume,
342 );
343 (
344 -da + temperature * density,
345 molar_volume * molar_volume * d2a + temperature,
346 -molar_volume * molar_volume * molar_volume * (d2a * 2.0 + molar_volume * d3a),
347 )
348 }
349
350 #[expect(clippy::type_complexity)]
352 fn dmu_drho(
353 &self,
354 temperature: D,
355 partial_density: &OVector<D, N>,
356 ) -> (D, OVector<D, N>, OVector<D, N>, OMatrix<D, N, N>)
357 where
358 N: Gradients,
359 DefaultAllocator: Allocator<N, N>,
360 {
361 let (f_res, mu_res, dmu_res) = N::hessian(
362 |rho, &t| {
363 let state = StateHD::new_density(t, &rho);
364 self.lift()
365 .reduced_residual_helmholtz_energy_density(&state)
366 * t
367 },
368 partial_density,
369 &temperature,
370 );
371 let p = mu_res.dot(partial_density) - f_res + temperature * partial_density.sum();
372 let dmu = dmu_res + OMatrix::from_diagonal(&partial_density.map(|d| temperature / d));
373 let dp = &dmu * partial_density;
374 (p, mu_res, dp, dmu)
375 }
376
377 fn dmu_dv(
379 &self,
380 temperature: D,
381 molar_volume: D,
382 molefracs: &OVector<D, N>,
383 ) -> (D, OVector<D, N>, D, OVector<D, N>)
384 where
385 N: Gradients,
386 {
387 let (_, mu_res, a_res_v, mu_res_v) = N::partial_hessian(
388 |x, v, &t| self.lift().residual_helmholtz_energy(t, v, &x),
389 molefracs,
390 molar_volume,
391 &temperature,
392 );
393 let p = -a_res_v + temperature / molar_volume;
394 let mu_v = mu_res_v.map(|m| m - temperature / molar_volume);
395 let p_v = mu_v.dot(molefracs) / molar_volume;
396 (p, mu_res, p_v, mu_v)
397 }
398
399 fn dmu_dt(&self, temperature: D, partial_density: &OVector<D, N>) -> (D, OVector<D, N>)
401 where
402 N: Gradients,
403 {
404 let (_, _, f_res_t, mu_res_t) = N::partial_hessian(
405 |rho, t, _: &()| {
406 let state = StateHD::new_density(t, &rho);
407 self.lift()
408 .reduced_residual_helmholtz_energy_density(&state)
409 * t
410 },
411 partial_density,
412 temperature,
413 &(),
414 );
415 let p_t = -f_res_t + partial_density.dot(&mu_res_t) + partial_density.sum();
416 (p_t, mu_res_t)
417 }
418}
419
420pub trait EntropyScaling<N: Dim = Dyn, D: DualNum<f64> + Copy = f64>
422where
423 DefaultAllocator: Allocator<N>,
424{
425 fn viscosity_reference(
426 &self,
427 temperature: Temperature<D>,
428 molar_volume: MolarVolume<D>,
429 molefracs: &OVector<D, N>,
430 ) -> Viscosity<D>;
431 fn viscosity_correlation(&self, s_res: D, x: &OVector<D, N>) -> D;
432 fn diffusion_reference(
433 &self,
434 temperature: Temperature<D>,
435 molar_volume: MolarVolume<D>,
436 molefracs: &OVector<D, N>,
437 ) -> Diffusivity<D>;
438 fn diffusion_correlation(&self, s_res: D, x: &OVector<D, N>) -> D;
439 fn thermal_conductivity_reference(
440 &self,
441 temperature: Temperature<D>,
442 molar_volume: MolarVolume<D>,
443 molefracs: &OVector<D, N>,
444 ) -> ThermalConductivity<D>;
445 fn thermal_conductivity_correlation(&self, s_res: D, x: &OVector<D, N>) -> D;
446}
447
448impl<C: Deref<Target = T>, T: EntropyScaling<N, D>, N: Dim, D: DualNum<f64> + Copy>
449 EntropyScaling<N, D> for C
450where
451 DefaultAllocator: Allocator<N>,
452{
453 fn viscosity_reference(
454 &self,
455 temperature: Temperature<D>,
456 molar_volume: MolarVolume<D>,
457 molefracs: &OVector<D, N>,
458 ) -> Viscosity<D> {
459 self.deref()
460 .viscosity_reference(temperature, molar_volume, molefracs)
461 }
462 fn viscosity_correlation(&self, s_res: D, x: &OVector<D, N>) -> D {
463 self.deref().viscosity_correlation(s_res, x)
464 }
465 fn diffusion_reference(
466 &self,
467 temperature: Temperature<D>,
468 molar_volume: MolarVolume<D>,
469 molefracs: &OVector<D, N>,
470 ) -> Diffusivity<D> {
471 self.deref()
472 .diffusion_reference(temperature, molar_volume, molefracs)
473 }
474 fn diffusion_correlation(&self, s_res: D, x: &OVector<D, N>) -> D {
475 self.deref().diffusion_correlation(s_res, x)
476 }
477 fn thermal_conductivity_reference(
478 &self,
479 temperature: Temperature<D>,
480 molar_volume: MolarVolume<D>,
481 molefracs: &OVector<D, N>,
482 ) -> ThermalConductivity<D> {
483 self.deref()
484 .thermal_conductivity_reference(temperature, molar_volume, molefracs)
485 }
486 fn thermal_conductivity_correlation(&self, s_res: D, x: &OVector<D, N>) -> D {
487 self.deref().thermal_conductivity_correlation(s_res, x)
488 }
489}
490
491pub struct NoResidual(pub usize);
493
494impl Subset for NoResidual {
495 fn subset(&self, component_list: &[usize]) -> Self {
496 Self(component_list.len())
497 }
498}
499
500impl ResidualDyn for NoResidual {
501 fn components(&self) -> usize {
502 self.0
503 }
504
505 fn compute_max_density<D: DualNum<f64> + Copy>(&self, _: &DVector<D>) -> D {
506 D::one()
507 }
508
509 fn reduced_helmholtz_energy_density_contributions<D: DualNum<f64> + Copy>(
510 &self,
511 _: &StateHD<D>,
512 ) -> Vec<(&'static str, D)> {
513 vec![]
514 }
515}