code wiki / _hdl_build / nx_evolve.nx
nx_evolve.nx source
↩ module page · 290 lines · 14400 B
1// nx_evolve.nx -- X-EVO-001: the EVOLUTION HARNESS (operator self-sufficiency arc;
2// configured from the EC taxonomy evo_params.tsv, not invention). Rung 1 validates
3// the MACHINERY on a fitness with a KNOWN optimum (you cannot trust an optimizer you
4// cannot verify) -- then rung 2 swaps fitness_id for a GATE permil (the exceed: we
5// grow evaluators). Every EC operator from the taxonomy, wired:
6// population = candidate integer vectors selection = TOURNAMENT (best-of-k)
7// mutation = per-gene +-1 at mutation_rate elitism = best carried unchanged
8// generation = one loop; best logged (lineage) termination = convergence streak
9// seeding = xorshift64 from cfg seed (FIXED-PER-RUN reproducibility law)
10// diversity = tournament_k tunes greed<->variety (the niching knob)
11// fitness 0 = -sphere(target) (unimodal, optimum known = target vector, fitness 0)
12// fitness 1 = -rastrigin-ish (multimodal: -(sum (g-t)^2 + 8*(g!=t)) -- many basins)
13// cfg ints: pop gens mut_rate_inv tour_k dims seed fitness_id target (mut_rate =
14// 1/mut_rate_inv per gene per gen). Durable: EVOLVE-GEN + EVOLVE rows ->
15// knowledge/status/evolve.log. Exit 0 = converged to optimum; 1 = did not.
16// argv[1..]=cfg override ints (gates drive scratch configs deterministically).
17// license_tier: ORIGINAL
18import "nx_syscalls.nx"
19import "nx_tool_run.nx" // tr_run_capture_to: the PROVEN fork+capture+timeout; never re-implemented here
20const K_MAGIC_1000000: i64 = 1000000
21const K_MAGIC_2463534242: i64 = 2463534242
22func _p(s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(1,s,n); return 0 }
23func _fp(fd: i64, s: *u8) -> i64 { var n: i64=0; while s[n]!=(0 as u8){n=n+1} sys_write(fd,s,n); return 0 }
24func _fn(fd: i64, v: i64) -> i64 { let bb: *u8=sys_mmap(28); var m: i64=v; if m<0{m=0-m; sys_write(fd,"-" as *u8,1)}; let t: *u8=sys_mmap(28); var k: i64=0; if m==0{t[0]=48 as u8;k=1}; while m>0{t[k]=(48+(m%10)) as u8;m=m/10;k=k+1}; var i: i64=0; while i<k{bb[i]=t[k-1-i];i=i+1}; sys_write(fd,bb,k); return 0 }
25func ev_atoi(s: *u8) -> i64 {
26 var i: i64 = 0
27 var neg: i64 = 0
28 if s[0] == (45 as u8) { neg = 1; i = 1 }
29 var v: i64 = 0
30 while s[i] >= (48 as u8) { if s[i] > (57 as u8) { i = i + K_MAGIC_1000000 } else { v = v * 10 + ((s[i] as i64) - 48); i = i + 1 } }
31 if neg == 1 { return 0 - v }
32 return v
33}
34// xorshift64* deterministic PRNG (state in s[0]); reproducibility law -- no clock
35func ev_rand(s: *i64) -> i64 {
36 var x: i64 = s[0]
37 x = x ^ (x << 13)
38 x = x ^ ((x >> 7) & 0x01FFFFFFFFFFFFFF)
39 x = x ^ (x << 17)
40 s[0] = x
41 var r: i64 = x
42 if r < 0 { r = 0 - r }
43 return r
44}
45// A FAILED EVALUATION MUST NEVER WIN SELECTION. This sentinel is worse than any reachable real
46// fitness, so an evaluator that times out, crashes or prints nothing loses every tournament instead
47// of quietly becoming the elite -- which is how a broken evaluator takes over a population.
48const EV_FIT_UNREADABLE: i64 = 0 - 1000000000
49const EV_EXT_TIMEOUT_MS: i64 = 60000
50const EV_EXT_OUTCAP: i64 = 65536
51// EXTERNAL FITNESS (kind 2) -- fork <cmd> with the genome as argv and read an ANCHORED score.
52// ★THE CONTRACT IS A NAMED FIELD, NOT 'the last number in the output'. A greedy trailing-integer
53// parse reads digits out of the evaluator's own prose; this estate has already been bitten by exactly
54// that. The evaluator MUST print FITNESS=<int> and we take the LAST such marker (positional anchoring,
55// the same discipline gv_last_line uses).
56// ★THE TIMEOUT IS MANDATORY: an evaluator that hangs hangs the entire search, and a search that never
57// returns is indistinguishable from one that is merely slow.
58// Convention matches the internal fitnesses: higher = better, 0 = optimum.
59func ev_fit_ext(g: *i64, dims: i64, cmd: *u8, pre: *i64) -> i64 {
60 // ★PREFIX ARGS COME BEFORE THE GENOME. A real evaluator needs context -- which mode, which target,
61 // which config -- and baking that into the harness would make the harness know about its
62 // evaluators, which is exactly backwards. `pre` is a NULL-TERMINATED array of char* passed
63 // straight through, so the caller decides the evaluator's grammar and this organ never guesses it.
64 var npre: i64 = 0
65 if (pre as i64) != 0 { while pre[npre] != 0 { npre = npre + 1 } }
66 let av: *i64 = sys_mmap(8 * (dims + npre + 2)) as *i64
67 let nums: *u8 = sys_mmap(32 * (dims + 1))
68 av[0] = cmd as i64
69 var pi: i64 = 0
70 while pi < npre { av[pi + 1] = pre[pi]; pi = pi + 1 }
71 var i: i64 = 0
72 var off: i64 = 0
73 while i < dims {
74 let base: i64 = nums as i64
75 let slot: *u8 = (base + off) as *u8
76 var m: i64 = g[i]
77 var neg: i64 = 0
78 if m < 0 { neg = 1; m = 0 - m }
79 let t: *u8 = sys_mmap(32)
80 var k: i64 = 0
81 if m == 0 { t[0] = 48 as u8; k = 1 }
82 while m > 0 { t[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
83 var w: i64 = 0
84 if neg == 1 { slot[0] = 45 as u8; w = 1 }
85 while k > 0 { k = k - 1; slot[w] = t[k]; w = w + 1 }
86 slot[w] = 0 as u8
87 av[npre + i + 1] = slot as i64
88 off = off + w + 1
89 i = i + 1
90 }
91 av[npre + dims + 1] = 0
92 let out: *u8 = sys_mmap(EV_EXT_OUTCAP)
93 let olen: *i64 = sys_mmap(16) as *i64
94 olen[0] = 0
95 if tr_run_capture_to(cmd, av, out, EV_EXT_OUTCAP, olen, EV_EXT_TIMEOUT_MS) != 0 { return EV_FIT_UNREADABLE }
96 if olen[0] <= 0 { return EV_FIT_UNREADABLE }
97 var pos: i64 = 0 - 1
98 var j: i64 = 0
99 while j + 8 <= olen[0] {
100 if out[j] == (70 as u8) { if out[j+1] == (73 as u8) { if out[j+2] == (84 as u8) {
101 if out[j+3] == (78 as u8) { if out[j+4] == (69 as u8) { if out[j+5] == (83 as u8) {
102 if out[j+6] == (83 as u8) { if out[j+7] == (61 as u8) { pos = j + 8 } } } } } } } }
103 j = j + 1
104 }
105 if pos < 0 { return EV_FIT_UNREADABLE }
106 var sg: i64 = 1
107 if pos < olen[0] { if out[pos] == (45 as u8) { sg = 0 - 1; pos = pos + 1 } }
108 var v: i64 = 0
109 var go: i64 = 1
110 while go == 1 {
111 if pos >= olen[0] { go = 0 } else {
112 let c: i64 = out[pos] as i64
113 if c >= 48 { if c <= 57 { v = v*10 + (c-48); pos = pos + 1 } else { go = 0 } } else { go = 0 }
114 }
115 }
116 return v * sg
117}
118// fitness: higher = better; optimum = 0 at g == target for kinds 0/1. kind 2 = EXTERNAL evaluator.
119func ev_fit(g: *i64, dims: i64, target: i64, kind: i64, cmd: *u8, pre: *i64) -> i64 {
120 if kind == 2 { return ev_fit_ext(g, dims, cmd, pre) }
121 var f: i64 = 0
122 var i: i64 = 0
123 while i < dims {
124 let d: i64 = g[i] - target
125 f = f - d * d
126 if kind == 1 { if d != 0 { f = f - 8 } }
127 i = i + 1
128 }
129 return f
130}
131func main(argc: i64, argv: *i64) -> i64 {
132 // ---- REFERENCE-FITNESS VERB: `nx_evolve fitness <kind> <target> <g0> <g1> ...` ----
133 // ★THE ORGAN CARRIES ITS OWN ORACLE. The external path (kind 2) is plumbing, and plumbing is only
134 // trustworthy if you can aim it at a fitness whose answer you already know. This verb exposes the
135 // INTERNAL fitness through the EXTERNAL contract, so a kind-2 run pointed at this very binary must
136 // reproduce a kind-0/1 run exactly. KEEP THE ORACLE RUNNABLE: a baseline you cannot re-run is a
137 // number, not a control.
138 if argc >= 4 {
139 let v0: *u8 = argv[1] as *u8
140 if v0[0] == (102 as u8) {
141 let fkind: i64 = ev_atoi(argv[2] as *u8)
142 let ftarget: i64 = ev_atoi(argv[3] as *u8)
143 let fdims: i64 = argc - 4
144 let fg: *i64 = sys_mmap(8 * (fdims + 1)) as *i64
145 var fi: i64 = 0
146 while fi < fdims { fg[fi] = ev_atoi(argv[fi + 4] as *u8); fi = fi + 1 }
147 _p("FITNESS=" as *u8); _fn(1, ev_fit(fg, fdims, ftarget, fkind, 0 as *u8, 0 as *i64)); _p("\n" as *u8)
148 sys_exit(0)
149 return 0
150 }
151 }
152 // defaults (overridable by argv for the gate's deterministic scratch runs)
153 var pop: i64 = 24
154 var gens: i64 = 120
155 var mut_inv: i64 = 4
156 var tour_k: i64 = 3
157 var dims: i64 = 6
158 var seed: i64 = K_MAGIC_2463534242
159 var kind: i64 = 1
160 var target: i64 = 7
161 if argc >= 2 { pop = ev_atoi(argv[1] as *u8) }
162 if argc >= 3 { gens = ev_atoi(argv[2] as *u8) }
163 if argc >= 4 { mut_inv = ev_atoi(argv[3] as *u8) }
164 if argc >= 5 { tour_k = ev_atoi(argv[4] as *u8) }
165 if argc >= 6 { dims = ev_atoi(argv[5] as *u8) }
166 if argc >= 7 { seed = ev_atoi(argv[6] as *u8) }
167 if argc >= 8 { kind = ev_atoi(argv[7] as *u8) }
168 if argc >= 9 { target = ev_atoi(argv[8] as *u8) }
169 // argv[9] = EXTERNAL evaluator path, required when fitness_id is 2.
170 // ★THE SEARCH SPACE IS NOT BAKED IN. Initial genes used to be (rand % 41) - 20 and mutation was a
171 // fixed +-1 -- fine for the toy fitnesses, and USELESS for any real evaluator whose genes live on a
172 // different scale: every candidate clamps to the bound, fitness goes flat, and the run LOOKS like a
173 // search while exploring nothing. A harness whose genome range is a literal can only ever search
174 // problems shaped like its own test.
175 var init_lo: i64 = 0 - 20
176 var init_hi: i64 = 20
177 var mut_step: i64 = 1
178 if argc >= 10 { init_lo = ev_atoi(argv[9] as *u8) }
179 if argc >= 11 { init_hi = ev_atoi(argv[10] as *u8) }
180 if argc >= 12 { mut_step = ev_atoi(argv[11] as *u8) }
181 if init_hi < init_lo { let sw: i64 = init_lo; init_lo = init_hi; init_hi = sw }
182 if mut_step < 1 { mut_step = 1 }
183 var extcmd: *u8 = 0 as *u8
184 if argc >= 13 { extcmd = argv[12] as *u8 }
185 // argv[13..] are PREFIX args handed to the evaluator before the genome, NULL-terminated.
186 let expre: *i64 = sys_mmap(8 * (argc + 2)) as *i64
187 var pn: i64 = 0
188 while 13 + pn < argc { expre[pn] = argv[13 + pn]; pn = pn + 1 }
189 expre[pn] = 0
190 if kind == 2 { if (extcmd as i64) == 0 {
191 // REFUSE rather than silently falling back to an internal fitness: a run that reports
192 // CONVERGED against a fitness the caller did not ask for is worse than no run at all.
193 _p(" EVOLVE REFUSE: fitness_id 2 is EXTERNAL and needs an evaluator path as argv[9]\n" as *u8)
194 sys_exit(2)
195 return 2
196 } }
197 if pop < 2 { pop = 2 }
198 if mut_inv < 1 { mut_inv = 1 }
199 if tour_k < 1 { tour_k = 1 }
200 _p("=== EVOLVE: EC machinery on a known-optimum fitness (validate, then gate-fit) ===\n" as *u8)
201 let s: *i64 = sys_mmap(16) as *i64
202 s[0] = seed
203 // population: pop x dims integers, init in [-20,20]
204 let g: *i64 = sys_mmap(8 * pop * dims) as *i64
205 let fit: *i64 = sys_mmap(8 * pop) as *i64
206 var p: i64 = 0
207 while p < pop {
208 var d: i64 = 0
209 while d < dims {
210 g[p * dims + d] = init_lo + (ev_rand(s) % (init_hi - init_lo + 1))
211 d = d + 1
212 }
213 fit[p] = ev_fit((g as i64 + p * dims * 8) as *i64, dims, target, kind, extcmd, expre)
214 p = p + 1
215 }
216 let lfd: i64 = sys_openat_append("knowledge/status/evolve.log" as *u8, 0x1a4)
217 if lfd < 0 { _p(" evolve log open failed\n" as *u8); sys_exit(1); return 1 }
218 _fp(lfd, "EVOLVE-RUN epoch=" as *u8); _fn(lfd, sys_now_realtime_sec())
219 _fp(lfd, " pop=" as *u8); _fn(lfd, pop); _fp(lfd, " mut_inv=" as *u8); _fn(lfd, mut_inv)
220 _fp(lfd, " tour_k=" as *u8); _fn(lfd, tour_k); _fp(lfd, " seed=" as *u8); _fn(lfd, seed)
221 _fp(lfd, " kind=" as *u8); _fn(lfd, kind); _fp(lfd, "\n" as *u8)
222 // gen-0 best (baseline for the improvement gate)
223 var best: i64 = fit[0]
224 var bi: i64 = 0
225 var q: i64 = 1
226 while q < pop { if fit[q] > best { best = fit[q]; bi = q } q = q + 1 }
227 let gen0best: i64 = best
228 let child: *i64 = sys_mmap(8 * pop * dims) as *i64
229 var stale: i64 = 0
230 var converged: i64 = 0
231 var gen: i64 = 0
232 while gen < gens {
233 // ELITISM: child slot 0 = current best, unchanged
234 var d0: i64 = 0
235 while d0 < dims { child[d0] = g[bi * dims + d0]; d0 = d0 + 1 }
236 // fill the rest by TOURNAMENT selection + MUTATION
237 var c: i64 = 1
238 while c < pop {
239 // tournament: best of tour_k random parents
240 var winner: i64 = ev_rand(s) % pop
241 var tk: i64 = 1
242 while tk < tour_k {
243 let challenger: i64 = ev_rand(s) % pop
244 if fit[challenger] > fit[winner] { winner = challenger }
245 tk = tk + 1
246 }
247 var d: i64 = 0
248 while d < dims {
249 var gv: i64 = g[winner * dims + d]
250 // MUTATION: at rate 1/mut_inv, perturb by +-1
251 if (ev_rand(s) % mut_inv) == 0 {
252 if (ev_rand(s) & 1) == 0 { gv = gv + mut_step } else { gv = gv - mut_step }
253 }
254 child[c * dims + d] = gv
255 d = d + 1
256 }
257 c = c + 1
258 }
259 // generational replace + re-evaluate
260 var x: i64 = 0
261 while x < pop * dims { g[x] = child[x]; x = x + 1 }
262 var nb: i64 = fit[0]
263 p = 0
264 while p < pop {
265 fit[p] = ev_fit((g as i64 + p * dims * 8) as *i64, dims, target, kind, extcmd, expre)
266 p = p + 1
267 }
268 nb = fit[0]; bi = 0
269 q = 1
270 while q < pop { if fit[q] > nb { nb = fit[q]; bi = q } q = q + 1 }
271 // CONVERGENCE: best unchanged for a streak
272 if nb <= best { stale = stale + 1 } else { stale = 0 }
273 best = nb
274 if (gen % 20) == 0 { _fp(lfd, "EVOLVE-GEN gen=" as *u8); _fn(lfd, gen); _fp(lfd, " best=" as *u8); _fn(lfd, best); _fp(lfd, "\n" as *u8) }
275 if best == 0 { converged = 1; gen = gens }
276 if stale >= 25 { gen = gens }
277 gen = gen + 1
278 }
279 _fp(lfd, "EVOLVE epoch=" as *u8); _fn(lfd, sys_now_realtime_sec())
280 _fp(lfd, " gen0_best=" as *u8); _fn(lfd, gen0best)
281 _fp(lfd, " final_best=" as *u8); _fn(lfd, best)
282 _fp(lfd, " optimum_found=" as *u8); _fn(lfd, converged)
283 if converged == 1 { _fp(lfd, " verdict=CONVERGED\n" as *u8) } else { _fp(lfd, " verdict=PARTIAL\n" as *u8) }
284 sys_close(lfd)
285 _p(" final_best=" as *u8); _fn(1, best); _p(" (0 = global optimum)\n" as *u8)
286 if converged == 1 { _p(" EVOLVE: CONVERGED to the known optimum (machinery validated)\n" as *u8); sys_exit(0); return 0 }
287 _p(" EVOLVE: PARTIAL (improved but not at optimum -- raise gens or tune knobs)\n" as *u8)
288 sys_exit(1)
289 return 1
290}