Skip to content

sethc555/ordchaos

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ordchaos

A small, honest instrument for asking "is there structure here?" — built well enough to know when the honest answer is "we can't know."

It started as a naive question — model a curve from perfect chaos to perfect stability (π) — and resolved into something sharper:

Order and chaos are not intrinsic properties of a signal. They are relative to the lens you measure with, and the one lens that would make the distinction absolute — Kolmogorov complexity K — is uncomputable, and adversarially so.

π is the proof: it looks maximally random to every computable lens, yet has a tiny generating program. It isn't a point on the order↔chaos curve; it's the pin showing the curve must fold into a higher-dimensional space whose decisive axis can't be computed.

The six lenses

ordchaos.py places any 1-D sequence on six complementary, validated lenses (core library needs only numpy + mpmath; matplotlib is for the demo scripts):

lens function what it sees
(H, C) plane statistical_complexity Bandt–Pompe permutation entropy + MPR Jensen–Shannon complexity
surrogate significance surrogate_test is structure more than linearly-filtered noise? (IAAFT)
calibrated compression compression_complexity LZ redundancy (gzip/bz2/lzma), random → 1.0
CTM / BDM bdm_complexity, ctm algorithmic complexity from an empirical Turing-machine table (works on short strings)
ε-machine epsilon_machine the generating machine — causal states, transitions, Cμ, hμ (CSSR-style, with determinization)
program-search K program_search_k one-sided algorithmic complexity estimate

place(series) returns (H, C, K); everything else is a direct call. See test_ordchaos.py for 131 known-value checks (golden-mean Cμ = 0.918, period-3, GUE statistics, calibration, etc.).

import ordchaos as oc
oc.place(my_signal)                 # -> Placement(H, C, K, in_bounds, feats)
m = oc.epsilon_machine(my_signal)   # the reconstructed causal-state machine
oc.simulate(m, 1000)               # generate synthetic data with the same causal structure
oc.predict(m, history)             # next-symbol distribution (the minimal optimal predictor)
oc.joint_epsilon_machine(X)        # causal states of a vector process (X: n_samples × channels)

