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}