dynachaos analyses time signals from simulations and experiments: maps and
coupled-map lattices, Lyapunov exponents, recurrence quantification, entropy
diagnostics, correlation dimension and multifractal spectra. Performance-critical
kernels have Rust backends where those have parity tests, with pure-Python
fallbacks everywhere else.
dynachaos is a reusable Python/Rust package for inspecting simulated or measured dynamical-systems time signals. It collects maps, coupled-map lattices, recurrence analysis, entropy diagnostics, Grassberger-Procaccia correlation dimension, multifractal spectra, and reproducible analysis pipelines in one codebase. Performance-sensitive kernels use Rust backends where they have parity tests, while pure-Python fallbacks support portability and reference checks.
Maps — logistic, circle, coupled logistic, delayed logistic, coupled delayed, modulated circle, torus doubling (Map I / Map IV)
Coupled Map Lattices — CML with nearest-neighbor diffusive coupling, globally coupled maps (GCM), pattern dynamics, cluster statistics
Diagnostics — Lyapunov exponents (1D + QR spectrum + flow systems),
0-1 test for chaos, SALI/GALI alignment indices, permutation entropy +
complexity-entropy planes, sample/approximate/fuzzy/multiscale entropy,
recurrence quantification analysis (RQA), Grassberger-Procaccia
correlation dimension, multifractal spectra (
Rust backends — correlation integral (Grassberger-Procaccia), fuzzy entropy sum, recurrence line extraction, ordinal distribution counting, AMI histograms, Cao dimension selection, multifractal moments
Visualization — bifurcation diagrams, cobweb plots, return maps, curated Swiss-inspired style themes
From a fresh checkout, run the tested external-signal workflow recipe:
uv run dynachaos analyze examples/recipes/external_signal/external_signal_recipe.jsoncThe command writes
examples/recipes/external_signal/outputs/external_signal_recipe/results.json,
examples/recipes/external_signal/outputs/external_signal_recipe/metadata.json,
and
examples/recipes/external_signal/outputs/external_signal_recipe/summary.md.
It is intentionally small so it runs in seconds; use the recipe gallery for the
long-signal streaming example.
- Real-analysis user guide: input expectations, diagnostic choice, long-signal scaling, workflow outputs, reliability metadata, and finite-data caveats.
- Example gallery: tested recipe commands for external signals and long-signal streaming RQA.
- RQA scaling design note: how much memory dense recurrence needs, and the streaming RQA design.
- Rust acceleration roadmap: measured acceleration claims and future kernel priorities.
- Claims checklist: wording boundaries for public documentation and manuscripts.
Run scalar/reduced time-series analyses with a JSONC config; all tuning lives in the config, not CLI flags.
Config schema summary:
input: either{ "path": "signal.npy" }/{ "path": "signal.npz", "npz_key": "x" }for a 1D finite scalar/reduced signal, or{ "generated": { "name": "logistic", "n": 1000, "seed": 0 } }for self-contained runs.output.dir: stable output directory, resolved relative to the config file.diagnostics: list of{ "name": ... }entries. Supported workflow names arepermutation_entropy,correlation_dimension,rqa_streaming, andrqa_dense.scale_limits: optional dense-RQA guard. Dense RQA builds anN x Ndistance matrix costing8*N^2bytes;dense_rqa_max_bytescaps that, andallow_dense_rqa_beyond_envelope: trueis required to exceed the cap.
Output layout is stable and referenceable by path: results.json
(machine-readable diagnostic values), metadata.json (N, shape, wall time in
seconds, peak RSS in MB, and per-diagnostic ReliabilityRecord metadata), and
summary.md (human-readable report with relative artifact names). Reliability
metadata records backend, parameters, data shape, sampling/downsampling notes,
warnings, unresolved verdicts, and scale evidence. It helps you decide how much
scientific confidence to place in a number; it is not an automatic pass/fail
certificate.
For long-signal local RQA, avoid dense recurrence matrices and set an explicit threshold in config:
The local/full-run command uv run dynachaos analyze long_signal_rqa.jsonc
requires a local long_signal_rqa.jsonc file and the long_signal.npy input it
names. The tested commands live in the quickstart and in examples/README.md.
Performance-critical algorithms are implemented as Rust kernels. The Rust
extension is required by default: import dynachaos fails loudly if it has not
been built.
Build the extension in editable mode:
uv run maturin develop --releaseThe Rust kernels are the intended path for production-sized all-pairs and
large-N diagnostics. Pure-Python paths are an explicit opt-in for parity testing
and portability, not an automatic silent fallback. Set DYNACHAOS_NO_RUST=1
when you need to exercise them; the test suite checks parity on representative
small workloads. Some fallback implementations remain exact and quadratic by
design, so large pure-Python runs should be treated as diagnostic or development
runs unless a future release explicitly makes large fallback performance a
target.
The all-pairs kernel evaluates all N(N−1)/2 pairs with Theiler-window exclusion and multi-radius binning in a single pass. The implementation is designed for correctness and low memory use, offering several advantages over common baseline scripts (e.g., notsebastiano/GP_algorithm):
-
Algorithmic Correctness — Supports the Theiler window (
$|i-j| > w$ ) to exclude temporally correlated pairs (Theiler 1986) and uses proper normalization ($C(r) \le 1$ ). - Scaling Region Detection — Employs a stable plateau search on local slopes instead of simple heuristics, making it robust to noise and saturation.
-
Memory Efficiency — Uses O(1) auxiliary memory per pair (streaming)
instead of an
$O(N^2)$ distance matrix.
The Rust kernel uses:
- Raw slice indexing — C-contiguous slice access, avoiding ndarray's
per-index
Indexoverhead while staying in safe Rust - Prefix-sum binning — one write per pair instead of up to 50; converted to cumulative counts with a single O(n_r) pass after the loop
- Squared-distance comparison — pre-squared thresholds eliminate
sqrt()in Euclidean mode (saves 10–20 cycles per pair) - Branch-separated loops — Chebyshev and Euclidean paths are fully separated, enabling independent auto-vectorization
- Rayon parallelism — outer loop distributed across all cores via
Rayon; GIL released with
py.detach()before the parallel region - Native compiler optimization — release builds can use the local CPU
target configured under
.cargo/.
Benchmark numbers should be regenerated on the release target hardware before being used in public documentation.
Computes Σ exp(−(d/r)ⁿ) over all upper-triangle pairs on mean-centered templates, using the same Rayon parallel fold + reduce pattern.
| Algorithm | Module | Rust kernel | Reference |
|---|---|---|---|
| Lyapunov exponent (1D) | diagnostics.lyapunov |
— | Benettin et al. 1980 |
| Lyapunov spectrum (QR) | diagnostics.lyapunov |
— | Benettin et al. 1980 |
| Flow Lyapunov spectrum | diagnostics.lyapunov |
— | Benettin et al. 1980 |
| 0-1 test for chaos | diagnostics.zero_one_test |
— | Gottwald & Melbourne 2004 |
| SALI / GALI | diagnostics.sali_gali |
— | Skokos et al. 2007 |
| Permutation entropy | diagnostics.permutation |
ordinal dist. | Bandt & Pompe 2002 |
| Complexity-entropy plane | diagnostics.permutation |
ordinal dist. | Rosso et al. 2007 |
| Sample entropy | diagnostics.entropy |
correlation counts | Richman & Moorman 2000 |
| Approximate entropy | diagnostics.entropy |
— | Pincus 1991 |
| Fuzzy entropy | diagnostics.entropy |
fuzzy sum | Chen et al. 2007 |
| Multiscale entropy | diagnostics.entropy |
correlation counts | Costa et al. 2002 |
| RQA (DET, LAM, ENTR, …) | diagnostics.recurrence |
line extraction | Marwan et al. 2007 |
| Correlation dimension | diagnostics.correlation |
all-pairs kernel | Grassberger & Procaccia 1983 |
| Multifractal spectrum ( |
diagnostics.multifractal |
multifractal moments | Mukherjee et al. 2024 |
| AMI (embedding) | diagnostics.embedding |
histogram | Fraser & Swinney 1986 |
| Cao's method | diagnostics.embedding |
dimension selector only | Cao 1997 |
| False nearest neighbors | diagnostics.embedding |
— | Kennel et al. 1992 |
diagnostics.recurrence keeps recurrence_matrix() for callers that need the
binary matrix. For large trajectories where only scalar RQA measures are needed,
use rqa_from_trajectory() to avoid materializing the dense recurrence matrix.
When starting from an existing recurrence matrix, compute public RQA metrics
through rqa(), which validates that the matrix is non-empty, square, and
symmetric. The direct line extractors, including the Rust-accelerated helpers,
are lower-level square-matrix scanners and do not replace that public RQA
validation boundary.
The figures/ tree holds a section-indexed set of reproductions of Kunihiko
Kaneko's published work on circle maps, torus doubling, fractalization,
coupled map lattices, and globally coupled maps. Each section is regenerated
from scratch by the same public entry points users call:
dynachaos list
dynachaos run sec02_circle_map
dynachaos run allThe committed .npz caches double as golden data for the reproducibility and
determinism tests, so the gallery is both a worked example and a standing
regression check. It is a demanding stress test of the package rather than its
boundary: for general use on your own signals, see the quickstart and recipe
gallery above.
Eight of the thirty-seven figures are shown below; click any of them for the full-resolution render. Browse the complete gallery for all thirty-seven, in section order, with captions.
The reproducible benchmark for Rust Grassberger-Procaccia parity and dense
recurrence/RQA memory limits lives in benchmarks/scale_envelope.py.
The local benchmark command is
uv run python benchmarks/scale_envelope.py benchmarks/scale_envelope.jsonc;
inspect benchmarks/results/scale_envelope.{json,md} after it runs. The
checked artifact reports a 42.95x CI-mode Rust Grassberger-Procaccia speedup at
N=1000 for the largest common logistic case, and a predicted dense-RQA
distance-matrix cost of 8*N^2 bytes, which becomes impractical near N≈23170
under the default cap. The measured Rust acceleration roadmap and local hotspot profiler are
documented in docs/rust-acceleration-roadmap.md and
benchmarks/rust_hotspot_profile.py.
These commands are for working on dynachaos itself. To use the package, see Quick Start above.
git clone https://github.com/openfluids/dynachaos.git
cd dynachaos
uv sync
uv run --extra viz pytest tests/ -q
uv run --extra viz ruff check src/ tests/ scripts/
uv run --extra viz ruff format --check src/ tests/ scripts/To exercise the installed Rust extension locally:
uv run maturin develop --release
uv run --extra viz pytest tests/ -qTo verify the pure-Python fallback path:
DYNACHAOS_NO_RUST=1 uv run --extra viz pytest tests/ -qTo rebuild the gallery thumbnails and the static gallery site:
uv run --extra viz python scripts/build_gallery.pyContributions are welcome, and questions and bug reports count. See CONTRIBUTING.md for setup and the checks CI runs, and the openfluids Code of Conduct for how we work together.
If you use dynachaos in published work, please cite the software:
@software{dynachaos2026,
author = {Frantz, Ricardo},
title = {dynachaos: Dynamical-systems analysis for time signals with Rust-accelerated kernels},
year = {2026},
version = {0.4.0},
url = {https://github.com/openfluids/dynachaos},
license = {Apache-2.0}
}This project is licensed under Apache-2.0.
Originally developed by Ricardo A S Frantz. See LICENSE and NOTICE for
license terms and attribution notices. As of v0.3.0, this project is licensed
under Apache-2.0; earlier (unpublished) versions were MIT.

{ "input": {"path": "long_signal.npy"}, "output": {"dir": "results/long_signal_rqa"}, "diagnostics": [ {"name": "rqa_streaming", "embedding": {"d": 3, "tau": 2}, "eps": 0.08, "l_min": 2, "v_min": 2} ] }