Rheon
A library for synthetic event log generation with injected concept drifts.
Concepts
Quick start
Generate a labeled log with the simple API.
Describe a base process and a list of drifts, then write a labeled log.
import rheon
drifts = [
{"type": "control_flow", "mode": "sudden", "drift_point": 0.5, "num_activities": 9},
{"type": "reassignment", "mode": "gradual", "start_point": 0.3, "end_point": 0.45},
{"type": "amount", "mode": "sudden", "drift_point": 0.6, "mean": 4000},
]
rheon.generate_log(drifts, "out/example.xes", num_traces=2000, num_activities=8)
This writes two files: the log out/example.xes and a ground-truth metadata
sidecar out/example_meta.md.
Read the generated log back with pm4py:
import pm4py
log = pm4py.read_xes("out/example.xes")
Installation
Get the runtime and dependencies in place.
Prerequisites
- Python 3.10 or newer
- uv package manager
Set up
Clone the repository and let uv resolve dependencies on first run:
git clone git@github.com:boraderen/rheon.git
cd rheon
uv sync
Example
Run the bundled script to generate a labeled log:
uv run python example/generate_log.py
It injects drifts across every perspective and writes example/example.csv plus
its example_meta.md ground-truth sidecar.
Package structure
How the public API and implementation are split.
The package exposes a single function, rheon.generate_log, from
rheon/__init__.py. The implementation is just seven small modules.
rheon/
__init__.py public exports
config.py column keys, labels, GeneratorConfig
drifts.py drift spec, validation, timeline helpers
process_tree.py build trees and play out activity sequences
generator.py the generation engine (the six steps)
metadata.py build and render the ground-truth metadata
write.py write XES / CSV and the metadata sidecar
example/
generate_log.py one mixed-drift example
tests/
test_*.py behavior coverage
| Path | Role |
|---|---|
rheon/config.py | Column names, label helpers, and the parameter bundle. |
rheon/drifts.py | Normalizes and validates the drift list. |
rheon/process_tree.py | Builds process trees and plays out traces. |
rheon/generator.py | The generation engine and the public generate_log. |
rheon/metadata.py | Assembles and renders the ground-truth metadata. |
rheon/write.py | Writes the log (XES or CSV) and the metadata sidecar. |
API
Function signature and generator options for rheon.generate_log().
Signature
rheon.generate_log(
drifts,
output_path,
*,
log_name=None,
format="xes", # "xes" or "csv"
num_traces=1000,
num_activities=10,
num_resources=8,
num_regions=4,
tree_weights={"sequence": 0.6, "choice": 0.25, "parallel": 0.1, "loop": 0.05},
start_date=datetime(2020, 1, 1),
end_date=datetime(2020, 12, 31),
activity_duration=(30.0, 100.0),
waiting_time=(15.0, 50.0),
amount=(1000.0, 40000.0),
seed=42,
)
drifts is a list of dictionaries, each with a type, a mode, and a
position; see Drift types and Drift modes. The
parameters below all describe the single base process.
| Argument | Default | Description |
|---|---|---|
drifts | — | List of drift dictionaries to inject. |
output_path | — | Destination of the log. |
log_name | file stem | Name used in metadata and the sidecar filename. |
format | "xes" | "xes" or "csv". |
num_traces | 1000 | Approximate number of cases (not strict). |
num_activities | 10 | Activities in the base process tree. |
num_resources | 8 | Size of the resource pool. |
num_regions | 4 | Number of regions. |
tree_weights | see above | Operator weights for the base tree (sequence, choice, parallel, loop). |
start_date | 2020-01-01 | Start of the time horizon. |
end_date | 2020-12-31 | End of the time horizon (with start_date it fixes the window). |
activity_duration | (30.0, 100.0) | (mean, variance) of activity processing time in minutes. |
waiting_time | (15.0, 50.0) | (mean, variance) of the waiting gap between events. |
amount | (1000.0, 40000.0) | (mean, variance) of the case amount. |
seed | 42 | Random seed. |
Cases are spread across [start_date, end_date]. The mean inter-arrival gap is
derived as (end_date − start_date) / num_traces. The arrival_rate drift changes that derived
rate after its drift point.
Drift modes
How the change is shaped over time.
Every drift declares a mode and a position. All positions are fractions of the time
horizon in (0, 1), where 0.5 is the midpoint. The metadata records each
position as both a fraction and an absolute timestamp.
| Mode | Position keys | Behavior |
|---|---|---|
sudden |
drift_point |
A hard switch: everything after drift_point uses the new behavior. |
gradual |
start_point, end_point |
A transition window: between the two points the new behavior is mixed in with a probability that rises linearly from 0 to 1. |
Drift types
The nine drifts and exactly what each one changes.
A drift's type selects the mechanism. Perspectives (intra-case, resource,
inter-case) are a documentation grouping only — generate_log dispatches purely on
type.
| Perspective | Type | What changes | Type-specific params |
|---|---|---|---|
| intra-case | control_flow | Activity-ordering structure (a different process tree). | num_activities, tree_weights |
| resource | pool_size | Resources added or removed; durations scale the opposite way. | delta, duration_factor |
| resource | reassignment | A new dominant resource per activity. | — |
| resource | workload | Traces duplicated or dropped; per-resource load shifts. | workload_factor |
| resource | duration | Processing time of the given resources is scaled. | resources, factor |
| inter-case | waiting_time | The mean waiting gap between consecutive events of a case shifts (all activities at once). | mean, variance |
| inter-case | amount | Case amounts drawn from a shifted distribution. | mean, variance |
| inter-case | arrival_rate | The mean gap between case arrivals changes. | inter_arrival or factor |
| inter-case | region | A new dominant region for later cases. | — |
Example valid drift specs
The following drifts list is valid with the default generator labels
(res_01 ... res_08 and region_1 ... region_4).
Use any subset of these dicts; if you add multiple drifts of the same type, their
windows must not overlap.
drifts = [
{
"type": "control_flow",
"mode": "sudden",
"drift_point": 0.35,
"num_activities": 9,
"tree_weights": {"sequence": 0.50, "choice": 0.30, "parallel": 0.15, "loop": 0.05},
},
{
"type": "pool_size",
"mode": "gradual",
"start_point": 0.20,
"end_point": 0.30,
"delta": 2,
"duration_factor": 1.25,
},
{"type": "reassignment", "mode": "sudden", "drift_point": 0.40},
{
"type": "workload",
"mode": "gradual",
"start_point": 0.70,
"end_point": 0.85,
"workload_factor": 1.40,
},
{
"type": "duration",
"mode": "sudden",
"drift_point": 0.55,
"resources": ["res_01", "res_02"],
"factor": 1.80,
},
{
"type": "waiting_time",
"mode": "gradual",
"start_point": 0.45,
"end_point": 0.60,
"mean": 45.0,
"variance": 80.0,
},
{
"type": "amount",
"mode": "sudden",
"drift_point": 0.65,
"mean": 3000.0,
"variance": 90000.0,
},
{
"type": "arrival_rate",
"mode": "sudden",
"drift_point": 0.50,
"inter_arrival": 240.0,
},
{"type": "region", "mode": "gradual", "start_point": 0.75, "end_point": 0.90},
]
For arrival_rate, use either an absolute inter_arrival value in minutes
as shown above or a relative factor, for example
{"type": "arrival_rate", "mode": "sudden", "drift_point": 0.50, "factor": 0.5}.
What each drift does
control_flow · intra-case
Cases after the drift are played out from a different process tree, so the
activity-ordering (trace variants) structure changes. The new tree is built from the drift's
num_activities and tree_weights.
In sudden mode every case after drift_point uses the new tree; in
gradual mode a case in the window picks the new tree with the rising window
probability.
pool_size · resource
delta adds (+) or removes (−) resources at the drift.
When it shrinks, the removed resources' later events are redistributed to the rest; when it
grows, each new resource becomes dominant for a free activity. Durations scale the opposite way
by duration_factor (fewer resources → slower, more → faster). Gradual
mode ramps the reassignment and the duration scaling across the window.
reassignment · resource
A new dominant resource is chosen for every activity and the resource column is re-filled after the drift (keeping 80% dominance). In gradual mode the share of events that use the new mapping rises across the window. The before/after dominance maps are written to the metadata.
workload · resource
After the drift, workload_factor sets how many traces to add or remove
(1.0 = no change). Added traces are duplicates of existing ones with freshly drawn
timing, amount, resources and region; removals never drop the last instance of a variant. The
total trace count may change. Gradual mode spreads the
duplicates across the window.
duration · resource
The processing time of the given resources' later events is multiplied by factor
("these resources slow down / speed up"). resources may be "all", a
count (the first N resources), or an explicit list. Gradual mode ramps the
factor from 1 to its target across the window.
waiting_time · inter-case
The mean of the waiting gap between events shifts to mean after the drift — the
main driver of throughput time. A gap is the idle time between consecutive events of the same
case, and every activity's waiting distribution is updated to the new
(mean, variance), so all activities shift at once.
Gradual mode blends each gap from its old value toward the new mean across the
window.
amount · inter-case
Case amounts after the drift are drawn from a shifted distribution (mean,
variance). Gradual mode interpolates the mean and variance across
the window.
arrival_rate · inter-case
The mean inter-arrival gap changes after the drift, set by a new absolute
inter_arrival or a factor applied to the base. Cases arrive denser or
sparser. Gradual mode moves the mean across the window.
region · inter-case
A new dominant region is chosen and the region of later cases is re-filled (keeping 80% dominance). Gradual mode raises the share of cases using the new region across the window.
Output & metadata
An XES or CSV log and a readable metadata sidecar.
Each run writes two files next to output_path (the log suffix follows
format):
<name>.xes (or <name>.csv)
<log_name>_meta.md
Log columns
Events carry:
event:id,
case:concept:name,
concept:name,
start_timestamp,
time:timestamp,
event:duration_min,
org:resource
Each case carries case:amount and case:region. For XES the full
metadata is also embedded as a log-level rheon:metadata attribute.
Metadata sidecar
The <log_name>_meta.md file is the ground truth, in three sections:
- General parameters — the structural, temporal and attribute parameters of the run.
- Base distributions — the starting state: every activity's dominant resource and its duration and waiting distributions
(mean, var), plus the case-level amount distribution, inter-arrival mean and dominant region. - Drifts — each drift with its mode and drift point or window (as both a horizon fraction and an absolute timestamp), followed by exactly which distributions or assignments it changed, per activity / resource.