Coxeter Folding Reversible Recurrence / experiment.py
Mechanism confirmed, baseline not beaten
1import json
2import math
3import numpy as np
4
5
6def fold(p, j):
7 """Apply the stated local rational fold to complex polygon coordinate j."""
8 q = np.array(p, copy=True)
9 n = len(q)
10 a, b, c = p[(j - 1) % n], p[j], p[(j + 1) % n]
11 den = (b - c) - (a - b) # 2*b-a-c
12 q[j] = ((b - c) * a - (a - b) * c) / den
13 return q, abs(den)
14
15
16def apply_schedule(p, schedule):
17 q = np.array(p, copy=True)
18 denoms = []
19 for j in schedule:
20 q, d = fold(q, int(j))
21 denoms.append(d)
22 return q, np.asarray(denoms)
23
24
25def relerr(a, b):
26 return float(np.linalg.norm(a - b) / max(np.linalg.norm(b), 1e-30))
27
28
29def polygon(seed, n=8, dtype=np.complex128):
30 rng = np.random.default_rng(seed)
31 # A separated, mildly perturbed convex polygon avoids accidental singularities.
32 theta = np.arange(n) * (2 * np.pi / n)
33 radius = 1.0 + .04 * rng.normal(size=n)
34 p = radius * np.exp(1j * theta) + .02 * (rng.normal(size=n) + 1j*rng.normal(size=n))
35 return p.astype(dtype)
36
37
38def fold_math_check():
39 rng = np.random.default_rng(10)
40 errs = []
41 for _ in range(2000):
42 a, c = rng.normal(size=2) + 1j*rng.normal(size=2)
43 b = rng.normal() + 1j*rng.normal()
44 if abs(2*b-a-c) < .1:
45 continue
46 q, _ = fold(np.array([a, b, c], dtype=np.complex128), 1)
47 r, _ = fold(q, 1)
48 errs.append(abs(r[1]-b) / max(abs(b), 1e-12))
49 return {"samples": len(errs), "max_relative_error": max(errs), "median_relative_error": float(np.median(errs))}
50
51
52def schedule_check(dtype, schedule, repeats=1):
53 p = polygon(3, dtype=dtype)
54 sched = list(schedule) * repeats
55 y, den = apply_schedule(p, sched)
56 z, den2 = apply_schedule(y, list(reversed(sched)))
57 return {"dtype": str(dtype), "steps": len(sched), "reconstruction_relative_error": relerr(z, p),
58 "minimum_denominator": float(min(np.min(den), np.min(den2))),
59 "max_abs_state": float(max(np.max(np.abs(p)), np.max(np.abs(y)), np.max(np.abs(z))))}
60
61
62def denominator_sweep():
63 # Directly approach the singular locus 2b-a-c=0 while retaining nonzero a,b,c.
64 a, c = 0.2 + .4j, -0.7 + .1j
65 out = []
66 for eps in [1e-1, 1e-3, 1e-5, 1e-7, 1e-9]:
67 b = (a+c)/2 + eps*(1+0.3j)
68 p = np.array([a,b,c], dtype=np.complex128)
69 q, d = fold(p, 1)
70 r, _ = fold(q, 1)
71 out.append({"epsilon": eps, "denominator": float(d),
72 "folded_abs": float(abs(q[1])), "involution_error": float(abs(r[1]-b))})
73 return out
74
75
76def main():
77 schedule = [0, 2, 4, 6, 1, 3, 5, 7]
78 results = {
79 "math_check": fold_math_check(),
80 "fixed_schedule": [schedule_check(np.complex128, schedule, k) for k in [1, 5, 20]],
81 "float32_schedule": [schedule_check(np.complex64, schedule, k) for k in [1, 5, 20]],
82 "singularity_sweep": denominator_sweep(),
83 "memory_note": {"reversible_saved_fold_states": 0,
84 "ordinary_unroll_saved_fold_states_20_steps": 20,
85 "state_size_points": 8},
86 }
87 with open("results.json", "w") as f:
88 json.dump(results, f, indent=2)
89 print(json.dumps(results, indent=2))
90
91if __name__ == "__main__":
92 main()