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