summaryrefslogtreecommitdiff
path: root/toy
diff options
context:
space:
mode:
authorYurenHao0426 <blackhao0426@gmail.com>2026-06-29 12:15:51 -0500
committerYurenHao0426 <blackhao0426@gmail.com>2026-06-29 12:15:51 -0500
commita6ec4288a2232988b130b2f00bb2565f81706966 (patch)
tree1bb86e7f0b899b823b9e7fdf383e832d30a181e0 /toy
Recursive reasoning dynamics: analysis pipeline, paper drafts, toy models
Failure=more-chaotic (task-general under validity labeling) reduces to convergence/completeness detection; mechanism (transient chaos vs multistability vs input-induced) under investigation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Diffstat (limited to 'toy')
-rw-r--r--toy/toy_transient_chaos.py112
1 files changed, 112 insertions, 0 deletions
diff --git a/toy/toy_transient_chaos.py b/toy/toy_transient_chaos.py
new file mode 100644
index 0000000..7a6c6bd
--- /dev/null
+++ b/toy/toy_transient_chaos.py
@@ -0,0 +1,112 @@
+"""Minimal analytically-grounded toy reproducing 'failure trajectories are more chaotic'.
+
+Framing: recursive reasoning = chaotic SEARCH of latent space until the trajectory lands in the
+solution basin, then it converges (the answer is a stable fixed point). At a fixed readout time T:
+ - captured by T => SUCCESS, finite-time Lyapunov exponent (FTLE) is low (chaotic search for a
+ while, then contraction).
+ - not captured => FAILURE, FTLE stays at the chaotic-saddle value.
+
+Map on [0,1] (input s = solution location, drawn per 'puzzle'):
+ search phase: x <- 4 x (1-x) (fully chaotic, lambda = ln 2 ~ +0.693)
+ capture: if |x - s| < eps -> converging phase: x <- s + 0.5 (x - s) (lambda = ln 0.5 < 0)
+
+This is a textbook chaotic-saddle / transient-chaos system: escape (capture) times are ~geometric,
+and FTLE over [0,T] = (ln2 * t_search + ln0.5 * (T - t_search)) / T. So FTLE separates outcome
+PURELY as a finite-time artifact of capture time -- matching our real-model findings:
+ (i) failure more chaotic, (ii) separation is concurrent/finite-time (vanishes as T->inf since
+ everyone eventually captures), (iii) it is convergence detection, not a deep correctness signal.
+
+Run: python toy_transient_chaos.py (CPU, seconds). Produces toy_transient_chaos.png + stats.
+"""
+from __future__ import annotations
+from pathlib import Path
+import numpy as np
+import matplotlib
+matplotlib.use("Agg")
+import matplotlib.pyplot as plt
+
+HERE = Path(__file__).resolve().parent
+rng = np.random.default_rng(0)
+
+
+def run(n=20000, T=16, eps=0.04, seed=0):
+ rg = np.random.default_rng(seed)
+ s = rg.uniform(0.15, 0.85, n) # 'puzzle': solution location
+ x = rg.uniform(0, 1, n) # initial latent
+ captured = np.zeros(n, bool)
+ cap_time = np.full(n, T, int)
+ log_deriv_sum = np.zeros(n)
+ drift = np.zeros((n, T))
+ for t in range(T):
+ x_prev = x.copy()
+ search = ~captured
+ # log|f'| this step
+ ld = np.empty(n)
+ ld[search] = np.log(np.abs(4 * (1 - 2 * x[search])) + 1e-12)
+ ld[~search] = np.log(0.5)
+ # update
+ x_new = x.copy()
+ x_new[search] = 4 * x[search] * (1 - x[search])
+ x_new[~search] = s[~search] + 0.5 * (x[~search] - s[~search])
+ # capture check (enter basin during search)
+ newly = search & (np.abs(x_new - s) < eps)
+ captured |= newly
+ cap_time[newly] = t + 1
+ x = x_new
+ log_deriv_sum += ld
+ drift[:, t] = np.abs(x - x_prev)
+ ftle = log_deriv_sum / T
+ success = captured & (np.abs(x - s) < 0.05) # near solution at readout
+ return dict(ftle=ftle, success=success, cap_time=cap_time, drift=drift, T=T, eps=eps)
+
+
+def auc(score, y):
+ p, n = score[y == 1], score[y == 0]
+ if len(p) == 0 or len(n) == 0:
+ return float("nan")
+ a = np.concatenate([p, n]); o = np.argsort(a); r = np.empty(len(a)); r[o] = np.arange(1, len(a) + 1)
+ return float((r[:len(p)].sum() - len(p) * (len(p) + 1) / 2) / (len(p) * len(n)))
+
+
+def main():
+ d = run(T=16)
+ y = d["success"].astype(int); ftle = d["ftle"]
+ print(f"=== Toy transient-chaos (T=16, eps={d['eps']}) ===")
+ print(f"success rate = {y.mean():.3f}")
+ print(f"FTLE: success median={np.median(ftle[y==1]):+.3f} failure median={np.median(ftle[y==0]):+.3f}")
+ print(f"AUC(-FTLE -> success) = {auc(-ftle, y):.3f} (>0.9 => reproduces 'failure more chaotic')")
+ late_drift = d["drift"][:, -4:].mean(1)
+ print(f"late drift: success median={np.median(late_drift[y==1]):.3f} failure median={np.median(late_drift[y==0]):.3f}")
+ print(f"AUC(-late_drift -> success) = {auc(-late_drift, y):.3f}")
+
+ # FTLE separation vs readout time T (the finite-time / concurrent-not-antecedent prediction)
+ Ts = [4, 8, 16, 32, 64, 128, 256]
+ seps, accs = [], []
+ for T in Ts:
+ dd = run(T=T)
+ yy = dd["success"].astype(int)
+ seps.append(auc(-dd["ftle"], yy)); accs.append(yy.mean())
+ print("\nFTLE-AUC and success-rate vs readout time T (separation is a finite-time effect):")
+ for T, a, ac in zip(Ts, seps, accs):
+ print(f" T={T:>3}: AUC(-FTLE->success)={a:.3f} success_rate={ac:.3f}")
+
+ fig, ax = plt.subplots(1, 3, figsize=(15, 4.2))
+ bins = np.linspace(-0.5, 0.75, 50)
+ ax[0].hist(ftle[y == 1], bins=bins, alpha=0.6, color="tab:green", density=True, label=f"success (n={int(y.sum())})")
+ ax[0].hist(ftle[y == 0], bins=bins, alpha=0.6, color="tab:red", density=True, label=f"failure (n={int((1-y).sum())})")
+ ax[0].axvline(0, color="gray", lw=0.6); ax[0].set_xlabel("finite-time Lyapunov exponent (T=16)")
+ ax[0].set_title(f"Toy: failure more chaotic\nAUC={auc(-ftle,y):.3f}"); ax[0].legend(fontsize=8)
+ ax[1].hist(d["cap_time"][d["cap_time"] < 16], bins=range(0, 17), color="tab:blue", alpha=0.8)
+ ax[1].set_xlabel("capture (escape) time"); ax[1].set_ylabel("count")
+ ax[1].set_title("escape-time distribution (~geometric:\nchaotic-saddle signature)")
+ ax[2].plot(Ts, seps, "o-", label="AUC(-FTLE->success)")
+ ax[2].plot(Ts, accs, "s--", color="tab:purple", label="success rate")
+ ax[2].set_xscale("log"); ax[2].axhline(0.5, color="gray", lw=0.5)
+ ax[2].set_xlabel("readout time T"); ax[2].set_title("separation is FINITE-TIME:\nvanishes as T->inf (all escape)")
+ ax[2].legend(fontsize=8)
+ fig.tight_layout(); fig.savefig(HERE / "toy_transient_chaos.png", dpi=140)
+ print("\nsaved toy_transient_chaos.png")
+
+
+if __name__ == "__main__":
+ main()