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::{
partially_ordered_workflow_language::{PartiallyOrderedWorkflowLanguage, PowlNode},
process_tree::{Node, Operator, TreeMarking},
},
strongly_connected_components::StronglyConnectedComponents,
};
use std::{
collections::{HashMap, HashSet, hash_map::Entry},
fmt::Display,
};
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);
impl InfinitelyManyTraces for PartiallyOrderedWorkflowLanguage {
type LivState = TreeMarking;
fn infinitely_many_traces(&self) -> Result<bool> {
for (node_index, node) in self.nodes().enumerate() {
match node {
PowlNode::Activity {
activity,
repeatable,
..
} => {
if *repeatable && activity.is_some() {
return Ok(true);
}
}
PowlNode::PartialOrder { repeatable, .. } => {
if *repeatable && !self.only_empty_trace(node_index) {
return Ok(true);
}
}
PowlNode::ChoiceGraph {
repeatable,
edges,
number_of_children,
..
} => {
if *repeatable && !self.only_empty_trace(node_index) {
return Ok(true);
}
return Ok((edges, number_of_children)
.strongly_connected_components()
.0
.iter()
.any(|scc| {
scc.len() >= 2
&& scc.iter().any(|child_rank| {
let child = self.get_child(node_index, *child_rank);
!self.only_empty_trace(child)
})
}));
}
}
}
Ok(false)
}
}
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, node_2_scc) = self.strongly_connected_components();
let mut candidate_sccs = HashSet::new();
{
for (_, source, target, activity) in self.transitions() {
if activity.is_some() && node_2_scc[source] == node_2_scc[target] {
candidate_sccs.insert(sccs[node_2_scc[source]].clone());
}
}
}
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().next().unwrap();
if livelock_cache
.is_state_part_of_livelock(&AutomatonState::of(*node))
.unwrap()
{
return false;
}
if !reachability_cache
.is_state_reachable(&AutomatonState::of(*node))
.unwrap()
{
return false;
}
true
});
}
return Ok(!candidate_sccs.is_empty());
}
}
};
}
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());
}
}