Struktura
Predict failure before it happens.
Struktura is a zero-dependency Rust crate for universal anomaly detection. It uses Detrended Fluctuation Analysis (DFA) to measure the structural health of any time series — bearings, heartbeats, spacecraft telemetry, drone motors, DNA sequences.
One algorithm. Any signal. No training data. No domain knowledge.
Why Struktura?
Current monitoring tools watch for threshold violations: “is the temperature above 80C?” But structural degradation changes the pattern of a signal before it changes the amplitude. A bearing that’s starting to wear changes its vibration structure weeks before it exceeds any alarm threshold.
Struktura catches what threshold monitors miss.
Install
cargo add struktura
Quick Start
Analyze any signal
#![allow(unused)]
fn main() {
use struktura::{analyze, health_check, HealthVerdict};
// Load your time series data
let data: Vec<f64> = load_csv("vibration.csv");
// Analyze structural health
let law = analyze(&data);
println!("DFA alpha: {:.3}", law.dfa.alpha);
println!("R-squared: {:.4}", law.dfa.r_squared);
println!("Quality: {:?}", law.quality);
// Compare against a known healthy baseline
let verdict = health_check(&law, 0.389); // baseline from calibration
match verdict {
HealthVerdict::Healthy => println!("System is healthy"),
HealthVerdict::Watch => println!("Minor structural shift detected"),
HealthVerdict::Warning => println!("Significant structural change"),
HealthVerdict::Critical => println!("CRITICAL: Major structural departure"),
}
}
What the numbers mean
- DFA alpha near 0.5: uncorrelated noise — no exploitable structure
- DFA alpha 0.5-1.0: long-range correlated — healthy complex system
- Alpha shift > 0.08 from baseline: something is changing
- R-squared > 0.7: the measurement is reliable
- R-squared < 0.7: ABSTAIN — not enough structure to diagnose
How DFA Works
Detrended Fluctuation Analysis measures long-range correlation in a time series.
The algorithm in 4 steps
- Profile: compute the cumulative sum of deviations from the mean
- Box: divide the profile into non-overlapping boxes of size s
- Detrend: fit and subtract a linear trend within each box
- Scale: measure the root-mean-square residual F(s) at each box size
The scaling exponent alpha is the slope of log F(s) vs log s.
What alpha means
| Alpha range | Interpretation |
|---|---|
| ~0.5 | White noise (uncorrelated) |
| 0.5 - 1.0 | Long-range correlated (healthy complexity) |
| ~1.0 | 1/f noise (pink noise) |
| > 1.0 | Non-stationary / trend-dominated |
Why it works for anomaly detection
Healthy complex systems maintain a characteristic alpha. When the system degrades, alpha shifts — often before any amplitude-based monitor fires. The structure changes first.
References
- Peng et al., “Mosaic organization of DNA nucleotide sequences,” Physical Review E 49(2), 1994.
- Peng et al., “Quantification of scaling exponents,” Chaos 5(1), 1995.
- Goldberger et al., “Fractal dynamics in physiology,” PNAS 99(suppl 1), 2002.
Bearing Fault Detection
Struktura detects bearing faults from raw vibration data with zero domain knowledge.
CWRU Bearing Data Center results
Using 12kHz vibration data from Case Western Reserve University:
| Condition | DFA alpha | Shift | Verdict |
|---|---|---|---|
| Normal (97.mat) | 0.389 | — | Healthy |
| Inner race fault (105.mat) | 0.146 | -0.243 | Critical |
| Outer race fault (130.mat) | 0.247 | -0.142 | Critical |
| Ball fault (118.mat) | 0.275 | -0.114 | Warning |
All three fault types detected. The shift magnitude correlates with fault severity.
How to use it
#![allow(unused)]
fn main() {
use struktura::{analyze, health_check};
let normal = analyze(&normal_vibration);
let baseline = normal.dfa.alpha; // establish during healthy operation
// Later, during monitoring:
let current = analyze(¤t_vibration);
let verdict = health_check(¤t, baseline);
// verdict == HealthVerdict::Critical if bearing is degrading
}
Why DFA catches what FFT misses
FFT detects frequency changes. But early bearing degradation changes the correlation structure of the vibration — the way peaks relate to each other over time — before it introduces new frequency components. DFA measures this correlation structure directly.
Cross-Domain Proof
The same algorithm works on completely different signal types.
| Domain | Signal | N | DFA alpha | R-squared |
|---|---|---|---|---|
| Spacecraft | Queue depth | 500 | 0.593 | 0.789 |
| Bearings | CWRU 12kHz vibration | 243,938 | 0.389 | 0.872 |
| Genome | Human chr1 GC% | 8,000 | 0.909 | 0.991 |
| Cardiac | RR intervals | 2,048 | 0.695 | 0.985 |
Genome: 8 chromosomes at R-squared > 0.99
| Chromosome | DFA alpha | R-squared |
|---|---|---|
| chr1 | 0.909 | 0.991 |
| chr2 | 0.699 | 0.991 |
| chr3 | 0.659 | 0.998 |
| chr4 | 0.894 | 0.997 |
| chr5 | 0.824 | 0.994 |
| chr6 | 0.822 | 0.998 |
| chr7 | 0.862 | 0.997 |
| chr8 | 0.816 | 0.995 |
Shuffle control
To prove the structure is real and not an artifact, we permute each signal and re-run DFA. If shuffling destroys the alpha (moves it toward 0.5), the original structure was real.
This is the empirical standard: the crate never claims structure it cannot prove.
API Reference
Core functions
dfa(values: &[f64]) -> DfaResult
Compute the DFA scaling exponent. Returns alpha and R-squared.
acr(values: &[f64]) -> DfaResult
Compute autocorrelation decay exponent.
analyze(values: &[f64]) -> StructuralLaw
Full structural analysis: DFA + ACR + statistics.
health_check(law: &StructuralLaw, baseline: f64) -> HealthVerdict
Compare current DFA alpha against a baseline.
Types
DfaResult { alpha: f64, r_squared: f64 }
StructuralLaw { hurst, dfa, acr, mean, std_dev, kurtosis, p99, max, n, quality }
LawQuality — Exact, Strong, Good, Approx, Abstain, Insufficient
HealthVerdict — Healthy, Watch, Warning, Critical
Thresholds
| Shift from baseline | Verdict |
|---|---|
| < 0.03 | Healthy |
| 0.03 - 0.08 | Watch |
| 0.08 - 0.15 | Warning |
| > 0.15 | Critical |