anomaly detection that actually works. not thresholds. not ML. not vibes. built while contributing to NASA F´ flight software.
DFA (Detrended Fluctuation Analysis) gives you one number that tells you if a signal's structure changed. works on literally anything with a time axis. spacecraft, bearings, financial data, heartbeats, DNA, text rhythm. you name it.
cargo install struktura
struktura demo # 🔧 bearing fault detection (real CWRU data)
struktura voyager # 🚀 Voyager 1 AACS anomaly (real NASA data)
struktura spacecraft # 🛰️ multi-channel spacecraft health
struktura text novel.txt # 📖 writing rhythm analysis
🚀 10 second demo
$ struktura voyager
VOYAGER 1 STRUCTURAL HEALTH ANALYSIS
====================================================================
2021 (healthy) alpha=0.989 R²=0.9869
2022 (anomaly) alpha=0.801 R²=0.9681
shift=-0.187 CRITICAL
the magnetic field's fractal structure changed during the anomaly.
detectable from public NASA data, zero training, zero ML.
real data. real spacecraft. one rust function caught it. 🎯
🤖 it flies missions autonomously
struktura mission — 24,000 samples, three scripted disasters, zero human calls:
t= 4004 ALARM RepeatedValue on temp (class: stuck)
t= 4004 QUARANTINE temp declared dead -> virtual mode
t= 10069 ALARM LevelShift on pointing (class: regime_shift)
t= 10769 RECALIBRATED guard passed -> new normal accepted
t= 19173 ALARM LevelShift on soc (class: drift_confirmed)
dead sensor? quarantined in 4 samples, its reading reconstructed from the other channels' physics (R² > 0.9). environment permanently changed? it re-learns "normal" through a guarded candidate — and rolls back if the new regime is actually a fault trying to sneak in. drift disguised as a regime change? refused. every decision above was made by the monitor alone.
🧬 it evolved its own detectors
struktura evolve — an adversarial RED/BLUE loop: RED invents faults the
monitor misses, BLUE synthesizes new detector legs from a grammar, accepted
only with ZERO false alarms on clean data:
| generation | fault coverage | detector legs |
|---|---|---|
| 1 | 71% | 2 |
| 4 | 89% | 5 |
| 9 | 97% | 6 |
the machine independently invented variance monitors, residual-trend monitors, and derivative-volatility monitors — detector classes nobody hand-coded. parameter tuning alone plateaued at 75%; structural synthesis broke through.
🛡️ the receipts
- 7/7 telemetry fault taxonomy detected by the hybrid monitor (packet loss, spike, stuck, drift, regime shift, mixed, correlation change) — DFA catches structural faults; residual-based legs catch value faults. neither alone covers all 7; the combination does. self-calibrated thresholds, 0 false alarms across 200,000 clean samples
- NASA IMS bearing run-to-failure: structural warning ~2 hours before failure (recording 970 of 984, α spikes from 0.17 to 0.53)
- compiles to flight-ready C99:
struktura generate-hybridbakes your calibration into a dependency-free monitor that passes-Wall -Werrorand detects a stuck sensor in its own self-test - every claim above → one command: see REPRODUCIBILITY.md
⚡ speed (benchmarked, not guessed)
| signal size | struktura (rust) | python nolds | speedup |
|---|---|---|---|
| 4,096 pts | 0.24 ms | ~20 ms | 85x |
| 16,384 pts | 0.93 ms | ~80 ms | 86x |
| 65,536 pts | 2.89 ms | ~325 ms | 112x |
at 1Hz spacecraft telemetry: 0.24ms per analysis = 4,000 channels on one core. yeah.
reproduce it yourself: cargo run --release --example speed_bench
🏆 head-to-head: struktura vs alternatives
tested on real datasets against ankane/AnomalyDetection.rs (STL decomposition) and classic 3σ threshold:
| dataset | struktura | ankane (STL) | threshold (3σ) |
|---|---|---|---|
| IMS bearing failure (NASA) | YES, 46μs | YES, 696μs | YES, 0μs |
| Voyager heliopause (NASA) | YES, 570μs | no | YES, 0μs |
| synthetic correlation shift | no | no | no |
struktura catches the Voyager structural shift that ankane completely misses — at 100x less latency. on the synthetic correlation change, nobody detects via simple API (that's the honest frontier we're working on). threshold is fastest but only catches amplitude anomalies, not structural changes.
different tools for different fault types. DFA detects when the STRUCTURE of a signal changes — not when values go out of bounds. ankane/threshold catch amplitude outliers. use both.
reproduce: cargo run --release --example comparison
🔧 as a library
use ;
let law = analyze;
let verdict = health_check;
// Healthy | Watch | Warning | Critical
streaming monitor for production:
use BaselineTracker;
let mut monitor = new;
for sample in telemetry_stream
spacecraft-specific stuff:
use ;
let mut rwa = new;
// push samples, get verdicts
🎯 what it detects (all verified, all real data)
| domain | signal | healthy α | fault α | shift | verdict |
|---|---|---|---|---|---|
| 🔧 bearings | CWRU 12kHz vibration | 0.689 | 0.183 | -0.506 | CRITICAL |
| 🚀 spacecraft | Voyager 1 magnetometer | 0.989 | 0.801 | -0.187 | CRITICAL |
| 📖 text | Austen vs shuffled | 0.749 | 0.572 | -0.177 | detected |
| 🧬 genome | Human chr1 GC% | 0.909 | R²=0.991 | ||
| ❤️ cardiac | HRV RR intervals | 0.695 | R²=0.985 |
every number from an actual run. reproduce with struktura demo / struktura voyager.
see USE_CASES.md for the full list with citations.
🪐 mars rover anomaly detection (NASA SMAP/MSL)
tested on the real NASA SMAP/MSL telemetry benchmark (55 labeled anomaly channels from Mars rovers + soil moisture satellite). zero training, zero tuning.
$ struktura smap
NASA SMAP/MSL — zero-training DFA baseline
channels: 55 · anomalies: 69 labeled
F1 = 0.755 · precision = 0.82 · recall = 0.70
F1 0.755 isn't SOTA (supervised models hit ~0.85+), but this is with literally zero training and one statistical test. honest baseline, not hype.
📖 text analysis
DFA measures the fractal rhythm of writing. sentence lengths in human prose have long-range correlations that disappear when you shuffle them.
$ struktura text pride_and_prejudice.txt shuffled.txt mechanical.txt
Jane Austen (original) α=0.749 STRONG RHYTHM (human literary)
Austen (shuffled) α=0.572 MODERATE RHYTHM
Mechanical uniform α=0.525 UNIFORM/MECHANICAL
same sentences, different order. α drops from 0.749 to 0.572. DFA catches sequential structure, not just statistics. pretty cool right?
🛰️ spacecraft health monitoring
built-in support for reaction wheels, magnetometers, batteries, thermal sensors, solar arrays, gyroscopes.
$ struktura spacecraft
[RWA:RWA_current] alpha=0.902 baseline=1.333 shift=-0.431 CRITICAL
[BAT:BAT_voltage] alpha=1.994 baseline=1.978 shift=+0.017 HEALTHY
[THM:THM_panel_A] alpha=1.981 baseline=1.960 shift=+0.021 HEALTHY
[MAG:MAG_B_total] alpha=0.985 baseline=0.914 shift=+0.070 WATCH
no_std compatible. runs on embedded flight computers. zero heap on the hot path via dfa_into().
🏗️ flight software codegen
generate complete monitoring apps for NASA flight frameworks:
struktura generate --cfs --db channels.json -o dfa_cfs_app/
struktura generate --fprime --db channels.json -o dfa_fprime_component/
struktura generate --ros --db channels.json -o dfa_ros_node/
compatible with nasa/ogma variable database format.
🧠 how DFA works
DFA (Peng et al., Physical Review E, 1994, 3000+ citations) measures long-range correlation:
- compute the cumulative profile (running sum minus mean)
- divide into boxes, detrend each with a linear fit
- measure residual fluctuation vs box size
- slope in log-log space = α (the scaling exponent)
| α | what it means |
|---|---|
| ~0.5 | random noise (no structure) |
| 0.5-1.0 | persistent correlations (healthy structure) |
| α shifts | something changed. go look. |
the crate reports R² alongside every α. if R² < 0.3, quality = Abstain. it never bluffs.
🧬 autonomous evolution (RED/BLUE)
the detection policy evolves itself. RED probes for faults the current config misses, BLUE mutates the policy and only keeps improvements that raise zero false alarms on clean data.
$ struktura redblue
round 1: coverage 60.0% → round 6: coverage 92.0%
dfa_persist 5→2, roll_persist 10→7, horizon 1M→148K
zero clean alarms on every acceptance
it finds its own blind spots and fixes them. no human tuning needed.
🎮 full command list
38 commands. here are the highlights:
| command | what it does |
|---|---|
demo |
bearing fault detection on real CWRU data |
voyager |
Voyager 1 AACS anomaly detection |
smap |
NASA SMAP/MSL Mars rover benchmark |
spacecraft |
multi-channel spacecraft health monitor |
scan <file> |
auto-classify + show trend in one shot |
watch <file> |
live monitoring with auto-refresh |
text <file> |
writing rhythm analysis (human vs AI) |
market <file> |
financial regime detection |
genome <file> |
DNA sequence structure |
rhythm <file> |
event timing (commits, heartbeats) |
fingerprint <file> |
structural DNA of a signal |
redblue |
autonomous fault-coverage evolution |
evolve |
generational policy optimization |
mission |
full autonomous monitoring mission |
guard <file> |
prognosis: when will this cross the threshold? |
when <file> |
time-to-failure estimation |
batch *.csv |
CI/CD: analyze many files, JSON output |
alert <cmd> |
exit-code monitoring for cron/systemd |
self-test |
verify all claims against real data |
nasa |
run all NASA benchmarks |
| pipe | stream DFA from stdin (Prometheus, MQTT, tail) |
run struktura --help for the full list.
🐳 docker / python / devops
# docker — zero install
&&
# python — 85x faster than nolds
&&
# pipe anything through DFA
|
|
# cron alert with slack webhook
see examples/devops_integration.sh for more.
⚠️ gotchas
stuff to know before you rely on this:
- DFA catches structural shifts, not point anomalies. a single spike won't move alpha much. use a residual detector alongside DFA for spike/outlier detection.
- preprocessing changes alpha. if you add a filter (notch, bandpass, artifact rejection) upstream, your baseline is invalid — recalibrate after any preprocessing change. (#8)
- alpha alone isn't a decision. you still need to decide what "shifted enough" means for your domain. the
HealthVerdictthresholds (0.03/0.08/0.15) are reasonable defaults, not universal truth. - F1 on SMAP/MSL is 0.755, not 0.95. supervised models beat this. the value prop is zero training + speed + embedded, not raw detection accuracy.
🧰 features
- adaptive box sizes geometrically spaced to signal length
no_stdusedefault-features = falsefor embedded- C FFI
struktura.hheader, call from C/C++/whatever - serde optional
features = ["serde"] - self-test
struktura self-testverifies everything
🏆 alternatives
| crate | DFA | speed vs python | license | deps | no_std |
|---|---|---|---|---|---|
| struktura | native | 85-112x | MIT/Apache | 1 (libm) | yes |
| anomaly_detection | no | GPL-3.0 | many | no | |
| extended-isolation-forest | no | MIT | many | no |
📚 references
- C.-K. Peng et al., "Mosaic organization of DNA nucleotide sequences," Physical Review E 49(2), 1994.
- C.-K. Peng et al., "Quantification of scaling exponents," Chaos 5(1), 1995.
- CWRU Bearing Data Center: https://engineering.case.edu/bearingdatacenter
- NASA SPDF Voyager Data: https://spdf.gsfc.nasa.gov/pub/data/voyager/
license
MIT OR Apache-2.0