use crate::{
ebi_framework::{displayable::Displayable, ebi_command::EbiCommand},
ebi_traits::{
ebi_trait_event_log_event_attributes::EbiTraitEventLogEventAttributes,
ebi_trait_semantics::EbiTraitSemantics,
},
semantics::semantics::Semantics,
techniques::{align::Align, resource_utilisation::set_resource_utilisations},
};
use chrono::{DateTime, FixedOffset};
use ebi_objects::{
Activity, ActivityKey, Attribute, Executions,
anyhow::{Context, Error, Ok, Result, anyhow},
ebi_objects::{
executions::Execution, labelled_petri_net::TransitionIndex, language_of_alignments::Move,
},
};
use intmap::IntMap;
use process_mining::core::event_data::case_centric::AttributeValue;
use rayon::iter::{IndexedParallelIterator, ParallelIterator};
use std::{
collections::VecDeque,
fmt::{Debug, Display},
hash::Hash,
sync::{Arc, Mutex},
};
pub trait FindExecutions {
fn find_executions(
&mut self,
log: &mut Box<dyn EbiTraitEventLogEventAttributes>,
) -> Result<Executions>;
}
impl FindExecutions for EbiTraitSemantics {
fn find_executions(
&mut self,
log: &mut Box<dyn EbiTraitEventLogEventAttributes>,
) -> Result<Executions> {
match self {
EbiTraitSemantics::Usize(sem) => sem.find_executions(log),
EbiTraitSemantics::AutomatonState(sem) => sem.find_executions(log),
EbiTraitSemantics::Marking(sem) => sem.find_executions(log),
EbiTraitSemantics::TreeMarking(sem) => sem.find_executions(log),
EbiTraitSemantics::BPMNMarking(sem) => sem.find_executions(log),
EbiTraitSemantics::FspolangMarking(sem) => sem.find_executions(log),
}
}
}
impl<T, State> FindExecutions for T
where
T: Semantics<SemState = State, AliState = State> + Send + Sync + ?Sized,
State: Displayable,
{
fn find_executions(
&mut self,
log: &mut Box<dyn EbiTraitEventLogEventAttributes>,
) -> Result<Executions> {
if self.get_initial_state().is_none() {
return Err(anyhow!(
"Model has the empty language, and can therefore not be aligned."
));
}
log::info!("Compute alignments");
let progress_bar = EbiCommand::get_progress_bar_ticks(log.number_of_traces());
let error: Arc<Mutex<Option<Error>>> = Arc::new(Mutex::new(None));
let resource_key = Arc::new(Mutex::new(ActivityKey::new()));
let attibute_key = Arc::new(Mutex::new(log.attribute_key().clone()));
self.translate_using_activity_key(log.activity_key_mut());
let trace_executions = log
.par_iter_traces()
.enumerate()
.filter_map(|(trace_index, trace)| {
let alignment = self.align_trace(&trace);
progress_bar.inc(1);
match alignment {
Result::Ok((aligned_trace, _)) => {
let c = C::new(trace_index, aligned_trace);
match c.alignment_to_executions(self, &log, &resource_key) {
Result::Ok(c) => Some(c),
Err(err) => {
let error = Arc::clone(&error);
*error.lock().unwrap() = Some(err);
None
}
}
}
Err(err) => {
let error = Arc::clone(&error);
*error.lock().unwrap() = Some(err);
None
}
}
})
.collect::<Vec<_>>();
progress_bar.finish_and_clear();
if let Result::Ok(mutex) = Arc::try_unwrap(error) {
if let Result::Ok(err) = mutex.into_inner() {
if let Some(err) = err {
return Err(err);
}
}
}
let execution_list = ExecutionsSorter::merge_sort(trace_executions);
let mut executions = (
log.activity_key().clone(),
Arc::try_unwrap(resource_key).unwrap().into_inner().unwrap(),
Arc::try_unwrap(attibute_key).unwrap().into_inner().unwrap(),
execution_list,
)
.into();
set_resource_utilisations(&mut executions)?;
Ok(executions)
}
}
struct C {
trace_index: usize,
moves: Vec<Move>,
}
impl C {
fn new(trace_index: usize, moves: Vec<Move>) -> Self {
Self {
trace_index: trace_index,
moves: moves,
}
}
fn alignment_to_executions<T, FS>(
&self,
semantics: &T,
log: &Box<dyn EbiTraitEventLogEventAttributes>,
resource_key: &Arc<Mutex<ActivityKey>>,
) -> Result<VecDeque<Execution>>
where
T: Semantics<SemState = FS> + Send + Sync + ?Sized,
FS: Display + Debug + Clone + Hash + Eq,
{
let mut state = semantics
.get_initial_state()
.context("The model does not have an initial state.")?;
let mut executions = VecDeque::with_capacity(self.moves.len());
for move_index in 0..self.moves.len() {
match self.moves[move_index] {
Move::LogMove { .. } => {
}
Move::ModelMove {
activity,
transition,
} => {
let move_index_of_enablement = self.get_enabling_move(move_index, semantics);
let mut other_enabled_transitions = semantics.get_enabled_transitions(&state);
other_enabled_transitions.retain(|t| *t != transition);
let l = other_enabled_transitions.len();
executions.push_back(Execution {
trace: self.trace_index,
move_index,
event_attributes: None,
activity: Some(activity),
also_in_log: false,
fired_transition: transition,
other_enabled_transitions,
move_index_of_enablement,
time_of_execution: None,
resource: None,
resource_utilisation_fired_transition: None,
resource_utilisation_other_enabled_transitions: vec![None; l],
});
semantics.execute_transition(&mut state, transition)?;
}
Move::SynchronousMove {
activity,
transition,
} => {
let move_index_of_enablement = self.get_enabling_move(move_index, semantics);
let mut other_enabled_transitions = semantics.get_enabled_transitions(&state);
other_enabled_transitions.retain(|t| *t != transition);
let event_attributes =
Some(self.get_event_attributes(move_index, log).ok_or_else(|| {
anyhow!("Could not obtain event attributes for move {}.", move_index)
})?);
let l = other_enabled_transitions.len();
executions.push_back(Execution {
trace: self.trace_index,
move_index,
event_attributes,
activity: Some(activity),
also_in_log: true,
fired_transition: transition,
other_enabled_transitions,
move_index_of_enablement,
time_of_execution: self.get_time(Some(move_index), log).cloned(),
resource: self.get_resource(Some(move_index), log, resource_key),
resource_utilisation_fired_transition: None,
resource_utilisation_other_enabled_transitions: vec![None; l],
});
semantics.execute_transition(&mut state, transition)?;
}
Move::SilentMove { transition } => {
let move_index_of_enablement = self.get_enabling_move(move_index, semantics);
let mut other_enabled_transitions = semantics.get_enabled_transitions(&state);
other_enabled_transitions.retain(|t| *t != transition);
let l = other_enabled_transitions.len();
executions.push_back(Execution {
trace: self.trace_index,
move_index,
event_attributes: None,
activity: None,
also_in_log: false,
fired_transition: transition,
other_enabled_transitions,
move_index_of_enablement,
time_of_execution: None,
resource: None,
resource_utilisation_fired_transition: None,
resource_utilisation_other_enabled_transitions: vec![None; l],
});
semantics.execute_transition(&mut state, transition)?;
}
}
}
Ok(executions)
}
fn get_time<'a>(
&self,
move_index: Option<usize>,
log: &'a Box<dyn EbiTraitEventLogEventAttributes>,
) -> Option<&'a DateTime<FixedOffset>> {
let event_index = self.get_event_index(move_index?)?;
log.get_event_time(self.trace_index, event_index)
}
fn get_resource<'a>(
&self,
move_index: Option<usize>,
log: &'a Box<dyn EbiTraitEventLogEventAttributes>,
resource_key: &Arc<Mutex<ActivityKey>>,
) -> Option<Activity> {
let event_index = self.get_event_index(move_index?)?;
let resource_string = log.get_event_resource(self.trace_index, event_index)?;
Some(
resource_key
.lock()
.as_mut()
.unwrap()
.process_activity(resource_string),
)
}
fn get_event_attributes<'a>(
&self,
move_index: usize,
log: &'a Box<dyn EbiTraitEventLogEventAttributes>,
) -> Option<IntMap<Attribute, AttributeValue>> {
let event_index = self.get_event_index(move_index)?;
log.get_event_attributes(self.trace_index, event_index)
}
fn get_event_index(&self, move_index: usize) -> Option<usize> {
let mut event_index = 0;
let mut last = false;
for movee in self.moves.iter().take(move_index + 1) {
match movee {
Move::LogMove { .. } | Move::SynchronousMove { .. } => {
event_index += 1;
last = true;
}
_ => last = false,
}
}
if last { Some(event_index - 1) } else { None }
}
fn get_enabling_move<T, FS>(&self, move_index: usize, semantics: &T) -> Option<usize>
where
T: Semantics<SemState = FS> + Send + Sync + ?Sized,
FS: Display + Debug + Clone + Hash + Eq,
{
let transition_that_may_get_enabled = self.moves[move_index].get_transition().unwrap();
let (mut result, mut state) =
MoveEnabled::start(semantics, transition_that_may_get_enabled)?;
for (move_index2, move2) in self.moves.iter().take(move_index).enumerate() {
if let Some(transition2) = move2.get_transition() {
result.execute_transition(
semantics,
&mut state,
transition_that_may_get_enabled,
transition2,
move_index2,
);
} else {
}
}
result.finalise()
}
}
#[derive(Debug, Copy, Clone)]
enum MoveEnabled {
FromStartOfTrace,
AsResultOfMove(usize),
NotEnabled,
}
impl MoveEnabled {
fn start<T, FS>(
semantics: &T,
transition_that_may_get_enabled: TransitionIndex,
) -> Option<(Self, FS)>
where
T: Semantics<SemState = FS> + Send + Sync + ?Sized,
FS: Display + Debug + Clone + Hash + Eq,
{
let state = semantics
.get_initial_state()
.expect("there is no initial state");
if semantics
.get_enabled_transitions(&state)
.contains(&transition_that_may_get_enabled)
{
Some((Self::FromStartOfTrace, state))
} else {
Some((Self::NotEnabled, state))
}
}
fn execute_transition<T, FS>(
&mut self,
semantics: &T,
state: &mut FS,
transition_that_may_get_enabled: TransitionIndex,
transition: TransitionIndex,
move_index: usize,
) where
T: Semantics<SemState = FS> + Send + Sync + ?Sized,
FS: Display + Debug + Clone + Hash + Eq,
{
semantics
.execute_transition(state, transition)
.expect("transition was not enabled and nevertheless fired");
let now_enabled = semantics
.get_enabled_transitions(&state)
.contains(&transition_that_may_get_enabled);
*self = match (now_enabled, &self) {
(true, MoveEnabled::FromStartOfTrace) => MoveEnabled::FromStartOfTrace,
(true, MoveEnabled::AsResultOfMove(x)) => MoveEnabled::AsResultOfMove(*x),
(true, MoveEnabled::NotEnabled) => MoveEnabled::AsResultOfMove(move_index),
(false, MoveEnabled::FromStartOfTrace) => MoveEnabled::NotEnabled,
(false, MoveEnabled::AsResultOfMove(_)) => MoveEnabled::NotEnabled,
(false, MoveEnabled::NotEnabled) => MoveEnabled::NotEnabled,
};
}
fn finalise(self) -> Option<usize> {
match self {
MoveEnabled::FromStartOfTrace => None,
MoveEnabled::AsResultOfMove(move_index) => Some(move_index),
MoveEnabled::NotEnabled => {
panic!("transition was not enabled and it nevertheless fired")
}
}
}
}
struct ExecutionsSorter {}
impl ExecutionsSorter {
fn merge_sort(mut traces: Vec<VecDeque<Execution>>) -> Vec<Execution> {
let number_of_executions = traces.iter().map(|t| t.len()).sum();
let mut result = Vec::with_capacity(number_of_executions);
log::info!("Sorting executions");
for trace_index in 0..traces.len() {
let trace = &mut traces[trace_index];
while !trace.is_empty() && trace[0].time_of_execution.is_none() {
result.push(trace.pop_front().unwrap());
}
}
let mut first_timestamps = (0..traces.len())
.map(|trace_index| Self::get_first_timestamp(&traces[trace_index]).cloned())
.collect::<Vec<_>>();
loop {
if let Some((trace_index, _)) = first_timestamps
.iter()
.enumerate()
.filter_map(|(trace_index, first_timestamp)| {
if let Some(first) = first_timestamp {
Some((trace_index, first))
} else {
None
}
})
.min_by(|a, b| {
let c = a.1.cmp(&b.1);
if !c.is_eq() { c } else { a.0.cmp(&b.0) }
})
{
result.push(traces[trace_index].pop_front().unwrap());
let trace = &mut traces[trace_index];
while !trace.is_empty() && trace[0].time_of_execution.is_none() {
result.push(trace.pop_front().unwrap());
}
first_timestamps[trace_index] =
Self::get_first_timestamp(&traces[trace_index]).cloned();
} else {
result.extend(traces.into_iter().flatten());
return result;
}
}
}
fn get_first_timestamp(trace: &VecDeque<Execution>) -> Option<&DateTime<FixedOffset>> {
for execution in trace {
if execution.time_of_execution.is_some() {
return execution.time_of_execution.as_ref();
}
}
None
}
}
#[cfg(test)]
mod tests {
use crate::{
ebi_framework::trait_importers::ToSemanticsTrait,
ebi_traits::ebi_trait_event_log_event_attributes::EbiTraitEventLogEventAttributes,
techniques::executions::FindExecutions,
};
use ebi_objects::{
LabelledPetriNet, StochasticDeterministicFiniteAutomaton,
StochasticNondeterministicFiniteAutomaton, ebi_arithmetic::is_exact_globally,
ebi_objects::event_log_event_attributes::EventLogEventAttributes,
};
use std::fs;
#[test]
fn executions() {
let fin = fs::read_to_string("testfiles/a-b.xes").unwrap();
let log = fin.parse::<EventLogEventAttributes>().unwrap();
let fin2 = fs::read_to_string("testfiles/a-b-c-livelock.sdfa").unwrap();
let mut model = fin2
.parse::<StochasticDeterministicFiniteAutomaton>()
.unwrap();
let out = fs::read_to_string("testfiles/a-b.exs").unwrap();
let mut log2: Box<dyn EbiTraitEventLogEventAttributes> = Box::new(log);
let x = model.find_executions(&mut log2).unwrap();
assert_eq!(out, x.to_string());
}
#[test]
fn svn60() {
let fin = fs::read_to_string("testfiles/svn60.xes").unwrap();
let log = fin.parse::<EventLogEventAttributes>().unwrap();
let mut log: Box<dyn EbiTraitEventLogEventAttributes> = Box::new(log);
let fin2 = fs::read_to_string("testfiles/svn60.lpn").unwrap();
let lpn = fin2.parse::<LabelledPetriNet>().unwrap();
let mut sem = lpn.to_semantics_trait();
let executions = sem.find_executions(&mut log).unwrap();
println!("{}", executions);
let fin3 = fs::read_to_string("testfiles/svn60.lpn.exs").unwrap();
if !is_exact_globally() {
return;
}
assert_eq!(executions.to_string(), fin3);
}
#[test]
fn snfa_executions() {
let fin = fs::read_to_string("testfiles/simple_log_markovian_abstraction.xes").unwrap();
let log = fin.parse::<EventLogEventAttributes>().unwrap();
let fin2 = fs::read_to_string("testfiles/aa-ab-ba.snfa").unwrap();
let mut model = fin2
.parse::<StochasticNondeterministicFiniteAutomaton>()
.unwrap();
let mut log2: Box<dyn EbiTraitEventLogEventAttributes> = Box::new(log);
model.find_executions(&mut log2).unwrap();
}
}