Skip to main content

use_state_step/
lib.rs

1#![forbid(unsafe_code)]
2//! Explicit state stepping helpers for finite simulations.
3//!
4//! The crate models each update as a concrete `StateStep` so simulations can
5//! retain both the previous and next state for later inspection.
6//!
7//! # Examples
8//!
9//! ```rust
10//! use use_state_step::{apply_steps, states};
11//!
12//! let path = states(1.0, &[0.5, -0.25]).unwrap();
13//! let steps = apply_steps(1.0, &[0.5, -0.25]).unwrap();
14//!
15//! assert_eq!(path, vec![1.0, 1.5, 1.25]);
16//! assert_eq!(steps[1].delta, -0.25);
17//! ```
18
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub struct StateStep {
21    pub step_index: usize,
22    pub previous_state: f64,
23    pub next_state: f64,
24    pub delta: f64,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum StateStepError {
29    NonFiniteState,
30    NonFiniteDelta,
31}
32
33pub fn step_state(
34    previous_state: f64,
35    delta: f64,
36    step_index: usize,
37) -> Result<StateStep, StateStepError> {
38    if !previous_state.is_finite() {
39        return Err(StateStepError::NonFiniteState);
40    }
41
42    if !delta.is_finite() {
43        return Err(StateStepError::NonFiniteDelta);
44    }
45
46    let next_state = previous_state + delta;
47    if !next_state.is_finite() {
48        return Err(StateStepError::NonFiniteState);
49    }
50
51    Ok(StateStep {
52        step_index,
53        previous_state,
54        next_state,
55        delta,
56    })
57}
58
59pub fn apply_steps(initial_state: f64, deltas: &[f64]) -> Result<Vec<StateStep>, StateStepError> {
60    if !initial_state.is_finite() {
61        return Err(StateStepError::NonFiniteState);
62    }
63
64    let mut current_state = initial_state;
65    let mut steps = Vec::with_capacity(deltas.len());
66
67    for (step_index, delta) in deltas.iter().copied().enumerate() {
68        let step = step_state(current_state, delta, step_index)?;
69        current_state = step.next_state;
70        steps.push(step);
71    }
72
73    Ok(steps)
74}
75
76pub fn states(initial_state: f64, deltas: &[f64]) -> Result<Vec<f64>, StateStepError> {
77    let steps = apply_steps(initial_state, deltas)?;
78    let mut values = Vec::with_capacity(steps.len() + 1);
79    values.push(initial_state);
80    values.extend(steps.iter().map(|step| step.next_state));
81    Ok(values)
82}
83
84#[cfg(test)]
85mod tests {
86    use super::{StateStepError, apply_steps, states, step_state};
87
88    #[test]
89    fn steps_state_forward() {
90        let step = step_state(1.0, 0.5, 0).unwrap();
91
92        assert_eq!(step.previous_state, 1.0);
93        assert_eq!(step.next_state, 1.5);
94        assert_eq!(step.delta, 0.5);
95    }
96
97    #[test]
98    fn applies_multiple_steps() {
99        let steps = apply_steps(1.0, &[0.5, -0.25, 1.0]).unwrap();
100
101        assert_eq!(steps.len(), 3);
102        assert_eq!(steps[2].next_state, 2.25);
103        assert_eq!(
104            states(1.0, &[0.5, -0.25, 1.0]).unwrap(),
105            vec![1.0, 1.5, 1.25, 2.25]
106        );
107    }
108
109    #[test]
110    fn allows_empty_step_lists() {
111        assert_eq!(apply_steps(3.0, &[]).unwrap(), Vec::new());
112        assert_eq!(states(3.0, &[]).unwrap(), vec![3.0]);
113    }
114
115    #[test]
116    fn rejects_invalid_inputs() {
117        assert_eq!(
118            step_state(f64::NAN, 1.0, 0),
119            Err(StateStepError::NonFiniteState)
120        );
121        assert_eq!(
122            step_state(1.0, f64::NAN, 0),
123            Err(StateStepError::NonFiniteDelta)
124        );
125        assert_eq!(
126            apply_steps(f64::INFINITY, &[1.0]),
127            Err(StateStepError::NonFiniteState)
128        );
129    }
130}