use crate::{
ebi_framework::displayable::Displayable,
semantics::{labelled_petri_net_semantics::LPNMarking, semantics::Semantics},
techniques::{livelock::IsPartOfLivelock, reachability::IsReachable},
};
use ebi_objects::{
AutomatonSemantics, AutomatonState, DeterministicFiniteAutomaton, DirectlyFollowsGraph,
DirectlyFollowsModel, EventLog, FiniteLanguage, FiniteStochasticLanguage, LabelledPetriNet,
ProcessTree, StochasticDeterministicFiniteAutomaton, StochasticDirectlyFollowsModel,
StochasticLabelledPetriNet, StochasticNondeterministicFiniteAutomaton, StochasticProcessTree,
anyhow::Result,
ebi_objects::process_tree::{Node, Operator, TreeMarking},
};
use intmap::IntMap;
use rustc_hash::FxHashMap;
use std::{
collections::{HashMap, HashSet, hash_map::Entry},
fmt::Display,
};
use strongly_connected_components::{Graph, SccDecomposition};
pub trait InfinitelyManyTraces {
type LivState: Displayable;
fn infinitely_many_traces(&self) -> Result<bool>;
}
macro_rules! tree {
($t:ident) => {
impl InfinitelyManyTraces for $t {
type LivState = TreeMarking;
fn infinitely_many_traces(&self) -> Result<bool> {
for node in 0..self.get_number_of_nodes() {
if let Some(Node::Operator(Operator::Loop, _)) = self.get_node(node) {
for child in self.get_descendants(node) {
if let Node::Activity(_) = child {
return Ok(true);
}
}
}
}
return Ok(false);
}
}
};
}
tree!(ProcessTree);
tree!(StochasticProcessTree);
macro_rules! lpn {
($t:ident) => {
impl InfinitelyManyTraces for $t {
type LivState = LPNMarking;
fn infinitely_many_traces(&self) -> Result<bool> {
let mut graph = CycleGraph::new();
let mut queue;
if let Some(initial_state) = self.get_initial_state() {
graph.add_state(&initial_state);
queue = vec![0usize];
} else {
return Ok(false);
}
while let Some(state_index) = queue.pop() {
let state = graph.get_state(state_index).clone();
let enabled_transitions = self.get_enabled_transitions(&state);
for transition in enabled_transitions {
let mut child_state = state.clone();
self.execute_transition(&mut child_state, transition)?;
let (child_state_index, new) = graph.add_state(&child_state);
if new {
queue.push(child_state_index);
}
graph.add_edge(
state_index,
child_state_index,
self.is_transition_silent(transition),
);
if graph.search_for_non_silent_cycle(child_state_index) {
return Ok(true);
}
}
}
Ok(false)
}
}
};
}
struct CycleGraph {
index2state: Vec<LPNMarking>,
state2index: HashMap<LPNMarking, usize>,
edges: Vec<Vec<usize>>, is_edge_silent: Vec<Vec<bool>>, }
impl CycleGraph {
fn new() -> Self {
Self {
index2state: vec![],
state2index: HashMap::new(),
edges: vec![],
is_edge_silent: vec![],
}
}
fn add_state(&mut self, marking: &LPNMarking) -> (usize, bool) {
match self.state2index.entry(marking.clone()) {
Entry::Occupied(occupied_entry) => (*occupied_entry.get(), false),
Entry::Vacant(vacant_entry) => {
let index = self.index2state.len();
vacant_entry.insert(self.index2state.len());
self.index2state.push(marking.clone());
self.edges.push(vec![]);
self.is_edge_silent.push(vec![]);
(index, true)
}
}
}
fn get_state(&self, state_index: usize) -> &LPNMarking {
&self.index2state[state_index]
}
fn add_edge(&mut self, source: usize, target: usize, is_silent: bool) {
self.edges[target].push(source);
self.is_edge_silent[target].push(is_silent);
}
fn search_for_non_silent_cycle(&self, state: usize) -> bool {
let mut reached = vec![false; self.index2state.len()]; let mut reached_with_non_silent_transition = vec![false; self.index2state.len()];
let mut queue = vec![];
queue.push(state);
while let Some(state) = queue.pop() {
for (predecessor, edge_silent) in self.edges[state]
.iter()
.zip(self.is_edge_silent[state].iter())
{
reached_with_non_silent_transition[*predecessor] |= !edge_silent;
if !reached[*predecessor] {
reached[*predecessor] = true;
queue.push(*predecessor);
}
if self.index2state[*predecessor]
.is_larger_than_or_equal_to(&self.index2state[state])
&& reached_with_non_silent_transition[*predecessor]
{
return true;
}
}
}
false
}
}
impl Display for CycleGraph {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(
f,
"nodes: {:?}, rev_edges {:?}, silent {:?}",
self.index2state, self.edges, self.is_edge_silent
)
}
}
macro_rules! aut {
($type:ty) => {
impl InfinitelyManyTraces for $type {
type LivState = AutomatonState;
fn infinitely_many_traces(&self) -> Result<bool> {
if self.get_initial_state().is_none() {
return Ok(false);
};
let (sccs, state_2_node, node_2_state) = create_sccs(self);
let mut candidate_sccs = HashSet::new();
{
for (_, source, target, activity) in self.transitions() {
if activity.is_some()
&& sccs.scc_of_node(*state_2_node.get(source).unwrap())
== sccs.scc_of_node(*state_2_node.get(target).unwrap())
{
candidate_sccs
.insert(sccs.scc_of_node(*state_2_node.get(source).unwrap()));
}
}
}
if candidate_sccs.is_empty() {
return Ok(false);
}
{
let mut livelock_cache = self.get_livelock_cache();
let mut reachability_cache = self.get_reachability_cache();
candidate_sccs.retain(|scc| {
let node = scc.iter_nodes().next().unwrap();
let state = node_2_state.get(&node).unwrap();
if livelock_cache.is_state_part_of_livelock(&state).unwrap() {
return false;
}
if !reachability_cache.is_state_reachable(&state).unwrap() {
return false;
}
true
});
}
return Ok(!candidate_sccs.is_empty());
}
}
};
}
fn create_sccs<T>(
automaton: &T,
) -> (
SccDecomposition,
IntMap<AutomatonState, strongly_connected_components::Node>,
FxHashMap<strongly_connected_components::Node, AutomatonState>,
)
where
T: AutomatonSemantics,
{
let mut graph = Graph::new();
let mut state_2_node = IntMap::new();
let mut node_2_state = FxHashMap::default();
for state in automaton.states() {
let node = graph.new_node();
state_2_node.insert(state, node);
node_2_state.insert(node, state);
}
for (_, source, target, _) in automaton.transitions() {
graph.new_edge(
*state_2_node.get(source).unwrap(),
*state_2_node.get(target).unwrap(),
);
}
(graph.find_sccs(), state_2_node, node_2_state)
}
macro_rules! lang {
($t:ident) => {
impl InfinitelyManyTraces for $t {
type LivState = usize;
fn infinitely_many_traces(&self) -> Result<bool> {
Ok(false)
}
}
};
}
aut!(DirectlyFollowsModel);
aut!(StochasticDirectlyFollowsModel);
aut!(StochasticDeterministicFiniteAutomaton);
aut!(DeterministicFiniteAutomaton);
aut!(StochasticNondeterministicFiniteAutomaton);
aut!(DirectlyFollowsGraph);
lpn!(LabelledPetriNet);
lpn!(StochasticLabelledPetriNet);
lang!(EventLog);
lang!(FiniteLanguage);
lang!(FiniteStochasticLanguage);
#[cfg(test)]
mod tests {
use crate::techniques::infinitely_many_traces::InfinitelyManyTraces;
use ebi_objects::{
AutomatonSemantics, DeterministicFiniteAutomaton, DirectlyFollowsGraph, LabelledPetriNet,
ProcessTree, StochasticDeterministicFiniteAutomaton, StochasticLabelledPetriNet,
StochasticNondeterministicFiniteAutomaton,
};
use std::fs;
#[test]
fn infinitely_many_traces_lpn() {
let fin = fs::read_to_string("testfiles/a-loop.lpn").unwrap();
let lpn = fin.parse::<LabelledPetriNet>().unwrap();
assert!(lpn.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_slpn() {
let fin = fs::read_to_string("testfiles/empty_net.slpn").unwrap();
let slpn = fin.parse::<StochasticLabelledPetriNet>().unwrap();
assert!(!slpn.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_dfa() {
let fin = fs::read_to_string("testfiles/a-loop.dfa").unwrap();
let dfa = fin.parse::<DeterministicFiniteAutomaton>().unwrap();
println!("states {:?}", dfa.states().collect::<Vec<_>>());
println!("transitions {:?}", dfa.transitions().collect::<Vec<_>>());
assert!(dfa.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_sdfa() {
let fin = fs::read_to_string("testfiles/a-livelock-zeroweight.sdfa").unwrap();
let sdfa = fin
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
println!("states {:?}", sdfa.states().collect::<Vec<_>>());
println!("transitions {:?}", sdfa.transitions().collect::<Vec<_>>());
assert!(!sdfa.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_tree() {
let fin = fs::read_to_string("testfiles/all_operators.ptree").unwrap();
let lpn = fin.parse::<ProcessTree>().unwrap();
assert!(lpn.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_sdfa_2() {
let fin =
fs::read_to_string("./testfiles/acb-abc-ad-aded-adeded-adededed.slang.sdfa").unwrap();
let sdfa = fin
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
assert!(!sdfa.infinitely_many_traces().unwrap());
}
#[test]
fn disconnected_and_livelock() {
let fin = fs::read_to_string("./testfiles/disconnected_and_livelock.sdfa").unwrap();
let sdfa = fin
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
assert!(!sdfa.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_dfa_bpic12() {
let fin = fs::read_to_string("./testfiles/bpic12-a.xes.gz-dfg.dfg").unwrap();
let dfg = fin.parse::<DirectlyFollowsGraph>().unwrap();
assert!(!dfg.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_sem_bpic12() {
let fin = fs::read_to_string("./testfiles/bpic12-a.xes.gz-dfg.dfg").unwrap();
let dfg = fin.parse::<DirectlyFollowsGraph>().unwrap();
assert!(!dfg.infinitely_many_traces().unwrap());
}
#[test]
fn infinitely_many_traces_snfa() {
let fin = fs::read_to_string("./testfiles/infinite_traces.snfa").unwrap();
let snfa = fin
.parse::<StochasticNondeterministicFiniteAutomaton>()
.unwrap();
assert!(snfa.infinitely_many_traces().unwrap());
}
}