1use crate::api::{ModelEstimator, PostProcessReader};
2use crate::datamodel::{f_get_probes, make_histogram, read_spatial_model_properties, Results};
3use crate::datamodel::{
4 get_n_export_real, read_avg_model_properties, read_model_mass, read_model_properties,
5 tallies::Tallies, vec_to_array_view2, vec_to_array_view3, Dim, Weight,
6};
7use crate::process::{
8 spatial_average_concentration, variance_concentration, Histogram,
9};
10use crate::{api::Estimator, api::Phase, error::ApiError};
11use ndarray::{s, Array1, Array2, ArrayView2, ArrayView3, Axis};
12
13#[derive(Debug)]
19pub struct PostProcess {
20 results: Results, }
22
23impl PostProcess {
24 pub fn new(folder: &str, root: Option<String>) -> Result<Self, ApiError> {
33 let _root = root.unwrap_or_else(|| "./results/".to_string());
34 let result_path = format!("{}/{}/{}.h5", _root, folder, folder);
35 let main = Results::new(&result_path, &_root, folder)?;
36 Ok(Self { results: main })
37 }
38}
39
40impl PostProcessReader for PostProcess {
41 fn time(&self) -> &[f64] {
42 self.results.main.time()
43 }
44
45 fn weight(&self) -> &Weight {
46 &self.results.main.weight
47 }
48
49 fn get_property_names(&self) -> Vec<String> {
50 self.results.property_name.clone()
51 }
52
53 fn get_spatial_average_mtr(&self, species: usize) -> Result<Array1<f64>, ApiError> {
54 let r = &self.results.main.records;
55 let nt = r.time.len();
56 let dim = &r.dim;
57
58 if let Some(mtr) = &r.mtr {
59 let mtr = vec_to_array_view3(mtr, dim, nt);
60
61 return match mtr.slice(s![.., .., species]).mean_axis(Axis(1)) {
62 Some(avg) => Ok(avg),
63 None => Err(ApiError::ShapeError),
64 };
65 }
66
67 Err(ApiError::RecordsError("mtr".to_string()))
68
69 }
71
72 fn v_liquid(&self) -> ArrayView2<'_, f64> {
73 let nt = self.results.main.records.time.len();
74 let dim = &self.results.main.records.dim;
75 vec_to_array_view2(&self.results.main.records.volume_liquid, nt, dim.0)
76 }
77
78 fn get_variance_concentration(
79 &self,
80 species: usize,
81 phase: Phase,
82 ) -> Result<Array1<f64>, ApiError> {
83 fn process_phase(
85 concentration: &Vec<f64>,
86 volume: &Vec<f64>,
87 nt: usize,
88 dim: &Dim,
89 species: usize,
90 ) -> Array1<f64> {
91 let c: ndarray::ArrayBase<ndarray::ViewRepr<&f64>, ndarray::Dim<[usize; 3]>> =
92 vec_to_array_view3(concentration, dim, nt);
93 let vol = vec_to_array_view2(volume, nt, dim.0);
94 let c_slice = &c.slice(s![.., .., species]);
95
96 variance_concentration(c_slice, &vol)
97 }
98
99 let records = &self.results.main.records;
100 let nt = records.time.len();
101 let dim = &records.dim;
102
103 match phase {
104 Phase::Gas => {
105 if let (Some(c), Some(v)) = (&records.concentration_gas, &records.volume_gas) {
106 return Ok(process_phase(c, v, nt, dim, species));
107 }
108
109 panic!("Gas is not present");
110 }
111 Phase::Liquid => Ok(process_phase(
112 &records.concentration_liquid,
113 &records.volume_liquid,
114 nt,
115 dim,
116 species,
117 )),
118 }
119 }
120
121 fn time_array(&self) -> Array1<f64> {
126 let time = self.results.main.time();
127 Array1::<f64>::from_vec(time.to_vec()) }
129
130 fn get_max_n_export_bio(&self) -> usize {
136 get_n_export_real(self.results.get_files()).unwrap_or(0)
137 }
138
139 fn n_export(&self) -> usize {
145 self.results.main.records.time.len()
146 }
147
148 fn get_concentrations(&self, phase: Phase) -> ArrayView3<f64> {
149 let records = &self.results.main.records;
150 let nt = records.time.len();
151 let dim = &records.dim;
152
153 match phase {
154 Phase::Gas => {
155 if let (Some(c), Some(_v)) = (&records.concentration_gas, &records.volume_gas) {
156 return vec_to_array_view3(c, dim, nt);
157 }
158
159 panic!("Gas is not present");
160 }
161 Phase::Liquid => vec_to_array_view3(&records.concentration_liquid, dim, nt),
162 }
163 }
164
165 fn get_spatial_average_concentration(&self, species: usize, phase: Phase) -> Array1<f64> {
166 fn process_phase(
168 concentration: &Vec<f64>,
169 volume: &Vec<f64>,
170 nt: usize,
171 dim: &Dim,
172 species: usize,
173 ) -> Array1<f64> {
174 let cl = vec_to_array_view3(concentration, dim, nt);
175 let vol = vec_to_array_view2(volume, nt, dim.0);
176 let res = spatial_average_concentration(&cl.slice(s![.., .., species]), &vol);
177
178 res
179 }
180
181 let records = &self.results.main.records;
182 let nt = records.time.len();
183 let dim = &records.dim;
184
185 match phase {
186 Phase::Gas => {
187 if let (Some(c), Some(v)) = (&records.concentration_gas, &records.volume_gas) {
188 return process_phase(c, v, nt, dim, species);
189 }
190
191 panic!("Gas is not present");
192 }
193 Phase::Liquid => process_phase(
194 &records.concentration_liquid,
195 &records.volume_liquid,
196 nt,
197 dim,
198 species,
199 ),
200 }
201 }
202
203 fn get_spatial_average_biomass_concentration(&self) -> Result<Array1<f64>, ApiError> {
204 let num_dimensions = self.results.main.records.dim.0;
205 let nt = self.results.main.records.time.len();
206 let volume =
207 vec_to_array_view2(&self.results.main.records.volume_liquid, nt, num_dimensions);
208 let vtot = volume.sum_axis(Axis(1));
209
210 let mut biomass_matrix = Array1::zeros(nt);
211
212 for i in 0..nt {
213 let m = self.get_properties("mass", i)?;
214 biomass_matrix[i] = m.sum() / vtot[i];
215 }
216
217 Ok(self.results.main.initial.initial_weight * biomass_matrix)
218 }
219
220 fn get_time_average_concentration(
221 &self,
222 species: usize,
223 position: usize,
224 phase: Phase,
225 ) -> Result<Array1<f64>, ApiError> {
226 let r = &self.results.main.records;
227 let nt = r.time.len();
228 let dim = &r.dim;
229
230 let callback = |c: &Vec<f64>| {
231 let cl = vec_to_array_view3(c, dim, nt);
232 cl.slice(s![.., .., species]).mean_axis(Axis(0)).unwrap()
233 };
234
235 match phase {
236 Phase::Liquid => Ok(callback(&r.concentration_liquid)),
237 Phase::Gas => {
238 if let Some(c) = &r.concentration_gas {
239 return Ok(callback(c));
240 }
241
242 Err(ApiError::RecordsError("Gas".to_string()))
243 }
244 }
245 }
246
247 fn get_spatial_average_property(&self, key: &str) -> Result<Array2<f64>, ApiError> {
249 let nt: usize = self.results.main.records.time.len(); let num_dimensions = self.results.main.records.dim.0; let mut biomass_matrix = Array2::zeros((nt, num_dimensions));
254
255 if !self.results.property_name.iter().any(|x| x == key) {
256 return Err(ApiError::KeyError(key.to_string()));
257 }
258
259 read_spatial_model_properties(key, self.results.get_files(), &mut biomass_matrix, nt)?;
260
261 biomass_matrix /= self.get_number_particle();
263
264 Ok(biomass_matrix)
265 }
266
267 fn get_probes(&self) -> Result<Array1<f64>, ApiError> {
268 f_get_probes(&self.results.files)
269 }
270
271 fn get_biomass_concentration(&self) -> Result<Array2<f64>, ApiError> {
276 let nt: usize = self.results.main.records.time.len(); let num_dimensions = self.results.main.records.dim.0; let mut biomass_matrix = Array2::zeros((nt, num_dimensions));
281
282 if !self.results.property_name.iter().any(|x| x == "mass") {
283 return Err(ApiError::KeyError("mass".to_string()));
284 }
285
286 read_model_mass(self.results.get_files(), &mut biomass_matrix, nt)?;
288
289 let volume =
291 vec_to_array_view2(&self.results.main.records.volume_liquid, nt, num_dimensions);
292
293 biomass_matrix = self.results.main.initial.initial_weight * (biomass_matrix / volume);
295
296 Ok(biomass_matrix)
297 }
298
299 fn get_growth_in_number(&self) -> Array1<f64> {
304 self.results.total_particle_repetition.sum_axis(Axis(1))
305 }
306
307 fn get_number_particle(&self) -> &Array2<f64> {
312 &self.results.total_particle_repetition
313 }
314
315 fn get_properties(&self, key: &str, i_export: usize) -> Result<Array1<f64>, ApiError> {
324 if i_export >= self.results.main.records.time.len() {
325 return Err(ApiError::OutOfRange(
326 i_export,
327 self.results.main.records.time.len(),
328 ));
329 }
330
331 if !self.results.property_name.iter().any(|x| x == key) {
332 return Err(ApiError::KeyError(key.to_string()));
333 }
334
335 match read_model_properties(key, self.results.get_files(), i_export) {
336 Ok(res) => Ok(res),
337 Err(e) => Err(ApiError::Io(e)),
339 }
340 }
341
342 fn get_time_population_mean(&self, key: &str) -> Result<Array1<f64>, ApiError> {
350 if !self.results.property_name.iter().any(|x| x == key) {
351 return Err(ApiError::KeyError(key.to_string()));
352 }
353
354 match read_avg_model_properties(key, self.results.get_files(), self.n_export()) {
355 Ok(res) => Ok(res),
356 Err(e) => Err(ApiError::Io(e)),
357 }
358 }
359
360 fn get_histogram_array(
361 &self,
362 n_bins: usize,
363 i_export: usize,
364 key: &str,
365 ) -> Result<(Array1<f64>, Array1<f64>), ApiError> {
366 let (b, c) = self.get_histogram(n_bins, i_export, key)?;
367 let b = Array1::from_vec(b);
368 let c = Array1::from_vec(c);
369 Ok((b, c))
370 }
371
372 fn get_histogram(
373 &self,
374 n_bins: usize,
375 i_export: usize,
376 key: &str,
377 ) -> Result<(Vec<f64>, Vec<f64>), ApiError> {
378 if i_export > self.n_export() {
379 return Err(ApiError::OutOfRange(i_export, self.n_export()));
380 }
381 if !self.results.property_name.iter().any(|x| x == key) {
382 return Err(ApiError::KeyError(key.to_string()));
383 }
384 let mut hist = Histogram::new(n_bins);
386
387 if let Err(e) = make_histogram(self.results.get_files(), i_export, key, &mut hist) {
388 return Err(ApiError::Io(e));
389 }
390
391 let b = hist.get_bins().to_vec();
392 let c = hist.get_counts().to_vec();
393 Ok((b, c))
394 }
395
396 fn get_population_mean(&self, key: &str, i_export: usize) -> Result<f64, ApiError> {
397 if i_export >= self.results.main.records.time.len() {
399 return Err(ApiError::OutOfRange(
400 i_export,
401 self.results.main.records.time.len(),
402 ));
403 }
404 if !self.results.property_name.iter().any(|x| x == key) {
405 return Err(ApiError::KeyError(key.to_string()));
406 }
407
408 match read_model_properties(key, self.results.get_files(), i_export) {
409 Ok(res) => res
410 .mean()
411 .ok_or(ApiError::Default("get_population_mean".to_string())),
412 Err(e) => Err(ApiError::Io(e)),
413 }
414 }
415 fn tallies(&self) -> Option<&Tallies> {
416 self.results.main.records.tallies.as_ref()
417 }
418}
419
420impl ModelEstimator for PostProcess {
421 fn mu_direct(&self) -> Result<Array1<f64>, ApiError> {
422 match self.weight() {
423 Weight::Single(_) => {
424 let nt = self.results.main.records.time.len();
425 let time = self.time();
426
427 let mu_functor = |i: usize, j: usize, im: usize| -> Result<f64, ApiError> {
428 let mass_i = self.get_properties("mass", i)?;
429 let mass_mi = self.get_properties("mass", j)?;
430 let mass_tot_i = self.get_properties("mass", im)?.sum();
431 let dm: f64 = mass_i.sum() - mass_mi.sum();
432 let dt = time[i] - time[j];
433 Ok(dm / dt / mass_tot_i)
434 };
435
436 let mut mu = Array1::zeros(nt);
437
438 mu[0] = mu_functor(1, 0, 0)?; for i in 1..nt - 1 {
441 mu[i] = mu_functor(i + 1, i - 1, i)?; }
443 mu[nt - 1] = mu_functor(nt - 1, nt - 2, nt - 1)?; Ok(mu)
446 }
447 Weight::Multiple(_) => todo!("Mu with different weight"),
448 }
449 }
450
451 fn estimate(&self, etype: Estimator, key: &str, i_export: usize) -> Result<f64, ApiError> {
452 crate::process::estimate(etype, self.weight(), &self.get_properties(key, i_export)?)
453 }
454
455 fn estimate_time(&self, etype: Estimator, key: &str) -> Result<Array1<f64>, ApiError> {
456 let nt = self.results.main.records.time.len();
457 let mut estimator = Array1::<f64>::zeros(nt);
458 for i in 0..nt {
459 estimator[i] = self.estimate(etype, key, i)?;
460 }
461 Ok(estimator)
462 }
463}