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}