use std::collections::HashMap;
use ocel::Ocel;
use crate::powl::Powl;
use crate::precision::{score, sequences_of, visit_states, PrecisionReport};
use crate::replay::{collect_variants, report, ReplayReport};
use crate::trace;
struct Compiled {
kinds: Vec<Kind>,
nullable: Vec<bool>,
owner: Vec<HashMap<u16, usize>>,
}
enum Kind {
Activity(u16),
Tau,
Exclusive(Vec<usize>),
Loop(Vec<usize>),
Po(Vec<usize>, Vec<Vec<bool>>),
}
fn compile(
model: &Powl,
intern: &mut HashMap<String, u16>,
out: &mut Compiled,
) -> (usize, Vec<u16>) {
let (kind, nullable, alphabet, owner) = match model {
Powl::Activity { label } => {
let next = u16::try_from(intern.len()).expect("more than u16::MAX activities");
let id = *intern.entry(label.clone()).or_insert(next);
(Kind::Activity(id), false, vec![id], HashMap::new())
}
Powl::Tau => (Kind::Tau, true, Vec::new(), HashMap::new()),
Powl::Exclusive { children } | Powl::Loop { children } => {
let (ids, nullables, alphabet, owner) = compile_children(children, intern, out);
let (kind, nullable) = if matches!(model, Powl::Exclusive { .. }) {
(Kind::Exclusive(ids), nullables.iter().any(|&n| n))
} else {
let body_nullable = nullables[0];
(Kind::Loop(ids), body_nullable)
};
(kind, nullable, alphabet, owner)
}
Powl::PartialOrder { children, order } => {
let (ids, nullables, alphabet, owner) = compile_children(children, intern, out);
let n = ids.len();
let mut before = vec![vec![false; n]; n];
for &(i, j) in order {
before[i][j] = true;
}
for k in 0..n {
let via = before[k].clone();
for row in &mut before {
if row[k] {
for (j, &reachable) in via.iter().enumerate() {
if reachable {
row[j] = true;
}
}
}
}
}
let nullable = nullables.iter().all(|&n| n);
(Kind::Po(ids, before), nullable, alphabet, owner)
}
};
out.kinds.push(kind);
out.nullable.push(nullable);
out.owner.push(owner);
(out.kinds.len() - 1, alphabet)
}
type Children = (Vec<usize>, Vec<bool>, Vec<u16>, HashMap<u16, usize>);
fn compile_children(
children: &[Powl],
intern: &mut HashMap<String, u16>,
out: &mut Compiled,
) -> Children {
let mut ids = Vec::with_capacity(children.len());
let mut nullables = Vec::with_capacity(children.len());
let mut alphabet: Vec<u16> = Vec::new();
let mut owner: HashMap<u16, usize> = HashMap::new();
for (index, child) in children.iter().enumerate() {
let (id, child_alphabet) = compile(child, intern, out);
nullables.push(out.nullable[id]);
ids.push(id);
for symbol in child_alphabet {
debug_assert!(!owner.contains_key(&symbol), "overlapping child alphabets");
owner.insert(symbol, index);
alphabet.push(symbol);
}
}
(ids, nullables, alphabet, owner)
}
struct Projections {
parts: Vec<Vec<u16>>,
first: Vec<Option<usize>>,
last: Vec<Option<usize>>,
}
impl Compiled {
fn project(&self, node: usize, children: usize, w: &[u16]) -> Option<Projections> {
let owner = &self.owner[node];
let mut parts: Vec<Vec<u16>> = vec![Vec::new(); children];
let mut first: Vec<Option<usize>> = vec![None; children];
let mut last: Vec<Option<usize>> = vec![None; children];
for (position, &s) in w.iter().enumerate() {
let &child = owner.get(&s)?;
parts[child].push(s);
first[child].get_or_insert(position);
last[child] = Some(position);
}
Some(Projections { parts, first, last })
}
fn order_respected(before: &[Vec<bool>], p: &Projections) -> bool {
for (i, row) in before.iter().enumerate() {
for (j, &ordered) in row.iter().enumerate() {
if !ordered {
continue;
}
if let (Some(last_i), Some(first_j)) = (p.last[i], p.first[j]) {
if last_i > first_j {
return false;
}
}
}
}
true
}
fn accepts(&self, node: usize, w: &[u16]) -> bool {
match &self.kinds[node] {
Kind::Activity(id) => w.len() == 1 && w[0] == *id,
Kind::Tau => w.is_empty(),
Kind::Exclusive(children) => {
if w.is_empty() {
return children.iter().any(|&c| self.nullable[c]);
}
let owner = &self.owner[node];
let Some(&child) = owner.get(&w[0]) else {
return false;
};
if w.iter().any(|s| owner.get(s) != Some(&child)) {
return false;
}
self.accepts(children[child], w)
}
Kind::Loop(children) => self.loop_reach(node, children, w)[w.len()],
Kind::Po(children, before) => {
let Some(p) = self.project(node, children.len(), w) else {
return false;
};
if !Self::order_respected(before, &p) {
return false;
}
children
.iter()
.zip(&p.parts)
.all(|(&c, part)| self.accepts(c, part))
}
}
}
fn loop_reach(&self, node: usize, children: &[usize], w: &[u16]) -> Vec<bool> {
let owner = &self.owner[node];
let body = children[0];
let n = w.len();
let body_run = |from: usize| {
let mut m = from;
while m < n && owner.get(&w[m]) == Some(&0) {
m += 1;
}
m
};
let redo_nullable = children[1..].iter().any(|&c| self.nullable[c]);
let mut reach = vec![false; n + 1];
let mut queue: Vec<usize> = Vec::new();
for j in 0..=body_run(0) {
if self.accepts(body, &w[..j]) {
reach[j] = true;
queue.push(j);
}
}
while let Some(j) = queue.pop() {
if redo_nullable {
for m in j + 1..=body_run(j) {
if !reach[m] && self.accepts(body, &w[j..m]) {
reach[m] = true;
queue.push(m);
}
}
}
if j >= n {
continue;
}
let Some(&child) = owner.get(&w[j]) else {
continue;
};
if child == 0 {
continue;
}
let mut end = j;
while end < n && owner.get(&w[end]) == Some(&child) {
end += 1;
}
for k in j + 1..=end {
if !self.accepts(children[child], &w[j..k]) {
continue;
}
for m in k..=body_run(k) {
if !reach[m] && self.accepts(body, &w[k..m]) {
reach[m] = true;
queue.push(m);
}
}
}
}
reach
}
fn prefix_ok(&self, node: usize, w: &[u16]) -> bool {
if w.is_empty() {
return true;
}
match &self.kinds[node] {
Kind::Activity(id) => w.len() == 1 && w[0] == *id,
Kind::Tau => false,
Kind::Exclusive(children) => {
let owner = &self.owner[node];
let Some(&child) = owner.get(&w[0]) else {
return false;
};
if w.iter().any(|s| owner.get(s) != Some(&child)) {
return false;
}
self.prefix_ok(children[child], w)
}
Kind::Loop(children) => self.prefix_loop(node, children, w),
Kind::Po(children, before) => {
let Some(p) = self.project(node, children.len(), w) else {
return false;
};
if !Self::order_respected(before, &p) {
return false;
}
let n = children.len();
for i in 0..n {
let successor_started = (0..n).any(|j| before[i][j] && p.first[j].is_some());
if p.first[i].is_none() {
if successor_started && !self.nullable[children[i]] {
return false;
}
continue;
}
if successor_started {
if !self.accepts(children[i], &p.parts[i]) {
return false;
}
} else if !self.prefix_ok(children[i], &p.parts[i]) {
return false;
}
}
true
}
}
}
fn prefix_loop(&self, node: usize, children: &[usize], w: &[u16]) -> bool {
let body = children[0];
if self.prefix_ok(body, w) {
return true;
}
let owner = &self.owner[node];
let n = w.len();
let redo_nullable = children[1..].iter().any(|&c| self.nullable[c]);
let reach = self.loop_reach(node, children, w);
if reach[n] {
return true;
}
for j in (0..n).filter(|&j| reach[j]) {
if redo_nullable && self.prefix_ok(body, &w[j..]) {
return true;
}
let Some(&child) = owner.get(&w[j]) else {
continue;
};
if child == 0 {
continue;
}
if self.prefix_ok(children[child], &w[j..]) {
return true;
}
let mut end = j;
while end < n && owner.get(&w[end]) == Some(&child) {
end += 1;
}
for k in j + 1..=end {
if self.accepts(children[child], &w[j..k]) && self.prefix_ok(body, &w[k..]) {
return true;
}
}
}
false
}
}
fn compile_model(model: &Powl, traces: &trace::Traces<'_>) -> (Compiled, usize, usize, usize) {
let mut intern: HashMap<String, u16> = traces
.activity_names
.iter()
.enumerate()
.map(|(id, &name)| {
(
name.to_owned(),
u16::try_from(id).expect("checked in build"),
)
})
.collect();
let mut compiled = Compiled {
kinds: Vec::new(),
nullable: Vec::new(),
owner: Vec::new(),
};
let (root, _) = compile(model, &mut intern, &mut compiled);
let logged = traces.activity_names.len();
let symbols = intern.len();
(compiled, root, logged, symbols)
}
#[must_use]
pub fn powl_replay(log: &Ocel, object_type: &str, model: &Powl) -> ReplayReport {
let traces = trace::build(log, object_type);
let (compiled, root, _, _) = compile_model(model, &traces);
let variants = collect_variants(&traces);
report(object_type, &traces.activity_names, variants, |w| {
compiled.accepts(root, w)
})
}
#[must_use]
pub fn powl_precision(log: &Ocel, object_type: &str, model: &Powl) -> PrecisionReport {
let traces = trace::build(log, object_type);
let (compiled, root, logged, symbols) = compile_model(model, &traces);
let sequences = sequences_of(&traces);
let (states, truncated) = visit_states(&sequences, logged, |prefix| {
compiled.prefix_ok(root, prefix)
});
score(object_type, &states, truncated, |prefix| {
let mut allowed = vec![false; logged];
let mut unlogged = 0usize;
let mut extended = Vec::with_capacity(prefix.len() + 1);
extended.extend_from_slice(prefix);
extended.push(0);
#[allow(clippy::needless_range_loop)] for id in 0..symbols {
*extended.last_mut().expect("just pushed") =
u16::try_from(id).expect("interned as u16");
if compiled.prefix_ok(root, &extended) {
if id < logged {
allowed[id] = true;
} else {
unlogged += 1;
}
}
}
(allowed, unlogged)
})
}