ocel
An OCEL 2.0 toolkit for Rust and Python — read, write, convert, validate, filter, and sample object-centric event logs. Verified against the official PM4Py example and the 21K-event Zenodo Order Management log.
Features
- OCEL 2.0-native data model — events, objects, qualified E2O/O2O relationships, dynamic (timestamped) object attributes, typed attribute values
- Three formats, one model — JSON / SQLite / XML read+write with declaration-driven typing: the same log reads identically from any format, and round-trips losslessly across them
- Validation — spec-conformance checks tuned against the official datasets
- Object interaction graph — neighbors and connected components
- OCEL-aware filtering & sampling — filters and connected-components sampling that always produce valid sub-logs (events, objects, and relations stay consistent)
ocelCLI — convert and validate from the command line- Python bindings — the
ocelmodule with columnar exports that feed straight into Polars/pandas - MIT licensed — note that PM4Py is AGPL-3.0; ocel is a permissive alternative for the OCEL 2.0 I/O + preprocessing layer
Performance
All numbers: Zenodo Order Management
(21,008 events / 10,840 objects), median of 7 runs, Apple M4 Max. Fetch the
dataset first: sh scripts/fetch-official-fixtures.sh --large.
Embedded in Rust
Applications (e.g. process-mining tools) work on the model and graph directly — no DataFrames involved:
| operation | time |
|---|---|
| read SQLite | 57 ms |
| read JSON | 49 ms |
| read XML | 72 ms |
| validate | 3 ms |
| object graph + connected components | 26 ms |
| filter by 3 event types | 10 ms |
| write SQLite | 252 ms |
Reproduce: cargo run -p ocel --example bench --release
From Python, vs pm4py
Python 3.13, pm4py 2.7.23. Both tools load identical events/objects/E2O/O2O counts. To keep the comparison fair, the read rows include materializing all six of ocel's columnar exports into Polars DataFrames, since pm4py's readers return pandas DataFrames:
| operation | ocel (Rust) | pm4py | speedup |
|---|---|---|---|
| read SQLite → DataFrames | 115 ms | 447 ms | 3.9x |
| read JSON → DataFrames | 111 ms | 603 ms | 5.4x |
| read XML → DataFrames | 133 ms | 410 ms | 3.1x |
| filter by 3 event types | 11 ms | 17 ms | 1.6x |
| write SQLite | 257 ms | 395 ms | 1.5x |
Python code that stays on OcelLog methods (filter / sample / validate) skips
the DataFrame cost entirely and runs at the Rust-native speeds above.
Reproduce with scripts/bench-pm4py-compare.py.
Quickstart (Rust)
use ;
// Read an OCEL 2.0 JSON log and write it out as SQLite.
let ocel = read_path?;
write_path?;
// Validate, filter, sample.
ocel.validate.map_err?;
let sub = ocel.filter_event_types;
let sample = ocel.sample_components;
See crates/ocel/examples/roundtrip.rs
for a runnable example (cargo run -p ocel --example roundtrip).
Quickstart (Python)
&&
= # .json/.jsonocel, .sqlite/.db, .xml/.xmlocel
assert == # spec-conformance (empty = valid)
= # id / type / time
= # E2O: event_id / object_id / qualifier
= # typed values -> mixed column
= # consistent sub-log
= # connected-components sampling
CLI
ocel validate doubles as a standalone OCEL 2.0 conformance checker — handy in
CI for any tool that produces OCEL exports:
Workspace
crates/
├── ocel/ # data model + I/O + validation + graph/filter/sampling
├── ocel-cli/ # `ocel` command-line tool
└── ocel-py/ # Python bindings (module name: ocel)
License
MIT