use wasm4pm::powl::conformance::token_replay::replay_trace;
use wasm4pm::powl::conversion::to_petri_net;
use wasm4pm::powl::extensive_playout::{extensive_playout, ExtensivePlayoutConfig};
use wasm4pm::powl::footprints;
use wasm4pm::powl_arena::PowlArena;
use wasm4pm::powl_event_log::{Event, Trace};
use wasm4pm_compat::powl::{ChoiceGraph, StandaloneChoiceGraphNode};
fn trace_of(case: &str, acts: &[&str]) -> Trace {
Trace {
case_id: case.to_string(),
events: acts
.iter()
.map(|a| Event {
name: (*a).to_string(),
timestamp: None,
lifecycle: None,
attributes: std::collections::BTreeMap::new(),
})
.collect(),
}
}
fn replay_fitness(arena: &PowlArena, root: u32, trace: &Trace) -> f64 {
let res = to_petri_net::apply(arena, root);
let r = replay_trace(&res.net, &res.initial_marking, &res.final_marking, trace);
r.fitness
}
fn get_models_trace_activities(trace: &wasm4pm::models::Trace) -> Vec<String> {
trace
.events
.iter()
.map(|ev| match ev.attributes.get("concept:name").unwrap() {
wasm4pm::models::AttributeValue::String(s) => s.clone(),
_ => panic!("Expected string value"),
})
.collect()
}
#[test]
fn test_choice_graph_cyclic_loop_behavior() {
use StandaloneChoiceGraphNode::*;
let nodes = vec![Start, Activity("A".into()), Activity("B".into()), End];
let edges = vec![
(0, 1), (1, 2), (2, 1), (2, 3), ];
let cg = ChoiceGraph::new(nodes, edges).unwrap();
let mut arena = PowlArena::new();
let root = arena.add_choice_graph(&cg);
let config = ExtensivePlayoutConfig {
min_length: 1,
max_length: 10,
max_loops: 2,
max_traces: 20,
};
let playout_res = extensive_playout(&arena, root, &config);
assert!(
!playout_res.traces.is_empty(),
"Playout must generate traces"
);
let mut found_acyclic = false;
for tr in &playout_res.traces {
let acts = get_models_trace_activities(tr);
if acts == vec!["A", "B"] {
found_acyclic = true;
}
}
assert!(found_acyclic, "Should find ['A', 'B'] trace in playout");
let fp = footprints::apply(&arena, root);
assert!(fp.start_activities.contains("A"));
assert!(fp.end_activities.contains("B"));
assert!(fp.parallel.contains(&("A".to_string(), "B".to_string())));
assert!(fp.parallel.contains(&("B".to_string(), "A".to_string())));
let t1 = trace_of("t1", &["A", "B"]);
let t2 = trace_of("t2", &["A", "B", "A", "B"]);
let t_bad = trace_of("t_bad", &["B", "A"]);
let f1 = replay_fitness(&arena, root, &t1);
let f2 = replay_fitness(&arena, root, &t2);
let f_bad = replay_fitness(&arena, root, &t_bad);
assert!(
f1 > 0.89 && f1 < 0.91,
"t1 fitness should be ~0.90, got {}",
f1
);
assert!(
f2 > 0.94 && f2 < 0.95,
"t2 fitness should be ~0.94, got {}",
f2
);
assert!(f_bad < 0.75, "t_bad fitness should be low, got {}", f_bad);
}
#[test]
fn test_choice_graph_start_end_boundaries() {
use StandaloneChoiceGraphNode::*;
let nodes = vec![Start, Activity("A".into()), Activity("B".into()), End];
let edges = vec![
(0, 1), (0, 2), (1, 3), (2, 3), ];
let cg = ChoiceGraph::new(nodes, edges).unwrap();
let mut arena = PowlArena::new();
let root = arena.add_choice_graph(&cg);
let config = ExtensivePlayoutConfig {
min_length: 1,
max_length: 5,
max_loops: 1,
max_traces: 10,
};
let playout_res = extensive_playout(&arena, root, &config);
let mut trace_acts: Vec<Vec<String>> = playout_res
.traces
.iter()
.map(get_models_trace_activities)
.collect();
trace_acts.sort();
assert_eq!(
trace_acts,
vec![vec!["A".to_string()], vec!["B".to_string()]]
);
let fp = footprints::apply(&arena, root);
assert!(fp.start_activities.contains("A"));
assert!(fp.start_activities.contains("B"));
assert!(fp.end_activities.contains("A"));
assert!(fp.end_activities.contains("B"));
let t_a = trace_of("t_a", &["A"]);
let t_b = trace_of("t_b", &["B"]);
let t_ab = trace_of("t_ab", &["A", "B"]);
assert!(replay_fitness(&arena, root, &t_a) >= 0.999);
assert!(replay_fitness(&arena, root, &t_b) >= 0.999);
assert!(replay_fitness(&arena, root, &t_ab) < 0.999);
}
#[test]
fn test_choice_graph_complex_routing_paths() {
use StandaloneChoiceGraphNode::*;
let nodes = vec![
Start,
Activity("Check".into()),
Activity("Approve".into()),
Activity("Reject".into()),
Activity("Review".into()),
End,
];
let edges = vec![
(0, 1), (1, 2), (1, 3), (2, 5), (3, 4), (4, 1), (4, 5), ];
let cg = ChoiceGraph::new(nodes, edges).unwrap();
let mut arena = PowlArena::new();
let root = arena.add_choice_graph(&cg);
let config = ExtensivePlayoutConfig {
min_length: 1,
max_length: 15,
max_loops: 2,
max_traces: 50,
};
let playout_res = extensive_playout(&arena, root, &config);
assert!(!playout_res.traces.is_empty());
let mut found_simple = false;
let mut found_reject_exit = false;
for tr in &playout_res.traces {
let acts = get_models_trace_activities(tr);
if acts == vec!["Check", "Approve"] {
found_simple = true;
} else if acts == vec!["Check", "Reject", "Review"] {
found_reject_exit = true;
}
}
assert!(found_simple, "Should play out ['Check', 'Approve']");
assert!(
found_reject_exit,
"Should play out ['Check', 'Reject', 'Review']"
);
let fp = footprints::apply(&arena, root);
assert!(fp.start_activities.contains("Check"));
assert!(fp.end_activities.contains("Approve"));
assert!(fp.end_activities.contains("Review"));
let t_simple = trace_of("t_simple", &["Check", "Approve"]);
let t_reject_exit = trace_of("t_reject", &["Check", "Reject", "Review"]);
let t_loop = trace_of("t_loop", &["Check", "Reject", "Review", "Check", "Approve"]);
let t_bad = trace_of("t_bad", &["Check", "Review"]);
let f_simple = replay_fitness(&arena, root, &t_simple);
let f_reject = replay_fitness(&arena, root, &t_reject_exit);
let f_loop = replay_fitness(&arena, root, &t_loop);
let f_bad = replay_fitness(&arena, root, &t_bad);
assert!(f_simple >= 0.999);
assert!(
f_reject > 0.92 && f_reject < 0.93,
"t_reject fitness should be ~0.928, got {}",
f_reject
);
assert!(f_loop >= 0.999);
assert!(f_bad < 0.85);
}