use crate::models::DFG;
use crate::streaming::{StreamStats, StreamingAlgorithm, StreamingDfgBuilder};
use std::marker::PhantomData;
pub trait BatchAlgorithm {
type Model: Clone;
fn discover_from_dfg(&self, dfg: &DFG) -> Self::Model;
}
pub struct StreamingHybrid<A>
where
A: BatchAlgorithm,
{
batch_algorithm: A,
state: StreamingDfgBuilder,
recompute_interval: usize,
trace_count: usize,
last_model: Option<A::Model>,
_phantom: PhantomData<A>,
}
impl<A> StreamingHybrid<A>
where
A: BatchAlgorithm,
{
pub fn new(batch_algorithm: A) -> Self {
StreamingHybrid {
batch_algorithm,
state: StreamingDfgBuilder::new(),
recompute_interval: 100,
trace_count: 0,
last_model: None,
_phantom: PhantomData,
}
}
pub fn with_interval(batch_algorithm: A, recompute_interval: usize) -> Self {
StreamingHybrid {
batch_algorithm,
state: StreamingDfgBuilder::new(),
recompute_interval,
trace_count: 0,
last_model: None,
_phantom: PhantomData,
}
}
pub fn recompute(&mut self) {
let dfg = self.state.snapshot();
self.last_model = Some(self.batch_algorithm.discover_from_dfg(&dfg));
}
}
impl<A> StreamingAlgorithm for StreamingHybrid<A>
where
A: BatchAlgorithm + Default,
{
type Model = A::Model;
fn new() -> Self {
StreamingHybrid::new(A::default())
}
fn add_event(&mut self, case_id: &str, activity: &str) {
self.state.add_event(case_id, activity);
}
fn close_trace(&mut self, case_id: &str) -> bool {
let closed = self.state.close_trace(case_id);
if closed {
self.trace_count += 1;
if self.trace_count.is_multiple_of(self.recompute_interval) {
self.recompute();
}
}
closed
}
fn snapshot(&self) -> Self::Model {
if let Some(ref model) = self.last_model {
model.clone()
} else {
let dfg = self.state.snapshot();
self.batch_algorithm.discover_from_dfg(&dfg)
}
}
fn finalize(mut self) -> Self::Model {
self.recompute();
self.last_model.unwrap()
}
fn stats(&self) -> StreamStats {
self.state.stats()
}
fn open_trace_ids(&self) -> Vec<String> {
self.state.open_trace_ids()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Default)]
struct MockBatchAlgorithm;
impl BatchAlgorithm for MockBatchAlgorithm {
type Model = DFG;
fn discover_from_dfg(&self, dfg: &DFG) -> Self::Model {
dfg.clone()
}
}
#[test]
fn test_hybrid_basic() {
let batch_algo = MockBatchAlgorithm;
let mut stream = StreamingHybrid::with_interval(batch_algo, 2);
stream.add_event("case1", "A");
stream.add_event("case1", "B");
stream.close_trace("case1");
stream.add_event("case2", "A");
stream.add_event("case2", "B");
stream.close_trace("case2");
let stats = stream.stats();
assert_eq!(stats.trace_count, 2);
}
#[test]
fn test_hybrid_recompute_interval() {
let batch_algo = MockBatchAlgorithm;
let mut stream = StreamingHybrid::with_interval(batch_algo, 5);
for i in 1..=4 {
stream.add_event(&format!("case{}", i), "A");
stream.add_event(&format!("case{}", i), "B");
stream.close_trace(&format!("case{}", i));
}
assert!(stream.last_model.is_none());
stream.add_event("case5", "A");
stream.add_event("case5", "B");
stream.close_trace("case5");
assert!(stream.last_model.is_some());
}
#[test]
fn test_hybrid_finalize() {
let batch_algo = MockBatchAlgorithm;
let mut stream = StreamingHybrid::with_interval(batch_algo, 100);
for i in 1..=3 {
stream.add_event(&format!("case{}", i), "A");
stream.add_event(&format!("case{}", i), "B");
stream.close_trace(&format!("case{}", i));
}
let model = stream.finalize();
assert_eq!(model.nodes.len(), 2);
assert_eq!(model.edges.len(), 1);
}
}