mod builder;
mod run;
mod transitions;
use crate::{TimeRange, grammar::*};
pub use builder::*;
use get_size2::GetSize;
pub use run::ProgramGraphRun;
use thiserror::Error;
use transitions::TransitionsIterator;
pub type LocationIdx = u32;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, GetSize)]
pub struct Location(LocationIdx);
pub type ActionIdx = u32;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, GetSize)]
pub struct Action(ActionIdx);
impl From<Action> for ActionIdx {
#[inline]
fn from(val: Action) -> Self {
val.0
}
}
pub(crate) const EPSILON: Action = Action(ActionIdx::MAX);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, GetSize)]
pub struct Var(u16);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord, GetSize)]
pub struct Clock(u16);
pub type PgExpression = Expression<Var>;
type PgGuard = BooleanExpr<Var>;
#[derive(Debug, Clone, GetSize)]
enum Effect {
Effects(Vec<(Var, Expression<Var>)>, Vec<Clock>),
Send(Vec<Expression<Var>>),
Receive(Vec<Var>),
}
type Transition = (Location, Option<BooleanExpr<Var>>, Vec<(Clock, TimeRange)>);
type LocationData = (Vec<(Action, Vec<Transition>)>, Vec<(Clock, TimeRange)>);
#[derive(Debug, Clone, Copy, Error)]
pub enum PgError {
#[error("action {0:?} does not belong to this program graph")]
MissingAction(Action),
#[error("clock {0:?} does not belong to this program graph")]
MissingClock(Clock),
#[error("location {0:?} does not belong to this program graph")]
MissingLocation(Location),
#[error("location {0:?} does not belong to this program graph")]
MissingVar(Var),
#[error("there is no such transition")]
MissingTransition,
#[error("type mismatch")]
TypeMismatch,
#[error("the guard has not been satisfied")]
UnsatisfiedGuard,
#[error("the tuple has no {0} component")]
MissingComponent(usize),
#[error("cannot add effects to a Receive action")]
EffectOnReceive,
#[error("cannot add effects to a Send action")]
EffectOnSend,
#[error("{0:?} is a communication (either Send or Receive)")]
Communication(Action),
#[error("Mismatching (i.e., wrong number) post states of transition")]
MismatchingPostStates,
#[error("{0:?} is a not a Send communication")]
NotSend(Action),
#[error("{0:?} is a not a Receive communication")]
NotReceive(Action),
#[error("The epsilon action has no effects")]
NoEffects,
#[error("A time invariant is not satisfied")]
Invariant,
#[error("synchronous composition")]
Sync,
#[error("type error")]
Type(#[source] TypeError),
}
#[derive(Debug, Clone, GetSize)]
pub struct ProgramGraph {
initial_states: Vec<Location>,
effects: Vec<Effect>,
locations: Vec<LocationData>,
vars: Vec<Val>,
clocks: u16,
}
impl ProgramGraph {
pub fn new_instance<'def>(&'def self) -> ProgramGraphRun<'def> {
ProgramGraphRun::new(self)
}
#[inline]
fn guards(
&self,
pre_state: Location,
action: Action,
post_state: Location,
) -> impl Iterator<Item = (Option<&PgGuard>, &[(Clock, TimeRange)])> {
let a_transitions = self.locations[pre_state.0 as usize].0.as_slice();
a_transitions
.binary_search_by_key(&action, |&(a, ..)| a)
.into_iter()
.flat_map(move |transitions_idx| {
let post_idx_lb = a_transitions[transitions_idx]
.1
.partition_point(|&(p, ..)| p < post_state);
a_transitions[transitions_idx].1[post_idx_lb..]
.iter()
.take_while(move |&&(p, ..)| p == post_state)
.map(|(_, g, c)| (g.as_ref(), c.as_slice()))
})
}
}
#[cfg(test)]
mod tests {
use rand::SeedableRng;
use rand::rngs::SmallRng;
use super::*;
#[test]
fn wait() {
let mut builder = ProgramGraphBuilder::new();
let _ = builder.new_initial_location();
let pg_def = builder.build();
let mut pg = pg_def.new_instance();
assert_eq!(pg.possible_transitions().count(), 0);
pg.wait(1).expect("wait 1 time unit");
}
#[test]
fn transition() {
let mut builder = ProgramGraphBuilder::new();
let initial = builder.new_initial_location();
let r#final = builder.new_location();
builder
.add_autonomous_transition(initial, r#final, None)
.expect("add transition");
let pg_def = builder.build();
let mut pg = pg_def.new_instance();
assert_eq!(pg.current_states(), &[initial]);
{
let mut possible_transitions = pg.possible_transitions();
assert!(
possible_transitions
.next()
.is_some_and(|(a, _)| a == EPSILON),
);
assert!(possible_transitions.next().is_none());
}
let mut rng = SmallRng::from_seed([0; 32]);
pg.transition(EPSILON, &[r#final], &mut rng)
.expect("transition to final");
assert_eq!(pg.current_states(), &[r#final]);
assert_eq!(pg.possible_transitions().count(), 0);
}
#[test]
fn guard() {
let mut builder = ProgramGraphBuilder::new();
let initial = builder.new_initial_location();
let r#final = builder.new_location();
builder
.add_autonomous_transition(initial, r#final, Some(BooleanExpr::Const(true)))
.expect("add transition");
builder
.add_autonomous_transition(r#final, initial, Some(BooleanExpr::Const(false)))
.expect("add transition");
let pg_def = builder.build();
let mut pg = pg_def.new_instance();
let mut rng = SmallRng::from_seed([0; 32]);
pg.transition(EPSILON, &[r#final], &mut rng)
.expect("transition to final");
assert_eq!(pg.current_states(), &[r#final]);
let mut possible_transitions = pg.possible_transitions();
let (next_action, mut next_locations) = possible_transitions.next().unwrap();
assert_eq!(next_action, EPSILON);
let mut next_location = next_locations.next().unwrap();
assert!(next_location.next().is_none());
assert!(possible_transitions.next().is_none());
}
#[test]
fn effect() {
const TRESHOLD: Natural = 3;
let mut builder = ProgramGraphBuilder::new();
let initial = builder.new_initial_location();
let r#final = builder.new_location();
let var = builder.new_var(Val::from(0 as Natural));
let action = builder.new_action();
builder
.add_effect(
action,
var,
Expression::Natural(NaturalExpr::Var(var) + NaturalExpr::Const(1)),
)
.expect("add effect");
builder
.add_transition(
initial,
action,
initial,
Some(
Expression::from_var(var, Type::Natural)
.less_than(Expression::from(TRESHOLD))
.expect("boolean expression"),
),
)
.expect("add transition");
builder
.add_autonomous_transition(
initial,
r#final,
Some(
Expression::from_var(var, Type::Natural)
.equal_to(Expression::from(TRESHOLD))
.expect("boolean expression"),
),
)
.expect("add transition");
let pg_def = builder.build();
let mut pg = pg_def.new_instance();
let mut rng = SmallRng::from_seed([0; 32]);
for _ in 0..TRESHOLD {
assert_eq!(pg.current_states(), &[initial]);
pg.transition(EPSILON, &[r#final], &mut rng)
.expect_err("transition to final not possible");
pg.transition(action, &[initial], &mut rng)
.expect("transition to initial");
}
assert_eq!(pg.current_states(), &[initial]);
pg.transition(action, &[initial], &mut rng)
.expect_err("transition to initial not possible");
pg.transition(EPSILON, &[r#final], &mut rng)
.expect("transition to final");
assert_eq!(pg.current_states(), &[r#final]);
}
#[test]
fn program_graph() -> Result<(), PgError> {
let mut builder = ProgramGraphBuilder::new();
let mut rng = SmallRng::from_seed([0; 32]);
let battery = builder.new_var(Val::from(0i64));
let initial = builder.new_initial_location();
let left = builder.new_location();
let center = builder.new_location();
let right = builder.new_location();
let initialize = builder.new_action();
builder.add_effect(initialize, battery, PgExpression::from(3i64))?;
let move_left = builder.new_action();
let discharge = PgExpression::Integer(IntegerExpr::Var(battery) + IntegerExpr::from(-1));
builder.add_effect(move_left, battery, discharge.clone())?;
let move_right = builder.new_action();
builder.add_effect(move_right, battery, discharge)?;
let out_of_charge =
BooleanExpr::IntGreater(IntegerExpr::Var(battery), IntegerExpr::from(0i64));
builder.add_transition(initial, initialize, center, None)?;
builder.add_transition(left, move_right, center, Some(out_of_charge.clone()))?;
builder.add_transition(center, move_right, right, Some(out_of_charge.clone()))?;
builder.add_transition(right, move_left, center, Some(out_of_charge.clone()))?;
builder.add_transition(center, move_left, left, Some(out_of_charge))?;
let pg_def = builder.build();
let mut pg = pg_def.new_instance();
assert_eq!(pg.possible_transitions().count(), 1);
pg.transition(initialize, &[center], &mut rng)
.expect("initialize");
assert_eq!(pg.possible_transitions().count(), 2);
pg.transition(move_right, &[right], &mut rng)
.expect("move right");
assert_eq!(pg.possible_transitions().count(), 1);
pg.transition(move_right, &[right], &mut rng)
.expect_err("already right");
assert_eq!(pg.possible_transitions().count(), 1);
pg.transition(move_left, &[center], &mut rng)
.expect("move left");
assert_eq!(pg.possible_transitions().count(), 2);
pg.transition(move_left, &[left], &mut rng)
.expect("move left");
assert!(
pg.possible_transitions()
.next()
.unwrap()
.1
.next()
.unwrap()
.next()
.is_none()
);
pg.transition(move_left, &[left], &mut rng)
.expect_err("battery = 0");
Ok(())
}
}