use biodivine_lib_bdd::{Bdd, BddValuation, BddVariableSet};
use bitvec::vec::BitVec;
use itertools::Itertools;
use maplit::hashmap;
use std::{
collections::{HashMap, HashSet, VecDeque},
fmt::Debug,
};
use thiserror::Error;
mod dot;
#[derive(Error, Debug)]
pub enum Error {
#[error("missing initial state, call `set_initial` before `build`")]
MissingInitialState,
}
#[derive(Clone)]
pub struct Automaton<Edg, Acc> {
initial_state: StateId,
states: Vec<State>,
transitions: Vec<Edg>,
acceptance: Acc,
variables: BddVariableSet,
}
#[derive(Clone)]
pub struct AutomatonBuilder<Edg, Acc> {
initial_state: Option<StateId>,
states: Vec<State>,
transitions: Vec<Edg>,
acceptance: Acc,
variables: BddVariableSet,
}
#[derive(Debug, Clone, Default)]
pub(crate) struct Alternating(Vec<(Bdd, HashSet<StateId>)>);
#[derive(Debug, Clone, Default)]
pub(crate) struct NonDeterministic(HashMap<StateId, Bdd>);
#[derive(Debug, Clone, Default)]
pub struct Deterministic(HashMap<StateId, Bdd>);
#[derive(Debug, Clone, Default)]
pub(crate) struct Büchi(Vec<StateId>);
#[derive(Debug, Clone, Copy, Default)]
pub struct Safety(());
pub(crate) type AlternatingBüchiAutomaton = Automaton<Alternating, Büchi>;
pub(crate) type NonDeterministicBüchiAutomaton = Automaton<NonDeterministic, Büchi>;
pub(crate) type NonDeterministicSafetyAutomaton = Automaton<NonDeterministic, Safety>;
pub type DeterministicSafetyAutomaton = Automaton<Deterministic, Safety>;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct StateId(usize);
#[derive(Debug, Clone)]
struct State {
id: StateId,
name: Option<String>,
}
impl<Edg, Acc: Default> Automaton<Edg, Acc> {
pub fn builder(variables: &BddVariableSet) -> AutomatonBuilder<Edg, Acc> {
AutomatonBuilder {
initial_state: None,
states: Vec::new(),
transitions: Vec::new(),
acceptance: Acc::default(),
variables: variables.clone(),
}
}
}
impl<Edg: Clone + Default, Acc: Clone> AutomatonBuilder<Edg, Acc> {
pub fn set_initial(&mut self, state: StateId) -> &mut Self {
self.initial_state = Some(state);
self
}
pub fn new_state(&mut self, name: Option<&str>) -> StateId {
let id = StateId(self.states.len());
self.states.push(State {
id,
name: name.map(str::to_string),
});
self.transitions.push(Edg::default());
assert_eq!(self.states.len(), self.transitions.len());
id
}
pub fn build(&self) -> Result<Automaton<Edg, Acc>, Error> {
Ok(Automaton {
initial_state: self.initial_state.ok_or(Error::MissingInitialState)?,
states: self.states.clone(),
transitions: self.transitions.clone(),
acceptance: self.acceptance.clone(),
variables: self.variables.clone(),
})
}
}
impl<Edg, Acc> Automaton<Edg, Acc> {
pub(crate) fn state_count(&self) -> usize {
self.states.len()
}
pub(crate) fn get_initial(&self) -> StateId {
self.initial_state
}
}
impl<Acc> AutomatonBuilder<NonDeterministic, Acc> {
pub(crate) fn add_edge(&mut self, state: StateId, guard: &Bdd, target: StateId) -> &mut Self {
if guard.is_false() {
return self;
}
let entry = self.transitions[state.0]
.0
.entry(target)
.or_insert_with(|| self.variables.mk_false());
*entry = entry.or(guard);
self
}
}
impl<Acc> AutomatonBuilder<Alternating, Acc> {
pub(crate) fn add_edge(
&mut self,
state: StateId,
guard: Bdd,
target: HashSet<StateId>,
) -> &mut Self {
if guard.is_false() {
return self;
}
for (other_guard, other_target) in &mut self.transitions[state.0].0 {
if target == *other_target {
*other_guard = other_guard.or(&guard);
return self;
}
}
self.transitions[state.0].0.push((guard, target));
self
}
}
impl<Acc> AutomatonBuilder<Deterministic, Acc> {
pub(crate) fn add_edge(&mut self, state: StateId, guard: &Bdd, target: StateId) -> &mut Self {
if guard.is_false() {
return self;
}
for (other_target, other_guard) in &self.transitions[state.0].0 {
if target == *other_target && guard == other_guard {
return self;
}
assert!(
guard.and(other_guard).is_false(),
"Overlapping guards are not allowed for deterministic automata",
);
}
let entry = self.transitions[state.0]
.0
.entry(target)
.or_insert_with(|| self.variables.mk_false());
*entry = entry.or(guard);
self
}
}
impl<Acc> Automaton<Deterministic, Acc> {
pub(crate) fn next_state(&self, state: StateId, valuation: &BddValuation) -> Option<StateId> {
self.transitions[state.0]
.0
.iter()
.find(|(_, guard)| guard.eval_in(valuation))
.map(|(target, _)| *target)
}
}
impl<Edg> AutomatonBuilder<Edg, Büchi> {
pub(crate) fn add_accepting(&mut self, state: StateId) {
self.acceptance.0.push(state);
}
}
impl NonDeterministicBüchiAutomaton {
pub(crate) fn from(alternating: &AlternatingBüchiAutomaton) -> Self {
let variables = &alternating.variables;
let mut automaton = NonDeterministicBüchiAutomaton::builder(variables);
let initial_state = (
PowerState::singleton(alternating, alternating.initial_state),
PowerState::empty(alternating),
);
let formatted = format_state(&initial_state);
let initial_state_id = automaton.new_state(Some(&formatted));
let mut mapping = hashmap! {initial_state.clone() => initial_state_id};
automaton.set_initial(initial_state_id);
automaton.add_accepting(initial_state_id);
let accepting_states = PowerState::from(alternating, &alternating.acceptance.0);
let mut queue = VecDeque::from([initial_state]);
while let Some(state) = queue.pop_front() {
for (guard, (left, right)) in state
.0
.iter()
.map(|s| {
alternating.transitions[s.0]
.0
.iter()
.zip(std::iter::repeat(state.1.contains(s)))
})
.multi_cartesian_product()
.map(|edges| {
edges.iter().fold(
(
variables.mk_true(),
(
PowerState::empty(alternating),
PowerState::empty(alternating),
),
),
|(val_guard, (mut left, mut right)), ((guard, targets), accepting)| {
for target in targets {
left.add(*target);
if *accepting {
right.add(*target);
}
}
(val_guard.and(guard), (left, right))
},
)
})
{
let accepting_states = if state.1.is_empty() {
left.without(&accepting_states)
} else {
right.without(&accepting_states)
};
let target_states = (left, accepting_states);
let formatted = format_state(&target_states);
let target = *mapping.entry(target_states.clone()).or_insert_with(|| {
let id = automaton.new_state(Some(&formatted));
if target_states.1.is_empty() {
automaton.add_accepting(id);
}
queue.push_back(target_states);
id
});
automaton.add_edge(mapping[&state], &guard, target);
}
}
automaton
.build()
.expect("automaton construction should not fail")
}
}
fn format_state((left, right): &(PowerState, PowerState)) -> String {
format!("({}, {})", left, right)
}
fn format_bdd(bdd: &Bdd, variables: &BddVariableSet) -> String {
if bdd.is_true() {
return "true".to_string();
} else if bdd.is_false() {
return "false".to_string();
}
let dnf = bdd
.sat_clauses()
.map(|clause| {
clause
.to_values()
.into_iter()
.map(|(var, val)| {
format!("{}({})", if val { "" } else { "!" }, variables.name_of(var))
})
.collect::<Vec<_>>()
.join(" & ")
})
.collect::<Vec<_>>()
.join(" | ");
assert!(!dnf.is_empty());
dnf
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct PowerState(BitVec);
impl PowerState {
fn empty<Edg, Acc>(automaton: &Automaton<Edg, Acc>) -> Self {
Self(BitVec::repeat(false, automaton.states.len()))
}
fn is_empty(&self) -> bool {
self.0.not_any()
}
fn all<Edg, Acc>(automaton: &Automaton<Edg, Acc>) -> Self {
Self(BitVec::repeat(true, automaton.states.len()))
}
fn singleton<Edg, Acc>(automaton: &Automaton<Edg, Acc>, state: StateId) -> Self {
let mut bitvec = BitVec::repeat(false, automaton.states.len());
bitvec.set(state.0, true);
Self(bitvec)
}
fn from<Edg, Acc>(automaton: &Automaton<Edg, Acc>, states: &[StateId]) -> Self {
let mut bitvec = BitVec::repeat(false, automaton.states.len());
for state in states {
bitvec.set(state.0, true);
}
Self(bitvec)
}
fn iter(&self) -> impl Iterator<Item = StateId> + '_ + Clone {
self.0
.iter()
.enumerate()
.filter(|(_, val)| **val)
.map(|(idx, _)| StateId(idx))
}
fn without(&self, other: &Self) -> PowerState {
PowerState(self.0.clone() & &!other.0.clone())
}
fn contains(&self, state: StateId) -> bool {
self.0[state.0]
}
fn add(&mut self, state: StateId) {
self.0.set(state.0, true);
}
}
impl std::fmt::Display for PowerState {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{:?}",
self.0
.iter()
.enumerate()
.filter(|(_, val)| **val)
.map(|(idx, _)| idx)
.collect::<Vec<_>>(),
)
}
}
impl NonDeterministicSafetyAutomaton {
pub(crate) fn from(automaton: &NonDeterministicBüchiAutomaton) -> Self {
Self {
initial_state: automaton.initial_state,
states: automaton.states.clone(),
transitions: automaton.transitions.clone(),
acceptance: Safety(()),
variables: automaton.variables.clone(),
}
}
pub fn is_deterministic(&self) -> bool {
self.states.iter().all(|state| {
self.transitions[state.id.0]
.0
.iter()
.tuple_combinations()
.all(|((target, guard), (other_target, other_guard))| {
assert!(target != other_target);
guard.and(other_guard).is_false()
})
})
}
pub(crate) fn determinize(&self) -> DeterministicSafetyAutomaton {
let initial_state = PowerState::singleton(self, self.initial_state);
let formatted = format!("{initial_state}");
let mut automaton = DeterministicSafetyAutomaton::builder(&self.variables);
let initial_state_id = automaton.new_state(Some(&formatted));
automaton.set_initial(initial_state_id);
let mut mapping = hashmap! {initial_state.clone() => initial_state_id};
let mut queue = VecDeque::from([initial_state]);
while let Some(state) = queue.pop_front() {
let transitions: Vec<_> = state
.iter()
.flat_map(|s| {
self.transitions[s.0]
.0
.iter()
.map(|(target, guard)| (guard.clone(), *target))
})
.collect();
for x in (0..transitions.len()).powerset().skip(1) {
let (guard, targets) = transitions.iter().enumerate().fold(
(automaton.variables.mk_true(), Vec::new()),
|(val_guard, mut val_target), (idx, (guard, target))| {
if x.contains(&idx) {
val_target.push(*target);
(val_guard.and(guard), val_target)
} else {
(val_guard.and(&guard.not()), val_target)
}
},
);
if guard.is_false() {
continue;
}
let targets = PowerState::from(self, &targets);
let formatted = format!("{targets}");
let target = *mapping.entry(targets.clone()).or_insert_with(|| {
let id = automaton.new_state(Some(&formatted));
queue.push_back(targets);
id
});
automaton.add_edge(mapping[&state], &guard, target);
}
}
automaton.build().expect("determinization should not fail")
}
}
impl DeterministicSafetyAutomaton {
#[cfg(test)]
pub(crate) fn new_invariant(variables: BddVariableSet, invariant: Bdd) -> Self {
Self {
initial_state: StateId(0),
states: vec![State {
id: StateId(0),
name: None,
}],
transitions: vec![Deterministic(hashmap! {StateId(0) => invariant})],
acceptance: Safety(()),
variables,
}
}
pub(crate) fn minimize(&self) -> Self {
let equivalence_classes = self.determine_equivalence_classes();
let initial_state = equivalence_classes
.iter()
.find(|class| class.contains(self.initial_state))
.expect("every state is in exactly one equivalence class");
let mut automaton = Self::builder(&self.variables);
let initial_state_id = automaton.new_state(Some(&format!("{initial_state}")));
automaton.set_initial(initial_state_id);
let mut mapping = hashmap! {initial_state.clone() => initial_state_id};
for class in &equivalence_classes {
let state_id = *mapping
.entry(class.clone())
.or_insert_with(|| automaton.new_state(Some(&format!("{class}"))));
for state in class.iter() {
for (target, guard) in &self.transitions[state.0].0 {
let target_class = equivalence_classes
.iter()
.find(|class| class.contains(*target))
.expect("every state is in exactly one equivalence class");
let target_state_id = *mapping
.entry(target_class.clone())
.or_insert_with(|| automaton.new_state(Some(&format!("{target_class}"))));
automaton.add_edge(state_id, guard, target_state_id);
}
}
}
automaton.build().expect("minimization should not fail")
}
fn determine_equivalence_classes(&self) -> Vec<PowerState> {
let mut equivalence_classes = vec![PowerState::all(self)];
let mut new_equivalence_classes = Vec::new();
loop {
for class in &equivalence_classes {
let pivot = class
.iter()
.next()
.expect("equivalence classes are not empty");
let mut new_equiv = PowerState::singleton(self, pivot);
let mut other_equiv = PowerState::empty(self);
for other in class.iter().skip(1) {
let mut different = false;
let mut rejecting = self.variables.mk_true();
let mut other_rejecting = self.variables.mk_true();
assert!(!self.transitions[pivot.0].0.is_empty());
assert!(!self.transitions[other.0].0.is_empty());
for ((target, guard), (other_target, other_guard)) in self.transitions[pivot.0]
.0
.iter()
.cartesian_product(&self.transitions[other.0].0)
{
rejecting = rejecting.and(&guard.not());
other_rejecting = other_rejecting.and(&other_guard.not());
if guard.and(other_guard).is_false() {
continue;
}
let next_equiv = equivalence_classes
.iter()
.find(|class| class.contains(*target))
.expect("every state is in exactly one equivalence class");
if !next_equiv.contains(*other_target) {
different = true;
}
}
if rejecting != other_rejecting {
different = true;
}
if different {
other_equiv.add(other);
} else {
new_equiv.add(other);
}
}
new_equivalence_classes.push(new_equiv);
if !other_equiv.is_empty() {
new_equivalence_classes.push(other_equiv);
}
}
if equivalence_classes.len() == new_equivalence_classes.len() {
break;
}
equivalence_classes = std::mem::take(&mut new_equivalence_classes);
}
equivalence_classes
}
fn concat(&self, other: &Self) -> Self {
let mut result = self.clone();
result.states.extend(other.states.iter().map(|s| State {
id: StateId(s.id.0 + self.state_count()),
name: s.name.clone(),
}));
result
.transitions
.extend(other.transitions.iter().map(|edge| {
Deterministic(
edge.0
.iter()
.map(|(target, guard)| {
(StateId(target.0 + self.state_count()), guard.clone())
})
.collect(),
)
}));
result
}
pub fn is_equivalent_to(&self, other: &Self) -> bool {
let combined = self.concat(other);
let left_initial = self.initial_state;
let right_initial = StateId(other.initial_state.0 + self.state_count());
let equivalences = combined.determine_equivalence_classes();
equivalences.iter().any(|powerstate| {
powerstate.contains(left_initial) && powerstate.contains(right_initial)
})
}
}
impl<'a> IntoIterator for &'a Deterministic {
type Item = (&'a StateId, &'a Bdd);
type IntoIter = std::collections::hash_map::Iter<'a, StateId, Bdd>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl<'a> IntoIterator for &'a NonDeterministic {
type Item = (&'a StateId, &'a Bdd);
type IntoIter = std::collections::hash_map::Iter<'a, StateId, Bdd>;
fn into_iter(self) -> Self::IntoIter {
(&self.0).iter()
}
}
impl std::ops::Index<&StateId> for Deterministic {
type Output = Bdd;
fn index(&self, index: &StateId) -> &Self::Output {
&self.0[index]
}
}
impl std::ops::Index<&StateId> for NonDeterministic {
type Output = Bdd;
fn index(&self, index: &StateId) -> &Self::Output {
&self.0[index]
}
}
#[cfg(test)]
mod test {
use super::DeterministicSafetyAutomaton;
use biodivine_lib_bdd::{BddVariableSet, BddVariableSetBuilder};
#[test]
fn same_is_equivalent() {
let mut builder = BddVariableSetBuilder::new();
let [a, b] = builder.make(&["a", "b"]);
let variables: BddVariableSet = builder.build();
let invariant = variables
.mk_literal(a, true)
.and(&variables.mk_literal(b, false));
let automaton = DeterministicSafetyAutomaton::new_invariant(variables.clone(), invariant);
let copy = automaton.clone();
assert!(automaton.is_equivalent_to(©));
let invariant = variables
.mk_literal(a, true)
.and(&variables.mk_literal(b, true));
let automaton2 = DeterministicSafetyAutomaton::new_invariant(variables, invariant);
assert!(!automaton.is_equivalent_to(&automaton2));
}
}