The ε-machine is a generative model, not just a descriptor: simulate walks it to produce synthetic sequences that re-fit to the same machine (a golden-mean machine's output keeps Cμ and never emits the forbidden 00), and predict returns the next-symbol distribution with hμ as the honest error floor.

The key asymmetry

The toolkit can confirm hidden structure soundly (a short program found ⇒ definitely not random) but can never certify randomness (none found ⇒ maybe you didn't search hard enough). The program_search_k LCG demonstration makes this concrete: a pseudo-random stream with tiny true K is mislabeled "random" because its generator isn't in the search space. K is one-sided and adversarially uncomputable — which is why every statistical lens here is, by design, blind to π.

Richer K and the depth axis

The K side has more lenses and a fourth coordinate:

capability function what it does
linear complexity linear_complexity Berlekamp–Massey: the shortest LFSR reproducing the binarized series — exact, ~n/2 for random, tiny for any linear PRNG (LFSR, m-sequence, xorshift bit-stream)
expanded K battery program_search_k now also cracks LFSRs and detects elementary-CA center columns (rule 30 = the "π of CAs": tiny program, looks random, fools the linear lens but is caught here), plus a wider named-constant library (√5, √7, ∛2, ζ(3), Catalan, γ, …)
logical depth logical_depth Bennett's fourth axis (the long-deferred one): not how short the program is (K) but how much work it does — π is K≈0 yet deep (a one-liner, but costly to compute), a periodic ramp is K≈0 and shallow, random is high-K and shallow. Deterministic (operation counts, never wall-clock); one-sided like K
MT predictor predict_mt recovers MT19937 state from 624 outputs and predicts the rest exactly — a famous PRNG cracked by prediction (kept standalone: MT's ~20 kbit state means it does not lower K)
oc.logical_depth(my_signal)   # -> {'k', 'generator', 'depth_ops', 'depth_per_symbol', 'deep'}
oc.linear_complexity(my_bits) # -> ~1 random, ~0 linear/ordered

Beyond a single series

The six lenses characterize one 1-D signal. Four further tools extend that to relationships, time, and uncertainty — same honest, validated discipline:

capability function what it does
multivariate (H, C) mv_statistical_complexity joint permutation entropy + MPR complexity over the (d!)^channels joint-pattern space; redundant channels lower joint H, independent ones push it toward 1
cross-series causality transfer_entropy, coupling, transfer_entropy_test symbolic transfer entropy TE(X→Y) in bits — directional, so it separates driver from driven; Miller–Madow bias-corrected, with a shuffled-source significance test and effective-TE estimate. epochs= keeps history windows from crossing trial/segment seams (no spurious coupling at concatenation joins)
continuous causality (kNN) transfer_entropy_ksg, coupling_ksg Kraskov/Frenzel–Pompe k-nearest-neighbor TE — no quantization; a quantization-free cross-check on the symbolic estimator. Uses scipy's cKDTree when installed (O(N log N); ~130× faster at N=8k), numpy-only brute-force fallback otherwise
network inference conditional_transfer_entropy, causal_network conditional TE removes common-driver confounds — CTE(X→Y|Z)≈0 when Z drives both — turning a multi-channel TE matrix into a directed connectivity graph; fdr=True controls the false discovery rate (Benjamini–Hochberg) across all edges for statistically sound large networks
change points change_points, sliding model-free location of regime shifts via binary segmentation on a windowed statistic, gated by both an effect-size jump and a permutation test
uncertainty bootstrap_ci, hc_ci moving-block-bootstrap confidence intervals for any scalar lens (blocks preserve temporal dependence; the interval always brackets the estimate). Returns a named CI(point, lo, hi); iid=True for the ordinary i.i.d. bootstrap
paired effect + power paired_test, min_detectable_effect, statistical_power matched-pair significance for discrete win/loss (exact sign test + sign-flip permutation, apt where a mean-bootstrap isn't) and the "know when you can't know" companion — the smallest effect N pairs could even detect, so a sub-floor result is named honestly instead of over-claimed
2-D / spatial spatial_statistical_complexity, spatial_forbidden_fraction the Ribeiro complexity-entropy plane for images/fields — 2-D ordinal patterns over dx×dy patches place an image exactly as the 1-D plane places a series (random→(1,0), gradient→(0,0), textures high on the arc)
coupling delay + embedding coupling_delay, embedding_delay, embedding_dimension which lag carries the coupling (a delay-d copy peaks at exactly d), plus τ (first autocorrelation zero) and d (false nearest neighbors: logistic→1, Hénon→2, noise high) — removes the fixed-parameter guesswork
missing data skipnan= on the (H,C) lenses, interpolate_gaps gappy series: skip windows containing NaN (measure the gap-free runs) or linearly bridge holes; NaN still raises without skipnan (no silent wrong number)
process distance process_distance, distance_matrix, machine_distance "how different are these two dynamics?" — Jensen–Shannon distance of ordinal-pattern (or ε-machine word) distributions, in [0,1]; 0 = same process, ~1 = disjoint. Feeds clustering, taxonomy, anomaly-by-drift
information decomposition partial_information_decomposition splits I(target; S1, S2) into redundant / unique / synergistic atoms (method='imin'/'mmi'; BROJA/CCS via the dit recipe): a copied target is redundant, target = S1 XOR S2 is purely synergistic. Also returns co_information — a synergy signal independent of the redundancy definition (<0 synergy, >0 redundancy)
oc.coupling(x, y)                      # {'te_xy', 'te_yx', 'net', 'direction'}
oc.causal_network(X)                   # directed TE graph over X's channels (confound-free)
oc.change_points(signal)               # -> [sample indices where dynamics shift]
oc.hc_ci(signal)                       # -> {'H': (point, lo, hi), 'C': (point, lo, hi)}
oc.mv_statistical_complexity(X)        # X is (n_samples, n_channels)

Streaming and scale

For live data, OnlineStatComplexity maintains the Bandt–Pompe histogram incrementally — update(x) is O(d log d) per sample and returns the running (H, C). It is exact (fed the whole series it equals the batch estimator); a trailing window decays old patterns out for non-stationary monitoring. On the performance side, the symbolic TE estimators are fully vectorized (mixed- radix coding + np.unique, no Python loop), and transfer_entropy_ksg uses scipy's cKDTree when the [scale] extra is installed (drop-in pip install ordchaos[scale]), keeping the core numpy + mpmath.

m = oc.OnlineStatComplexity(window=1000)
for x in stream:
    H, C = m.update(x)        # running (H, C), live

One-call report, CLI, persistence

report(x) runs the battery and returns a JSON-serializable verdict; ordchaos analyze file.csv does the same from the shell. ε-machines and causal networks save/load as JSON for reproducible pipelines, and batch_report maps the battery over a corpus.

ordchaos analyze signal.csv          # one-page characterization + verdict
ordchaos analyze signal.csv --json   # same, machine-readable
ordchaos batch *.csv                 # corpus -> JSON list
oc.report(signal)                    # {H, C, K, depth, ..., 'verdict': '...'}
oc.report_html(oc.report(signal))    # self-contained HTML one-pager (no deps)
oc.report_figure(signal, "fig.png")  # signal + (H,C) plane (needs [demos])
oc.save_machine(oc.epsilon_machine(signal), "m.json"); oc.load_machine("m.json")

Applications (validation + demonstration, not new science)

  • the_fold.py — the whole thesis in one figure. Places π, e, √2, φ, an LCG stream and genuine noise on (H, C, K) via oc.place(): they collapse into a single corner of the (H,C) plane (order and chaos indistinguishable), and only the uncomputable K axis folds them apart — π splits to K≈0 while noise stays at K≈1, and the LCG stays stuck with the noise despite a tiny true K (the axis is one-sided). Start here.

Each script below characterizes a real object and reports an honest verdict:

  • cf_experiment.py / cf_sqrtD.py / cf_degree.py / cf_bounded.py — continued fractions. Representation surfaces quadratic-irrational structure invisible in base-10 (ε-machine recovers the exact period); Cμ(√D) = log₂(CF period) = a Pell/regulator invariant; degree-≥3 algebraics are empirically Gauss–Kuzmin / unbounded (an open problem).
  • zeta_zeros.py — reproduces Montgomery–Odlyzko: Riemann ζ-zero spacings follow GUE, not Poisson; no structure beyond GUE. (A tempting baseline artifact was caught and not over-claimed.)
  • gw_strain.py — LIGO GW150914: characterizes detector noise (coloration, 60 Hz/violin lines, non-stationarity); recovers the actual chirp to 20 ms by whitening. Draws its own limit: a characterizer, not a transient detector.
  • ising_phase.py — Cμ as a model-free phase-transition locator: it peaks at the 2D-Ising critical temperature without ever seeing the magnetization.
  • complexity_entropy_*.py, effective_dim_vs_M.py, k_axis_program_search.py, surrogate_demo.py, bdm_demo.py — the conceptual scaffolding: the (H,C) plane, the M-dimensional sweep showing the computable manifold is ~5–7-D, the program-search K-axis (win + wall), and the demos.

ctm_build.py builds ctm_table.json from 3M small Turing machines (the genuine coding-theorem-method construction, small-scale).

Honest scope

This is a characterization toolkit, not a predictor or detector. The applications recover known structure; the discoveries here were of the tools' correctness, not of nature. The components (computational mechanics, CTM/BDM, surrogate testing) predate this — the value is integration, a six-lens cross-check, full ownership, and a validation/honest-attribution discipline.

External datasets (data/, gitignored) are re-fetchable; see each script header for the source.

Install

pip install -e .                # core library (numpy + mpmath)
pip install -e ".[scale]"       # + scipy for the KSG fast path (large data)
pip install -e ".[demos]"       # also pull matplotlib for the demo scripts
pip install -r requirements.txt # exact pinned versions the checks were validated against

The CTM table (ctm_table.json) loads relative to the module, so an editable install keeps it side by side; that is the supported install today.

Run

python3 test_ordchaos.py        # 131 known-value checks (also run in CI)
python3 the_fold.py             # the central claim in one figure (the fold)
python3 ising_phase.py          # Cμ finds the Ising transition
python3 cf_experiment.py        # the continued-fraction representation flip

CI runs the 131 known-value checks on every push/PR across Python 3.10–3.12, plus a pinned-environment job, so "validated" stays continuously true rather than a one-time claim — see .github/workflows/ci.yml.

About

An honest instrument for asking 'is there structure here?' — six validated order/chaos lenses (permutation entropy, surrogate, compression, CTM/BDM, epsilon-machine, program-search K) on 1-D signals

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages