mod builder;
use crate::{DummyRng, Time, grammar::*};
pub use builder::*;
use rand::{Rng, SeedableRng, seq::IteratorRandom};
use smallvec::SmallVec;
use thiserror::Error;
pub type LocationIdx = u32;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Location(LocationIdx);
pub type ActionIdx = u32;
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
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)]
pub struct Var(u16);
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Clock(u16);
pub type TimeConstraint = (Clock, Option<Time>, Option<Time>);
pub type PgExpression = Expression<Var>;
pub type PgGuard = BooleanExpr<Var>;
#[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("type error")]
Type(#[source] TypeError),
}
#[derive(Debug, Clone)]
enum Effect {
Effects(Vec<(Var, Expression<Var>)>, Vec<Clock>),
Send(SmallVec<[Expression<Var>; 2]>),
Receive(SmallVec<[Var; 2]>),
}
type LocationData = (Vec<(Action, Vec<Transition>)>, Vec<TimeConstraint>);
#[derive(Debug, Clone)]
pub struct ProgramGraph {
initial_states: SmallVec<[Location; 8]>,
effects: Vec<Effect>,
locations: Vec<LocationData>,
vars: Vec<Val>,
clocks: u16,
}
impl ProgramGraph {
pub fn new_instance<'def>(&'def self) -> ProgramGraphRun<'def> {
ProgramGraphRun {
current_states: self.initial_states.clone(),
vars: self.vars.clone(),
def: self,
clocks: vec![0; self.clocks as usize],
}
}
#[inline]
fn guards(
&self,
pre_state: Location,
action: Action,
post_state: Location,
) -> impl Iterator<Item = (Option<&PgGuard>, &[TimeConstraint])> {
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()))
})
}
}
#[derive(Debug, Clone)]
pub struct ProgramGraphRun<'def> {
current_states: SmallVec<[Location; 8]>,
vars: Vec<Val>,
clocks: Vec<Time>,
def: &'def ProgramGraph,
}
impl<'def> ProgramGraphRun<'def> {
#[inline]
pub fn current_states(&self) -> &SmallVec<[Location; 8]> {
&self.current_states
}
pub fn possible_transitions(
&self,
) -> impl Iterator<Item = (Action, impl Iterator<Item = impl Iterator<Item = Location>>)> {
self.current_states
.first()
.into_iter()
.flat_map(|loc| {
self.def.locations[loc.0 as usize]
.0
.iter()
.map(|&(action, ..)| action)
})
.chain(
self.current_states
.is_empty()
.then(|| (0..self.def.effects.len() as ActionIdx).map(Action))
.into_iter()
.flatten(),
)
.map(|action| (action, self.possible_transitions_action(action)))
}
#[inline]
fn possible_transitions_action(
&self,
action: Action,
) -> impl Iterator<Item = impl Iterator<Item = Location>> {
self.current_states
.iter()
.map(move |&loc| self.possible_transitions_action_loc(action, loc))
}
fn possible_transitions_action_loc(
&self,
action: Action,
current_state: Location,
) -> impl Iterator<Item = Location> {
let mut last_post_state: Option<Location> = None;
self.def.locations[current_state.0 as usize]
.0
.binary_search_by_key(&action, |&(a, ..)| a)
.into_iter()
.flat_map(move |action_idx| {
self.def.locations[current_state.0 as usize].0[action_idx]
.1
.iter()
.filter_map(move |(post_state, guard, constraints)| {
if last_post_state.is_some_and(|s| s == *post_state) {
return None;
}
let (_, ref invariants) = self.def.locations[post_state.0 as usize];
if if action == EPSILON {
self.active_autonomous_transition(
guard.as_ref(),
constraints,
invariants,
)
} else {
match self.def.effects[action.0 as usize] {
Effect::Effects(_, ref resets) => self.active_transition(
guard.as_ref(),
constraints,
invariants,
resets,
),
Effect::Send(_) | Effect::Receive(_) => self
.active_autonomous_transition(
guard.as_ref(),
constraints,
invariants,
),
}
} {
last_post_state = Some(*post_state);
last_post_state
} else {
None
}
})
})
}
pub(crate) fn nosync_possible_transitions(
&self,
) -> impl Iterator<Item = (Action, impl Iterator<Item = Location>)> {
assert_eq!(self.current_states.len(), 1);
let current_loc = self.current_states[0];
let mut last_post_state: Option<Location> = None;
self.def.locations[current_loc.0 as usize]
.0
.iter()
.map(move |(action, transitions)| {
(
*action,
transitions
.iter()
.filter_map(move |(post_state, guard, constraints)| {
if last_post_state.is_some_and(|s| s == *post_state) {
return None;
}
let (_, ref invariants) = self.def.locations[post_state.0 as usize];
if if *action == EPSILON {
self.active_autonomous_transition(
guard.as_ref(),
constraints,
invariants,
)
} else {
match self.def.effects[action.0 as usize] {
Effect::Effects(_, ref resets) => self.active_transition(
guard.as_ref(),
constraints,
invariants,
resets,
),
Effect::Send(_) | Effect::Receive(_) => self
.active_autonomous_transition(
guard.as_ref(),
constraints,
invariants,
),
}
} {
last_post_state = Some(*post_state);
last_post_state
} else {
None
}
}),
)
})
}
#[inline]
fn active_transition(
&self,
guard: Option<&PgGuard>,
constraints: &[TimeConstraint],
invariants: &[TimeConstraint],
resets: &[Clock],
) -> bool {
guard.is_none_or(|guard| {
guard.eval(&|var| self.vars[var.0 as usize], &mut DummyRng)
}) && constraints.iter().all(|(c, l, u)| {
let time = self.clocks[c.0 as usize];
l.is_none_or(|l| l <= time) && u.is_none_or(|u| time < u)
}) && invariants.iter().all(|(c, l, u)| {
let time = if resets.binary_search(c).is_ok() {
0
} else {
self.clocks[c.0 as usize]
};
l.is_none_or(|l| l <= time) && u.is_none_or(|u| time < u)
})
}
#[inline]
fn active_autonomous_transition(
&self,
guard: Option<&PgGuard>,
constraints: &[TimeConstraint],
invariants: &[TimeConstraint],
) -> bool {
guard.is_none_or(|guard| {
guard.eval(&|var| self.vars[var.0 as usize], &mut DummyRng)
}) && constraints.iter().chain(invariants).all(|(c, l, u)| {
let time = self.clocks[c.0 as usize];
l.is_none_or(|l| l <= time) && u.is_none_or(|u| time < u)
})
}
fn active_transitions(
&self,
action: Action,
post_states: &[Location],
resets: &[Clock],
) -> bool {
self.current_states
.iter()
.zip(post_states)
.all(|(current_state, post_state)| {
self.def
.guards(*current_state, action, *post_state)
.any(|(guard, constraints)| {
self.active_transition(
guard,
constraints,
&self.def.locations[post_state.0 as usize].1,
resets,
)
})
})
}
fn active_autonomous_transitions(&self, post_states: &[Location]) -> bool {
self.current_states
.iter()
.zip(post_states)
.all(|(current_state, post_state)| {
self.def
.guards(*current_state, EPSILON, *post_state)
.any(|(guard, constraints)| {
self.active_autonomous_transition(
guard,
constraints,
&self.def.locations[post_state.0 as usize].1,
)
})
})
}
pub fn transition<R: Rng>(
&mut self,
action: Action,
post_states: &[Location],
rng: &mut R,
) -> Result<(), PgError> {
if post_states.len() != self.current_states.len() {
return Err(PgError::MismatchingPostStates);
}
if let Some(ps) = post_states
.iter()
.find(|ps| ps.0 >= self.def.locations.len() as LocationIdx)
{
return Err(PgError::MissingLocation(*ps));
}
if action == EPSILON {
if !self.active_autonomous_transitions(post_states) {
return Err(PgError::UnsatisfiedGuard);
}
} else if action.0 >= self.def.effects.len() as LocationIdx {
return Err(PgError::MissingAction(action));
} else if let Effect::Effects(ref effects, ref resets) = self.def.effects[action.0 as usize]
{
if self.active_transitions(action, post_states, resets) {
effects.iter().for_each(|(var, effect)| {
self.vars[var.0 as usize] = effect.eval(&|var| self.vars[var.0 as usize], rng)
});
resets
.iter()
.for_each(|clock| self.clocks[clock.0 as usize] = 0);
} else {
return Err(PgError::UnsatisfiedGuard);
}
} else {
return Err(PgError::Communication(action));
}
self.current_states.copy_from_slice(post_states);
Ok(())
}
#[inline]
pub fn can_wait(&self, delta: Time) -> bool {
self.current_states
.iter()
.flat_map(|current_state| self.def.locations[current_state.0 as usize].1.iter())
.all(|(c, l, u)| {
let start_time = self.clocks[c.0 as usize];
let end_time = start_time + delta;
l.is_none_or(|l| l <= start_time) && u.is_none_or(|u| end_time < u)
})
}
#[inline]
pub fn wait(&mut self, delta: Time) -> Result<(), PgError> {
if self.can_wait(delta) {
self.clocks.iter_mut().for_each(|t| *t += delta);
Ok(())
} else {
Err(PgError::Invariant)
}
}
pub(crate) fn send<'a, R: Rng>(
&'a mut self,
action: Action,
post_states: &[Location],
rng: &'a mut R,
) -> Result<SmallVec<[Val; 2]>, PgError> {
if action == EPSILON {
Err(PgError::NotSend(action))
} else if self.active_transitions(action, post_states, &[]) {
if let Effect::Send(effects) = &self.def.effects[action.0 as usize] {
let vals = effects
.iter()
.map(|effect| effect.eval(&|var| self.vars[var.0 as usize], rng))
.collect();
self.current_states.copy_from_slice(post_states);
Ok(vals)
} else {
Err(PgError::NotSend(action))
}
} else {
Err(PgError::UnsatisfiedGuard)
}
}
pub(crate) fn receive(
&mut self,
action: Action,
post_states: &[Location],
vals: &[Val],
) -> Result<(), PgError> {
if action == EPSILON {
Err(PgError::NotReceive(action))
} else if self.active_transitions(action, post_states, &[]) {
if let Effect::Receive(ref vars) = self.def.effects[action.0 as usize] {
if vars.len() == vals.len()
&& vals.iter().zip(vars).all(|(val, var)| {
self.vars
.get(var.0 as usize)
.expect("variable exists")
.r#type()
== val.r#type()
})
{
vals.iter().zip(vars).for_each(|(&val, var)| {
*self.vars.get_mut(var.0 as usize).expect("variable exists") = val
});
self.current_states.copy_from_slice(post_states);
Ok(())
} else {
Err(PgError::TypeMismatch)
}
} else {
Err(PgError::NotReceive(action))
}
} else {
Err(PgError::UnsatisfiedGuard)
}
}
#[inline]
pub(crate) fn eval(&self, expr: &Expression<Var>) -> Val {
expr.eval(
&|v: &Var| *self.vars.get(v.0 as usize).unwrap(),
&mut DummyRng,
)
}
#[inline]
pub(crate) fn val(&self, var: Var) -> Result<Val, PgError> {
self.vars
.get(var.0 as usize)
.copied()
.ok_or(PgError::MissingVar(var))
}
}
impl<'def> ProgramGraphRun<'def> {
pub(crate) fn montecarlo<R: Rng + SeedableRng>(&mut self, rng: &mut R) -> Option<Action> {
let mut rand = R::from_rng(rng);
if let Some((action, post_states)) = self
.possible_transitions()
.filter_map(|(action, post_state)| {
post_state
.map(|locs| locs.choose(rng))
.collect::<Option<SmallVec<[Location; 4]>>>()
.map(|loc| (action, loc))
})
.choose(&mut rand)
{
self.transition(action, post_states.as_slice(), rng)
.expect("successful transition");
return Some(action);
}
None
}
}
#[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 transitions() {
let mut builder = ProgramGraphBuilder::new();
let initial = builder.new_initial_location();
let r#final = builder.new_location();
let action = builder.new_action();
builder
.add_transition(initial, action, r#final, None)
.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(action, &[r#final], &mut rng)
.expect("transition to final");
assert_eq!(pg.possible_transitions().count(), 0);
}
#[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(())
}
}