Both techniques work on a SlimLinkedOCEL, the linked form of an OCEL that indexes all event-to-object relations. The event data types page explains the difference to OCEL.
Object-centric directly-follows graph
An object-centric DFG (OC-DFG) has one directly-follows graph per object type. For each type, every object becomes a trace with the events it is involved in.
import r4pm
ocel = r4pm.import_item("SlimLinkedOCEL", "data/order-management.xml.gz")
# One directly-follows graph per object type, each from the log flattened on that type.
ocdfg = r4pm.bindings.discover_dfg_from_ocel(ocel)
for object_type, dfg in sorted(ocdfg["object_type_to_dfg"].items()):
print(f"{object_type:<10} {len(dfg['activities']):>2} activities, {len(dfg['directly_follows_relations']):>3} edges")customers 2 activities, 4 edges
employees 8 activities, 34 edges
items 11 activities, 56 edges
orders 4 activities, 5 edges
packages 4 activities, 5 edges
products 11 activities, 121 edgesOrders follow a short, regular life cycle (4 activities, 5 edges). Products are involved in every activity and show the most varied behavior. Reference: discover_dfg_from_ocel.
OC-DECLARE constraints
OC-DECLARE describes behavior as constraints between activities, scoped by the objects involved. A constraint such as EF(place order, create package, Each(items), ...) reads: for each item involved in a place order event, a create package event involving that item must occur afterwards. Discovery finds every constraint that holds for all but a noise_threshold share of events.
import r4pm
ocel = r4pm.import_item("SlimLinkedOCEL", "data/order-management.xml.gz")
# These are the defaults. A dict passed here must contain every field.
options = {
"noise_threshold": 0.2, # share of events allowed to violate a constraint
"o2o_mode": "None",
"acts_to_use": None, # None: all activities
"counts_for_generation": [1, None],
"counts_for_filter": [1, 20],
"reduction": "None",
"refinement": False,
"considered_arrow_types": ["AS", "EF", "EP", "DF", "DP"],
}
arcs = r4pm.bindings.discover_oc_declare(ocel, options)
print(len(arcs), "constraints")
def describe(arc):
"""TYPE(from, to, Each(...), All(...), Any(...)), as the Rust example prints it."""
involvement = [
f"{kind.capitalize()}({','.join(o['object_type'] for o in arc['label'][kind])})"
for kind in ("each", "all", "any")
if arc["label"][kind]
]
return f"{arc['arc_type']}({arc['from']}, {arc['to']}, {', '.join(involvement)})"
# Strongest first, ties by name, so the output is stable.
scored = sorted(
((r4pm.bindings.oc_declare_conformance(ocel, arc), describe(arc)) for arc in arcs),
key=lambda pair: (-pair[0], pair[1]),
)
for share, text in scored[:5]:
print(f"{share:.1%} {text}")116 constraints
100.0% AS(confirm order, pick item, Each(items), Any(products))
100.0% AS(confirm order, pick item, Each(products), Any(items))
100.0% AS(create package, confirm order, Each(items), Any(products))
100.0% AS(create package, pay order, Each(items), Any(products))
100.0% AS(create package, pay order, Each(products), Any(items))The discovery order of constraints can differ between runs, so the snippets sort them by score and then by name. oc_declare_conformance returns the share of events that satisfy a constraint, so discovered constraints score at least 80% with the default noise threshold of 0.2.
| Arc type | Meaning (from the crate documentation) |
|---|---|
AS | Association: no temporal restriction |
EF | Eventually-follows: the target occurs after the source event |
EP | Eventually-precedes: the target occurs before the source event |
DF | Directly-follows: the target occurs directly after the source event, among events that involve all required objects |
DP | Directly-precedes: the target occurs directly before the source event, among events that involve all required objects |
Each constraint also carries counts (the minimum and maximum number of matching target events) and the object involvement. Each(t) requires the target events for every object of type t separately, Any(t) for at least one of them, and All(t) for target events that involve all of them. References: discover_oc_declare, oc_declare_conformance.
Back to case-centric
Some questions are easier with a single case notion. Filter and flatten turns the OCEL into an event log per object type, ready for Alpha+++ and alignments.