Skip to main content

cmtool_data/transitioner/
mod.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::{CMCase, DataError, states::IterationState};
4use std::sync::Arc;
5mod buffer;
6use buffer::{FlowMapBuffer, read_descriptors};
7
8/// A trait describing an abstraction over time-indexed flow-map states.
9///
10/// `FlowMapTransitioner` provides a unified interface to:
11///
12/// * step forward in time (`advance`, `advance_arc`)
13/// * check whether a transition is required (`need_advance`)
14/// * access specific stored states (`get_at`, `get_current`, `get_current_arc`)
15/// * query the number of available flow-map entries (`size`)
16/// * build the transitioner from a `FlowMapBuffer` or from input case data (`new`, `from_case`)
17///
18/// # Semantics
19///
20/// A `FlowMapTransitioner` maintains an time iteration
21/// Calling `advance()` or `advance_arc()` advances this index if `need_advance()`
22/// returns `true`
23//
24///
25/// Implementations may optimize state caching to minimize `Arc` cloning or
26/// recomputation.
27///
28/// # Errors
29///
30/// `from_case()` returns a `DataError` if the underlying descriptors cannot be
31/// read or if the flow-map buffer cannot be constructed.
32///
33/// # Example
34///
35/// ```ignore
36/// let transitioner = MyTransitioner::from_case("root/path", &case)?;
37/// let state = transitioner.advance(12.0, 0.1);
38/// println!("Current state: {:?}", state);
39/// ```
40pub trait FlowMapTransitioner {
41    /// Advances the internal state according to `current_time` and
42    /// `time_step`, and returns a **borrowed** reference to the active
43    /// `IterationState`.
44    ///
45    /// Implementations must not clone or allocate here
46    fn advance(&mut self, current_time: f64, time_step: f64) -> &IterationState;
47
48    /// Same as [`advance`], but returns an owned `Arc<IterationState>`.
49    ///
50    /// Implementations should minimize `Arc` cloning and may cache the
51    /// currently active state to avoid atomic operations inside tight loops.
52    fn advance_arc(&mut self, current_time: f64, time_step: f64) -> Arc<IterationState>;
53
54    /// Returns `true` if the flow-map index should advance when evaluating
55    /// the given `current_time` and `time_step`.
56    ///
57    /// This does not mutate internal state.
58    fn need_advance(&self, current_time: f64, time_step: f64) -> bool;
59
60    /// Retrieves the state at the given buffer index as an owned `Arc`.
61    ///
62    /// Returns `None` if `idx` exceeds the buffer size.
63    fn get_at(&self, idx: usize) -> Option<Arc<IterationState>>;
64
65    /// Returns the current state as an owned `Arc<IterationState>`.
66    fn get_current_arc(&self) -> Arc<IterationState>;
67
68    /// Returns the current state as a borrowed reference.
69    fn get_current(&self) -> &IterationState;
70
71    /// Returns the number of flow-map states stored in this transitioner.
72    fn size(&self) -> usize;
73
74    /// Constructs a new transitioner from the duration of each flow-map (`time_per_flomap`)
75    /// and a validated [`FlowMapBuffer`].
76    ///
77    /// The buffer must already satisfy the invariant that either all entries
78    /// contain gas descriptors or none do.
79    fn new(time_per_flomap: f64, buffer: FlowMapBuffer) -> Self;
80
81    /// Constructs a transitioner by loading flow-map descriptors from disk
82    /// using the provided case definition.
83    ///
84    /// For recursive cases, all flow-map descriptor folders are gathered and
85    /// combined into a multi-entry [`FlowMapBuffer`].
86    /// For non-recursive cases, only the root folder is read.
87    ///
88    /// # Errors
89    /// Returns `DataError` if descriptor reading fails or if the produced
90    /// `FlowMapBuffer` violates flow-map invariants.
91    fn from_case(root: &str, case: &CMCase) -> Result<Self, DataError>
92    where
93        Self: Sized,
94    {
95        let buffer = if case.is_recursive {
96            let folders = case.get_folders(root)?;
97
98            let mut buffers = Vec::with_capacity(folders.len());
99            for folder in folders.iter() {
100                buffers.push(read_descriptors(&format!("{}/{}", root, folder), case)?);
101            }
102            // `None` here means no `i_*` folder was found, or that some of them
103            // carry a gas phase and others do not.
104            FlowMapBuffer::new(buffers).ok_or(DataError::BadData)?
105        } else {
106            let buffer = read_descriptors(root, case)?;
107            FlowMapBuffer::new_unique(buffer)
108        };
109
110        Ok(Self::new(case.time_per_flow_map, buffer))
111    }
112}
113
114///Type of available transitionner
115pub enum TransitionerType {
116    ///Time based discontinous transition, easier to manipulate
117    Discontinuous,
118    ///Index based discontinous transitionner, alway keep current state
119    Simple,
120
121    None,
122}
123
124/// A transitioner that handles **discontinuous transitions** between flow-map states.
125///
126/// Each `IterationState` in `state_buffer` represents a flow-map state, and
127/// consecutive states are separated by a fixed duration `time_per_flomap`.
128///
129/// # Behavior
130///
131/// * The transitioner does **not** maintain an internal time counter;
132///   calling `advance` or `advance_arc` with the same `current_time` multiple
133///   times are idempotent and always returns the correct state corresponding to `current_time`.
134/// * Delta time argument is therefore not used
135/// * Transitions are **discontinuous**: as `current_time` crosses multiples of
136///   `time_per_flomap`, `current_index` jumps directly to the correct state
137///   in `state_buffer`.
138/// * Iteration is **cyclic**: when the `current_index` reaches the last state,
139///   it wraps around to index 0. For example, with 15 states, iteration goes:
140///   `14 -> 0 -> 1 -> ...`.
141/// * `current_index` always points to the active flow-map state based on `current_time`.
142///
143/// # Fields
144///
145/// * `state_buffer` — A `Vec<Arc<IterationState>>` containing all flow-map states.
146/// * `time_per_flomap` — Duration of each flow-map in seconds (or any consistent time unit).
147/// * `current_index` — The index of the currently active flow-map state in `state_buffer`.
148///
149/// # Example
150///
151/// ```ignore
152/// let transitioner = DiscontinuousTransitioner {
153///     state_buffer: vec![Arc::new(state1), Arc::new(state2), Arc::new(state3)],
154///     time_per_flomap: 0.5,
155///     current_index: 0,
156/// };
157///
158/// // current_time = 1.3 -> index = 2 (3rd state)
159/// let state = transitioner.advance(1.3, 0.1);
160///
161/// // current_time = 1.6 -> cycles back to index 0 (if 3 states)
162/// let state = transitioner.advance(1.6, 0.1);
163/// ```
164pub struct DiscontinuousTransitioner {
165    state_buffer: Vec<Arc<IterationState>>,
166    time_per_flomap: f64,
167    current_index: usize,
168}
169
170impl DiscontinuousTransitioner {
171    #[inline(always)]
172    fn index_for_time(&self, t: f64) -> usize {
173        (t / self.time_per_flomap).floor() as usize % self.state_buffer.len()
174    }
175
176    fn get_index_and_state(&mut self, current_time: f64) -> (usize, &Arc<IterationState>) {
177        let index_map = self.index_for_time(current_time);
178        self.current_index = index_map;
179        (index_map, &self.state_buffer[index_map])
180    }
181    fn get_state(&mut self, current_time: f64) -> &Arc<IterationState> {
182        let index_map = self.index_for_time(current_time);
183        self.current_index = index_map;
184        &self.state_buffer[index_map]
185    }
186
187    pub fn advance_mut(
188        &mut self,
189        state: &mut Arc<IterationState>,
190        current_time: f64,
191        _time_step: f64,
192    ) -> bool {
193        let (_, new_arc) = self.get_index_and_state(current_time);
194        let old_ptr = Arc::as_ptr(state);
195        let new_ptr = Arc::as_ptr(new_arc);
196
197        if old_ptr != new_ptr {
198            *state = new_arc.clone();
199            true
200        } else {
201            false
202        }
203    }
204}
205
206impl FlowMapTransitioner for DiscontinuousTransitioner {
207    fn advance(&mut self, current_time: f64, _time_step: f64) -> &IterationState {
208        self.get_state(current_time)
209    }
210
211    fn advance_arc(&mut self, current_time: f64, _time_step: f64) -> Arc<IterationState> {
212        let state = self.get_state(current_time);
213        state.clone() // cheap clone
214    }
215
216    fn need_advance(&self, current_time: f64, _time_step: f64) -> bool {
217        self.state_buffer.len() > 1 && self.index_for_time(current_time) != self.current_index
218    }
219
220    fn size(&self) -> usize {
221        self.state_buffer.len()
222    }
223
224    fn get_current_arc(&self) -> Arc<IterationState> {
225        self.state_buffer[self.current_index].clone()
226    }
227
228    fn get_current(&self) -> &IterationState {
229        &self.state_buffer[self.current_index]
230    }
231
232    fn get_at(&self, idx: usize) -> Option<Arc<IterationState>> {
233        self.state_buffer.get(idx).cloned()
234    }
235
236    fn new(time_per_flomap: f64, buffer: FlowMapBuffer) -> Self {
237        let state_buffer = buffer.into_state_buffer();
238        Self {
239            time_per_flomap,
240            state_buffer,
241            current_index: 0,
242        }
243    }
244}
245
246pub struct SimpleTransitioner {
247    state_buffer: Vec<Arc<IterationState>>,
248    time_per_flomap: f64,
249    remaining_time: f64,
250    current_index: usize,
251}
252
253impl FlowMapTransitioner for SimpleTransitioner {
254    fn advance_arc(&mut self, _current_time: f64, time_step: f64) -> Arc<IterationState> {
255        if self.remaining_time >= self.time_per_flomap {
256            self.current_index = (self.current_index + 1) % self.state_buffer.len();
257            self.remaining_time = 0.;
258        }
259        self.remaining_time += time_step;
260        self.state_buffer[self.current_index].clone()
261    }
262
263    fn get_at(&self, idx: usize) -> Option<Arc<IterationState>> {
264        self.state_buffer.get(idx).cloned()
265    }
266
267    fn advance(&mut self, _current_time: f64, time_step: f64) -> &IterationState {
268        self.remaining_time += time_step;
269
270        if self.remaining_time >= self.time_per_flomap {
271            self.remaining_time -= self.time_per_flomap; // more stable than =0
272            self.current_index = (self.current_index + 1) % self.state_buffer.len();
273        }
274
275        &self.state_buffer[self.current_index]
276    }
277
278    fn need_advance(&self, _current_time: f64, _time_step: f64) -> bool {
279        true //Actually needs to be alsways updated because of remaining_time
280    }
281
282    fn get_current(&self) -> &IterationState {
283        &self.state_buffer[self.current_index]
284    }
285
286    fn get_current_arc(&self) -> Arc<IterationState> {
287        self.state_buffer[self.current_index].clone()
288    }
289
290    fn size(&self) -> usize {
291        self.state_buffer.len()
292    }
293
294    fn new(time_per_flomap: f64, buffer: FlowMapBuffer) -> Self {
295        let state_buffer = buffer.into_state_buffer();
296
297        Self {
298            state_buffer,
299            time_per_flomap,
300            remaining_time: 0.,
301            current_index: 0,
302        }
303    }
304}