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}