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}