Skip to main content

scan_core/
program_graph.rs

1//! Implementation of the PG model of computation.
2//!
3//! A _Program Graph_ is given by:
4//!
5//! - a finite set `L` of _locations_;
6//! - a finite set `A` of _actions_;
7//! - a finite set `V` of _typed variables_;
8//! - a _transition relation_ that associates pairs of locations (pre-location and post-location) and an action with a Boolean expression (the _guard_ of the transition);
9//! - for each actions, a set of _effects_, i.e., a variable `x` from `V` and an expression in the variables of `V` of the same type as `x`.
10//!
11//! The state of a PG is given by its current location and the value assigned to each variable.
12//! The PG's state evolves by non-deterministically choosing a transition whose pre-state is the current state,
13//! and whose guard expression evaluates to `true`.
14//! Then, the post-state of the chosen transition becomes the current state of the PG.
15//! Finally, the effects of the transition's associated action are applied in order,
16//! by assigning each effect's variable the value of the effect's expression evaluation.
17//!
18//! A PG is represented by a [`ProgramGraph`] and defined through a [`ProgramGraphBuilder`],
19//! by adding, one at a time, new locations, actions, effects, guards and transitions.
20//! Then, the [`ProgramGraph`] is built from the [`ProgramGraphBuilder`]
21//! and can be executed by performing transitions,
22//! though the structure of the PG itself can no longer be altered.
23//!
24//! ```
25//! # use scan_core::program_graph::{ProgramGraphBuilder, Location};
26//! # use smallvec::smallvec;
27//! // Create a new PG builder
28//! let mut pg_builder = ProgramGraphBuilder::new();
29//!
30//! // The builder is initialized with an initial location
31//! let initial_loc = pg_builder.new_initial_location();
32//!
33//! // Create a new action
34//! let action = pg_builder.new_action();
35//!
36//! // Create a new location
37//! let post_loc = pg_builder.new_location();
38//!
39//! // Add a transition (the guard is optional, and None is equivalent to the guard always being true)
40//! let result = pg_builder.add_transition(initial_loc, action, post_loc, None);
41//!
42//! // This can only fail if the builder does not recognize either the locations
43//! // or the action defining the transition
44//! result.expect("both the initial location and the action belong to the PG");
45//!
46//! // Build the PG from its builder
47//! // The builder is always guaranteed to build a well-defined PG and building cannot fail
48//! let pg = pg_builder.build();
49//! let mut instance = pg.new_instance();
50//!
51//! // Execution starts in the initial location
52//! assert_eq!(instance.current_states().as_slice(), &[initial_loc]);
53//!
54//! // Compute the possible transitions on the PG
55//! {
56//!     let mut iter = instance .possible_transitions();
57//!     let (act, mut trans) = iter.next().unwrap();
58//!     assert_eq!(act, action);
59//!     let post_locs: Vec<Location> = trans.next().unwrap().collect();
60//!     assert_eq!(post_locs, vec![post_loc]);
61//!     assert!(iter.next().is_none());
62//! }
63//!
64//! // Perform a transition
65//! # use rand::{Rng, SeedableRng};
66//! # use rand::rngs::SmallRng;
67//! let mut rng: SmallRng = rand::make_rng();
68//! let result = instance .transition(action, &[post_loc], &mut rng);
69//!
70//! // Performing a transition can fail, in particular, if the transition was not allowed
71//! result.expect("The transition from the initial location onto itself is possible");
72//!
73//! // There are no more possible transitions
74//! assert!(instance.possible_transitions().next().is_none());
75//!
76//! // Attempting to transition results in an error
77//! instance.transition(action, &[post_loc], &mut rng).expect_err("The transition is not possible");
78//! ```
79
80mod builder;
81
82use crate::{DummyRng, Time, grammar::*};
83pub use builder::*;
84use rand::{Rng, SeedableRng, seq::IteratorRandom};
85use smallvec::SmallVec;
86use thiserror::Error;
87
88/// The index for [`Location`]s in a [`ProgramGraph`].
89pub type LocationIdx = u32;
90
91/// An indexing object for locations in a PG.
92///
93/// These cannot be directly created or manipulated,
94/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
95#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
96pub struct Location(LocationIdx);
97
98/// The index for [`Action`]s in a [`ProgramGraph`].
99pub type ActionIdx = u32;
100
101/// An indexing object for actions in a PG.
102///
103/// These cannot be directly created or manipulated,
104/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
105#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
106
107pub struct Action(ActionIdx);
108
109impl From<Action> for ActionIdx {
110    #[inline]
111    fn from(val: Action) -> Self {
112        val.0
113    }
114}
115
116/// Epsilon action to enable autonomous transitions.
117/// It cannot have effects.
118pub(crate) const EPSILON: Action = Action(ActionIdx::MAX);
119
120/// An indexing object for typed variables in a PG.
121///
122/// These cannot be directly created or manipulated,
123/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
124#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
125pub struct Var(u16);
126
127/// An indexing object for clocks in a PG.
128///
129/// These cannot be directly created or manipulated,
130/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
131#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
132pub struct Clock(u16);
133
134/// A time constraint given by a clock and, optionally, a lower bound and/or an upper bound.
135pub type TimeConstraint = (Clock, Option<Time>, Option<Time>);
136
137/// An expression using PG's [`Var`] as variables.
138pub type PgExpression = Expression<Var>;
139
140/// A Boolean expression over [`Var`] variables.
141pub type PgGuard = BooleanExpr<Var>;
142
143/// The error type for operations with [`ProgramGraphBuilder`]s and [`ProgramGraph`]s.
144#[derive(Debug, Clone, Copy, Error)]
145pub enum PgError {
146    /// There is no such action in the PG.
147    #[error("action {0:?} does not belong to this program graph")]
148    MissingAction(Action),
149    /// There is no such clock in the PG.
150    #[error("clock {0:?} does not belong to this program graph")]
151    MissingClock(Clock),
152    /// There is no such location in the PG.
153    #[error("location {0:?} does not belong to this program graph")]
154    MissingLocation(Location),
155    /// There is no such variable in the PG.
156    #[error("location {0:?} does not belong to this program graph")]
157    MissingVar(Var),
158    /// The PG does not allow this transition.
159    #[error("there is no such transition")]
160    MissingTransition,
161    /// Types that should be matching are not,
162    /// or are not compatible with each other.
163    #[error("type mismatch")]
164    TypeMismatch,
165    /// Transition's guard is not satisfied.
166    #[error("the guard has not been satisfied")]
167    UnsatisfiedGuard,
168    /// The tuple has no component for such index.
169    #[error("the tuple has no {0} component")]
170    MissingComponent(usize),
171    /// Cannot add effects to a Receive action.
172    #[error("cannot add effects to a Receive action")]
173    EffectOnReceive,
174    /// Cannot add effects to a Send action.
175    #[error("cannot add effects to a Send action")]
176    EffectOnSend,
177    /// This action is a communication (either Send or Receive).
178    #[error("{0:?} is a communication (either Send or Receive)")]
179    Communication(Action),
180    /// Mismatching (i.e., wrong number) post states of transition.
181    #[error("Mismatching (i.e., wrong number) post states of transition")]
182    MismatchingPostStates,
183    /// The action is a not a Send communication.
184    #[error("{0:?} is a not a Send communication")]
185    NotSend(Action),
186    /// The action is a not a Receive communication.
187    #[error("{0:?} is a not a Receive communication")]
188    NotReceive(Action),
189    /// The epsilon action has no effects.
190    #[error("The epsilon action has no effects")]
191    NoEffects,
192    /// A time invariant is not satisfied.
193    #[error("A time invariant is not satisfied")]
194    Invariant,
195    /// A type error
196    #[error("type error")]
197    Type(#[source] TypeError),
198}
199
200#[derive(Debug, Clone)]
201enum Effect {
202    // NOTE: Could use a SmallVec for clock resets
203    Effects(Vec<(Var, Expression<Var>)>, Vec<Clock>),
204    Send(SmallVec<[Expression<Var>; 2]>),
205    Receive(SmallVec<[Var; 2]>),
206}
207
208type LocationData = (Vec<(Action, Vec<Transition>)>, Vec<TimeConstraint>);
209
210/// A definition object for a PG.
211/// It represents the abstract definition of a PG.
212///
213/// The only way to produce a [`ProgramGraph`] is through a [`ProgramGraphBuilder`].
214/// This guarantees that there are no type errors involved in the definition of action's effects and transitions' guards,
215/// and thus the PG will always be in a consistent state.
216///
217/// The only way to execute the [`ProgramGraph`] is to generate a new [`ProgramGraphRun`] through [`ProgramGraph::new_instance`].
218/// The [`ProgramGraphRun`] cannot outlive its [`ProgramGraph`], as it holds references to it.
219/// This allows to cheaply generate multiple [`ProgramGraphRun`]s from the same [`ProgramGraph`].
220///
221/// Example:
222///
223/// ```
224/// # use scan_core::program_graph::ProgramGraphBuilder;
225/// # use rand::rngs::SmallRng;
226/// # use rand::SeedableRng;
227/// // Create and populate a PG builder object
228/// let mut pg_builder = ProgramGraphBuilder::new();
229/// let initial = pg_builder.new_initial_location();
230/// pg_builder.add_autonomous_transition(initial, initial, None).expect("add transition");
231///
232/// // Build the builder object to get a PG definition object.
233/// let pg_def = pg_builder.build();
234///
235/// // Instantiate a PG with the previously built definition.
236/// let mut pg = pg_def.new_instance();
237///
238/// // Perform the (unique) active transition available.
239/// let (e, mut post_locs) = pg.possible_transitions().last().expect("autonomous transition");
240/// let post_loc = post_locs.last().expect("post location").last().expect("post location");
241/// assert_eq!(post_loc, initial);
242/// let mut rng: SmallRng = rand::make_rng();
243/// pg.transition(e, &[initial], &mut rng).expect("transition is active");
244/// ```
245#[derive(Debug, Clone)]
246pub struct ProgramGraph {
247    initial_states: SmallVec<[Location; 8]>,
248    effects: Vec<Effect>,
249    locations: Vec<LocationData>,
250    // Time invariants of each location
251    vars: Vec<Val>,
252    // Number of clocks
253    clocks: u16,
254}
255
256impl ProgramGraph {
257    /// Creates a new [`ProgramGraphRun`] which allows to execute the PG as defined.
258    ///
259    /// The new instance borrows the caller to refer to the PG definition without copying its data,
260    /// so that spawning instances is (relatively) inexpensive.
261    pub fn new_instance<'def>(&'def self) -> ProgramGraphRun<'def> {
262        ProgramGraphRun {
263            current_states: self.initial_states.clone(),
264            vars: self.vars.clone(),
265            def: self,
266            clocks: vec![0; self.clocks as usize],
267        }
268    }
269
270    // Returns transition's guard.
271    // Panics if the pre- or post-state do not exist.
272    #[inline]
273    fn guards(
274        &self,
275        pre_state: Location,
276        action: Action,
277        post_state: Location,
278    ) -> impl Iterator<Item = (Option<&PgGuard>, &[TimeConstraint])> {
279        let a_transitions = self.locations[pre_state.0 as usize].0.as_slice();
280        a_transitions
281            .binary_search_by_key(&action, |&(a, ..)| a)
282            .into_iter()
283            .flat_map(move |transitions_idx| {
284                let post_idx_lb = a_transitions[transitions_idx]
285                    .1
286                    .partition_point(|&(p, ..)| p < post_state);
287                a_transitions[transitions_idx].1[post_idx_lb..]
288                    .iter()
289                    .take_while(move |&&(p, ..)| p == post_state)
290                    .map(|(_, g, c)| (g.as_ref(), c.as_slice()))
291            })
292    }
293}
294
295/// Representation of a PG that can be executed transition-by-transition.
296///
297/// The structure of the PG cannot be changed,
298/// meaning that it is not possible to introduce new locations, actions, variables, etc.
299/// Though, this restriction makes it so that cloning the [`ProgramGraphRun`] is cheap,
300/// because only the internal state needs to be duplicated.
301#[derive(Debug, Clone)]
302pub struct ProgramGraphRun<'def> {
303    current_states: SmallVec<[Location; 8]>,
304    vars: Vec<Val>,
305    clocks: Vec<Time>,
306    def: &'def ProgramGraph,
307}
308
309impl<'def> ProgramGraphRun<'def> {
310    /// Returns the current location.
311    ///
312    /// ```
313    /// # use scan_core::program_graph::ProgramGraphBuilder;
314    /// // Create a new PG builder
315    /// let mut pg_builder = ProgramGraphBuilder::new();
316    ///
317    /// // The builder is initialized with an initial location
318    /// let initial_loc = pg_builder.new_initial_location();
319    ///
320    /// // Build the PG from its builder
321    /// // The builder is always guaranteed to build a well-defined PG and building cannot fail
322    /// let pg = pg_builder.build();
323    /// let instance = pg.new_instance();
324    ///
325    /// // Execution starts in the initial location
326    /// assert_eq!(instance.current_states().as_slice(), &[initial_loc]);
327    /// ```
328    #[inline]
329    pub fn current_states(&self) -> &SmallVec<[Location; 8]> {
330        &self.current_states
331    }
332
333    /// Iterates over all transitions that can be admitted in the current state.
334    ///
335    /// An admissible transition is characterized by the required action and the post-state
336    /// (the pre-state being necessarily the current state of the machine).
337    /// The guard (if any) is guaranteed to be satisfied.
338    pub fn possible_transitions(
339        &self,
340    ) -> impl Iterator<Item = (Action, impl Iterator<Item = impl Iterator<Item = Location>>)> {
341        self.current_states
342            .first()
343            .into_iter()
344            .flat_map(|loc| {
345                self.def.locations[loc.0 as usize]
346                    .0
347                    .iter()
348                    .map(|&(action, ..)| action)
349            })
350            .chain(
351                self.current_states
352                    .is_empty()
353                    .then(|| (0..self.def.effects.len() as ActionIdx).map(Action))
354                    .into_iter()
355                    .flatten(),
356            )
357            .map(|action| (action, self.possible_transitions_action(action)))
358    }
359
360    #[inline]
361    fn possible_transitions_action(
362        &self,
363        action: Action,
364    ) -> impl Iterator<Item = impl Iterator<Item = Location>> {
365        self.current_states
366            .iter()
367            .map(move |&loc| self.possible_transitions_action_loc(action, loc))
368    }
369
370    fn possible_transitions_action_loc(
371        &self,
372        action: Action,
373        current_state: Location,
374    ) -> impl Iterator<Item = Location> {
375        let mut last_post_state: Option<Location> = None;
376        self.def.locations[current_state.0 as usize]
377            .0
378            .binary_search_by_key(&action, |&(a, ..)| a)
379            .into_iter()
380            .flat_map(move |action_idx| {
381                self.def.locations[current_state.0 as usize].0[action_idx]
382                    .1
383                    .iter()
384                    .filter_map(move |(post_state, guard, constraints)| {
385                        // prevent post_states to be duplicated wastefully
386                        if last_post_state.is_some_and(|s| s == *post_state) {
387                            return None;
388                        }
389                        let (_, ref invariants) = self.def.locations[post_state.0 as usize];
390                        if if action == EPSILON {
391                            self.active_autonomous_transition(
392                                guard.as_ref(),
393                                constraints,
394                                invariants,
395                            )
396                        } else {
397                            match self.def.effects[action.0 as usize] {
398                                Effect::Effects(_, ref resets) => self.active_transition(
399                                    guard.as_ref(),
400                                    constraints,
401                                    invariants,
402                                    resets,
403                                ),
404                                Effect::Send(_) | Effect::Receive(_) => self
405                                    .active_autonomous_transition(
406                                        guard.as_ref(),
407                                        constraints,
408                                        invariants,
409                                    ),
410                            }
411                        } {
412                            last_post_state = Some(*post_state);
413                            last_post_state
414                        } else {
415                            None
416                        }
417                    })
418            })
419    }
420
421    pub(crate) fn nosync_possible_transitions(
422        &self,
423    ) -> impl Iterator<Item = (Action, impl Iterator<Item = Location>)> {
424        assert_eq!(self.current_states.len(), 1);
425        let current_loc = self.current_states[0];
426        let mut last_post_state: Option<Location> = None;
427        self.def.locations[current_loc.0 as usize]
428            .0
429            .iter()
430            .map(move |(action, transitions)| {
431                (
432                    *action,
433                    transitions
434                        .iter()
435                        .filter_map(move |(post_state, guard, constraints)| {
436                            // prevent post_states to be duplicated wastefully
437                            if last_post_state.is_some_and(|s| s == *post_state) {
438                                return None;
439                            }
440                            let (_, ref invariants) = self.def.locations[post_state.0 as usize];
441                            if if *action == EPSILON {
442                                self.active_autonomous_transition(
443                                    guard.as_ref(),
444                                    constraints,
445                                    invariants,
446                                )
447                            } else {
448                                match self.def.effects[action.0 as usize] {
449                                    Effect::Effects(_, ref resets) => self.active_transition(
450                                        guard.as_ref(),
451                                        constraints,
452                                        invariants,
453                                        resets,
454                                    ),
455                                    Effect::Send(_) | Effect::Receive(_) => self
456                                        .active_autonomous_transition(
457                                            guard.as_ref(),
458                                            constraints,
459                                            invariants,
460                                        ),
461                                }
462                            } {
463                                last_post_state = Some(*post_state);
464                                last_post_state
465                            } else {
466                                None
467                            }
468                        }),
469                )
470            })
471    }
472
473    #[inline]
474    fn active_transition(
475        &self,
476        guard: Option<&PgGuard>,
477        constraints: &[TimeConstraint],
478        invariants: &[TimeConstraint],
479        resets: &[Clock],
480    ) -> bool {
481        guard.is_none_or(|guard| {
482            // TODO FIXME: is there a way to avoid creating a dummy RNG?
483            guard.eval(&|var| self.vars[var.0 as usize], &mut DummyRng)
484        }) && constraints.iter().all(|(c, l, u)| {
485            let time = self.clocks[c.0 as usize];
486            l.is_none_or(|l| l <= time) && u.is_none_or(|u| time < u)
487        }) && invariants.iter().all(|(c, l, u)| {
488            let time = if resets.binary_search(c).is_ok() {
489                0
490            } else {
491                self.clocks[c.0 as usize]
492            };
493            l.is_none_or(|l| l <= time) && u.is_none_or(|u| time < u)
494        })
495    }
496
497    #[inline]
498    fn active_autonomous_transition(
499        &self,
500        guard: Option<&PgGuard>,
501        constraints: &[TimeConstraint],
502        invariants: &[TimeConstraint],
503    ) -> bool {
504        guard.is_none_or(|guard| {
505            // TODO FIXME: is there a way to avoid creating a dummy RNG?
506            guard.eval(&|var| self.vars[var.0 as usize], &mut DummyRng)
507        }) && constraints.iter().chain(invariants).all(|(c, l, u)| {
508            let time = self.clocks[c.0 as usize];
509            l.is_none_or(|l| l <= time) && u.is_none_or(|u| time < u)
510        })
511    }
512
513    fn active_transitions(
514        &self,
515        action: Action,
516        post_states: &[Location],
517        resets: &[Clock],
518    ) -> bool {
519        self.current_states
520            .iter()
521            .zip(post_states)
522            .all(|(current_state, post_state)| {
523                self.def
524                    .guards(*current_state, action, *post_state)
525                    .any(|(guard, constraints)| {
526                        self.active_transition(
527                            guard,
528                            constraints,
529                            &self.def.locations[post_state.0 as usize].1,
530                            resets,
531                        )
532                    })
533            })
534    }
535
536    fn active_autonomous_transitions(&self, post_states: &[Location]) -> bool {
537        self.current_states
538            .iter()
539            .zip(post_states)
540            .all(|(current_state, post_state)| {
541                self.def
542                    .guards(*current_state, EPSILON, *post_state)
543                    .any(|(guard, constraints)| {
544                        self.active_autonomous_transition(
545                            guard,
546                            constraints,
547                            &self.def.locations[post_state.0 as usize].1,
548                        )
549                    })
550            })
551    }
552
553    /// Executes a transition characterized by the argument action and post-state.
554    ///
555    /// Fails if the requested transition is not admissible,
556    /// or if the post-location time invariants are violated.
557    pub fn transition<R: Rng>(
558        &mut self,
559        action: Action,
560        post_states: &[Location],
561        rng: &mut R,
562    ) -> Result<(), PgError> {
563        if post_states.len() != self.current_states.len() {
564            return Err(PgError::MismatchingPostStates);
565        }
566        if let Some(ps) = post_states
567            .iter()
568            .find(|ps| ps.0 >= self.def.locations.len() as LocationIdx)
569        {
570            return Err(PgError::MissingLocation(*ps));
571        }
572        if action == EPSILON {
573            if !self.active_autonomous_transitions(post_states) {
574                return Err(PgError::UnsatisfiedGuard);
575            }
576        } else if action.0 >= self.def.effects.len() as LocationIdx {
577            return Err(PgError::MissingAction(action));
578        } else if let Effect::Effects(ref effects, ref resets) = self.def.effects[action.0 as usize]
579        {
580            if self.active_transitions(action, post_states, resets) {
581                effects.iter().for_each(|(var, effect)| {
582                    self.vars[var.0 as usize] = effect.eval(&|var| self.vars[var.0 as usize], rng)
583                });
584                resets
585                    .iter()
586                    .for_each(|clock| self.clocks[clock.0 as usize] = 0);
587            } else {
588                return Err(PgError::UnsatisfiedGuard);
589            }
590        } else {
591            return Err(PgError::Communication(action));
592        }
593        self.current_states.copy_from_slice(post_states);
594        Ok(())
595    }
596
597    /// Checks if it is possible to wait a given amount of time-units without violating the time invariants.
598    #[inline]
599    pub fn can_wait(&self, delta: Time) -> bool {
600        self.current_states
601            .iter()
602            .flat_map(|current_state| self.def.locations[current_state.0 as usize].1.iter())
603            .all(|(c, l, u)| {
604                // Invariants need to be satisfied during the whole wait.
605                let start_time = self.clocks[c.0 as usize];
606                let end_time = start_time + delta;
607                l.is_none_or(|l| l <= start_time) && u.is_none_or(|u| end_time < u)
608            })
609    }
610
611    /// Waits a given amount of time-units.
612    ///
613    /// Returns error if the waiting would violate the current location's time invariant (if any).
614    #[inline]
615    pub fn wait(&mut self, delta: Time) -> Result<(), PgError> {
616        if self.can_wait(delta) {
617            self.clocks.iter_mut().for_each(|t| *t += delta);
618            Ok(())
619        } else {
620            Err(PgError::Invariant)
621        }
622    }
623
624    pub(crate) fn send<'a, R: Rng>(
625        &'a mut self,
626        action: Action,
627        post_states: &[Location],
628        rng: &'a mut R,
629    ) -> Result<SmallVec<[Val; 2]>, PgError> {
630        if action == EPSILON {
631            Err(PgError::NotSend(action))
632        } else if self.active_transitions(action, post_states, &[]) {
633            if let Effect::Send(effects) = &self.def.effects[action.0 as usize] {
634                let vals = effects
635                    .iter()
636                    .map(|effect| effect.eval(&|var| self.vars[var.0 as usize], rng))
637                    .collect();
638                self.current_states.copy_from_slice(post_states);
639                Ok(vals)
640            } else {
641                Err(PgError::NotSend(action))
642            }
643        } else {
644            Err(PgError::UnsatisfiedGuard)
645        }
646    }
647
648    pub(crate) fn receive(
649        &mut self,
650        action: Action,
651        post_states: &[Location],
652        vals: &[Val],
653    ) -> Result<(), PgError> {
654        if action == EPSILON {
655            Err(PgError::NotReceive(action))
656        } else if self.active_transitions(action, post_states, &[]) {
657            if let Effect::Receive(ref vars) = self.def.effects[action.0 as usize] {
658                // let var_content = self.vars.get_mut(var.0 as usize).expect("variable exists");
659                if vars.len() == vals.len()
660                    && vals.iter().zip(vars).all(|(val, var)| {
661                        self.vars
662                            .get(var.0 as usize)
663                            .expect("variable exists")
664                            .r#type()
665                            == val.r#type()
666                    })
667                {
668                    vals.iter().zip(vars).for_each(|(&val, var)| {
669                        *self.vars.get_mut(var.0 as usize).expect("variable exists") = val
670                    });
671                    self.current_states.copy_from_slice(post_states);
672                    // self.current_states = post_states;
673                    // self.update_buf();
674                    Ok(())
675                } else {
676                    Err(PgError::TypeMismatch)
677                }
678            } else {
679                Err(PgError::NotReceive(action))
680            }
681        } else {
682            Err(PgError::UnsatisfiedGuard)
683        }
684    }
685
686    #[inline]
687    pub(crate) fn eval(&self, expr: &Expression<Var>) -> Val {
688        expr.eval(
689            &|v: &Var| *self.vars.get(v.0 as usize).unwrap(),
690            &mut DummyRng,
691        )
692    }
693
694    #[inline]
695    pub(crate) fn val(&self, var: Var) -> Result<Val, PgError> {
696        self.vars
697            .get(var.0 as usize)
698            .copied()
699            .ok_or(PgError::MissingVar(var))
700    }
701}
702
703impl<'def> ProgramGraphRun<'def> {
704    pub(crate) fn montecarlo<R: Rng + SeedableRng>(&mut self, rng: &mut R) -> Option<Action> {
705        let mut rand = R::from_rng(rng);
706        if let Some((action, post_states)) = self
707            .possible_transitions()
708            .filter_map(|(action, post_state)| {
709                post_state
710                    .map(|locs| locs.choose(rng))
711                    .collect::<Option<SmallVec<[Location; 4]>>>()
712                    .map(|loc| (action, loc))
713            })
714            .choose(&mut rand)
715        {
716            self.transition(action, post_states.as_slice(), rng)
717                .expect("successful transition");
718            return Some(action);
719        }
720        None
721    }
722}
723
724#[cfg(test)]
725mod tests {
726    use rand::SeedableRng;
727    use rand::rngs::SmallRng;
728
729    use super::*;
730
731    #[test]
732    fn wait() {
733        let mut builder = ProgramGraphBuilder::new();
734        let _ = builder.new_initial_location();
735        let pg_def = builder.build();
736        let mut pg = pg_def.new_instance();
737        assert_eq!(pg.possible_transitions().count(), 0);
738        pg.wait(1).expect("wait 1 time unit");
739    }
740
741    #[test]
742    fn transitions() {
743        let mut builder = ProgramGraphBuilder::new();
744        let initial = builder.new_initial_location();
745        let r#final = builder.new_location();
746        let action = builder.new_action();
747        builder
748            .add_transition(initial, action, r#final, None)
749            .expect("add transition");
750        let pg_def = builder.build();
751        let mut pg = pg_def.new_instance();
752        // assert_eq!(pg.current_states().as_slice(), &[initial]);
753        // assert_eq!(
754        //     pg.possible_transitions().collect::<Vec<_>>(),
755        //     vec![(
756        //         action,
757        //         SmallVec::<[_; 4]>::from(vec![SmallVec::<[_; 8]>::from(vec![r#final])])
758        //     )]
759        // );
760        let mut rng = SmallRng::from_seed([0; 32]);
761        pg.transition(action, &[r#final], &mut rng)
762            .expect("transition to final");
763        assert_eq!(pg.possible_transitions().count(), 0);
764    }
765
766    #[test]
767    fn program_graph() -> Result<(), PgError> {
768        // Create Program Graph
769        let mut builder = ProgramGraphBuilder::new();
770        // Variables
771        let mut rng = SmallRng::from_seed([0; 32]);
772        let battery = builder.new_var(Val::from(0i64))?;
773        // Locations
774        let initial = builder.new_initial_location();
775        let left = builder.new_location();
776        let center = builder.new_location();
777        let right = builder.new_location();
778        // Actions
779        let initialize = builder.new_action();
780        builder.add_effect(initialize, battery, PgExpression::from(3i64))?;
781        let move_left = builder.new_action();
782        let discharge = PgExpression::Integer(IntegerExpr::Var(battery) + IntegerExpr::from(-1));
783        builder.add_effect(move_left, battery, discharge.clone())?;
784        let move_right = builder.new_action();
785        builder.add_effect(move_right, battery, discharge)?;
786        // Guards
787        let out_of_charge =
788            BooleanExpr::IntGreater(IntegerExpr::Var(battery), IntegerExpr::from(0i64));
789        // Program graph definition
790        builder.add_transition(initial, initialize, center, None)?;
791        builder.add_transition(left, move_right, center, Some(out_of_charge.clone()))?;
792        builder.add_transition(center, move_right, right, Some(out_of_charge.clone()))?;
793        builder.add_transition(right, move_left, center, Some(out_of_charge.clone()))?;
794        builder.add_transition(center, move_left, left, Some(out_of_charge))?;
795        // Execution
796        let pg_def = builder.build();
797        let mut pg = pg_def.new_instance();
798        assert_eq!(pg.possible_transitions().count(), 1);
799        pg.transition(initialize, &[center], &mut rng)
800            .expect("initialize");
801        assert_eq!(pg.possible_transitions().count(), 2);
802        pg.transition(move_right, &[right], &mut rng)
803            .expect("move right");
804        assert_eq!(pg.possible_transitions().count(), 1);
805        pg.transition(move_right, &[right], &mut rng)
806            .expect_err("already right");
807        assert_eq!(pg.possible_transitions().count(), 1);
808        pg.transition(move_left, &[center], &mut rng)
809            .expect("move left");
810        assert_eq!(pg.possible_transitions().count(), 2);
811        pg.transition(move_left, &[left], &mut rng)
812            .expect("move left");
813        assert!(
814            pg.possible_transitions()
815                .next()
816                .unwrap()
817                .1
818                .next()
819                .unwrap()
820                .next()
821                .is_none()
822        );
823        pg.transition(move_left, &[left], &mut rng)
824            .expect_err("battery = 0");
825        Ok(())
826    }
827}