use std::collections::HashMap;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use uuid::Uuid;
use crate::{
conformance::alignments::{
cost::CostFunction,
petri_net::{TokenCount, TracePos},
AlignmentMove,
},
core::process_models::petri_net::{ArcType, PlaceID, TransitionID},
PetriNet,
};
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct SyncProdNetTransition {
pub(crate) move_type: AlignmentMove,
pub(crate) cost: u32,
pub(crate) inputs: Vec<(usize, TokenCount)>,
pub(crate) outputs: Vec<(usize, TokenCount)>,
}
#[derive(Debug, PartialEq)]
pub(crate) struct SyncProductNet {
pub(crate) num_model_places: usize,
pub(crate) trace_length: TracePos,
pub(crate) transitions: Vec<SyncProdNetTransition>,
pub(crate) initial_marking: Vec<TokenCount>,
pub(crate) final_marking: Vec<TokenCount>,
pub(crate) transitions_by_trace_pos: Vec<Vec<usize>>,
pub(crate) num_model_trans: usize,
pub(crate) max_edge_cost: u32,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, JsonSchema)]
pub enum SyncProdNetConstructionError {
InvalidPlaceInMarking(PlaceID),
NoFinalMarking,
NoInitialMarking,
}
impl std::fmt::Display for SyncProdNetConstructionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{self:?}")
}
}
impl std::error::Error for SyncProdNetConstructionError {}
impl SyncProductNet {
pub(crate) fn construct(
net: &PetriNet,
trace: &[&str],
cost_fn: &CostFunction,
) -> Result<Self, SyncProdNetConstructionError> {
let mut transitions = Vec::new();
let model_place_map: HashMap<&Uuid, usize> = net
.places
.iter()
.enumerate()
.map(|(i, p)| (p.0, i))
.collect();
let num_model_places = net.places.len();
let mut model_trans_map: HashMap<&Uuid, usize> = HashMap::new();
let mut model_trans_with_label: HashMap<&str, Vec<usize>> = HashMap::new();
let num_model_trans = net.transitions.len();
let mut transitions_by_rank = vec![vec![]; trace.len()];
for (trans_id, trans) in &net.transitions {
let model_idx = transitions.len();
model_trans_map.insert(trans_id, model_idx);
if let Some(label) = &trans.label {
model_trans_with_label
.entry(label)
.or_default()
.push(model_idx);
}
let inputs = net
.arcs
.iter()
.filter_map(|arc| match arc.from_to {
ArcType::PlaceTransition(x, to) if &to == trans_id => {
Some((*model_place_map.get(&x)?, arc.weight as TokenCount))
}
_ => None,
})
.collect();
let outputs = net
.arcs
.iter()
.filter_map(|arc| match arc.from_to {
ArcType::TransitionPlace(from, x) if &from == trans_id => {
Some((*model_place_map.get(&x)?, arc.weight as TokenCount))
}
_ => None,
})
.collect();
transitions.push(SyncProdNetTransition {
move_type: AlignmentMove::ModelMove {
transition: TransitionID(*trans_id),
},
cost: if trans.label.is_none() {
cost_fn.silent_move_cost
} else {
cost_fn.model_move_cost
},
inputs,
outputs,
});
}
for (index, activity) in trace.iter().enumerate() {
transitions_by_rank[index].push(transitions.len());
transitions.push(SyncProdNetTransition {
move_type: AlignmentMove::LogMove {
trace_event_index: index,
},
cost: cost_fn.log_move_cost,
inputs: vec![],
outputs: vec![],
});
let model_trans_that_can_sync = model_trans_with_label.get(activity);
for s in model_trans_that_can_sync.into_iter().flatten() {
transitions_by_rank[index].push(transitions.len());
let trans_id = match transitions[*s].move_type {
AlignmentMove::ModelMove { transition } => transition,
_ => unreachable!("Has to be a model move"),
};
transitions.push(SyncProdNetTransition {
move_type: AlignmentMove::SyncMove {
transition: trans_id,
trace_event_index: index,
},
cost: cost_fn.sync_move_cost,
inputs: transitions[*s].inputs.clone(),
outputs: transitions[*s].outputs.clone(),
});
}
}
let mut initial_marking: Vec<TokenCount> = vec![0; num_model_places];
let im = net
.initial_marking
.as_ref()
.ok_or(SyncProdNetConstructionError::NoInitialMarking)?;
for (place_id, count) in im {
let index = model_place_map.get(&place_id.0).ok_or(
SyncProdNetConstructionError::InvalidPlaceInMarking(*place_id),
)?;
initial_marking[*index] = *count as TokenCount;
}
let mut final_marking: Vec<TokenCount> = vec![0; num_model_places];
let fm = net
.final_markings
.as_ref()
.and_then(move |f| f.first())
.ok_or(SyncProdNetConstructionError::NoFinalMarking)?;
for (place_id, count) in fm {
let index = model_place_map.get(&place_id.0).ok_or(
SyncProdNetConstructionError::InvalidPlaceInMarking(*place_id),
)?;
final_marking[*index] = *count as TokenCount;
}
let max_edge_cost = transitions.iter().map(|t| t.cost).max().unwrap_or(1);
Ok(Self {
num_model_places,
trace_length: trace.len() as TracePos,
transitions,
initial_marking,
final_marking,
transitions_by_trace_pos: transitions_by_rank,
num_model_trans,
max_edge_cost,
})
}
}