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}