Skip to main content

scan_core/program_graph/
run.rs

1use std::ops::{Bound, RangeBounds};
2
3use bumpalo::{Bump, collections::CollectIn};
4use rand::{Rng, rngs::SmallRng};
5
6use super::{
7    Action, Clock, EPSILON, Effect, Location, LocationIdx, PgError, PgGuard, ProgramGraph,
8    Transition, TransitionsIterator, Var,
9};
10use crate::{BooleanExpr, Time, Val, program_graph::TimeRange};
11
12/// Representation of a PG that can be executed transition-by-transition.
13///
14/// The structure of the PG cannot be changed,
15/// meaning that it is not possible to introduce new locations, actions, variables, etc.
16/// Though, this restriction makes it so that cloning the [`ProgramGraphRun`] is cheap,
17/// because only the internal state needs to be duplicated.
18#[derive(Debug)]
19pub struct ProgramGraphRun<'def> {
20    current_states: Vec<Location>,
21    vars: Vec<Val>,
22    clocks: Vec<Time>,
23    def: &'def ProgramGraph,
24    bump: Bump,
25}
26
27impl<'def> Clone for ProgramGraphRun<'def> {
28    fn clone(&self) -> Self {
29        Self {
30            current_states: self.current_states.clone(),
31            vars: self.vars.clone(),
32            clocks: self.clocks.clone(),
33            def: self.def,
34            bump: Bump::new(),
35        }
36    }
37}
38
39impl<'def> ProgramGraphRun<'def> {
40    /// Create a new executions from a given PG definition.
41    pub fn new(program_graph: &'def ProgramGraph) -> Self {
42        Self {
43            current_states: program_graph.initial_states.clone(),
44            vars: program_graph.vars.clone(),
45            clocks: vec![0; program_graph.clocks as usize],
46            def: program_graph,
47            bump: Bump::new(),
48        }
49    }
50
51    /// Returns the current location.
52    ///
53    /// ```
54    /// # use scan_core::program_graph::ProgramGraphBuilder;
55    /// // Create a new PG builder
56    /// let mut pg_builder = ProgramGraphBuilder::new();
57    ///
58    /// // The builder is initialized with an initial location
59    /// let initial_loc = pg_builder.new_initial_location();
60    ///
61    /// // Build the PG from its builder
62    /// // The builder is always guaranteed to build a well-defined PG and building cannot fail
63    /// let pg = pg_builder.build();
64    /// let instance = pg.new_instance();
65    ///
66    /// // Execution starts in the initial location
67    /// assert_eq!(instance.current_states().as_slice(), &[initial_loc]);
68    /// ```
69    #[inline]
70    pub fn current_states(&self) -> &[Location] {
71        &self.current_states
72    }
73
74    #[inline]
75    fn transitions<'a>(
76        &'a self,
77    ) -> TransitionsIterator<'a, impl Iterator<Item = &'a (Action, Vec<Transition>)>> {
78        let iters = self
79            .current_states
80            .iter()
81            .map(|loc| self.def.locations[loc.0 as usize].0.iter())
82            .collect_in::<bumpalo::collections::Vec<_>>(&self.bump)
83            .into_bump_slice_mut();
84        TransitionsIterator::new(iters, &self.bump)
85    }
86
87    /// Iterates over all transitions that can be admitted in the current state.
88    ///
89    /// An admissible transition is characterized by the required action and the post-state
90    /// (the pre-state being necessarily the current state of the machine).
91    /// The guard (if any) is guaranteed to be satisfied.
92    pub fn possible_transitions(
93        &self,
94    ) -> impl Iterator<Item = (Action, impl Iterator<Item = impl Iterator<Item = Location>>)> {
95        self.transitions().map(move |(action, loc_transitions)| {
96            (
97                action,
98                loc_transitions.iter().map(move |transitions| {
99                    transitions
100                        .iter()
101                        .filter(move |(post_state, guard, constraints)| {
102                            self.check_transition(action, *post_state, guard.as_ref(), constraints)
103                        })
104                        .map(|(post_state, ..)| *post_state)
105                }),
106            )
107        })
108    }
109
110    /// Iterates over all transitions that can be admitted in the current state,
111    /// optimized for the special (but common) case in which the state is given by a single location.
112    ///
113    /// An admissible transition is characterized by the required action and the post-state
114    /// (the pre-state being necessarily the current state of the machine).
115    /// The guard (if any) is guaranteed to be satisfied.
116    ///
117    /// Returns error if the state is not given by a single location.
118    pub fn nosync_possible_transitions(
119        &self,
120    ) -> Result<impl Iterator<Item = (Action, impl Iterator<Item = Location>)>, PgError> {
121        if self.current_states.len() == 1 {
122            let current_loc = self.current_states[0];
123            Ok(self.def.locations[current_loc.0 as usize].0.iter().map(
124                move |(action, transitions)| {
125                    (
126                        *action,
127                        transitions
128                            .iter()
129                            .filter(move |(post_state, guard, constraints)| {
130                                self.check_transition(
131                                    *action,
132                                    *post_state,
133                                    guard.as_ref(),
134                                    constraints,
135                                )
136                            })
137                            .map(|(post_state, ..)| *post_state),
138                    )
139                },
140            ))
141        } else {
142            Err(PgError::Sync)
143        }
144    }
145
146    fn check_transition(
147        &self,
148        action: Action,
149        post_state: Location,
150        guard: Option<&BooleanExpr<Var>>,
151        constraints: &[(Clock, TimeRange)],
152    ) -> bool {
153        let (_, ref invariants) = self.def.locations[post_state.0 as usize];
154        if action != EPSILON
155            && let Effect::Effects(_, ref resets) = self.def.effects[action.0 as usize]
156        {
157            self.active_transition(guard, constraints, invariants, resets)
158        } else {
159            self.active_autonomous_transition(guard, constraints, invariants)
160        }
161    }
162
163    fn active_transition(
164        &self,
165        guard: Option<&PgGuard>,
166        constraints: &[(Clock, TimeRange)],
167        invariants: &[(Clock, TimeRange)],
168        resets: &[Clock],
169    ) -> bool {
170        guard.is_none_or(|guard| guard.eval::<SmallRng>(&|var| self.vars[var.0 as usize], None))
171            && constraints.iter().all(|(c, range)| {
172                let time = self.clocks[c.0 as usize];
173                range.contains(&time)
174            })
175            && invariants.iter().all(|(c, range)| {
176                let time = if resets.binary_search(c).is_ok() {
177                    0
178                } else {
179                    self.clocks[c.0 as usize]
180                };
181                range.contains(&time)
182            })
183    }
184
185    #[inline]
186    fn active_autonomous_transition(
187        &self,
188        guard: Option<&PgGuard>,
189        constraints: &[(Clock, TimeRange)],
190        invariants: &[(Clock, TimeRange)],
191    ) -> bool {
192        guard.is_none_or(|guard| guard.eval::<SmallRng>(&|var| self.vars[var.0 as usize], None))
193            && constraints.iter().chain(invariants).all(|(c, range)| {
194                let time = self.clocks[c.0 as usize];
195                range.contains(&time)
196            })
197    }
198
199    fn active_transitions(
200        &self,
201        action: Action,
202        post_states: &[Location],
203        resets: &[Clock],
204    ) -> bool {
205        self.current_states
206            .iter()
207            .zip(post_states)
208            .all(|(current_state, post_state)| {
209                self.def
210                    .guards(*current_state, action, *post_state)
211                    .any(|(guard, constraints)| {
212                        self.active_transition(
213                            guard,
214                            constraints,
215                            &self.def.locations[post_state.0 as usize].1,
216                            resets,
217                        )
218                    })
219            })
220    }
221
222    fn active_autonomous_transitions(&self, post_states: &[Location]) -> bool {
223        self.current_states
224            .iter()
225            .zip(post_states)
226            .all(|(current_state, post_state)| {
227                self.def
228                    .guards(*current_state, EPSILON, *post_state)
229                    .any(|(guard, constraints)| {
230                        self.active_autonomous_transition(
231                            guard,
232                            constraints,
233                            &self.def.locations[post_state.0 as usize].1,
234                        )
235                    })
236            })
237    }
238
239    /// Executes a transition characterized by the argument action and post-state.
240    ///
241    /// Fails if the requested transition is not admissible,
242    /// or if the post-location time invariants are violated.
243    pub fn transition<R: Rng>(
244        &mut self,
245        action: Action,
246        post_states: &[Location],
247        rng: &mut R,
248    ) -> Result<(), PgError> {
249        self.bump.reset();
250        if post_states.len() != self.current_states.len() {
251            return Err(PgError::MismatchingPostStates);
252        }
253        if let Some(ps) = post_states
254            .iter()
255            .find(|ps| ps.0 >= self.def.locations.len() as LocationIdx)
256        {
257            return Err(PgError::MissingLocation(*ps));
258        }
259        if action == EPSILON {
260            if !self.active_autonomous_transitions(post_states) {
261                return Err(PgError::UnsatisfiedGuard);
262            }
263        } else if action.0 >= self.def.effects.len() as LocationIdx {
264            return Err(PgError::MissingAction(action));
265        } else if let Effect::Effects(ref effects, ref resets) = self.def.effects[action.0 as usize]
266        {
267            if self.active_transitions(action, post_states, resets) {
268                effects.iter().for_each(|(var, effect)| {
269                    self.vars[var.0 as usize] =
270                        effect.eval(&|var| self.vars[var.0 as usize], Some(rng))
271                });
272                resets
273                    .iter()
274                    .for_each(|clock| self.clocks[clock.0 as usize] = 0);
275            } else {
276                return Err(PgError::UnsatisfiedGuard);
277            }
278        } else {
279            return Err(PgError::Communication(action));
280        }
281        self.current_states.copy_from_slice(post_states);
282        Ok(())
283    }
284
285    /// Checks if it is possible to wait a given amount of time-units without violating the time invariants.
286    #[inline]
287    pub fn can_wait(&self, delta: Time) -> bool {
288        self.current_states
289            .iter()
290            .flat_map(|current_state| self.def.locations[current_state.0 as usize].1.iter())
291            .all(|(c, range)| {
292                // Invariants need to be satisfied during the whole wait.
293                let start_time = self.clocks[c.0 as usize];
294                let end_time = start_time + delta;
295                // range.contains(&start_time) &&
296                range.contains(&end_time)
297            })
298    }
299
300    /// Waits a given amount of time-units.
301    ///
302    /// Returns error if the waiting would violate the current location's time invariant (if any).
303    #[inline]
304    pub fn wait(&mut self, delta: Time) -> Result<(), PgError> {
305        self.bump.reset();
306        if self.can_wait(delta) {
307            self.clocks.iter_mut().for_each(|t| *t += delta);
308            Ok(())
309        } else {
310            Err(PgError::Invariant)
311        }
312    }
313
314    pub(crate) fn send<'a, R: Rng>(
315        &'a mut self,
316        action: Action,
317        post_states: &[Location],
318        rng: &'a mut R,
319    ) -> Result<Vec<Val>, PgError> {
320        self.bump.reset();
321        if action == EPSILON {
322            Err(PgError::NotSend(action))
323        } else if self.active_transitions(action, post_states, &[]) {
324            if let Effect::Send(effects) = &self.def.effects[action.0 as usize] {
325                let vals = effects
326                    .iter()
327                    .map(|effect| effect.eval(&|var| self.vars[var.0 as usize], Some(rng)))
328                    .collect();
329                self.current_states.copy_from_slice(post_states);
330                Ok(vals)
331            } else {
332                Err(PgError::NotSend(action))
333            }
334        } else {
335            Err(PgError::UnsatisfiedGuard)
336        }
337    }
338
339    pub(crate) fn receive(
340        &mut self,
341        action: Action,
342        post_states: &[Location],
343        vals: &[Val],
344    ) -> Result<(), PgError> {
345        self.bump.reset();
346        if action == EPSILON {
347            Err(PgError::NotReceive(action))
348        } else if self.active_transitions(action, post_states, &[]) {
349            if let Effect::Receive(ref vars) = self.def.effects[action.0 as usize] {
350                // let var_content = self.vars.get_mut(var.0 as usize).expect("variable exists");
351                if vars.len() == vals.len()
352                    && vals.iter().zip(vars).all(|(val, var)| {
353                        self.vars
354                            .get(var.0 as usize)
355                            .expect("variable exists")
356                            .r#type()
357                            == val.r#type()
358                    })
359                {
360                    vals.iter().zip(vars).for_each(|(&val, var)| {
361                        *self.vars.get_mut(var.0 as usize).expect("variable exists") = val
362                    });
363                    self.current_states.copy_from_slice(post_states);
364                    Ok(())
365                } else {
366                    Err(PgError::TypeMismatch)
367                }
368            } else {
369                Err(PgError::NotReceive(action))
370            }
371        } else {
372            Err(PgError::UnsatisfiedGuard)
373        }
374    }
375
376    /// Returns `true` if there is any transition from the current state that will be unlocked at some point in the future,
377    /// either because of a temporal guard on the transition becoming true, or a time invariant on the post-location becoming true.
378    pub fn is_waiting(&self) -> bool {
379        let unsatisfied_lower_bound = |(c, range): &(Clock, TimeRange)| {
380            let time = self.clocks[c.0 as usize];
381            let bound = range.start_bound();
382            match bound {
383                Bound::Included(b) => time < *b,
384                Bound::Excluded(b) => time <= *b,
385                Bound::Unbounded => false,
386            }
387        };
388        let satisfied_upper_bound = |(c, range): &(Clock, TimeRange)| {
389            let time = self.clocks[c.0 as usize];
390            let bound = range.end_bound();
391            match bound {
392                Bound::Included(b) => time <= *b,
393                Bound::Excluded(b) => time < *b,
394                Bound::Unbounded => true,
395            }
396        };
397        self.can_wait(1)
398            && self.transitions().any(move |(_, loc_transitions)| {
399                loc_transitions.iter().any(move |transitions| {
400                    transitions.iter().any(move |(post_state, _, constraints)| {
401                        let invariants = self.def.locations[post_state.0 as usize].1.as_slice();
402                        (constraints.iter().any(unsatisfied_lower_bound)
403                            && constraints.iter().all(satisfied_upper_bound))
404                            || (invariants.iter().any(unsatisfied_lower_bound)
405                                && invariants.iter().all(satisfied_upper_bound))
406                    })
407                })
408            })
409    }
410}