code wiki / _hdl_build / nx_dpendulum_gate.nx
nx_dpendulum_gate.nx source
↩ module page · 138 lines · 6744 B
1// nx_dpendulum_gate.nx -- the DOUBLE PENDULUM, the continuous chaotic system named as the sim
2// workstream's flagship "no-float determinism under chaos" vehicle (companion to the discrete
3// nx_chaos_logistic_gate). Pure integer/no-float fixed point (all quantities x1e6 = "q"); reuses the
4// Taylor sin/cos from nx_pendulum. Equal masses + lengths (m1=m2=1, l1=l2=1, g=9.8); full nonlinear
5// COUPLED equations of motion (myphysicslab form), denom = 3 - cos(2(th1-th2)) in [2,4] => no
6// singularity. Semi-implicit (symplectic-style) Euler: update omega from current theta, then theta
7// from the new omega -- energy-bounded enough to integrate the chaotic regime over a short horizon.
8//
9// THREE measured properties + liar-kill (GREEN means it discriminates):
10// (1) SENSITIVE DEPENDENCE: at LARGE amplitude (th1=th2=2.5 rad) two runs whose th2 differ by 1
11// micro-rad DIVERGE to O(1 rad) -- deterministic chaos.
12// (2) DETERMINISM: the SAME integer run recomputed gives a BIT-IDENTICAL max-separation (no-float
13// => exactly reproducible even under chaos = the Reproducibility-EXCEEDS property).
14// (3) NEGATIVE CONTROL: the SAME integrator at SMALL amplitude (th1=th2=0.05 rad) is quasi-linear
15// (two normal modes, NOT chaotic) -> the 1-micro-rad perturbation stays bounded/tiny. So the
16// gate cannot be fooled into calling regular motion "chaos". GREEN needs all three.
17// Sovereign: imports only nx_syscalls. Additive. license_tier: ORIGINAL
18import "nx_syscalls.nx"
19
20const SC: i64 = 1000000 // x1e6 fixed-point scale
21const GQ: i64 = 9800000 // g = 9.8 in q
22const DTDIV: i64 = 1000 // dt = 1ms: x += rate/1000
23
24func dp_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
25func dp_p(s: *u8) -> i64 { let n: i64 = dp_len(s); sys_write(1, s, n); return 0 }
26func dp_pn(v: i64) -> i64 {
27 let bb: *u8 = sys_mmap(28); var m: i64 = v
28 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
29 let t: *u8 = sys_mmap(28); var k: i64 = 0
30 if m == 0 { t[0] = 48 as u8; k = 1 }
31 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
32 var i: i64 = 0; while i < k { bb[i] = t[k - 1 - i]; i = i + 1 }
33 sys_write(1, bb, k); return 0
34}
35
36// Taylor sine; input micro-rad, output sin x1e6 (the proven nx_pendulum sin_fixed).
37func sin_fixed(Xin: i64) -> i64 {
38 let PI: i64 = 3141593; let TWO_PI: i64 = 6283185; let HALF: i64 = 1570796
39 var sign: i64 = 1; var X: i64 = Xin
40 if X < 0 { X = 0 - X; sign = 0 - 1 }
41 X = X % TWO_PI
42 if X > PI { X = TWO_PI - X; sign = 0 - sign }
43 if X > HALF { X = PI - X }
44 let x2: i64 = X * X / SC
45 var term: i64 = X; var sum: i64 = X; var k: i64 = 1
46 while k <= 8 { term = (0 - term) * x2 / SC / ((2 * k) * (2 * k + 1)); sum = sum + term; k = k + 1 }
47 return sum * sign
48}
49func cos_fixed(X: i64) -> i64 { return sin_fixed(X + 1570796) }
50
51// fixed-point multiply of two q values -> q.
52func mulq(a: i64, b: i64) -> i64 { return a * b / SC }
53
54// one semi-implicit step of the double pendulum. s[0..3] = th1,th2,om1,om2 (all q).
55func dp_step(s: *i64) -> i64 {
56 let th1: i64 = s[0]; let th2: i64 = s[1]; let om1: i64 = s[2]; let om2: i64 = s[3]
57 let d: i64 = th1 - th2
58 let sind: i64 = sin_fixed(d)
59 let cosd: i64 = cos_fixed(d)
60 let cos2d: i64 = cos_fixed(2 * d)
61 let denom: i64 = 3 * SC - cos2d
62 let w1s: i64 = mulq(om1, om1)
63 let w2s: i64 = mulq(om2, om2)
64 let s1: i64 = sin_fixed(th1)
65 let s12: i64 = sin_fixed(th1 - 2 * th2)
66 let c1: i64 = cos_fixed(th1)
67 // alpha1 = [-3g sin th1 - g sin(th1-2th2) - 2 sin d (om2^2 + om1^2 cos d)] / denom
68 let inner1: i64 = w2s + mulq(w1s, cosd)
69 let num1: i64 = (0 - 3) * mulq(GQ, s1) - mulq(GQ, s12) - 2 * mulq(sind, inner1)
70 let a1: i64 = num1 * SC / denom
71 // alpha2 = [2 sin d (2 om1^2 + 2g cos th1 + om2^2 cos d)] / denom
72 let inner2: i64 = 2 * w1s + 2 * mulq(GQ, c1) + mulq(w2s, cosd)
73 let num2: i64 = 2 * mulq(sind, inner2)
74 let a2: i64 = num2 * SC / denom
75 // semi-implicit (symplectic-style): omega first, then theta with NEW omega
76 let no1: i64 = om1 + a1 / DTDIV
77 let no2: i64 = om2 + a2 / DTDIV
78 s[2] = no1; s[3] = no2
79 s[0] = th1 + no1 / DTDIV
80 s[1] = th2 + no2 / DTDIV
81 return 0
82}
83
84// run two pendulums from the same start except B's th2 is perturbed by `pert` micro-rad; both from
85// rest. Return MAX (|d th1| + |d th2|) over all N steps. Deterministic -> reproducible.
86func dp_run_sep(th0: i64, pert: i64, n: i64) -> i64 {
87 let A: *i64 = sys_mmap(8 * 4) as *i64
88 let B: *i64 = sys_mmap(8 * 4) as *i64
89 A[0] = th0; A[1] = th0; A[2] = 0; A[3] = 0
90 B[0] = th0; B[1] = th0 + pert; B[2] = 0; B[3] = 0
91 var mx: i64 = 0
92 var i: i64 = 0
93 while i < n {
94 dp_step(A); dp_step(B)
95 var d1: i64 = A[0] - B[0]; if d1 < 0 { d1 = 0 - d1 }
96 var d2: i64 = A[1] - B[1]; if d2 < 0 { d2 = 0 - d2 }
97 let sep: i64 = d1 + d2
98 if sep > mx { mx = sep }
99 i = i + 1
100 }
101 return mx
102}
103
104func main() -> i64 {
105 dp_p("=== nx_dpendulum_gate: no-float determinism UNDER CHAOS (double pendulum) ===\n" as *u8)
106 let BIG: i64 = 100000 // chaos threshold: > 0.1 rad of divergence
107 let SMALL: i64 = 10000 // control threshold: < 0.01 rad
108 let N: i64 = 14000 // 14 s at dt=1ms -- enough for the (observed) Lyapunov growth to saturate
109 let LARGE_TH: i64 = 2500000 // 2.5 rad (chaotic)
110 let SMALL_TH: i64 = 50000 // 0.05 rad (quasi-linear, NOT chaotic)
111
112 let chaos1: i64 = dp_run_sep(LARGE_TH, 1, N)
113 let chaos2: i64 = dp_run_sep(LARGE_TH, 1, N) // recompute -> must be identical
114 let order1: i64 = dp_run_sep(SMALL_TH, 1, N)
115
116 dp_p(" m1=m2=1 l1=l2=1 g=9.8 dt=1ms N=" as *u8); dp_pn(N)
117 dp_p("; perturbation = 1 micro-rad in th2\n" as *u8)
118 dp_p(" (1) sensitive-dependence th=2.5rad max sep = " as *u8); dp_pn(chaos1)
119 dp_p(" (need > " as *u8); dp_pn(BIG); dp_p(")\n" as *u8)
120 dp_p(" (2) determinism re-run th=2.5rad max sep = " as *u8); dp_pn(chaos2)
121 dp_p(" (must == run 1)\n" as *u8)
122 dp_p(" (3) neg-control (order) th=0.05rad max sep = " as *u8); dp_pn(order1)
123 dp_p(" (must < " as *u8); dp_pn(SMALL); dp_p(")\n" as *u8)
124
125 var pass: i64 = 0
126 var ok: i64 = 1
127 if chaos1 > BIG { pass = pass + 1 } else { ok = 0 }
128 if chaos1 == chaos2 { pass = pass + 1 } else { ok = 0 }
129 if order1 < SMALL { pass = pass + 1 } else { ok = 0 }
130
131 dp_p(" RESULT pass=" as *u8); dp_pn(pass); dp_p("/3 verdict=" as *u8)
132 if ok == 1 {
133 dp_p("GREEN (chaotic divergence, bit-reproducible no-float, regular motion stays regular)\n" as *u8)
134 sys_exit(0); return 0
135 }
136 dp_p("RED\n" as *u8)
137 sys_exit(1); return 1
138}