code wiki / _hdl_build / nx_eco_graph_forest.nx

nx_eco_graph_forest.nx source

↩ module page · 195 lines · 8528 B

1// nx_eco_graph_forest.nx -- THE WHOLE TREE. Renders the ENTIRE ecosystem graph (all ~16k organs, all 2// ~42k import edges) as one generational roots-to-god raster: god/roots at the apex, every organ placed 3// by its longest-path-to-god generation, every import edge a ribbon converging upward. NOT a per-organ 4// sample (operator 2026-07-16: "you just sampled not the whole tree") -- this IS the forest. Dense 5// generations render as honest density bands; hubs (high Ca) get size + labels. DEFLATE PNG via nx_png. 6// Usage: nx_eco_graph_forest <store-prefix> <out.png> [width] license_tier: ORIGINAL expect_exit:0 7import "nx_syscalls.nx" 8import "nx_eco_graph.nx" 9import "nx_raster2d_lib.nx" 10import "nx_png.nx" 11const FO_MAGIC_20000: i64 = 20000 12 13const FO_LAYH: i64 = 190 // tall bands: the forest needs inter-generation air for 42k edge ribbons 14const FO_DEFW: i64 = 8000 15 16func foslen(s: *u8) -> i64 { var n:i64=0; while s[n]!=(0 as u8){n=n+1} return n } 17func fonum(v: i64) -> i64 { let b:*u8=sys_mmap(24); var m:i64=v; if m<0{sys_write(1,"-" as *u8,1);m=0-m} let t:*u8=sys_mmap(24); 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 j:i64=0; while j<k{b[j]=t[k-1-j];j=j+1} sys_write(1,b,k); return 0 } 18func fow(s: *u8) -> i64 { sys_write(1, s, foslen(s)); return 0 } 19 20func main(argc: i64, argv: *i64) -> i64 { 21 if argc < 3 { fow("usage: nx_eco_graph_forest <store-prefix> <out.png> [width]\n" as *u8); return 2 } 22 var W: i64 = FO_DEFW 23 if argc >= 4 { 24 // parse width 25 let ws: *u8 = argv[3] as *u8 26 var wv: i64 = 0 27 var wi: i64 = 0 28 while ws[wi] != (0 as u8) { wv = wv*10 + ((ws[wi] as i64) - 48); wi = wi + 1 } 29 if wv >= 1000 { if wv <= FO_MAGIC_20000 { W = wv } } 30 } 31 let g: *EcoGraph = eg_load(argv[1] as *u8) 32 if (g as i64) == 0 { fow("ERROR store not found\n" as *u8); return 3 } 33 let n: i64 = g.node_count 34 35 // ---- generation = SHORTEST import-hops to a root ("one import-hop closer to god"), multi-source 36 // BFS from the true roots (out-degree 0). CYCLE-SAFE BY CONSTRUCTION: the full 16k-organ graph 37 // contains import cycles (longest-path relax diverged to gen=32449 > n and a 216GB canvas -> the 38 // first forest attempt SIGSEGVed). BFS min-levels terminate on any digraph. Unreached nodes (cycle 39 // islands with no root path) get parked one band past maxgen. ---- 40 let gen: *i64 = sys_mmap((n+2)*8) as *i64 41 let work: *i64 = sys_mmap((n+2)*8) as *i64 42 var i: i64 = 0 43 while i < n { gen[i] = 0 - 1; i = i + 1 } 44 var wt: i64 = 0 45 i = 0 46 while i < n { 47 if g.out_head[i] == g.out_head[i+1] { gen[i] = 0; work[wt] = i; wt = wt + 1 } 48 i = i + 1 49 } 50 var wh: i64 = 0 51 while wh < wt { 52 let v: i64 = work[wh] 53 var p: i64 = g.in_head[v]; let e: i64 = g.in_head[v+1] 54 while p < e { 55 let u: i64 = g.in_list[p] 56 if gen[u] < 0 { gen[u] = gen[v] + 1; work[wt] = u; wt = wt + 1 } 57 p = p + 1 58 } 59 wh = wh + 1 60 } 61 var maxgen: i64 = 0 62 i = 0; while i < n { if gen[i] > maxgen { maxgen = gen[i] } i = i + 1 } 63 var unreached: i64 = 0 64 i = 0 65 while i < n { if gen[i] < 0 { gen[i] = maxgen + 1; unreached = unreached + 1 } i = i + 1 } 66 if unreached > 0 { maxgen = maxgen + 1 } 67 fow("bfs: reached=" as *u8); fonum(wt); fow(" cyclic-unreached=" as *u8); fonum(unreached); fow("\n" as *u8) 68 69 // ---- per-generation counts + running slot -> x position ---- 70 let gcount: *i64 = sys_mmap((maxgen+3)*8) as *i64 71 let gslot: *i64 = sys_mmap((maxgen+3)*8) as *i64 72 var l: i64 = 0 73 while l <= maxgen { gcount[l] = 0; gslot[l] = 0; l = l + 1 } 74 i = 0; while i < n { gcount[gen[i]] = gcount[gen[i]] + 1; i = i + 1 } 75 var gmax: i64 = 0 76 l = 0; while l <= maxgen { if gcount[l] > gmax { gmax = gcount[l] } l = l + 1 } 77 let px: *i64 = sys_mmap((n+2)*8) as *i64 78 let py: *i64 = sys_mmap((n+2)*8) as *i64 79 i = 0 80 while i < n { 81 let gl: i64 = gen[i] 82 px[i] = 70 + (gslot[gl]+1) * (W - 140) / (gcount[gl]+1) 83 py[i] = 54 + gl * FO_LAYH 84 gslot[gl] = gslot[gl] + 1 85 i = i + 1 86 } 87 // ---- barycenter ordering (Sugiyama median heuristic, bucket-sorted): re-place each generation 88 // g>=1 so a node sits near the average x of its LOWER-gen imports -> edges bundle instead of 89 // crossing the full canvas. Bucket sort by barycenter (buckets = x pixels), then spread evenly. ---- 90 let bh: *i64 = sys_mmap((W+2)*8) as *i64 91 let bnext: *i64 = sys_mmap((n+2)*8) as *i64 92 var gpass: i64 = 1 93 while gpass <= maxgen { 94 var bi: i64 = 0 95 while bi < W { bh[bi] = 0 - 1; bi = bi + 1 } 96 i = 0 97 while i < n { 98 if gen[i] == gpass { 99 var sum: i64 = 0 100 var cnt: i64 = 0 101 var p: i64 = g.out_head[i]; let e: i64 = g.out_head[i+1] 102 while p < e { 103 let d: i64 = g.out_list[p] 104 if gen[d] < gpass { sum = sum + px[d]; cnt = cnt + 1 } 105 p = p + 1 106 } 107 var bx: i64 = px[i] 108 if cnt > 0 { bx = sum / cnt } 109 if bx < 0 { bx = 0 } 110 if bx >= W { bx = W - 1 } 111 bnext[i] = bh[bx] 112 bh[bx] = i 113 i = i + 1 114 } else { i = i + 1 } 115 } 116 var slot: i64 = 0 117 bi = 0 118 while bi < W { 119 var cur: i64 = bh[bi] 120 while cur >= 0 { 121 px[cur] = 70 + (slot+1) * (W - 140) / (gcount[gpass]+1) 122 slot = slot + 1 123 cur = bnext[cur] 124 } 125 bi = bi + 1 126 } 127 gpass = gpass + 1 128 } 129 let H: i64 = 54 + maxgen * FO_LAYH + 60 130 131 fow("forest: nodes=" as *u8); fonum(n); fow(" gens=" as *u8); fonum(maxgen+1); fow(" densest-gen=" as *u8); fonum(gmax); fow(" canvas=" as *u8); fonum(W); fow("x" as *u8); fonum(H); fow("\n" as *u8) 132 133 // ---- canvas ---- 134 let fb: *i64 = sys_mmap(W * H * 8 + 64) as *i64 135 r2_fill_rect(fb, W, H, 0, 0, W, H, 11, 14, 22) 136 var bl: i64 = 0 137 while bl <= maxgen { 138 if bl % 2 == 0 { r2_fill_rect(fb, W, H, 0, 2 + bl*FO_LAYH, W, FO_LAYH, 21, 32, 54) } 139 var lx: i64 = 14 140 r2_char(fb, W, H, lx, 50 + bl*FO_LAYH, 103, 60, 74, 102, 2); lx = lx + 12 141 r2_char(fb, W, H, lx, 50 + bl*FO_LAYH, 101, 60, 74, 102, 2); lx = lx + 12 142 r2_char(fb, W, H, lx, 50 + bl*FO_LAYH, 110, 60, 74, 102, 2); lx = lx + 16 143 let gnum: i64 = maxgen - bl 144 if gnum >= 10 { r2_char(fb, W, H, lx, 50 + bl*FO_LAYH, 48 + gnum/10, 60, 74, 102, 2); lx = lx + 12 } 145 r2_char(fb, W, H, lx, 50 + bl*FO_LAYH, 48 + gnum%10, 60, 74, 102, 2) 146 bl = bl + 1 147 } 148 // ---- ALL edges as an ADDITIVE DENSITY MAP: one edge is faint (+8,+13,+21), bundles glow ---- 149 var edges: i64 = 0 150 i = 0 151 while i < n { 152 var p: i64 = g.out_head[i]; let e: i64 = g.out_head[i+1] 153 while p < e { 154 let d: i64 = g.out_list[p] 155 r2_ribbon_add(fb, W, H, px[i], py[i], px[d], py[d], 8, 13, 21) 156 edges = edges + 1 157 p = p + 1 158 } 159 i = i + 1 160 } 161 // ---- ALL nodes: dot size by Ca; roots gold; hubs bright ---- 162 i = 0 163 while i < n { 164 let ca: i64 = eg_ca(g, i) 165 var r: i64 = 1 166 if ca > 3 { r = 2 } 167 if ca > 12 { r = 3 } 168 if ca > 40 { r = 5 } 169 if ca > 150 { r = 7 } 170 if ca > 1000 { r = 11 } 171 var cr: i64 = 111; var cg: i64 = 139; var cb: i64 = 216 172 if ca >= 20 { cr = 143; cg = 180; cb = 255 } 173 if gen[i] == 0 { cr = 255; cg = 212; cb = 121; if r < 2 { r = 2 } } 174 if r <= 1 { r2_px(fb, W, H, px[i], py[i], cr, cg, cb) } else { 175 r2_fill_disk(fb, W, H, px[i], py[i], r, cr, cg, cb) 176 if r >= 5 { r2_ring(fb, W, H, px[i], py[i], r, 10, 13, 22) } 177 } 178 i = i + 1 179 } 180 // ---- labels for the hubs (Ca >= 120), y-staggered 3 rows to dodge collisions ---- 181 var labels: i64 = 0 182 i = 0 183 while i < n { 184 if eg_ca(g, i) >= 120 { 185 let sy: i64 = py[i] - 30 - (labels % 3) * 18 186 r2_name(fb, W, H, g, i, px[i], sy, 222, 230, 245, 2) 187 labels = labels + 1 188 } 189 i = i + 1 190 } 191 192 write_png(fb, W, H, argv[2] as *u8) 193 fow("wrote FOREST png edges=" as *u8); fonum(edges); fow(" labels=" as *u8); fonum(labels); fow(" -> " as *u8); fow(argv[2] as *u8); fow("\n" as *u8) 194 return 0 195}