1use std::{io, path::Path};
2
3use nalgebra::U2;
4use ndarray::{Array1, Array2, ArrayView1, ArrayView2};
5use serde::{Deserialize, Serialize};
6
7use crate::Residual;
8use crate::ad::Gradient;
9use crate::ad::properties::{BubblePointPressure, DewPointPressure, PropertyAD};
10
11use super::{Dataset, DatasetAD, DatasetRecord, DatasetStorage, ParametersAD};
12
13#[derive(Deserialize, Serialize)]
15pub struct BubblePointRecord {
16 pub temperature_k: f64,
17 pub liquid_molefrac_1: f64,
18 pub bubble_pressure_pa: f64,
19}
20
21impl DatasetRecord for BubblePointRecord {
22 const N_INPUTS: usize = 3;
23
24 fn input(&self, column: usize) -> f64 {
25 match column {
26 0 => self.temperature_k,
27 1 => self.liquid_molefrac_1,
28 2 => self.bubble_pressure_pa,
29 _ => unreachable!("invalid bubble point input column"),
30 }
31 }
32
33 fn target(&self) -> f64 {
34 self.bubble_pressure_pa
35 }
36}
37
38#[derive(Deserialize, Serialize)]
40pub struct DewPointRecord {
41 pub temperature_k: f64,
42 pub vapor_molefrac_1: f64,
43 pub dew_pressure_pa: f64,
44}
45
46impl DatasetRecord for DewPointRecord {
47 const N_INPUTS: usize = 3;
48
49 fn input(&self, column: usize) -> f64 {
50 match column {
51 0 => self.temperature_k,
52 1 => self.vapor_molefrac_1,
53 2 => self.dew_pressure_pa,
54 _ => unreachable!("invalid dew point input column"),
55 }
56 }
57
58 fn target(&self) -> f64 {
59 self.dew_pressure_pa
60 }
61}
62
63macro_rules! binary_properties {
68 ($(
69 $variant:ident {
70 record: $record:ty,
71 property: $prop:ty,
72 default_name: $default:expr,
73 input_names: $inputs:expr,
74 target_name: $target:expr,
75 constructor: $ctor:ident,
76 }
77 ),* $(,)?) => {
78 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
80 pub enum BinaryProperty {
81 $($variant,)*
82 }
83
84 impl BinaryProperty {
85 pub fn default_name(self) -> &'static str {
86 match self { $(Self::$variant => $default,)* }
87 }
88
89 pub fn input_names(self) -> &'static [&'static str] {
90 match self { $(Self::$variant => $inputs,)* }
91 }
92
93 pub fn target_name(self) -> &'static str {
94 match self { $(Self::$variant => $target,)* }
95 }
96
97 fn evaluate_ad<T: ParametersAD<U2>, const P: usize>(
98 self,
99 names: [String; P],
100 parameters: &[f64],
101 inputs: ArrayView2<f64>,
102 ) -> (Array1<f64>, Array2<f64>, Array1<bool>)
103 where
104 T::Lifted<Gradient<P>>: Sync,
105 {
106 match self {
107 $(Self::$variant => <$prop>::evaluate_parallel_derivatives::<T, P>(names, parameters, inputs),)*
108 }
109 }
110
111 fn evaluate<E>(self, eos: &E, inputs: ArrayView2<f64>) -> (Array1<f64>, Array1<bool>)
112 where
113 E: Residual + Sync,
114 {
115 match self {
116 $(Self::$variant => <$prop>::evaluate_parallel(eos, inputs),)*
117 }
118 }
119 }
120
121 impl BinaryDataset {
122 $(
123 pub fn $ctor(records: Vec<$record>) -> Self {
124 Self {
125 property: BinaryProperty::$variant,
126 storage: DatasetStorage::from_records(records),
127 }
128 }
129 )*
130
131 pub fn from_csv(property: BinaryProperty, path: &Path) -> Result<Self, csv::Error> {
132 let storage = match property {
133 $(BinaryProperty::$variant => DatasetStorage::from_csv::<$record>(path)?,)*
134 };
135 Ok(Self { property, storage })
136 }
137
138 pub fn from_reader(
139 property: BinaryProperty,
140 reader: impl io::Read,
141 ) -> Result<Self, csv::Error> {
142 let storage = match property {
143 $(BinaryProperty::$variant => DatasetStorage::from_reader::<$record>(reader)?,)*
144 };
145 Ok(Self { property, storage })
146 }
147 }
148 };
149}
150
151binary_properties! {
152 BubblePointPressure {
153 record: BubblePointRecord,
154 property: BubblePointPressure,
155 default_name: "bubble point pressure",
156 input_names: &["temperature_k", "liquid_molefrac_1"],
157 target_name: "bubble_pressure_pa",
158 constructor: bubble_point_pressure,
159 },
160 DewPointPressure {
161 record: DewPointRecord,
162 property: DewPointPressure,
163 default_name: "dew point pressure",
164 input_names: &["temperature_k", "vapor_molefrac_1"],
165 target_name: "dew_pressure_pa",
166 constructor: dew_point_pressure,
167 },
168}
169
170#[derive(Clone)]
172pub struct BinaryDataset {
173 property: BinaryProperty,
174 storage: DatasetStorage,
175}
176
177impl BinaryDataset {
178 pub fn with_name(mut self, name: impl Into<String>) -> Self {
179 self.storage.set_name(name.into());
180 self
181 }
182
183 pub fn property(&self) -> BinaryProperty {
184 self.property
185 }
186
187 pub fn inputs(&self) -> ArrayView2<'_, f64> {
188 self.storage.inputs()
189 }
190
191 pub fn target(&self) -> ArrayView1<'_, f64> {
192 self.storage.target()
193 }
194
195 pub fn name(&self) -> &str {
196 self.storage.name().unwrap_or(self.property.default_name())
197 }
198
199 pub fn input_names(&self) -> &'static [&'static str] {
200 self.property.input_names()
201 }
202
203 pub fn target_name(&self) -> &'static str {
204 self.property.target_name()
205 }
206}
207
208impl Dataset for BinaryDataset {
209 fn inputs(&self) -> ArrayView2<'_, f64> {
210 self.inputs()
211 }
212
213 fn target(&self) -> ArrayView1<'_, f64> {
214 self.target()
215 }
216
217 fn name(&self) -> &str {
218 self.name()
219 }
220
221 fn input_names(&self) -> &'static [&'static str] {
222 self.input_names()
223 }
224
225 fn target_name(&self) -> &'static str {
226 self.target_name()
227 }
228
229 fn evaluate<E: Residual + Sync>(&self, eos: &E) -> (Array1<f64>, Array1<bool>) {
230 self.property.evaluate(eos, self.inputs())
231 }
232}
233
234impl DatasetAD<2> for BinaryDataset {
235 fn evaluate_ad_const<T: ParametersAD<U2>, const P: usize>(
236 &self,
237 names: [String; P],
238 parameters: &[f64],
239 inputs: ArrayView2<f64>,
240 ) -> (Array1<f64>, Array2<f64>, Array1<bool>)
241 where
242 T::Lifted<Gradient<P>>: Sync,
243 {
244 self.property.evaluate_ad::<T, P>(names, parameters, inputs)
245 }
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251 use std::io::Cursor;
252
253 fn csv(s: &str) -> Cursor<&[u8]> {
254 Cursor::new(s.as_bytes())
255 }
256
257 #[test]
258 fn bubble_point_from_reader() {
259 let data = "\
260temperature_k,liquid_molefrac_1,bubble_pressure_pa
261300.0,0.3,500000.0
262320.0,0.5,800000.0
263";
264 let ds =
265 BinaryDataset::from_reader(BinaryProperty::BubblePointPressure, csv(data)).unwrap();
266
267 assert_eq!(ds.inputs().ncols(), 3);
268 assert_eq!(ds.inputs().nrows(), 2);
269 assert_eq!(ds.inputs()[[0, 0]], 300.0);
270 assert_eq!(ds.inputs()[[0, 1]], 0.3);
271 assert_eq!(ds.inputs()[[0, 2]], 500000.0);
272 assert_eq!(ds.target()[0], 500000.0);
273 assert_eq!(ds.target()[1], 800000.0);
274 assert_eq!(ds.name(), "bubble point pressure");
275 }
276
277 #[test]
278 fn dew_point_from_reader() {
279 let data = "\
280temperature_k,vapor_molefrac_1,dew_pressure_pa
281310.0,0.7,400000.0
282330.0,0.9,700000.0
283";
284 let ds = BinaryDataset::from_reader(BinaryProperty::DewPointPressure, csv(data)).unwrap();
285
286 assert_eq!(ds.inputs().ncols(), 3);
287 assert_eq!(ds.inputs()[[0, 0]], 310.0);
288 assert_eq!(ds.inputs()[[0, 1]], 0.7);
289 assert_eq!(ds.inputs()[[0, 2]], 400000.0);
290 assert_eq!(ds.target()[1], 700000.0);
291 assert_eq!(ds.name(), "dew point pressure");
292 }
293
294 #[test]
295 fn dew_point_pressure_doubles_as_initial_guess() {
296 let records = vec![DewPointRecord {
297 temperature_k: 310.0,
298 vapor_molefrac_1: 0.7,
299 dew_pressure_pa: 400000.0,
300 }];
301 let ds = BinaryDataset::dew_point_pressure(records);
302 assert_eq!(ds.inputs()[[0, 2]], ds.target()[0]);
303 }
304}