Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Changelog

## Unreleased

- Optimal alignments (`conformance::alignments`):
- `align_log` / `align_projection` / `align_trace` / `align_empty_trace` compute cost-optimal alignments via Dijkstra over a synchronous product net
- `compute_fitness` derives log/trace fitness for pre-computed alignments
- Configurable `AlignmentOptions` (`CostFunction`, `max_states`)
- Exposed as bindings
- Generic, reusable state-space search in `utils::dijkstra_search` (`SearchProblem` trait + `search`)
- `register_binding` macro accepts slice arguments (`&[T]` extracted as `Vec<T>`)

## 0.5.6

- Translate a `ProcessTree` into a `PetriNet`:
Expand Down
7 changes: 7 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion macros_process_mining/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,15 @@ pub fn register_binding(args: TokenStream, item: TokenStream) -> TokenStream {
&& !(BIG_TYPES_NAMES.iter().any(|tn| ty_as_str.ends_with(tn)));
let type_without_ref = match &ty_no_life {
Type::Reference(type_reference) if change_from_ref => {
*type_reference.elem.clone()
match &*type_reference.elem {
// `&[T]` -> extract a `Vec<T>`; `&Vec<T>` coerces back to `&[T]` at the
// call site. (Does not apply to `&[&str]`: `Vec<&str>` is not owned.)
Type::Slice(slice) => {
let elem = &slice.elem;
syn::parse_quote!(Vec<#elem>)
}
_ => *type_reference.elem.clone(),
}
}
x => x.clone(),
};
Expand Down
6 changes: 6 additions & 0 deletions process_mining/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ uuid = {version = "1.16.0", features = ["v4", "serde"]}
schemars = { version = "1.1.0", features = ["chrono04", "uuid1"]}
inventory = { version = "0.3", optional = true }
csv = "1.4.0"
hashbrown = "0.17.1"
rustc-hash = "2.1.2"

[dev-dependencies]
criterion = { version = "0.5.1", features = ["html_reports"] }
Expand Down Expand Up @@ -103,3 +105,7 @@ harness = false
[[bench]]
name = "load_events_mem"
harness = false

[[bench]]
name = "alignments"
harness = false
47 changes: 47 additions & 0 deletions process_mining/benches/alignments.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
//! Benchmark the time taken to compute optimal alignments of a log against a Petri net
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use process_mining::{
conformance::alignments::{align_log, AlignmentOptions},
core::event_data::case_centric::utils::activity_projection::{
log_to_activity_projection, EventLogActivityProjection,
},
test_utils::get_test_data_path,
EventLog, Importable, PetriNet,
};
use std::time::Duration;

fn load(log_name: &str, net_name: &str) -> (PetriNet, EventLogActivityProjection) {
let root = get_test_data_path();
let log = EventLog::import_from_path(root.join("xes").join(log_name)).unwrap();
let net = PetriNet::import_pnml(root.join("petri-net").join(net_name)).unwrap();
(net, log_to_activity_projection(&log))
}

fn bench_alignments(c: &mut Criterion) {
let options = AlignmentOptions::default();

let (sepsis_net, sepsis_proj) =
load("Sepsis Cases - Event Log.xes.gz", "sepsis-DISCovered.apnml");
let mut sepsis = c.benchmark_group("alignments");
sepsis.sample_size(25);
sepsis.measurement_time(Duration::from_secs(20));
sepsis.bench_function("sepsis", |b| {
b.iter(|| black_box(align_log(&sepsis_net, &sepsis_proj, &options)))
});
sepsis.finish();

let (rtfm_net, rtfm_proj) = load(
"Road_Traffic_Fine_Management_Process.xes.gz",
"rtfm-imf-02.apnml",
);
let mut rtfm = c.benchmark_group("alignments");
rtfm.sample_size(10);
rtfm.measurement_time(Duration::from_secs(60));
rtfm.bench_function("rtfm", |b| {
b.iter(|| black_box(align_log(&rtfm_net, &rtfm_proj, &options)))
});
rtfm.finish();
}

criterion_group!(benches, bench_alignments);
criterion_main!(benches);
31 changes: 31 additions & 0 deletions process_mining/src/conformance/case_centric/alignments/cost.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! Cost functions for alignments

use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

/// Cost function for alignment moves.
///
/// Defines the cost of model moves, log moves, synchronous moves, and silent moves.
#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
pub struct CostFunction {
/// Default cost for a model move (visible transition fires without matching log event)
pub model_move_cost: u32,
/// Default cost for a log move (log event not matched by model)
pub log_move_cost: u32,
/// Default cost for a synchronous move
pub sync_move_cost: u32,
/// Default cost for a silent/tau move
pub silent_move_cost: u32,
}
Comment thread
aarkue marked this conversation as resolved.

impl CostFunction {
/// Standard cost function: model and log moves cost 1, sync and silent moves cost 0.
pub fn standard() -> Self {
Self {
model_move_cost: 1,
log_move_cost: 1,
sync_move_cost: 0,
silent_move_cost: 0,
}
}
}
Loading
Loading