1use crate::equation_of_state::Residual;
2use crate::{FeosError, FeosResult};
3use nalgebra::allocator::Allocator;
4use nalgebra::{DefaultAllocator, Dim, Dyn, OVector, U1, U2, dvector, vector};
5use num_dual::{DualNum, DualStruct};
6use quantity::Moles;
7
8pub trait Composition<D: DualNum<f64> + Copy, N: Dim>
25where
26 DefaultAllocator: Allocator<N>,
27{
28 #[expect(clippy::type_complexity)]
30 fn into_molefracs<E: Residual<N, D>>(
31 self,
32 eos: &E,
33 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)>;
34}
35
36impl<D: DualNum<f64> + Copy, N: Dim> Composition<D, N> for (OVector<D, N>, Moles<D>)
38where
39 DefaultAllocator: Allocator<N>,
40{
41 fn into_molefracs<E: Residual<N, D>>(
42 self,
43 _: &E,
44 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)> {
45 Ok((self.0, Some(self.1)))
46 }
47}
48
49impl<D: DualNum<f64> + Copy, N: Dim> Composition<D, N> for (OVector<D, N>, Option<Moles<D>>)
50where
51 DefaultAllocator: Allocator<N>,
52{
53 fn into_molefracs<E: Residual<N, D>>(
54 self,
55 _: &E,
56 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)> {
57 Ok((self.0, self.1))
58 }
59}
60
61impl<D: DualNum<f64> + Copy> Composition<D, U1> for () {
63 fn into_molefracs<E: Residual<U1, D>>(
64 self,
65 _: &E,
66 ) -> FeosResult<(OVector<D, U1>, Option<Moles<D>>)> {
67 Ok(((vector![D::one()]), None))
68 }
69}
70impl<D: DualNum<f64> + Copy> Composition<D, Dyn> for () {
71 fn into_molefracs<E: Residual<Dyn, D>>(
72 self,
73 eos: &E,
74 ) -> FeosResult<(OVector<D, Dyn>, Option<Moles<D>>)> {
75 if eos.components() == 1 {
76 Ok(((dvector![D::one()]), None))
77 } else {
78 Err(FeosError::UndeterminedState(
79 "The composition needs to be specified for a system with more than one component."
80 .into(),
81 ))
82 }
83 }
84}
85
86impl<D: DualNum<f64> + Copy> Composition<D, U2> for D {
88 fn into_molefracs<E: Residual<U2, D>>(
89 self,
90 _: &E,
91 ) -> FeosResult<(OVector<D, U2>, Option<Moles<D>>)> {
92 Ok(((vector![self, -self + 1.0]), None))
93 }
94}
95
96impl Composition<f64, Dyn> for f64 {
98 fn into_molefracs<E: Residual>(
99 self,
100 eos: &E,
101 ) -> FeosResult<(OVector<f64, Dyn>, Option<Moles>)> {
102 if eos.components() == 2 {
103 Ok(((dvector![self, 1.0 - self]), None))
104 } else {
105 Err(FeosError::UndeterminedState(format!(
106 "A scalar ({}) can only be used to specify a binary mixture!",
107 self
108 )))
109 }
110 }
111}
112
113impl<D: DualNum<f64> + Copy> Composition<D, U1> for Moles<D> {
115 fn into_molefracs<E: Residual<U1, D>>(
116 self,
117 _: &E,
118 ) -> FeosResult<(OVector<D, U1>, Option<Moles<D>>)> {
119 Ok(((vector![D::one()]), Some(self)))
120 }
121}
122
123impl<D: DualNum<f64> + Copy> Composition<D, Dyn> for Moles<D> {
124 fn into_molefracs<E: Residual<Dyn, D>>(
125 self,
126 eos: &E,
127 ) -> FeosResult<(OVector<D, Dyn>, Option<Moles<D>>)> {
128 if eos.components() == 1 {
129 Ok(((dvector![D::one()]), Some(self)))
130 } else {
131 Err(FeosError::UndeterminedState(format!(
132 "A single mole number ({}) can only be used to specify a pure component!",
133 self.re()
134 )))
135 }
136 }
137}
138
139impl<D: DualNum<f64> + Copy, N: Dim> Composition<D, N> for OVector<D, N>
144where
145 DefaultAllocator: Allocator<N>,
146{
147 fn into_molefracs<E: Residual<N, D>>(
148 self,
149 eos: &E,
150 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)> {
151 (&self).into_molefracs(eos)
152 }
153}
154
155impl<D: DualNum<f64> + Copy, N: Dim> Composition<D, N> for &OVector<D, N>
156where
157 DefaultAllocator: Allocator<N>,
158{
159 fn into_molefracs<E: Residual<N, D>>(
160 self,
161 eos: &E,
162 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)> {
163 let sum = self.sum();
164 if eos.components() == self.len() {
165 Ok(((self.clone() / sum), None))
166 } else if eos.components() == self.len() + 1 {
167 let mut x = OVector::zeros_generic(N::from_usize(eos.components()), U1);
168 for i in 0..self.len() {
169 x[i] = self[i];
170 }
171 x[self.len()] = -sum + 1.0;
172 Ok(((x), None))
173 } else {
174 Err(FeosError::UndeterminedState(format!(
175 "The length of the composition vector ({}) does not match the number of components ({})!",
176 self.len(),
177 eos.components()
178 )))
179 }
180 }
181}
182
183impl<D: DualNum<f64> + Copy, N: Dim> Composition<D, N> for Moles<OVector<D, N>>
185where
186 DefaultAllocator: Allocator<N>,
187{
188 fn into_molefracs<E: Residual<N, D>>(
189 self,
190 eos: &E,
191 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)> {
192 (&self).into_molefracs(eos)
193 }
194}
195
196impl<D: DualNum<f64> + Copy, N: Dim> Composition<D, N> for &Moles<OVector<D, N>>
197where
198 DefaultAllocator: Allocator<N>,
199{
200 fn into_molefracs<E: Residual<N, D>>(
201 self,
202 eos: &E,
203 ) -> FeosResult<(OVector<D, N>, Option<Moles<D>>)> {
204 if eos.components() == self.len() {
205 let total_moles = self.sum();
206 Ok(((self.convert_to(total_moles)), Some(total_moles)))
207 } else {
208 Err(FeosError::UndeterminedState(format!(
209 "The length of the composition vector ({}) does not match the number of components ({})!",
210 self.len(),
211 eos.components()
212 )))
213 }
214 }
215}