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//! // Create a new PG builder
27//! let mut pg_builder = ProgramGraphBuilder::new();
28//!
29//! // The builder is initialized with an initial location
30//! let initial_loc = pg_builder.new_initial_location();
31//!
32//! // Create a new action
33//! let action = pg_builder.new_action();
34//!
35//! // Create a new location
36//! let post_loc = pg_builder.new_location();
37//!
38//! // Add a transition (the guard is optional, and None is equivalent to the guard always being true)
39//! let result = pg_builder.add_transition(initial_loc, action, post_loc, None);
40//!
41//! // This can only fail if the builder does not recognize either the locations
42//! // or the action defining the transition
43//! result.expect("both the initial location and the action belong to the PG");
44//!
45//! // Build the PG from its builder
46//! // The builder is always guaranteed to build a well-defined PG and building cannot fail
47//! let pg = pg_builder.build();
48//! let mut instance = pg.new_instance();
49//!
50//! // Execution starts in the initial location
51//! assert_eq!(instance.current_states().as_slice(), &[initial_loc]);
52//!
53//! // Compute the possible transitions on the PG
54//! {
55//!     let mut iter = instance .possible_transitions();
56//!     let (act, mut trans) = iter.next().unwrap();
57//!     assert_eq!(act, action);
58//!     let post_locs: Vec<Location> = trans.next().unwrap().collect();
59//!     assert_eq!(post_locs, vec![post_loc]);
60//!     assert!(iter.next().is_none());
61//! }
62//!
63//! // Perform a transition
64//! # use rand::{Rng, SeedableRng};
65//! # use rand::rngs::SmallRng;
66//! let mut rng: SmallRng = rand::make_rng();
67//! let result = instance .transition(action, &[post_loc], &mut rng);
68//!
69//! // Performing a transition can fail, in particular, if the transition was not allowed
70//! result.expect("The transition from the initial location onto itself is possible");
71//!
72//! // There are no more possible transitions
73//! assert!(instance.possible_transitions().next().is_none());
74//!
75//! // Attempting to transition results in an error
76//! instance.transition(action, &[post_loc], &mut rng).expect_err("The transition is not possible");
77//! ```
78
79mod builder;
80mod run;
81mod transitions;
82
83use crate::{TimeRange, grammar::*};
84pub use builder::*;
85use get_size2::GetSize;
86pub use run::ProgramGraphRun;
87use thiserror::Error;
88use transitions::TransitionsIterator;
89
90/// The index for [`Location`]s in a [`ProgramGraph`].
91pub type LocationIdx = u32;
92
93/// An indexing object for locations in a PG.
94///
95/// These cannot be directly created or manipulated,
96/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
97#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, GetSize)]
98pub struct Location(LocationIdx);
99
100/// The index for [`Action`]s in a [`ProgramGraph`].
101pub type ActionIdx = u32;
102
103/// An indexing object for actions in a PG.
104///
105/// These cannot be directly created or manipulated,
106/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
107#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, GetSize)]
108pub struct Action(ActionIdx);
109
110impl From<Action> for ActionIdx {
111    #[inline]
112    fn from(val: Action) -> Self {
113        val.0
114    }
115}
116
117/// Epsilon action to enable autonomous transitions.
118/// It cannot have effects.
119pub(crate) const EPSILON: Action = Action(ActionIdx::MAX);
120
121/// An indexing object for typed variables in a PG.
122///
123/// These cannot be directly created or manipulated,
124/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
125#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, GetSize)]
126pub struct Var(u16);
127
128/// An indexing object for clocks in a PG.
129///
130/// These cannot be directly created or manipulated,
131/// but have to be generated and/or provided by a [`ProgramGraphBuilder`] or [`ProgramGraph`].
132#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, GetSize)]
133pub struct Clock(u16);
134
135/// An expression using PG's [`Var`] as variables.
136pub type PgExpression = Expression<Var>;
137
138/// A Boolean expression over [`Var`] variables.
139type PgGuard = BooleanExpr<Var>;
140
141#[derive(Debug, Clone, GetSize)]
142enum Effect {
143    Effects(Vec<(Var, Expression<Var>)>, Vec<Clock>),
144    Send(Vec<Expression<Var>>),
145    Receive(Vec<Var>),
146}
147
148type Transition = (Location, Option<BooleanExpr<Var>>, Vec<(Clock, TimeRange)>);
149
150type LocationData = (Vec<(Action, Vec<Transition>)>, Vec<(Clock, TimeRange)>);
151
152/// The error type for operations with [`ProgramGraphBuilder`]s and [`ProgramGraph`]s.
153#[derive(Debug, Clone, Copy, Error)]
154pub enum PgError {
155    /// There is no such action in the PG.
156    #[error("action {0:?} does not belong to this program graph")]
157    MissingAction(Action),
158    /// There is no such clock in the PG.
159    #[error("clock {0:?} does not belong to this program graph")]
160    MissingClock(Clock),
161    /// There is no such location in the PG.
162    #[error("location {0:?} does not belong to this program graph")]
163    MissingLocation(Location),
164    /// There is no such variable in the PG.
165    #[error("location {0:?} does not belong to this program graph")]
166    MissingVar(Var),
167    /// The PG does not allow this transition.
168    #[error("there is no such transition")]
169    MissingTransition,
170    /// Types that should be matching are not,
171    /// or are not compatible with each other.
172    #[error("type mismatch")]
173    TypeMismatch,
174    /// Transition's guard is not satisfied.
175    #[error("the guard has not been satisfied")]
176    UnsatisfiedGuard,
177    /// The tuple has no component for such index.
178    #[error("the tuple has no {0} component")]
179    MissingComponent(usize),
180    /// Cannot add effects to a Receive action.
181    #[error("cannot add effects to a Receive action")]
182    EffectOnReceive,
183    /// Cannot add effects to a Send action.
184    #[error("cannot add effects to a Send action")]
185    EffectOnSend,
186    /// This action is a communication (either Send or Receive).
187    #[error("{0:?} is a communication (either Send or Receive)")]
188    Communication(Action),
189    /// Mismatching (i.e., wrong number) post states of transition.
190    #[error("Mismatching (i.e., wrong number) post states of transition")]
191    MismatchingPostStates,
192    /// The action is a not a Send communication.
193    #[error("{0:?} is a not a Send communication")]
194    NotSend(Action),
195    /// The action is a not a Receive communication.
196    #[error("{0:?} is a not a Receive communication")]
197    NotReceive(Action),
198    /// The epsilon action has no effects.
199    #[error("The epsilon action has no effects")]
200    NoEffects,
201    /// A time invariant is not satisfied.
202    #[error("A time invariant is not satisfied")]
203    Invariant,
204    /// Program graph is a synchronous composition
205    #[error("synchronous composition")]
206    Sync,
207    /// A type error
208    #[error("type error")]
209    Type(#[source] TypeError),
210}
211
212/// A definition object for a PG.
213/// It represents the abstract definition of a PG.
214///
215/// The only way to produce a [`ProgramGraph`] is through a [`ProgramGraphBuilder`].
216/// This guarantees that there are no type errors involved in the definition of action's effects and transitions' guards,
217/// and thus the PG will always be in a consistent state.
218///
219/// The only way to execute the [`ProgramGraph`] is to generate a new [`ProgramGraphRun`] through [`ProgramGraph::new_instance`].
220/// The [`ProgramGraphRun`] cannot outlive its [`ProgramGraph`], as it holds references to it.
221/// This allows to cheaply generate multiple [`ProgramGraphRun`]s from the same [`ProgramGraph`].
222///
223/// Example:
224///
225/// ```
226/// # use scan_core::program_graph::ProgramGraphBuilder;
227/// # use rand::rngs::SmallRng;
228/// # use rand::SeedableRng;
229/// // Create and populate a PG builder object
230/// let mut pg_builder = ProgramGraphBuilder::new();
231/// let initial = pg_builder.new_initial_location();
232/// pg_builder.add_autonomous_transition(initial, initial, None).expect("add transition");
233///
234/// // Build the builder object to get a PG definition object.
235/// let pg_def = pg_builder.build();
236///
237/// // Instantiate a PG with the previously built definition.
238/// let mut pg = pg_def.new_instance();
239///
240/// // Perform the (unique) active transition available.
241/// let (e, mut post_locs) = pg.possible_transitions().last().expect("autonomous transition");
242/// let post_loc = post_locs.last().expect("post location").last().expect("post location");
243/// assert_eq!(post_loc, initial);
244/// let mut rng: SmallRng = rand::make_rng();
245/// pg.transition(e, &[initial], &mut rng).expect("transition is active");
246/// ```
247#[derive(Debug, Clone, GetSize)]
248pub struct ProgramGraph {
249    initial_states: Vec<Location>,
250    effects: Vec<Effect>,
251    locations: Vec<LocationData>,
252    // Time invariants of each location
253    vars: Vec<Val>,
254    // Number of clocks
255    clocks: u16,
256}
257
258impl ProgramGraph {
259    /// Creates a new [`ProgramGraphRun`] which allows to execute the PG as defined.
260    ///
261    /// The new instance borrows the caller to refer to the PG definition without copying its data,
262    /// so that spawning instances is (relatively) inexpensive.
263    pub fn new_instance<'def>(&'def self) -> ProgramGraphRun<'def> {
264        ProgramGraphRun::new(self)
265    }
266
267    // Returns transition's guard.
268    // Panics if the pre- or post-state do not exist.
269    #[inline]
270    fn guards(
271        &self,
272        pre_state: Location,
273        action: Action,
274        post_state: Location,
275    ) -> impl Iterator<Item = (Option<&PgGuard>, &[(Clock, TimeRange)])> {
276        let a_transitions = self.locations[pre_state.0 as usize].0.as_slice();
277        a_transitions
278            .binary_search_by_key(&action, |&(a, ..)| a)
279            .into_iter()
280            .flat_map(move |transitions_idx| {
281                let post_idx_lb = a_transitions[transitions_idx]
282                    .1
283                    .partition_point(|&(p, ..)| p < post_state);
284                a_transitions[transitions_idx].1[post_idx_lb..]
285                    .iter()
286                    .take_while(move |&&(p, ..)| p == post_state)
287                    .map(|(_, g, c)| (g.as_ref(), c.as_slice()))
288            })
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use rand::SeedableRng;
295    use rand::rngs::SmallRng;
296
297    use super::*;
298
299    #[test]
300    fn wait() {
301        let mut builder = ProgramGraphBuilder::new();
302        let _ = builder.new_initial_location();
303        let pg_def = builder.build();
304        let mut pg = pg_def.new_instance();
305        assert_eq!(pg.possible_transitions().count(), 0);
306        pg.wait(1).expect("wait 1 time unit");
307    }
308
309    #[test]
310    fn transition() {
311        let mut builder = ProgramGraphBuilder::new();
312        let initial = builder.new_initial_location();
313        let r#final = builder.new_location();
314        builder
315            .add_autonomous_transition(initial, r#final, None)
316            .expect("add transition");
317        let pg_def = builder.build();
318        let mut pg = pg_def.new_instance();
319        assert_eq!(pg.current_states(), &[initial]);
320        {
321            let mut possible_transitions = pg.possible_transitions();
322            assert!(
323                possible_transitions
324                    .next()
325                    .is_some_and(|(a, _)| a == EPSILON),
326            );
327            assert!(possible_transitions.next().is_none());
328        }
329        let mut rng = SmallRng::from_seed([0; 32]);
330        pg.transition(EPSILON, &[r#final], &mut rng)
331            .expect("transition to final");
332        assert_eq!(pg.current_states(), &[r#final]);
333        assert_eq!(pg.possible_transitions().count(), 0);
334    }
335
336    #[test]
337    fn guard() {
338        let mut builder = ProgramGraphBuilder::new();
339        let initial = builder.new_initial_location();
340        let r#final = builder.new_location();
341        builder
342            .add_autonomous_transition(initial, r#final, Some(BooleanExpr::Const(true)))
343            .expect("add transition");
344        builder
345            .add_autonomous_transition(r#final, initial, Some(BooleanExpr::Const(false)))
346            .expect("add transition");
347        let pg_def = builder.build();
348        let mut pg = pg_def.new_instance();
349        let mut rng = SmallRng::from_seed([0; 32]);
350        // It is possible to transition from initial to final
351        pg.transition(EPSILON, &[r#final], &mut rng)
352            .expect("transition to final");
353        assert_eq!(pg.current_states(), &[r#final]);
354        // It is not possible to transition from final to initial
355        let mut possible_transitions = pg.possible_transitions();
356        let (next_action, mut next_locations) = possible_transitions.next().unwrap();
357        assert_eq!(next_action, EPSILON);
358        let mut next_location = next_locations.next().unwrap();
359        assert!(next_location.next().is_none());
360        assert!(possible_transitions.next().is_none());
361    }
362
363    #[test]
364    fn effect() {
365        const TRESHOLD: Natural = 3;
366        let mut builder = ProgramGraphBuilder::new();
367        let initial = builder.new_initial_location();
368        let r#final = builder.new_location();
369        let var = builder.new_var(Val::from(0 as Natural));
370        let action = builder.new_action();
371        builder
372            .add_effect(
373                action,
374                var,
375                Expression::Natural(NaturalExpr::Var(var) + NaturalExpr::Const(1)),
376            )
377            .expect("add effect");
378        builder
379            .add_transition(
380                initial,
381                action,
382                initial,
383                Some(
384                    Expression::from_var(var, Type::Natural)
385                        .less_than(Expression::from(TRESHOLD))
386                        .expect("boolean expression"),
387                ),
388            )
389            .expect("add transition");
390        builder
391            .add_autonomous_transition(
392                initial,
393                r#final,
394                Some(
395                    Expression::from_var(var, Type::Natural)
396                        .equal_to(Expression::from(TRESHOLD))
397                        .expect("boolean expression"),
398                ),
399            )
400            .expect("add transition");
401        let pg_def = builder.build();
402        let mut pg = pg_def.new_instance();
403        let mut rng = SmallRng::from_seed([0; 32]);
404        for _ in 0..TRESHOLD {
405            assert_eq!(pg.current_states(), &[initial]);
406            // It is not possible to transition from initial to final
407            pg.transition(EPSILON, &[r#final], &mut rng)
408                .expect_err("transition to final not possible");
409            // It is possible to transition from initial to initial
410            pg.transition(action, &[initial], &mut rng)
411                .expect("transition to initial");
412        }
413        assert_eq!(pg.current_states(), &[initial]);
414        // It is not possible to transition from initial to initial
415        pg.transition(action, &[initial], &mut rng)
416            .expect_err("transition to initial not possible");
417        // It is possible to transition from initial to final
418        pg.transition(EPSILON, &[r#final], &mut rng)
419            .expect("transition to final");
420        assert_eq!(pg.current_states(), &[r#final]);
421    }
422
423    #[test]
424    fn program_graph() -> Result<(), PgError> {
425        // Create Program Graph
426        let mut builder = ProgramGraphBuilder::new();
427        // Variables
428        let mut rng = SmallRng::from_seed([0; 32]);
429        let battery = builder.new_var(Val::from(0i64));
430        // Locations
431        let initial = builder.new_initial_location();
432        let left = builder.new_location();
433        let center = builder.new_location();
434        let right = builder.new_location();
435        // Actions
436        let initialize = builder.new_action();
437        builder.add_effect(initialize, battery, PgExpression::from(3i64))?;
438        let move_left = builder.new_action();
439        let discharge = PgExpression::Integer(IntegerExpr::Var(battery) + IntegerExpr::from(-1));
440        builder.add_effect(move_left, battery, discharge.clone())?;
441        let move_right = builder.new_action();
442        builder.add_effect(move_right, battery, discharge)?;
443        // Guards
444        let out_of_charge =
445            BooleanExpr::IntGreater(IntegerExpr::Var(battery), IntegerExpr::from(0i64));
446        // Program graph definition
447        builder.add_transition(initial, initialize, center, None)?;
448        builder.add_transition(left, move_right, center, Some(out_of_charge.clone()))?;
449        builder.add_transition(center, move_right, right, Some(out_of_charge.clone()))?;
450        builder.add_transition(right, move_left, center, Some(out_of_charge.clone()))?;
451        builder.add_transition(center, move_left, left, Some(out_of_charge))?;
452        // Execution
453        let pg_def = builder.build();
454        let mut pg = pg_def.new_instance();
455        assert_eq!(pg.possible_transitions().count(), 1);
456        pg.transition(initialize, &[center], &mut rng)
457            .expect("initialize");
458        assert_eq!(pg.possible_transitions().count(), 2);
459        pg.transition(move_right, &[right], &mut rng)
460            .expect("move right");
461        assert_eq!(pg.possible_transitions().count(), 1);
462        pg.transition(move_right, &[right], &mut rng)
463            .expect_err("already right");
464        assert_eq!(pg.possible_transitions().count(), 1);
465        pg.transition(move_left, &[center], &mut rng)
466            .expect("move left");
467        assert_eq!(pg.possible_transitions().count(), 2);
468        pg.transition(move_left, &[left], &mut rng)
469            .expect("move left");
470        assert!(
471            pg.possible_transitions()
472                .next()
473                .unwrap()
474                .1
475                .next()
476                .unwrap()
477                .next()
478                .is_none()
479        );
480        pg.transition(move_left, &[left], &mut rng)
481            .expect_err("battery = 0");
482        Ok(())
483    }
484}