Skip to main content

use_simulation_result/
lib.rs

1#![forbid(unsafe_code)]
2//! Summary helpers for finite simulation result series.
3//!
4//! The crate turns a sequence of finite `f64` values into a reusable summary
5//! with simple aggregate helpers.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_simulation_result::summarize;
11//!
12//! let result = summarize(&[1.0, 2.0, 1.5, 3.0]).unwrap();
13//!
14//! assert_eq!(result.final_value, 3.0);
15//! assert_eq!(result.steps, 3);
16//! assert_eq!(result.range(), 2.0);
17//! ```
18
19#[derive(Debug, Clone, PartialEq)]
20pub struct SimulationResult {
21    pub values: Vec<f64>,
22    pub final_value: f64,
23    pub min_value: f64,
24    pub max_value: f64,
25    pub steps: usize,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub enum SimulationResultError {
30    EmptySeries,
31    NonFiniteValue,
32}
33
34impl SimulationResult {
35    pub fn mean(&self) -> f64 {
36        self.values.iter().sum::<f64>() / self.values.len() as f64
37    }
38
39    pub fn range(&self) -> f64 {
40        self.max_value - self.min_value
41    }
42}
43
44pub fn summarize(values: &[f64]) -> Result<SimulationResult, SimulationResultError> {
45    if values.is_empty() {
46        return Err(SimulationResultError::EmptySeries);
47    }
48
49    if values.iter().any(|value| !value.is_finite()) {
50        return Err(SimulationResultError::NonFiniteValue);
51    }
52
53    let mut min_value = values[0];
54    let mut max_value = values[0];
55    for value in values.iter().copied().skip(1) {
56        if value < min_value {
57            min_value = value;
58        }
59        if value > max_value {
60            max_value = value;
61        }
62    }
63
64    Ok(SimulationResult {
65        values: values.to_vec(),
66        final_value: *values.last().unwrap(),
67        min_value,
68        max_value,
69        steps: values.len() - 1,
70    })
71}
72
73#[cfg(test)]
74mod tests {
75    use super::{SimulationResultError, summarize};
76
77    #[test]
78    fn summarizes_finite_series() {
79        let result = summarize(&[1.0, 2.0, 1.5, 3.0]).unwrap();
80
81        assert_eq!(result.final_value, 3.0);
82        assert_eq!(result.min_value, 1.0);
83        assert_eq!(result.max_value, 3.0);
84        assert_eq!(result.steps, 3);
85        assert_eq!(result.mean(), 1.875);
86        assert_eq!(result.range(), 2.0);
87    }
88
89    #[test]
90    fn handles_single_value_series() {
91        let result = summarize(&[4.0]).unwrap();
92
93        assert_eq!(result.steps, 0);
94        assert_eq!(result.range(), 0.0);
95        assert_eq!(result.mean(), 4.0);
96    }
97
98    #[test]
99    fn rejects_invalid_series() {
100        assert_eq!(summarize(&[]), Err(SimulationResultError::EmptySeries));
101        assert_eq!(
102            summarize(&[1.0, f64::NAN]),
103            Err(SimulationResultError::NonFiniteValue)
104        );
105    }
106}