aga8/composition.rs
1//! Gas composition
2
3/// A complete gas composition made up of gas components.
4///
5/// A gas composition contains 21 gas components named by the field names in the struct.
6/// The unit for each component is *mole fraction*, so the sum of all components should be `1.0`.
7/// If the initial sum of components is not `1.0`, you can use the normalize function to scale it to `1.0`.
8///
9/// # Example
10/// ```
11/// let air = aga8::composition::Composition {
12/// nitrogen: 0.78,
13/// oxygen: 0.21,
14/// argon: 0.009,
15/// carbon_dioxide: 0.000_4,
16/// water: 0.000_6,
17/// ..Default::default()
18/// };
19///
20/// assert!((air.sum() - 1.0).abs() < 1.0e-10);
21/// ```
22#[repr(C)]
23#[derive(Default)]
24pub struct Composition {
25 /// Methane CH<sub>4</sub>
26 pub methane: f64,
27 /// Nitrogen N
28 pub nitrogen: f64,
29 /// Carbon Dioxide CO<sub>2</sub>
30 pub carbon_dioxide: f64,
31 /// Ethane C<sub>2</sub>H<sub>6</sub>
32 pub ethane: f64,
33 /// Propane C<sub>3</sub>H<sub>8</sub>
34 pub propane: f64,
35 /// Isobutane C<sub>4</sub>H<sub>10</sub>
36 pub isobutane: f64,
37 /// Butane C<sub>4</sub>H<sub>10</sub>
38 pub n_butane: f64,
39 /// Isopentane C<sub>5</sub>H<sub>12</sub>
40 pub isopentane: f64,
41 /// Pentane C<sub>5</sub>H<sub>12</sub>
42 pub n_pentane: f64,
43 /// Isopentane C<sub>6</sub>H<sub>14</sub>
44 pub hexane: f64,
45 /// Heptane C<sub>7</sub>H<sub>16</sub>
46 pub heptane: f64,
47 /// Octane C<sub>8</sub>H<sub>18</sub>
48 pub octane: f64,
49 /// Nonane C<sub>9</sub>H<sub>20</sub>
50 pub nonane: f64,
51 /// Decane C<sub>10</sub>H<sub>22</sub>
52 pub decane: f64,
53 /// Hydrogen H
54 pub hydrogen: f64,
55 /// Oxygen O
56 pub oxygen: f64,
57 /// Carbon monoxide CO
58 pub carbon_monoxide: f64,
59 /// Water H<sub>2</sub>O
60 pub water: f64,
61 /// Hydrogen sulfide H<sub>2</sub>S
62 pub hydrogen_sulfide: f64,
63 /// Helium He
64 pub helium: f64,
65 /// Argon Ar
66 pub argon: f64,
67}
68
69impl Composition {
70 /// Compute the sum of all components.
71 ///
72 /// # Example
73 /// ```
74 /// let comp = aga8::composition::Composition {
75 /// methane: 50.0,
76 /// ethane: 25.0,
77 /// carbon_dioxide: 25.0,
78 /// ..Default::default()
79 /// };
80 ///
81 /// assert!((comp.sum() - 100.0).abs() < 1.0e-10);
82 /// ```
83 pub fn sum(&self) -> f64 {
84 self.methane
85 + self.nitrogen
86 + self.carbon_dioxide
87 + self.ethane
88 + self.propane
89 + self.isobutane
90 + self.n_butane
91 + self.isopentane
92 + self.n_pentane
93 + self.hexane
94 + self.heptane
95 + self.octane
96 + self.nonane
97 + self.decane
98 + self.hydrogen
99 + self.oxygen
100 + self.carbon_monoxide
101 + self.water
102 + self.hydrogen_sulfide
103 + self.helium
104 + self.argon
105 }
106
107 /// Normalizes the composition sum to 1.0.
108 ///
109 /// # Example
110 /// ```
111 /// let mut comp = aga8::composition::Composition {
112 /// methane: 50.0,
113 /// ethane: 50.0,
114 /// ..Default::default()
115 /// };
116 ///
117 /// comp.normalize();
118 ///
119 /// assert!((comp.ethane - 0.5).abs() < 1.0e-10);
120 /// assert!((comp.methane - 0.5).abs() < 1.0e-10);
121 /// ```
122 pub fn normalize(&mut self) -> Result<(), CompositionError> {
123 let sum = self.sum();
124 if sum > 0.0 {
125 let factor = 1.0 / sum;
126
127 self.methane *= factor;
128 self.nitrogen *= factor;
129 self.carbon_dioxide *= factor;
130 self.ethane *= factor;
131 self.propane *= factor;
132 self.isobutane *= factor;
133 self.n_butane *= factor;
134 self.isopentane *= factor;
135 self.n_pentane *= factor;
136 self.hexane *= factor;
137 self.heptane *= factor;
138 self.octane *= factor;
139 self.nonane *= factor;
140 self.decane *= factor;
141 self.hydrogen *= factor;
142 self.oxygen *= factor;
143 self.carbon_monoxide *= factor;
144 self.water *= factor;
145 self.hydrogen_sulfide *= factor;
146 self.helium *= factor;
147 self.argon *= factor;
148 } else {
149 return Err(CompositionError::Empty);
150 }
151 Ok(())
152 }
153
154 /// Checks that the composition is valid.
155 ///
156 /// # Example
157 /// ```
158 /// let mut comp = aga8::composition::Composition {
159 /// methane: 0.5,
160 /// ethane: 0.5,
161 /// ..Default::default()
162 /// };
163 ///
164 /// assert_eq!(comp.check(), Ok(()));
165 /// ```
166 pub fn check(&self) -> Result<(), CompositionError> {
167 if (self.sum() - 0.0).abs() < 1.0e-10 {
168 return Err(CompositionError::Empty);
169 }
170 if (self.sum() - 1.0).abs() > 1.0e-2 {
171 return Err(CompositionError::BadSum);
172 }
173 Ok(())
174 }
175}
176
177/// Error conditions for composition
178#[repr(C)]
179#[derive(Debug, PartialEq, Eq)]
180pub enum CompositionError {
181 /// Composition is valid
182 Ok = 0,
183 /// Composition is empty, i.e. all component values are zero.
184 Empty,
185 /// The sum of the components is not 1.0000
186 BadSum,
187}
188
189#[cfg(test)]
190mod tests {
191 use super::*;
192
193 #[test]
194 fn valid_is_ok() {
195 let comp = Composition {
196 methane: 0.5,
197 ethane: 0.5,
198 ..Default::default()
199 };
200
201 assert_eq!(comp.check(), Ok(()));
202 }
203
204 #[test]
205 fn empty_is_error() {
206 let comp = Composition {
207 ..Default::default()
208 };
209
210 assert_eq!(comp.check(), Err(CompositionError::Empty));
211 }
212
213 #[test]
214 fn too_big_is_error() {
215 let comp = Composition {
216 ethane: 0.5,
217 methane: 0.6,
218 propane: 0.4,
219 ..Default::default()
220 };
221
222 assert_eq!(comp.check(), Err(CompositionError::BadSum));
223 }
224
225 #[test]
226 fn normalized_is_1() {
227 let mut comp = Composition {
228 methane: 83.0,
229 ethane: 17.0,
230 ..Default::default()
231 };
232
233 comp.normalize().unwrap();
234
235 assert_eq!(comp.sum(), 1.0);
236 }
237
238 #[test]
239 fn normalize_empty_is_error() {
240 let mut comp = Composition {
241 ..Default::default()
242 };
243
244 assert_eq!(comp.normalize(), Err(CompositionError::Empty));
245 }
246}