use bitvec::{bitvec, vec::BitVec};
use ebi_objects::{
Activity,
ebi_arithmetic::{Fraction, Zero},
ebi_objects::finite_stochastic_partially_ordered_language::PartiallyOrderedTrace,
};
use itertools::Itertools;
use pathfinding::prelude::astar;
use std::fmt::{Debug, Display};
pub trait PartiallyOrderedTraceDistance {
fn partially_ordered_trace_distance(&self, other: &PartiallyOrderedTrace) -> usize;
fn normalised_partially_ordered_trace_distance(
&self,
other: &PartiallyOrderedTrace,
) -> Fraction;
}
impl PartiallyOrderedTraceDistance for Vec<Activity> {
fn partially_ordered_trace_distance(&self, other: &PartiallyOrderedTrace) -> usize {
let initial_po_marking = PartiallyOrderedTraceMarking::from_trace(other);
let start = (0, initial_po_marking);
let successors = |(trace_index, state): &(usize, PartiallyOrderedTraceMarking)| {
let mut result = vec![];
if trace_index < &self.len() {
result.push(((trace_index + 1, state.clone()), 1));
}
for event in state.enabled_events(other) {
let new_state = state.execute_event(event);
result.push(((*trace_index, new_state.clone()), 1));
if trace_index < &self.len() {
result.push(((*trace_index + 1, new_state.clone()), 1));
}
if let Some(po_activity) = other.event_2_activity.get(event) {
if let Some(activity) = self.get(*trace_index)
&& po_activity == activity
{
result.push(((trace_index + 1, new_state), 0));
}
}
}
result
};
let heuristic = |(_, _): &(usize, PartiallyOrderedTraceMarking)| 0;
let success = |(trace_index, state): &(usize, PartiallyOrderedTraceMarking)| {
trace_index == &self.len() && state.event_2_executed.all()
};
astar(&start, successors, heuristic, success).unwrap().1
}
fn normalised_partially_ordered_trace_distance(
&self,
other: &PartiallyOrderedTrace,
) -> Fraction {
if !self.is_empty() || other.number_of_events() != 0 {
let distance = self.partially_ordered_trace_distance(other);
Fraction::from((distance, self.len().max(other.number_of_events())))
} else {
Fraction::zero()
}
}
}
impl PartiallyOrderedTraceDistance for PartiallyOrderedTrace {
fn partially_ordered_trace_distance(&self, other: &PartiallyOrderedTrace) -> usize {
let marking_self = PartiallyOrderedTraceMarking::from_trace(self);
let marking_other = PartiallyOrderedTraceMarking::from_trace(other);
let start = (marking_self, marking_other);
let successors = |(marking_self, marking_other): &(
PartiallyOrderedTraceMarking,
PartiallyOrderedTraceMarking,
)| {
let mut result = vec![];
let mut successors_self = vec![];
for event in marking_self.enabled_events(self) {
let new_marking_self = marking_self.execute_event(event);
successors_self.push((new_marking_self.clone(), self.event_2_activity[event]));
result.push(((new_marking_self, marking_other.clone()), 1));
}
let mut successors_other = vec![];
for event in marking_other.enabled_events(other) {
let new_marking_other = marking_other.execute_event(event);
successors_other.push((new_marking_other.clone(), other.event_2_activity[event]));
result.push(((marking_self.clone(), new_marking_other), 1));
}
for ((new_marking_self, activity_self), (new_marking_other, activity_other)) in
successors_self
.into_iter()
.cartesian_product(successors_other.into_iter())
{
if activity_self == activity_other {
result.push(((new_marking_self, new_marking_other), 0));
} else {
result.push(((new_marking_self, new_marking_other), 1));
}
}
result
};
let heuristic = |(_, _): &(PartiallyOrderedTraceMarking, PartiallyOrderedTraceMarking)| 0;
let success = |(marking_self, marking_other): &(
PartiallyOrderedTraceMarking,
PartiallyOrderedTraceMarking,
)| {
marking_self.event_2_executed.all() && marking_other.event_2_executed.all()
};
astar(&start, successors, heuristic, success).unwrap().1
}
fn normalised_partially_ordered_trace_distance(
&self,
other: &PartiallyOrderedTrace,
) -> Fraction {
if self.number_of_events() > 0 || other.number_of_events() > 0 {
let distance = self.partially_ordered_trace_distance(other);
Fraction::from((
distance,
self.number_of_events().max(other.number_of_events()),
))
} else {
Fraction::zero()
}
}
}
#[derive(Clone, Eq, PartialEq, Hash)]
struct PartiallyOrderedTraceMarking {
event_2_executed: BitVec,
}
impl PartiallyOrderedTraceMarking {
pub fn from_trace(po_trace: &PartiallyOrderedTrace) -> Self {
let result = Self {
event_2_executed: bitvec!(0; po_trace.number_of_events()),
};
result
}
pub fn enabled_events(&self, po_trace: &PartiallyOrderedTrace) -> impl Iterator<Item = usize> {
self.event_2_executed.iter_zeros().filter(|event| {
po_trace.event_2_predecessors[*event]
.iter()
.all(|input| self.event_2_executed[*input])
})
}
pub fn execute_event(&self, event: usize) -> Self {
let mut result = self.clone();
result.event_2_executed.set(event, true);
result
}
}
impl Debug for PartiallyOrderedTraceMarking {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self)
}
}
impl Display for PartiallyOrderedTraceMarking {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"partially ordered trace marking; events {:?}",
self.event_2_executed.iter_ones().collect::<Vec<_>>()
)
}
}
#[cfg(test)]
mod tests {
use crate::techniques::partially_ordered_trace_distance::PartiallyOrderedTraceDistance;
use ebi_objects::{
FiniteStochasticLanguage, FiniteStochasticPartiallyOrderedLanguage, HasActivityKey,
ebi_arithmetic::{Fraction, One, Zero},
};
use std::fs;
#[test]
fn po_trace_distance() {
let fin1 = fs::read_to_string("testfiles/model.sbpmn.spolang").unwrap();
let spolang = fin1
.parse::<FiniteStochasticPartiallyOrderedLanguage>()
.unwrap();
let po_trace = &spolang.traces[4];
println!("{:?}", po_trace);
let ac0 = spolang
.activity_key()
.process_activity_attempt("Register claim\n(2min)")
.unwrap();
let ac1 = spolang
.activity_key()
.process_activity_attempt("Check easy claim\n(5 min)")
.unwrap();
let trace = vec![ac0, ac1];
assert_eq!(trace.partially_ordered_trace_distance(po_trace), 0);
assert_eq!(
trace.normalised_partially_ordered_trace_distance(po_trace),
Fraction::zero()
);
let trace = vec![ac1, ac0];
assert_eq!(trace.partially_ordered_trace_distance(po_trace), 2);
assert_eq!(
trace.normalised_partially_ordered_trace_distance(po_trace),
Fraction::one()
);
}
#[test]
fn po_trace_distance_flower() {
let fin1 = fs::read_to_string("testfiles/flower.sbpmn.spolang").unwrap();
let spolang = fin1
.parse::<FiniteStochasticPartiallyOrderedLanguage>()
.unwrap();
let fin2 = fs::read_to_string("testfiles/empty_trace.slang").unwrap();
let slang = fin2.parse::<FiniteStochasticLanguage>().unwrap();
let (slangtrace, _) = slang.traces.iter().next().unwrap();
for spotrace in spolang.traces {
println!(
"trace length {:?}, distance {}",
spotrace.number_of_events(),
slangtrace.partially_ordered_trace_distance(&spotrace)
);
assert_eq!(
spotrace.number_of_events(),
slangtrace.partially_ordered_trace_distance(&spotrace)
);
if spotrace.number_of_events() == 0 {
assert_eq!(
slangtrace.normalised_partially_ordered_trace_distance(&spotrace),
Fraction::zero()
);
} else {
assert_eq!(
slangtrace.normalised_partially_ordered_trace_distance(&spotrace),
Fraction::one()
);
}
}
}
}