motif-rs
A high-performance matrix profile library for time series analysis, validated against stumpy.
The matrix profile stores the distance between every subsequence of a time series and its nearest neighbor, enabling discovery of motifs (recurring patterns), discords (anomalies), regime changes, and more.
Features
- Batch STOMP — diagonal traversal with O(1) QT recurrence updates per element
- Streaming STAMPI — incremental matrix profile with grow and egress (sliding window) modes
- AB-Join — cross-series comparison (find shared patterns between two time series)
- Top-k neighbors — store the k nearest neighbors per subsequence, not just the best
- Motif discovery — extract the top-k most repeated patterns
- Discord detection — find the top-k most anomalous subsequences
- FLUSS segmentation — detect regime changes via the Corrected Arc Curve
- Snippets — extract k representative subsequences that best summarize a time series
- MPdist — scalar distance between two time series based on matrix profile
- SCRUMP — approximate matrix profile via PreSCRIMP diagonal sampling
- Ostinato — consensus motif discovery across multiple time series
- STIMP — pan matrix profile across a range of window sizes
- Time series chains — discover evolving patterns via bidirectional nearest-neighbor links (ATSC/ALLC)
- MASS — fast z-normalized distance profile for a query subsequence
- Pattern matching — find all occurrences of a query pattern in a time series
- Multi-dimensional matrix profile (MSTUMP) — compute matrix profiles across multiple co-evolving time series
- Subspace selection — identify which dimensions best characterize a motif pair
- MDL dimensionality — find optimal number of dimensions via Minimum Description Length
- Multi-dimensional motifs (mmotifs) — discover motifs that span multiple dimensions with automatic subspace selection
- AAMP — non-normalized (absolute) Euclidean distance matrix profile
- P-norm — Minkowski p-norm distance (Manhattan, Euclidean, or arbitrary p≥1) for self-join, AB-join, and MPdist
- Configurable thresholds —
sigma_thresholdfor constant-subsequence detection (matches stumpy'sconfig.STUMPY_STDDEV_THRESHOLD) - Parallel computation — load-balanced diagonal partitioning via Rayon
- Left/right matrix profiles — directional nearest-neighbor distances
- Extensible metric trait —
DistanceMetrictrait with static dispatch via monomorphization
Performance
See validation/PERFORMANCE.md for detailed benchmark tables against stumpy across all features and scales.
Installation
Add to your Cargo.toml:
[]
= { = "https://github.com/sipemu/motif-rs" }
Quick Start
Batch Computation
use ;
let ts = vec!;
let engine = new;
let mp = engine.compute;
println!;
println!;
Motif and Discord Discovery
use ;
let ts: = .map.collect;
let engine = new;
let mp = engine.compute;
// Top-3 motifs (most repeated patterns)
let motifs = find_motifs;
for m in &motifs
// Top-3 discords (most anomalous subsequences)
let discords = find_discords;
for d in &discords
AB-Join (Cross-Series Comparison)
use ;
let ts_a: = .map.collect;
let ts_b: = .map.collect;
let engine = new;
let = engine.ab_join;
// join_a: for each A subsequence, its nearest neighbor in B
// join_b: for each B subsequence, its nearest neighbor in A
Regime Change Detection (FLUSS)
use ;
let ts: = .map.collect;
let engine = new;
let mp = engine.compute;
let seg = fluss; // detect 2 regime boundaries
println!;
// seg.cac contains the Corrected Arc Curve (low values = boundaries)
Snippets (Time Series Summarization)
use find_snippets;
let ts: = .map.collect;
let result = find_snippets; // m=50, k=3 snippets
for in result.indices.iter.enumerate
// result.regimes[j] = which snippet each position is closest to
Top-k Nearest Neighbors
use ;
let ts: = .map.collect;
let engine = new;
let topk = engine.compute_topk; // k=3 nearest neighbors
// topk.distances[i] = [d1, d2, d3] sorted ascending for subsequence i
AAMP (Non-normalized Distance)
use ;
// AAMP uses raw Euclidean distance — amplitude and offset matter
let ts: = .map.collect;
let engine = new;
let mp = engine.compute;
P-norm (Minkowski Distance)
use ;
let ts: = .map.collect;
let config = new;
// Manhattan distance (p=1)
let mp_manhattan = stomp_pnorm;
// Cubic norm (p=3)
let mp_cubic = stomp_pnorm;
// p=2.0 automatically delegates to the optimized AAMP path
let mp_euclidean = stomp_pnorm;
// P-norm AB-join
let ts_b: = .map.collect;
let = ab_join_pnorm;
// P-norm MPdist
let dist = mpdist_pnorm;
Also available via Engine methods: engine.compute_pnorm(&ts, p), engine.ab_join_pnorm(&ts_a, &ts_b, p), and engine.mpdist_pnorm(&ts_a, &ts_b, p, percentage).
MPdist (Time Series Distance)
use ;
let ts_a: = .map.collect;
let ts_b: = .map.collect;
// Scalar distance between two time series (robust to length differences)
let dist = ;
println!;
SCRUMP (Approximate Matrix Profile)
use ;
let ts: = .map.collect;
let engine = new;
// Approximate profile using 25% of diagonals (faster, less accurate)
let mp_approx = engine.scrump;
// percentage=1.0 computes the exact profile (same as engine.compute)
let mp_exact = engine.scrump;
Ostinato (Consensus Motif)
use ;
let ts1: = .map.collect;
let ts2: = .map.collect;
let ts3: = .map.collect;
// Find the motif that appears across all time series
let result = ;
println!;
STIMP (Pan Matrix Profile)
use ;
let ts: = .map.collect;
// Compute matrix profiles across window sizes 10 to 50
let pan = ;
for in pan.windows.iter.enumerate
Time Series Chains (Evolving Patterns)
use ;
let ts: = .map.collect;
let engine = new;
let mp = engine.compute;
// Discover all chains (evolving patterns linked by bidirectional nearest neighbors)
let result = allc;
println!;
println!;
// Or trace a single chain from a specific anchor index
let chain = atsc;
println!;
Pattern Matching (MASS / match)
use ;
let ts: = .map.collect;
let query = &ts; // extract a 30-point query
// MASS: compute the z-normalized distance profile for the query
let distance_profile = mass;
println!;
// find_matches: extract all occurrences below a threshold
let matches = find_matches; // auto threshold + default exclusion zone
for m in &matches
// With explicit threshold and exclusion zone
let matches = find_matches;
Multi-Dimensional Matrix Profile (MSTUMP)
use ;
// Three co-evolving time series (e.g., x/y/z accelerometer axes)
let ts0: = .map.collect;
let ts1: = .map.collect;
let ts2: = .map.collect;
let ts_refs: = vec!;
let m = 20;
// Compute multi-dimensional matrix profile
let profile = mstump;
// profile.profile[k][j] = best (k+1)-dimensional average distance at position j
// profile.profile_index[k][j] = nearest neighbor index
// Find which dimensions matter most for a motif pair
let idx = 0; // query position
let nn = profile.profile_index; // its nearest neighbor
let dims = subspace; // best 2 dimensions
println!;
// MDL: find optimal number of dimensions
let d = ts_refs.len;
let subseq_idx: = vec!;
let nn_idx: = .map.collect;
let = mdl;
let optimal_k = bit_sizes.iter.enumerate
.min_by
.map.unwrap;
println!;
// Discover multi-dimensional motifs automatically
let motifs = mmotifs;
for motif in &motifs
Streaming (Grow Mode)
use ;
let initial_ts: = .map.collect;
let engine = new;
let mut stream = engine.streaming; // grow mode
stream.update;
stream.update;
let mp = stream.profile;
Streaming (Egress Mode)
use ;
let initial_ts: = .map.collect;
let engine = new;
// Fixed-size window: oldest points dropped as new ones arrive
let mut stream = engine.streaming;
stream.update;
Output
MatrixProfile contains six arrays, each of length n - m + 1:
| Field | Description |
|---|---|
profile |
Nearest-neighbor distance for each subsequence |
profile_index |
Index of the nearest neighbor |
left_profile |
Distance to nearest neighbor with a smaller index |
left_profile_index |
Index of the left nearest neighbor |
right_profile |
Distance to nearest neighbor with a larger index |
right_profile_index |
Index of the right nearest neighbor |
Cargo Features
| Feature | Default | Description |
|---|---|---|
parallel |
yes | Parallel diagonal computation via Rayon |
# With parallelism (default)
# Without parallelism
Building
For best performance, ensure .cargo/config.toml targets your CPU:
[]
= ["-C", "target-cpu=native"]
Examples
Self-contained examples covering each feature (see examples/):
Validation
All features are validated against stumpy via golden integration tests. Each test loads reference data generated by stumpy and compares output at epsilon < 1e-6:
To regenerate golden data or run the stumpy comparison benchmarks:
Dependencies
License
MIT License — see LICENSE.
For dependency licenses and academic attribution, see THIRD_PARTY_NOTICES.md.