use itertools::MultiUnzip;
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::ops::{AddAssign, DivAssign};
use uuid::Uuid;
use crate::core::event_data::object_centric::linked_ocel::index_linked_ocel::{
EventIndex, ObjectIndex,
};
use crate::core::event_data::object_centric::linked_ocel::{IndexLinkedOCEL, LinkedOCELAccess};
use crate::core::process_models::object_centric::ocdfg::object_centric_dfg_struct::OCDirectlyFollowsGraph;
use crate::core::process_models::object_centric::ocpt::object_centric_process_tree_struct::{
OCPTLeafLabel, OCPTNode, OCPT,
};
use crate::core::process_models::object_centric::ocpt::{EventType, ObjectType};
#[derive(Debug, Serialize, Deserialize, Default)]
pub struct OCLanguageAbstraction {
pub start_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
pub end_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
pub directly_follows_ev_types_per_ob_type: HashMap<ObjectType, HashSet<(EventType, EventType)>>,
pub related_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
pub divergent_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
pub convergent_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
pub deficient_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
pub optional_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>>,
}
impl OCLanguageAbstraction {
pub fn create_from_oc_process_tree(ocpt: &OCPT) -> Self {
if ocpt.is_valid() {
let node_uuids = ocpt.find_all_node_uuids();
match &ocpt.root {
OCPTNode::Operator(op) => {
let node_map: HashMap<Uuid, HashMap<&EventType, HashSet<&ObjectType>>> =
node_uuids
.iter()
.map(|&uuid| (*uuid, HashMap::new()))
.collect();
let mut related_ev_type_per_ob_type: HashMap<
Uuid,
HashMap<&EventType, HashSet<&ObjectType>>,
> = node_map.clone();
op.compute_related(&mut related_ev_type_per_ob_type);
let mut divergent_ev_type_per_ob_type: HashMap<
Uuid,
HashMap<&EventType, HashSet<&ObjectType>>,
> = node_map.clone();
op.compute_div(
&mut divergent_ev_type_per_ob_type,
&related_ev_type_per_ob_type,
);
let mut leaf_conv_ob_types_per_node: HashMap<
Uuid,
HashMap<&EventType, HashSet<&ObjectType>>,
> = node_map.clone();
op.compute_leaf_conv(&mut leaf_conv_ob_types_per_node);
let mut leaf_def_ob_types_per_node: HashMap<
Uuid,
HashMap<&EventType, HashSet<&ObjectType>>,
> = node_map.clone();
op.compute_leaf_def(&mut leaf_def_ob_types_per_node);
let mut optional_ev_type_per_ob_type: HashMap<
Uuid,
HashMap<&EventType, HashSet<&ObjectType>>,
> = divergent_ev_type_per_ob_type.clone();
op.compute_opt(
&mut optional_ev_type_per_ob_type,
&related_ev_type_per_ob_type,
);
let convergent_ev_type_per_ob_type: HashMap<&EventType, HashSet<&ObjectType>> =
op.compute_conv(
related_ev_type_per_ob_type.get(&op.uuid).unwrap(),
optional_ev_type_per_ob_type.get(&op.uuid).unwrap(),
leaf_def_ob_types_per_node.get(&op.uuid).unwrap(),
divergent_ev_type_per_ob_type.get(&op.uuid).unwrap(),
leaf_conv_ob_types_per_node.get(&op.uuid).unwrap(),
);
let deficient_ev_type_per_ob_type: HashMap<&EventType, HashSet<&ObjectType>> =
op.compute_def(
related_ev_type_per_ob_type.get(&op.uuid).unwrap(),
optional_ev_type_per_ob_type.get(&op.uuid).unwrap(),
leaf_conv_ob_types_per_node.get(&op.uuid).unwrap(),
divergent_ev_type_per_ob_type.get(&op.uuid).unwrap(),
leaf_def_ob_types_per_node.get(&op.uuid).unwrap(),
);
let dfg_per_ob_type = related_ev_type_per_ob_type
.get(&op.uuid)
.unwrap()
.values()
.flatten()
.collect::<HashSet<_>>()
.iter()
.map(|&&ob_type| {
(
ob_type,
op.get_directly_follows_relations(
ob_type,
&related_ev_type_per_ob_type,
&divergent_ev_type_per_ob_type,
),
)
})
.collect::<HashMap<&ObjectType, (_, _, _, _)>>();
let mut start_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
let mut end_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
let mut directly_follows_ev_types_per_ob_type: HashMap<
ObjectType,
HashSet<(EventType, EventType)>,
> = HashMap::new();
dfg_per_ob_type
.iter()
.for_each(|(&ob_type, (start_evs, end_evs, dfr, _))| {
start_ev_type_per_ob_type.insert(
ob_type.clone(),
start_evs
.iter()
.map(|&start_ev_type| start_ev_type.clone())
.collect(),
);
end_ev_type_per_ob_type.insert(
ob_type.clone(),
end_evs
.iter()
.map(|&end_ev_type| end_ev_type.to_owned())
.collect(),
);
directly_follows_ev_types_per_ob_type.insert(
ob_type.clone(),
dfr.iter()
.map(|&(from, to)| (from.clone(), to.clone()))
.collect(),
);
});
let related_ob_type_per_ev_type: HashMap<ObjectType, HashSet<EventType>> =
Self::unpack_root_ev_type_per_ob_type(
&op.uuid,
related_ev_type_per_ob_type,
);
let ob_types = compute_rel_ob_types(&related_ob_type_per_ev_type);
Self {
start_ev_type_per_ob_type,
end_ev_type_per_ob_type,
directly_follows_ev_types_per_ob_type,
related_ev_type_per_ob_type: change_ob_type_as_key(
related_ob_type_per_ev_type,
&ob_types,
),
divergent_ev_type_per_ob_type: change_ob_type_as_key(
Self::unpack_root_ev_type_per_ob_type(
&op.uuid,
divergent_ev_type_per_ob_type,
),
&ob_types,
),
convergent_ev_type_per_ob_type: change_ob_type_as_key(
convergent_ev_type_per_ob_type
.iter()
.map(|(&ob_type, ev_types)| {
(
ob_type.clone(),
ev_types.iter().map(|&ev_type| ev_type.clone()).collect(),
)
})
.collect(),
&ob_types,
),
deficient_ev_type_per_ob_type: change_ob_type_as_key(
deficient_ev_type_per_ob_type
.iter()
.map(|(&ob_type, ev_types)| {
(
ob_type.clone(),
ev_types.iter().map(|&ev_type| ev_type.clone()).collect(),
)
})
.collect(),
&ob_types,
),
optional_ev_type_per_ob_type: change_ob_type_as_key(
Self::unpack_root_ev_type_per_ob_type(
&op.uuid,
optional_ev_type_per_ob_type,
),
&ob_types,
),
}
}
OCPTNode::Leaf(leaf) => match &leaf.activity_label {
OCPTLeafLabel::Activity(label) => {
let (
start_ev_type_per_ob_type,
end_ev_type_per_ob_type,
directly_follows_ev_types_per_ob_type,
related_ev_type_per_ob_type,
divergent_ev_type_per_ob_type,
convergent_ev_type_per_ob_type,
deficient_ev_type_per_ob_type,
optional_ev_type_per_ob_type,
) = leaf
.related_ob_types
.iter()
.map(|ob_type| {
let (start_evs, end_evs, dfr, _) =
leaf.get_directly_follows_relations(ob_type);
(
(ob_type.clone(), start_evs.into_iter().cloned().collect()),
(ob_type.clone(), end_evs.into_iter().cloned().collect()),
(
ob_type.clone(),
dfr.into_iter()
.map(|(from, to)| (from.clone(), to.clone()))
.collect(),
),
(ob_type.clone(), HashSet::from([label.clone()])),
(
ob_type.clone(),
if leaf.divergent_ob_types.contains(ob_type) {
HashSet::from([label.clone()])
} else {
HashSet::new()
},
),
(
ob_type.clone(),
if leaf.convergent_ob_types.contains(ob_type) {
HashSet::from([label.clone()])
} else {
HashSet::new()
},
),
(
ob_type.clone(),
if leaf.deficient_ob_types.contains(ob_type) {
HashSet::from([label.clone()])
} else {
HashSet::new()
},
),
(
ob_type.clone(),
if leaf.divergent_ob_types.contains(ob_type) {
HashSet::from([label.clone()])
} else {
HashSet::new()
},
),
)
})
.multiunzip();
Self {
start_ev_type_per_ob_type,
end_ev_type_per_ob_type,
directly_follows_ev_types_per_ob_type,
related_ev_type_per_ob_type,
divergent_ev_type_per_ob_type,
convergent_ev_type_per_ob_type,
deficient_ev_type_per_ob_type,
optional_ev_type_per_ob_type,
}
}
OCPTLeafLabel::Tau => Self::default(),
},
}
} else {
Self::default()
}
}
pub fn unpack_root_ev_type_per_ob_type(
root_uuid: &Uuid,
ev_type_per_ob_type: HashMap<Uuid, HashMap<&ObjectType, HashSet<&EventType>>>,
) -> HashMap<ObjectType, HashSet<EventType>> {
ev_type_per_ob_type
.get(root_uuid)
.unwrap()
.iter()
.map(|(&ob_type, ev_types)| {
(ob_type.clone(), ev_types.iter().cloned().cloned().collect())
})
.collect()
}
pub fn create_from_ocel(locel: &IndexLinkedOCEL) -> Self {
let directly_follows_graph: OCDirectlyFollowsGraph<'_> =
OCDirectlyFollowsGraph::create_from_ocel(locel);
let mut start_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> = HashMap::new();
let mut end_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> = HashMap::new();
let mut directly_follows_ev_types_per_ob_type: HashMap<
ObjectType,
HashSet<(EventType, EventType)>,
> = HashMap::new();
let mut related_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
let mut divergent_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
let mut convergent_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
let mut deficient_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
let mut optional_ev_type_per_ob_type: HashMap<ObjectType, HashSet<EventType>> =
HashMap::new();
locel.get_ob_types().for_each(|ob_type| {
let ev_type_dfg = directly_follows_graph
.object_type_to_dfg
.get(ob_type)
.unwrap();
start_ev_type_per_ob_type
.insert(ob_type.to_string(), ev_type_dfg.start_activities.clone());
end_ev_type_per_ob_type.insert(ob_type.to_string(), ev_type_dfg.end_activities.clone());
let ev_type_directly_follows: HashSet<(String, String)> = ev_type_dfg
.directly_follows_relations
.keys()
.map(|(from, to)| (from.to_string(), to.to_string()))
.collect();
directly_follows_ev_types_per_ob_type
.insert(ob_type.to_string(), ev_type_directly_follows);
let ev_types: HashSet<String> = locel
.get_ev_types()
.map(|event_type| event_type.to_string())
.collect();
related_ev_type_per_ob_type.insert(ob_type.to_string(), ev_types);
divergent_ev_type_per_ob_type.insert(ob_type.to_string(), HashSet::new());
convergent_ev_type_per_ob_type.insert(ob_type.to_string(), HashSet::new());
deficient_ev_type_per_ob_type.insert(ob_type.to_string(), HashSet::new());
optional_ev_type_per_ob_type.insert(ob_type.to_string(), HashSet::new());
});
locel.get_ev_types().for_each(|ev_type| {
let ev_type_e2o_relations: HashSet<(&EventIndex, &ObjectIndex)> = locel
.get_evs_of_type(ev_type)
.flat_map(|ev_index| {
locel
.get_e2o(ev_index)
.map(move |(_, ob_index)| (ev_index, ob_index))
})
.collect();
locel.get_ob_types().for_each(|ob_type| {
let ob_type_e2o_relations: HashSet<(&EventIndex, &ObjectIndex)> = locel
.get_obs_of_type(ob_type)
.flat_map(|ob_index| {
locel
.get_e2o_rev(ob_index)
.map(move |(_, ev_index)| (ev_index, ob_index))
})
.collect::<HashSet<(&EventIndex, &ObjectIndex)>>();
let ev_ob_type_e2o_relations: HashSet<&(&EventIndex, &ObjectIndex)> =
ob_type_e2o_relations
.intersection(&ev_type_e2o_relations)
.collect::<HashSet<_>>();
let unique_ev_count_e2o = ev_type_e2o_relations
.iter()
.map(|&(ev_index, _)| ev_index)
.collect::<HashSet<_>>()
.len();
let unique_ev_count_e2o_rev = ev_ob_type_e2o_relations
.iter()
.map(|&(ev_index, _)| ev_index)
.collect::<HashSet<_>>()
.len();
if unique_ev_count_e2o != unique_ev_count_e2o_rev {
if unique_ev_count_e2o_rev == 0 {
related_ev_type_per_ob_type
.get_mut(ob_type)
.unwrap()
.remove(&ev_type.to_string());
} else if unique_ev_count_e2o_rev < unique_ev_count_e2o {
deficient_ev_type_per_ob_type
.get_mut(ob_type)
.unwrap()
.insert(ev_type.to_string());
}
}
let num_of_obj_with_ob_type = locel.get_obs_of_type(ob_type).count();
let num_of_obj_with_ob_and_ev_type = ev_ob_type_e2o_relations
.iter()
.map(|&&(_, ob_index)| ob_index)
.collect::<HashSet<_>>()
.len();
if num_of_obj_with_ob_type > num_of_obj_with_ob_and_ev_type
&& related_ev_type_per_ob_type
.get(ob_type)
.unwrap()
.contains(&ev_type.to_string())
{
optional_ev_type_per_ob_type
.get_mut(ob_type)
.unwrap()
.insert(ev_type.to_string());
}
if Self::is_convergent_locel(locel, &ev_ob_type_e2o_relations, ob_type) {
convergent_ev_type_per_ob_type
.get_mut(ob_type)
.unwrap()
.insert(ev_type.to_string());
}
if Self::is_divergent_locel(locel, &ev_ob_type_e2o_relations, ob_type) {
divergent_ev_type_per_ob_type
.get_mut(ob_type)
.unwrap()
.insert(ev_type.to_string());
}
});
});
Self {
start_ev_type_per_ob_type,
end_ev_type_per_ob_type,
directly_follows_ev_types_per_ob_type,
related_ev_type_per_ob_type,
divergent_ev_type_per_ob_type,
convergent_ev_type_per_ob_type,
deficient_ev_type_per_ob_type,
optional_ev_type_per_ob_type,
}
}
pub fn is_convergent_locel(
locel: &IndexLinkedOCEL,
ev_ob_type_e2o_relations: &HashSet<&(&EventIndex, &ObjectIndex)>,
ob_type: &str,
) -> bool {
let mut object_index_to_event_indices = HashSet::new();
for &&(ev_index, ob_index) in ev_ob_type_e2o_relations {
if locel.get_full_ob(ob_index).object_type.eq(ob_type) {
if object_index_to_event_indices.contains(&ev_index) {
return true;
}
object_index_to_event_indices.insert(ev_index);
}
}
false
}
pub fn is_divergent_locel(
locel: &IndexLinkedOCEL,
ev_ob_type_e2o_relations: &HashSet<&(&EventIndex, &ObjectIndex)>,
ob_type: &str,
) -> bool {
let mut object_index_to_event_indices = HashMap::new();
ev_ob_type_e2o_relations
.iter()
.for_each(|&&(ev_index, ob_index)| {
object_index_to_event_indices
.entry(ob_index)
.or_insert_with(HashSet::new)
.insert(ev_index);
});
for (_, ev_indices) in object_index_to_event_indices {
if ev_indices.len() > 1 {
let ob_indices_of_ev_indices = ev_indices
.iter()
.map(|&ev_index| {
locel
.get_e2o_set(ev_index)
.iter()
.filter(|&ob_index| {
!locel.get_full_ob(ob_index).object_type.eq(ob_type)
})
.collect::<HashSet<&ObjectIndex>>()
})
.collect::<Vec<_>>();
let mut ob_indices_of_ev_indices_iter = ob_indices_of_ev_indices.into_iter();
let reference_set = ob_indices_of_ev_indices_iter.next().unwrap();
for curr_set in ob_indices_of_ev_indices_iter {
if !reference_set.eq(&curr_set) {
return true;
}
}
}
}
false
}
}
pub fn change_ob_type_as_key(
ob_type_per_ev_type_mapping: HashMap<EventType, HashSet<ObjectType>>,
ob_types: &HashSet<ObjectType>,
) -> HashMap<ObjectType, HashSet<EventType>> {
let mut result: HashMap<ObjectType, HashSet<EventType>> = ob_types
.iter()
.map(|ob_type| (ob_type.clone(), HashSet::new()))
.collect();
ob_type_per_ev_type_mapping
.iter()
.for_each(|(ev_type, ob_types)| {
ob_types.iter().for_each(|ob_type| {
result
.entry(ob_type.clone())
.or_default()
.insert(ev_type.clone());
})
});
result
}
pub fn compute_rel_ob_types(
related_ob_type_per_ev_type: &HashMap<EventType, HashSet<ObjectType>>,
) -> HashSet<ObjectType> {
related_ob_type_per_ev_type
.values()
.flat_map(|ob_types| ob_types.clone())
.collect()
}
pub fn compute_fitness_precision(
log_abstraction: &OCLanguageAbstraction,
model_abstraction: &OCLanguageAbstraction,
) -> (f64, f64) {
let all_ob_types = log_abstraction
.related_ev_type_per_ob_type
.keys()
.collect::<HashSet<_>>()
.union(
&model_abstraction
.related_ev_type_per_ob_type
.keys()
.collect::<HashSet<_>>(),
)
.cloned()
.collect::<HashSet<_>>();
let all_ev_types: HashSet<&EventType> = log_abstraction
.directly_follows_ev_types_per_ob_type
.values()
.flat_map(|dfr| {
let mut dfr_ev_types: HashSet<&EventType> = HashSet::new();
dfr.iter().for_each(|(from, to)| {
dfr_ev_types.insert(from);
dfr_ev_types.insert(to);
});
dfr_ev_types
})
.collect::<HashSet<_>>();
#[allow(clippy::type_complexity)]
let pattern: Vec<(
&HashMap<ObjectType, HashSet<EventType>>,
&HashMap<ObjectType, HashSet<EventType>>,
)> = vec![
(
&log_abstraction.start_ev_type_per_ob_type,
&model_abstraction.start_ev_type_per_ob_type,
),
(
&log_abstraction.end_ev_type_per_ob_type,
&model_abstraction.end_ev_type_per_ob_type,
),
(
&log_abstraction.related_ev_type_per_ob_type,
&model_abstraction.related_ev_type_per_ob_type,
),
(
&log_abstraction.divergent_ev_type_per_ob_type,
&model_abstraction.divergent_ev_type_per_ob_type,
),
(
&log_abstraction.convergent_ev_type_per_ob_type,
&model_abstraction.convergent_ev_type_per_ob_type,
),
(
&log_abstraction.deficient_ev_type_per_ob_type,
&model_abstraction.deficient_ev_type_per_ob_type,
),
(
&log_abstraction.optional_ev_type_per_ob_type,
&model_abstraction.optional_ev_type_per_ob_type,
),
];
let mut fit_per_ev_type = all_ev_types
.iter()
.map(|&ev_type| (ev_type, 0.0))
.collect::<HashMap<_, _>>();
let mut prec_per_ev_type = fit_per_ev_type.clone();
let mut valid_fit_ev_type_count: usize = 0;
let mut valid_prec_ev_type_count: usize = 0;
all_ev_types.iter().for_each(|&ev_type| {
let mut fit_pattern_matching: usize = 0;
let mut prec_pattern_matching: usize = 0;
pattern
.iter()
.for_each(|&(log_ev_type_per_ob_type, model_ev_type_per_ob_type)| {
let mut matches_log = 0;
let mut matches_model = 0;
let mut matches_log_model = 0;
all_ob_types.iter().for_each(|&ob_type| {
let in_log: bool = log_ev_type_per_ob_type
.get(ob_type)
.is_some_and(|set| set.contains(ev_type));
let in_model: bool = model_ev_type_per_ob_type
.get(ob_type)
.is_some_and(|set| set.contains(ev_type));
if in_log && in_model {
matches_log += 1;
matches_model += 1;
matches_log_model += 1;
} else if in_log {
matches_log += 1;
} else if in_model {
matches_model += 1;
}
});
if matches_log != 0 {
fit_per_ev_type
.get_mut(ev_type)
.unwrap()
.add_assign(matches_log_model as f64 / matches_log as f64);
fit_pattern_matching += 1;
}
if matches_model != 0 {
prec_per_ev_type
.get_mut(ev_type)
.unwrap()
.add_assign(matches_log_model as f64 / matches_model as f64);
prec_pattern_matching += 1;
}
});
if fit_pattern_matching > 0 {
fit_per_ev_type
.get_mut(ev_type)
.unwrap()
.div_assign(fit_pattern_matching as f64);
valid_fit_ev_type_count += 1;
}
if prec_pattern_matching > 0 {
prec_per_ev_type
.get_mut(ev_type)
.unwrap()
.div_assign(prec_pattern_matching as f64);
valid_prec_ev_type_count += 1;
}
});
let mut fit_per_dfr = all_ev_types
.iter()
.flat_map(|&from| {
all_ev_types
.iter()
.map(|&to| ((from, to), 0.0))
.collect::<HashMap<_, _>>()
})
.collect::<HashMap<_, _>>();
let mut prec_per_dfr = fit_per_dfr.clone();
let mut dfr_matches_fit = 0.0;
let mut dfr_matches_prec = 0.0;
all_ev_types.iter().for_each(|&from| {
all_ev_types.iter().for_each(|&to| {
let mut matches_log = 0.0;
let mut matches_model = 0.0;
let mut matches_log_model = 0.0;
all_ob_types.iter().for_each(|&ob_type| {
let in_log: bool = log_abstraction
.directly_follows_ev_types_per_ob_type
.get(ob_type)
.unwrap_or(&HashSet::new())
.contains(&(from.to_string(), to.to_string()));
let in_model: bool = model_abstraction
.directly_follows_ev_types_per_ob_type
.get(ob_type)
.unwrap_or(&HashSet::new())
.contains(&(from.to_string(), to.to_string()));
if in_log && in_model {
matches_log += 1.0;
matches_model += 1.0;
matches_log_model += 1.0;
} else if in_log {
matches_log += 1.0;
} else if in_model {
matches_model += 1.0;
}
});
if matches_log != 0.0 {
fit_per_dfr
.get_mut(&(from, to))
.unwrap()
.add_assign(matches_log_model / matches_log);
dfr_matches_fit += 1.0;
}
if matches_model != 0.0 {
prec_per_dfr
.get_mut(&(from, to))
.unwrap()
.add_assign(matches_log_model / matches_model);
dfr_matches_prec += 1.0;
}
})
});
let mut fitness_weighted_ev_types = 0.0;
fit_per_ev_type.iter().for_each(|(_, fit)| {
fitness_weighted_ev_types += fit;
});
if valid_fit_ev_type_count > 0 {
fitness_weighted_ev_types /= valid_fit_ev_type_count as f64;
}
let mut fitness_weighted_dfr = 0.0;
fit_per_dfr.iter().for_each(|(_, fit)| {
fitness_weighted_dfr += fit;
});
if dfr_matches_fit > 0.0 {
fitness_weighted_dfr /= dfr_matches_fit;
}
let mut precision_weighted_ev_types = 0.0;
prec_per_ev_type.iter().for_each(|(_, prec)| {
precision_weighted_ev_types += prec;
});
if valid_prec_ev_type_count > 0 {
precision_weighted_ev_types /= valid_prec_ev_type_count as f64;
}
let mut precision_weighted_dfr = 0.0;
prec_per_dfr.iter().for_each(|(_, prec)| {
precision_weighted_dfr += prec;
});
if dfr_matches_prec > 0.0 {
precision_weighted_dfr /= dfr_matches_prec;
}
(
(fitness_weighted_ev_types + fitness_weighted_dfr) / 2.0,
(precision_weighted_ev_types + precision_weighted_dfr) / 2.0,
)
}
#[cfg(test)]
mod tests {
use std::{fs::File, time::Instant};
use crate::{
conformance::object_centric::object_centric_language_abstraction::{
compute_fitness_precision, OCLanguageAbstraction,
},
core::{
event_data::object_centric::linked_ocel::IndexLinkedOCEL,
process_models::object_centric::ocpt::object_centric_process_tree_struct::{
OCPTNode, OCPTOperatorType, OCPT,
},
OCEL,
},
ocel,
test_utils::get_test_data_path,
Importable,
};
fn create_test_tree() -> OCPT {
let mut root_op = OCPTNode::new_operator(OCPTOperatorType::Sequence);
let mut place: OCPTNode = OCPTNode::new_leaf(Some("place".to_string()));
place.add_convergent_ob_type(&"i".to_string());
place.add_divergent_ob_type(&"c".to_string());
place.add_related_ob_type(&"c".to_string());
place.add_related_ob_type(&"o".to_string());
place.add_related_ob_type(&"i".to_string());
root_op.add_child(place);
let mut pay_pack_operator = OCPTNode::new_operator(OCPTOperatorType::Concurrency);
let mut pay: OCPTNode = OCPTNode::new_leaf(Some("pay".to_string()));
pay.add_convergent_ob_type(&"i".to_string());
pay.add_divergent_ob_type(&"c".to_string());
pay.add_related_ob_type(&"c".to_string());
pay.add_related_ob_type(&"o".to_string());
pay.add_related_ob_type(&"i".to_string());
pay_pack_operator.add_child(pay);
let mut pack: OCPTNode = OCPTNode::new_leaf(Some("pack".to_string()));
pack.add_convergent_ob_type(&"i".to_string());
pack.add_divergent_ob_type(&"o".to_string());
pack.add_divergent_ob_type(&"e".to_string());
pack.add_related_ob_type(&"o".to_string());
pack.add_related_ob_type(&"i".to_string());
pack.add_related_ob_type(&"e".to_string());
pay_pack_operator.add_child(pack);
root_op.add_child(pay_pack_operator);
let mut refund_pickup_operator = OCPTNode::new_operator(OCPTOperatorType::ExclusiveChoice);
let mut refund: OCPTNode = OCPTNode::new_leaf(Some("refund".to_string()));
refund.add_convergent_ob_type(&"i".to_string());
refund.add_divergent_ob_type(&"o".to_string());
refund.add_divergent_ob_type(&"e".to_string());
refund.add_related_ob_type(&"o".to_string());
refund.add_related_ob_type(&"i".to_string());
refund.add_related_ob_type(&"e".to_string());
refund_pickup_operator.add_child(refund);
let mut pickup: OCPTNode = OCPTNode::new_leaf(Some("pickup".to_string()));
pickup.add_convergent_ob_type(&"i".to_string());
pickup.add_deficient_ob_type(&"e".to_string());
pickup.add_divergent_ob_type(&"c".to_string());
pickup.add_divergent_ob_type(&"e".to_string());
pickup.add_related_ob_type(&"c".to_string());
pickup.add_related_ob_type(&"o".to_string());
pickup.add_related_ob_type(&"i".to_string());
pickup.add_related_ob_type(&"e".to_string());
refund_pickup_operator.add_child(pickup);
root_op.add_child(refund_pickup_operator);
OCPT::new(root_op)
}
fn create_test_ocel() -> OCEL {
ocel!(
events:
("place", ["c:1", "o:1", "i:1", "i:2"]),
("pack", ["o:1", "i:2", "e:1"]),
("place", ["c:1", "o:2", "i:3", "i:4"]),
("pay", ["c:1", "o:1", "i:1", "i:2"]),
("pickup", ["c:1", "o:1", "i:2", "e:1"]),
("pay", ["c:1", "o:2", "i:3", "i:4"]),
("pack", ["o:1", "o:2", "i:1", "i:3", "e:1"]),
("place", ["c:1", "o:3", "i:5"]),
("pack", ["o:2", "i:4", "e:1", "e:2"]),
("pickup", ["c:1", "o:2", "i:4", "e:1"]),
("pay", ["c:1", "o:3", "i:5"]),
("pack", ["o:3", "i:5", "e:1"]),
("pickup", ["c:1", "o:1", "o:2", "i:1", "i:3"]),
("refund", ["o:3", "i:5", "e:1"]),
o2o:
)
}
#[test]
fn test_fitness_precision_computation() {
let tree = create_test_tree();
let ocel = create_test_ocel();
let preprocessed_ocel = ocel.remove_orphan_objects();
let locel = IndexLinkedOCEL::from_ocel(preprocessed_ocel);
let time_start = Instant::now();
let abstraction_tree = OCLanguageAbstraction::create_from_oc_process_tree(&tree);
let abstraction_log = OCLanguageAbstraction::create_from_ocel(&locel);
let (fitness, precision) = compute_fitness_precision(&abstraction_log, &abstraction_tree);
let time_elapsed = time_start.elapsed().as_millis();
println!("Time elapsed is {time_elapsed}ms");
println!("Fitness: {fitness}");
println!("Precision: {precision}");
}
#[test]
fn test_two_equal_ocpts() {
let ocpt: OCPT = serde_json::from_reader(
File::open(get_test_data_path().join("ocpt-discovered-im.json")).unwrap(),
)
.unwrap();
let abstraction1 = OCLanguageAbstraction::create_from_oc_process_tree(&ocpt);
let abstraction2 = OCLanguageAbstraction::create_from_oc_process_tree(&ocpt);
let (fitness, precision) = compute_fitness_precision(&abstraction1, &abstraction2);
println!("fitness={}, precision={}", fitness, precision);
assert!(
(fitness - 1.0).abs() < f64::EPSILON,
"Wrong fitness: got {}!={} <- should be this value",
fitness,
1.0
);
assert!(
(precision - 1.0).abs() < f64::EPSILON,
"Wrong precision: got {}!={} <- should be this value",
precision,
1.0
);
}
#[test]
fn test_ocpt_and_ocel() {
let ocpt: OCPT = serde_json::from_reader(
File::open(get_test_data_path().join("ocpt-discovered-im.json")).unwrap(),
)
.unwrap();
let ocel = OCEL::import_from_path(
get_test_data_path()
.join("ocel")
.join("order-management.json"),
)
.unwrap();
let locel = IndexLinkedOCEL::from_ocel(ocel.remove_orphan_objects());
let abstraction_model = OCLanguageAbstraction::create_from_oc_process_tree(&ocpt);
let abstraction_log = OCLanguageAbstraction::create_from_ocel(&locel);
let (fitness, precision) = compute_fitness_precision(&abstraction_log, &abstraction_model);
println!("fitness={}, precision={}", fitness, precision);
assert!(
(fitness - 1.0).abs() < f64::EPSILON,
"Wrong fitness: got {}!={} <- should be this value",
fitness,
1.0
);
const EXPECTED_PRECISION: f64 = 0.6876033057851241;
assert!(
(precision - EXPECTED_PRECISION).abs() < 0.00000001,
"Wrong precision: got {}!={} <- should be this value",
precision,
EXPECTED_PRECISION
);
}
}