Skip to main content

wasm4pm_compat/
process_cube.rs

1//! # Process Cube
2//!
3//! Typed shapes for the Process Cube framework (van der Aalst, 2013).
4//!
5//! ## What this is
6//!
7//! The dimensional structure for multi-perspective process comparison:
8//! cube dimensions, slice projections, and perspective witnesses. The process
9//! cube is a multi-dimensional framework for comparing process behavior across
10//! different slices of an event log — e.g., slicing by resource, time window,
11//! or case attribute and comparing the resulting sub-logs.
12//!
13//! ## What this is not
14//!
15//! The cube computation engine. Slicing, dicing, and cross-cell comparison
16//! graduate to `wasm4pm`. This module carries the shapes those operations
17//! produce and consume. No sub-log extraction, no model discovery per cell,
18//! no cross-cell conformance comparison belongs here.
19//!
20//! ## Paper authority
21//!
22//! van der Aalst, W.M.P. (2013). *Process Cubes: Slicing, Dicing, Rolling Up
23//! and Drilling Down Event Data for Process Mining.* In: Proceedings of the
24//! 1st Asia Pacific Conference on Business Process Management, LNBIP 159.
25//!
26//! ## Graduate to `wasm4pm`
27//!
28//! When you need to *run* the cube — extract sub-logs per cell, apply discovery
29//! per slice, or compute cross-cell conformance distances — graduate to `wasm4pm`.
30//! The shapes here travel with the evidence into the engine.
31
32use core::marker::PhantomData;
33
34/// A named dimension in the process cube (e.g., resource, time, activity).
35///
36/// ## What this is
37///
38/// A zero-cost compile-time name for one axis of a process cube. `NAME` is a
39/// `&'static str` const parameter, so `CubeDimension<"resource">` and
40/// `CubeDimension<"time">` are **different types** — the compiler rejects
41/// a function expecting one where the other is passed.
42///
43/// ## What this is not
44///
45/// Not a runtime dimension value or an enumerated attribute bag. The runtime
46/// attribute value lives in [`crate::process_cube::CubeSlice::value`]. This is the axis label only.
47///
48/// ## Graduate to `wasm4pm`
49///
50/// Actual log partitioning by this dimension requires the `wasm4pm` engine.
51///
52/// # Examples
53///
54/// ```ignore
55/// use wasm4pm_compat::process_cube::CubeDimension;
56/// let _resource_dim: CubeDimension<"resource"> = CubeDimension;
57/// let _time_dim: CubeDimension<"time"> = CubeDimension;
58/// ```
59pub struct CubeDimension<const NAME: &'static str>;
60
61/// A slice through the process cube along a specific dimension and value.
62///
63/// ## What this is
64///
65/// The typed shape of a single dimension-value binding. `D` names the
66/// dimension; `V` carries the concrete value for that dimension (e.g.,
67/// a resource name or a time-bucket tag). Together they identify one
68/// "column" through the cube along the named axis.
69///
70/// ## What this is not
71///
72/// Not the slice operation — partitioning an event log by this slice requires
73/// the `wasm4pm` engine.
74///
75/// ## Graduate to `wasm4pm`
76///
77/// Log partitioning and sub-log extraction graduate to `wasm4pm`.
78///
79/// # Examples
80///
81/// ```ignore
82/// use wasm4pm_compat::process_cube::{CubeDimension, CubeSlice};
83/// use core::marker::PhantomData;
84///
85/// let slice: CubeSlice<CubeDimension<"resource">, &str> = CubeSlice {
86///     dimension: PhantomData,
87///     value: "Alice",
88/// };
89/// let _ = slice.value;
90/// ```
91pub struct CubeSlice<D, V> {
92    /// Phantom binding to the dimension type.
93    pub dimension: PhantomData<D>,
94    /// The concrete value for this slice along the dimension.
95    pub value: V,
96}
97
98/// A cell in the process cube — the intersection of multiple dimension slices.
99///
100/// ## What this is
101///
102/// The structural shape of a single process-cube cell. `DIMS` is the number of
103/// dimensions this cell is indexed by. Each cell corresponds to a sub-log
104/// resulting from applying a conjunction of dimension-slice filters; the
105/// sub-log extraction is an engine concern.
106///
107/// ## What this is not
108///
109/// Not the sub-log itself, not the process model discovered from the cell's
110/// sub-log. Those graduate to `wasm4pm`.
111///
112/// ## Graduate to `wasm4pm`
113///
114/// Sub-log extraction, model discovery per cell, and cell-level conformance
115/// all graduate to `wasm4pm`.
116///
117/// # Examples
118///
119/// ```ignore
120/// use wasm4pm_compat::process_cube::CubeCell;
121/// let cell: CubeCell<3> = CubeCell::new();
122/// ```
123pub struct CubeCell<const DIMS: usize> {
124    _private: (),
125}
126
127impl<const DIMS: usize> CubeCell<DIMS> {
128    /// Construct a new `CubeCell` shape marker.
129    ///
130    /// This is a structure-only constructor — no sub-log is extracted.
131    ///
132    /// # Examples
133    ///
134    /// ```ignore
135    /// use wasm4pm_compat::process_cube::CubeCell;
136    /// let cell: CubeCell<2> = CubeCell::new();
137    /// ```
138    #[inline]
139    pub fn new() -> Self {
140        Self { _private: () }
141    }
142
143    /// The number of dimensions this cell is indexed along.
144    ///
145    /// # Examples
146    ///
147    /// ```ignore
148    /// use wasm4pm_compat::process_cube::CubeCell;
149    /// let cell: CubeCell<3> = CubeCell::new();
150    /// assert_eq!(cell.dim_count(), 3);
151    /// ```
152    #[inline]
153    pub const fn dim_count(&self) -> usize {
154        DIMS
155    }
156}
157
158impl<const DIMS: usize> Default for CubeCell<DIMS> {
159    fn default() -> Self {
160        Self::new()
161    }
162}
163
164/// Witness that a projection was performed along a named set of cube dimensions.
165///
166/// ## What this is
167///
168/// A zero-cost shape recording that a projection reduced FROM_DIMS original
169/// cube dimensions down to TO_DIMS projected dimensions. This is the receipt
170/// shape for a projection step — it names that a projection happened and what
171/// the arity reduction was.
172///
173/// ## What this is not
174///
175/// Not the projection computation. The engine produces this shape; this crate
176/// only defines the shape and validates its structural invariants.
177///
178/// ## Graduate to `wasm4pm`
179///
180/// The actual projection computation (sub-log extraction and merging along the
181/// dropped dimensions) graduates to `wasm4pm`.
182///
183/// # Examples
184///
185/// ```ignore
186/// use wasm4pm_compat::process_cube::CubeProjectionWitness;
187/// let _w: CubeProjectionWitness<3, 2> = CubeProjectionWitness::new();
188/// ```
189pub struct CubeProjectionWitness<const FROM_DIMS: usize, const TO_DIMS: usize> {
190    _private: (),
191}
192
193impl<const FROM_DIMS: usize, const TO_DIMS: usize> CubeProjectionWitness<FROM_DIMS, TO_DIMS> {
194    /// Construct a new `CubeProjectionWitness` shape marker.
195    ///
196    /// # Examples
197    ///
198    /// ```ignore
199    /// use wasm4pm_compat::process_cube::CubeProjectionWitness;
200    /// let w: CubeProjectionWitness<4, 2> = CubeProjectionWitness::new();
201    /// assert_eq!(w.from_dims(), 4);
202    /// assert_eq!(w.to_dims(), 2);
203    /// ```
204    #[inline]
205    pub fn new() -> Self {
206        Self { _private: () }
207    }
208
209    /// The number of dimensions before the projection.
210    #[inline]
211    pub const fn from_dims(&self) -> usize {
212        FROM_DIMS
213    }
214
215    /// The number of dimensions after the projection.
216    #[inline]
217    pub const fn to_dims(&self) -> usize {
218        TO_DIMS
219    }
220}
221
222impl<const FROM_DIMS: usize, const TO_DIMS: usize> Default
223    for CubeProjectionWitness<FROM_DIMS, TO_DIMS>
224{
225    fn default() -> Self {
226        Self::new()
227    }
228}
229
230/// The process cube metamodel — typed shape without execution.
231///
232/// ## What this is
233///
234/// The top-level structure for the process cube: a `Log` type parameter
235/// represents the kind of event log the cube is built over, and `DIMENSIONS`
236/// is the count of named dimensions the cube is indexed by at this usage site.
237///
238/// This is structure only: holding a `ProcessCube<Log, N>` means you have
239/// declared that you intend to analyze `Log` across N dimensions. The actual
240/// cube computation (sub-log extraction, cell discovery, cross-cell comparison)
241/// graduates to `wasm4pm`.
242///
243/// ## What this is not
244///
245/// Not a runtime cube. No sub-log extraction, no model per cell, no comparison
246/// engine is present here.
247///
248/// ## Graduate to `wasm4pm`
249///
250/// All computation on this shape graduates to `wasm4pm`.
251///
252/// # Examples
253///
254/// ```ignore
255/// use wasm4pm_compat::process_cube::ProcessCube;
256/// use core::marker::PhantomData;
257///
258/// struct MyLog;
259/// let _cube: ProcessCube<MyLog, 3> = ProcessCube { log: PhantomData };
260/// ```
261pub struct ProcessCube<Log, const DIMENSIONS: usize> {
262    /// Phantom binding to the log type the cube is built over.
263    pub log: PhantomData<Log>,
264}
265
266impl<Log, const DIMENSIONS: usize> ProcessCube<Log, DIMENSIONS> {
267    /// Construct a new `ProcessCube` shape marker.
268    ///
269    /// # Examples
270    ///
271    /// ```ignore
272    /// use wasm4pm_compat::process_cube::ProcessCube;
273    /// struct MyLog;
274    /// let cube: ProcessCube<MyLog, 2> = ProcessCube::new();
275    /// assert_eq!(cube.dimension_count(), 2);
276    /// ```
277    #[inline]
278    pub fn new() -> Self {
279        Self { log: PhantomData }
280    }
281
282    /// The number of dimensions this cube is indexed by.
283    ///
284    /// # Examples
285    ///
286    /// ```ignore
287    /// use wasm4pm_compat::process_cube::ProcessCube;
288    /// struct MyLog;
289    /// let cube: ProcessCube<MyLog, 4> = ProcessCube::new();
290    /// assert_eq!(cube.dimension_count(), 4);
291    /// ```
292    #[inline]
293    pub const fn dimension_count(&self) -> usize {
294        DIMENSIONS
295    }
296}
297
298impl<Log, const DIMENSIONS: usize> Default for ProcessCube<Log, DIMENSIONS> {
299    fn default() -> Self {
300        Self::new()
301    }
302}
303
304/// Dimension kinds from the process cube framework.
305///
306/// ## What this is
307///
308/// An enumeration of the *semantic kinds* of dimensions that appear in the
309/// process cube framework. These are the standard analytical axes used to
310/// partition and compare process behavior.
311///
312/// ## What this is not
313///
314/// Not a runtime filter or partitioning key. The partitioning logic graduates
315/// to `wasm4pm`.
316///
317/// # Examples
318///
319/// ```ignore
320/// use wasm4pm_compat::process_cube::CubeDimensionKind;
321/// let kind = CubeDimensionKind::Resource;
322/// assert_eq!(format!("{}", kind), "resource");
323/// ```
324#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
325pub enum CubeDimensionKind {
326    /// Activity dimension — slices by event/activity name.
327    Activity,
328    /// Resource dimension — slices by resource (performer/actor).
329    Resource,
330    /// Time dimension — slices by time window, period, or granularity.
331    Time,
332    /// Data attribute dimension — slices by a named case or event attribute.
333    DataAttribute,
334    /// Object type dimension — slices by OCEL object type (OC logs only).
335    ObjectType,
336    /// Case attribute dimension — slices by a case-level attribute value.
337    CaseAttribute,
338}
339
340impl core::fmt::Display for CubeDimensionKind {
341    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342        match self {
343            Self::Activity => write!(f, "activity"),
344            Self::Resource => write!(f, "resource"),
345            Self::Time => write!(f, "time"),
346            Self::DataAttribute => write!(f, "data-attribute"),
347            Self::ObjectType => write!(f, "object-type"),
348            Self::CaseAttribute => write!(f, "case-attribute"),
349        }
350    }
351}
352
353/// A comparison between two cube cells — structure for the result shape.
354///
355/// ## What this is
356///
357/// The structural shape of a cross-cell comparison result. Two cells at the
358/// same number of dimensions `DIM_COUNT` are named and held together. This
359/// shape is the receipt that a comparison was declared between these cells.
360///
361/// ## What this is not
362///
363/// Not the comparison computation. Conformance checking, model discovery, or
364/// cross-cell difference analysis all graduate to `wasm4pm`.
365///
366/// ## Graduate to `wasm4pm`
367///
368/// The actual comparison engine (fitness difference, model distance, variant
369/// overlap) graduates to `wasm4pm`.
370///
371/// # Examples
372///
373/// ```ignore
374/// use wasm4pm_compat::process_cube::{CubeCell, CellComparison};
375/// let cmp = CellComparison {
376///     cell_a: CubeCell::<2>::new(),
377///     cell_b: CubeCell::<2>::new(),
378/// };
379/// assert_eq!(cmp.cell_a.dim_count(), 2);
380/// ```
381pub struct CellComparison<const DIM_COUNT: usize> {
382    /// The first cell in the comparison.
383    pub cell_a: CubeCell<DIM_COUNT>,
384    /// The second cell in the comparison.
385    pub cell_b: CubeCell<DIM_COUNT>,
386}