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}