#[cfg(feature = "token-based-replay")]
use macros_process_mining::register_binding;
#[cfg(feature = "token-based-replay")]
use nalgebra::{DMatrix, DVector};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use uuid::Uuid;
use crate::core::process_models::case_centric::petri_net::petri_net_struct::Marking;
#[cfg(feature = "token-based-replay")]
use crate::core::{
event_data::case_centric::utils::activity_projection::EventLogActivityProjection, PetriNet,
};
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub enum TokenBasedReplayError {
NoInitialMarking,
NoFinalMarking,
TooManyFinalMarkings,
DuplicateLabelOrSilentTransitionError,
}
impl std::fmt::Display for TokenBasedReplayError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
TokenBasedReplayError::NoInitialMarking => {
write!(f, "No initial marking")
}
TokenBasedReplayError::NoFinalMarking => {
write!(f, "No final marking")
}
TokenBasedReplayError::TooManyFinalMarkings => {
write!(f, "Too many final markings")
}
TokenBasedReplayError::DuplicateLabelOrSilentTransitionError => {
write!(
f,
"Petri net contains duplicate labels or silent transitions"
)
}
}
}
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, JsonSchema)]
pub struct TokenBasedReplayResult {
pub produced: u64,
pub consumed: u64,
pub missing: u64,
pub remaining: u64,
}
impl TokenBasedReplayResult {
pub fn new() -> TokenBasedReplayResult {
Self::default()
}
pub fn compute_fitness(&self) -> f64 {
0.5 * (1.0 - (self.missing as f64 / self.consumed as f64))
+ 0.5 * (1.0 - (self.remaining as f64 / self.produced as f64))
}
}
#[cfg(feature = "token-based-replay")]
#[register_binding]
pub fn apply_token_based_replay(
petri_net: &PetriNet,
event_log: &EventLogActivityProjection,
) -> Result<TokenBasedReplayResult, TokenBasedReplayError> {
if petri_net.initial_marking.is_none() {
return Err(TokenBasedReplayError::NoInitialMarking);
} else if petri_net.final_markings.as_ref().is_none()
|| petri_net.final_markings.as_ref().unwrap().is_empty()
{
return Err(TokenBasedReplayError::NoFinalMarking);
} else if petri_net.final_markings.as_ref().unwrap().len() > 1 {
return Err(TokenBasedReplayError::TooManyFinalMarkings);
} else if petri_net.contains_duplicate_or_silent_transitions() {
return Err(TokenBasedReplayError::DuplicateLabelOrSilentTransitionError);
}
let mut result = TokenBasedReplayResult::new();
let node_to_pos = petri_net.create_vector_dictionary();
let pre_matrix =
change_matrix_type_to_i64(&petri_net.create_pre_incidence_matrix(&node_to_pos));
let post_matrix =
change_matrix_type_to_i64(&petri_net.create_post_incidence_matrix(&node_to_pos));
let pos_array: Vec<Option<usize>> = event_log
.activities
.iter()
.map(|activity| {
let pos;
if let Some((transition_id, _)) =
petri_net.transitions.iter().find(|(_, transition)| {
transition
.label
.as_ref()
.is_some_and(|label| label.eq(activity))
})
{
pos = Some(*node_to_pos.get(transition_id).unwrap());
} else {
pos = None;
};
pos
})
.collect();
let m_init = marking_to_vector(
petri_net.initial_marking.as_ref().unwrap(),
&node_to_pos,
petri_net.places.len(),
);
let m_final = marking_to_vector(
petri_net.final_markings.as_ref().unwrap().first().unwrap(),
&node_to_pos,
petri_net.places.len(),
);
event_log.traces.iter().for_each(|(trace, freq)| {
result.produced += m_init.sum() as u64 * *freq;
result.consumed += m_final.sum() as u64 * *freq;
let mut marking: DVector<i64> = DVector::zeros(petri_net.places.len());
marking += &m_init;
trace.iter().for_each(|event| {
let pos_option: &Option<usize> = pos_array.get(*event).unwrap();
if pos_option.is_some() {
let pos = pos_option.unwrap();
let t_in = pre_matrix.column(pos);
let t_out = post_matrix.column(pos);
marking -= t_in;
result.consumed += t_in.sum() as u64 * *freq;
result.missing += count_missing(&mut marking) * freq;
marking += t_out;
result.produced += t_out.sum() as u64 * *freq;
}
});
marking -= &m_final;
result.missing += count_missing(&mut marking) * freq;
result.remaining += marking.sum() as u64 * *freq;
});
Ok(result)
}
fn change_matrix_type_to_i64(input: &DMatrix<u8>) -> DMatrix<i64> {
input.map(|e| e as i64)
}
pub fn marking_to_vector(
marking: &Marking,
node_to_pos: &HashMap<Uuid, usize>,
vector_len: usize,
) -> DVector<i64> {
let mut result: DVector<i64> = DVector::zeros(vector_len);
marking.iter().for_each(|(place, count)| {
result[*node_to_pos.get(&place.get_uuid()).unwrap()] += *count as i64;
});
result
}
pub fn count_missing(marking: &mut DVector<i64>) -> u64 {
let mut result = 0;
marking.iter_mut().for_each(|place_tokens| {
if *place_tokens < 0 {
result += place_tokens.unsigned_abs();
*place_tokens = 0;
}
});
result
}
#[cfg(test)]
mod tests {
use crate::core::{
event_data::case_centric::{Event, Trace},
process_models::case_centric::petri_net::petri_net_struct::ArcType,
EventLog,
};
use super::*;
#[test]
fn token_based_replay_test() {
let mut net = PetriNet::new();
let p1 = net.add_place(None);
let p2 = net.add_place(None);
let p3 = net.add_place(None);
let t1 = net.add_transition(Some("a".into()), None);
let t2 = net.add_transition(Some("b".into()), None);
let t3 = net.add_transition(Some("c".into()), None);
let t4 = net.add_transition(Some("d".into()), None);
net.add_arc(ArcType::place_to_transition(p1, t1), None);
net.add_arc(ArcType::place_to_transition(p1, t2), None);
net.add_arc(ArcType::transition_to_place(t1, p2), None);
net.add_arc(ArcType::transition_to_place(t2, p2), None);
net.add_arc(ArcType::place_to_transition(p2, t3), None);
net.add_arc(ArcType::transition_to_place(t3, p3), None);
net.add_arc(ArcType::transition_to_place(t4, p2), None);
net.add_arc(ArcType::place_to_transition(p2, t4), None);
let mut initial_marking = Marking::new();
initial_marking.insert(p1, 1);
net.initial_marking = Some(initial_marking);
let mut final_marking = Marking::new();
final_marking.insert(p3, 1);
let final_markings = vec![final_marking];
net.final_markings = Some(final_markings);
let mut trace_1 = Trace::new();
trace_1.events.push(Event::new("a".to_string()));
trace_1.events.push(Event::new("b".to_string()));
trace_1.events.push(Event::new("c".to_string()));
trace_1.events.push(Event::new("c".to_string()));
trace_1.events.push(Event::new("d".to_string()));
let mut event_log = EventLog::new();
event_log.traces.push(trace_1);
let event_log_abstraction = EventLogActivityProjection::from(&event_log);
let tbr_result = apply_token_based_replay(&net, &event_log_abstraction);
println!("After replaying trace 1, the result is: {tbr_result:?}");
assert!(tbr_result.is_ok());
let result = tbr_result.unwrap();
assert_eq!(result.produced, 6);
assert_eq!(result.consumed, 6);
assert_eq!(result.missing, 2);
assert_eq!(result.remaining, 2);
let mut trace_2 = Trace::new();
trace_2.events.push(Event::new("b".to_string()));
trace_2.events.push(Event::new("b".to_string()));
trace_2.events.push(Event::new("d".to_string()));
trace_2.events.push(Event::new("b".to_string()));
event_log.traces.push(trace_2);
let event_log_abstraction_2 = EventLogActivityProjection::from(&event_log);
let tbr_result_2 = apply_token_based_replay(&net, &event_log_abstraction_2);
println!("After replaying trace 1 and trace 2, the result is: {tbr_result_2:?}");
assert!(tbr_result_2.is_ok());
let result_2 = tbr_result_2.unwrap();
assert_eq!(result_2.produced, 6 + 5);
assert_eq!(result_2.consumed, 6 + 5);
assert_eq!(result_2.missing, 2 + 3);
assert_eq!(result_2.remaining, 2 + 3);
}
}