OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-layer-discovery commits / 8dce47f

experiments/fractal: harness (graded connectors: backoff parse + tiered level-2 ranking)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8dce47fea17903e2a2974bc6d90736dfdea86dc3
by plankatron <fleeky@gmail.com> · 2026-10-18T20:02:19.000Z

Changed files (3) +298 -0

experiments/fractal/.gitignore +1 -0
……@@ -0,0 +1 @@
1fractal
experiments/fractal/fractal.c +285 -0
……@@ -0,0 +1,285 @@
1// Graded ("fractal") connectors: see PREREG.md. Reuses the planted grammar, window-pair gate and Eisner
2// parser of ../across-cluster/ac.c unchanged.
3//
4// fractal <seed> [--tiers 3000,10000,30000] [--heldout N]
5// Output rows:
6// P <seed> <tier> <scorer> <F1> <P> <R> level-1 link F1 on held-out
7// R <seed> <tier> <arm> <budget> <recall> <listlen> level-2 type recall at budget (budget -1 = all)
8
9#define main ac_main
10#include "../across-cluster/ac.c"
11#undef main
12
13#define NLEV 4 // W, G, S1, S2
14#define LMAX 6
15
16// ---------------------------------------------------------------- hierarchy (level2/l2.c merge_class)
17
18static int is_noun (int c) { return c <= N_THING_PL; }
19static int is_verb (int c) { return c >= V_I_SG && c <= V_SAY_PL; }
20static int is_func (int c) { return (c >= DET_SG && c <= DET_ANY) || c == PRON_SG || c == PRON_PL || c == PREP_LOC || c == PREP_WITH || c == COMP; }
21
22static int merge_class (int c, int level) {
23 if (level >= 1) {
24 if (is_noun(c) || is_verb(c)) c -= (c - (is_noun(c) ? N_ANIM_SG : V_I_SG)) % 2;
25 if (c >= DET_SG && c <= DET_ANY) c = DET_SG;
26 if (c == PRON_PL) c = PRON_SG;
27 }
28 if (level >= 2) {
29 if (is_noun(c)) c = N_ANIM_SG;
30 if (is_verb(c)) c = V_I_SG;
31 }
32 if (level >= 3 && is_func(c)) c = DET_SG;
33 return c;
34}
35
36// Level symbols for every token: level 0 = word, 1 = class, 2/3 = coarser. `cls` gives the level-1 class
37// of a token (gold: the token's class; random: Rmap of its word); levels 2-3 merge it like S1/S2.
38static void level_symbols (const corpus_t *C, const int *rmap, int lev, int *out) {
39 for (int t = 0; t < C->ntok; t++) {
40 if (lev == 0) { out[t] = C->w[t]; continue; }
41 int c = rmap ? rmap[C->w[t]] : C->c[t];
42 out[t] = lev == 1 ? c : merge_class(c, lev - 1); // lev 2 = S1, lev 3 = S2
43 }
44}
45
46// ---------------------------------------------------------------- per-level scores
47
48typedef struct { int A[NLEV]; double *S[NLEV]; int *ho[NLEV]; } hier_t;
49
50static corpus_t with_symbols (const corpus_t *C, int *sym) { corpus_t S = *C; S.c = sym; return S; }
51
52static void build_hier (hier_t *H, const corpus_t *tr, const corpus_t *ho, const int *rmap) {
53 int *st = malloc(sizeof(int) * tr->ntok);
54 for (int l = 0; l < NLEV; l++) {
55 H->A[l] = l == 0 ? V : NCLS;
56 level_symbols(tr, rmap, l, st);
57 corpus_t ts = with_symbols(tr, st);
58 arm_t a = { "lev", H->A[l], NULL, 1 };
59 pairs_t P = count_pairs(&a, &ts);
60 long ev, acc;
61 H->S[l] = hard_scores(&P, &ev, &acc);
62 free_pairs(&P);
63 H->ho[l] = malloc(sizeof(int) * ho->ntok);
64 level_symbols(ho, rmap, l, H->ho[l]);
65 }
66 free(st);
67}
68
69static void free_hier (hier_t *H) { for (int l = 0; l < NLEV; l++) { free(H->S[l]); free(H->ho[l]); } }
70
71// Edge scorers. ctx = { hier, symbols-per-level for the corpus being parsed, mode }.
72typedef struct { hier_t *H; int *sym[NLEV]; int mode; } edge_ctx_t; // mode 0..3 = single level, 4 = backoff
73
74static double edge_level (const corpus_t *C, int o, int i, int j, void *vctx, int window) {
75 (void) C; (void) window;
76 edge_ctx_t *e = vctx;
77 if (e->mode < NLEV) {
78 int l = e->mode;
79 return e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]];
80 }
81 for (int l = 0; l < NLEV; l++) { // backoff: the finest level that passes the gate
82 double s = e->H->S[l][(size_t) e->sym[l][o + i] * e->H->A[l] + e->sym[l][o + j]];
83 if (s > NEG) return s;
84 }
85 return NEG;
86}
87
88// Parse every sentence; head[t] = in-sentence offset of head, or -1 (as level2 parse_all).
89static void parse_heads (const corpus_t *C, edge_ctx_t *e, int *head) {
90 int hd[MAXN + 1];
91 for (int s = 0; s < C->ns; s++) {
92 int o = C->off[s], n = C->off[s + 1] - o, m = n + 1;
93 for (int h = 0; h < m; h++) for (int d = 0; d < m; d++) sc[h][d] = NEG;
94 for (int d = 1; d < m; d++) sc[0][d] = 0;
95 for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++)
96 sc[i + 1][j + 1] = sc[j + 1][i + 1] = edge_level(C, o, i, j, e, 1);
97 eisner(m, hd);
98 for (int d = 1; d < m; d++) head[o + d - 1] = hd[d] - 1;
99 }
100}
101
102static int connected (const int *head, int o, int i, int j) {
103 int e = 0;
104 for (int k = i; k <= j; k++) { int h = head[o + k]; if (h >= i && h <= j) e++; }
105 return e == j - i;
106}
107
108static int complete (const int *head, int o, int n, int i, int j) {
109 if (!connected(head, o, i, j)) return 0;
110 for (int k = 0; k < n; k++) { if (k >= i && k <= j) continue; int h = head[o + k]; if (h >= i && h <= j) return 0; }
111 return 1;
112}
113
114static int structure (const int *head, int o, int n, int i, int j) { return complete(head, o, n, i, j) ? 2 : connected(head, o, i, j) ? 1 : 0; }
115
116// ---------------------------------------------------------------- level-2 type table (G-class sequences)
117
118#define NARM 3 // tiering sources: 0 = backoff (gold hierarchy), 1 = G-level parse, 2 = backoff (random hierarchy)
119
120typedef struct { uint64_t key; double n, ncomp_g; int tier[NARM]; int used; } type_t;
121static type_t *types;
122static size_t tmask2;
123
124static uint64_t type_key (const int *cls, int i, int len) {
125 uint64_t k = (uint64_t) len;
126 for (int q = 0; q < len; q++) k = k * 32 + (uint64_t) cls[i + q];
127 return k;
128}
129
130static type_t *type_get (uint64_t key, int create) {
131 size_t i = (size_t) (key * 0x9e3779b97f4a7c15ULL) & tmask2;
132 while (types[i].used) {
133 if (types[i].key == key) return &types[i];
134 i = (i + 1) & tmask2;
135 }
136 if (!create) return NULL;
137 memset(&types[i], 0, sizeof(type_t));
138 types[i].used = 1; types[i].key = key;
139 return &types[i];
140}
141
142static int gold_spans (const corpus_t *C, int s, int *gi, int *gj) {
143 int o = C->off[s], n = C->off[s + 1] - o, lo[MAXN], hi[MAXN], k = 0;
144 for (int t = 0; t < n; t++) lo[t] = hi[t] = t;
145 for (int t = 0; t < n; t++) for (int h = C->h[o + t]; h >= 0; h = C->h[o + h]) {
146 if (t < lo[h]) lo[h] = t;
147 if (t > hi[h]) hi[h] = t;
148 }
149 for (int t = 0; t < n; t++) {
150 int c = C->c[o + t];
151 if (!(is_noun(c) || c == PREP_LOC || c == PREP_WITH) || hi[t] == lo[t] || hi[t] - lo[t] + 1 > LMAX) continue;
152 gi[k] = lo[t]; gj[k] = hi[t]; k++;
153 }
154 return k;
155}
156
157typedef struct { type_t *t; double k1, k2; } rank_t;
158
159static int by_key (const void *a, const void *b) {
160 const rank_t *x = a, *y = b;
161 if (x->k1 != y->k1) return x->k1 > y->k1 ? -1 : 1;
162 if (x->k2 != y->k2) return x->k2 > y->k2 ? -1 : 1;
163 return (x->t->key > y->t->key) - (x->t->key < y->t->key);
164}
165
166static const long fbudgets[] = { 300, 1000, 3000, 10000, -1 };
167#define NBUD 5
168
169// ---------------------------------------------------------------- main
170
171int main (int argc, char **argv) {
172 if (argc < 2) { fprintf(stderr, "usage: fractal <seed> [--tiers a,b] [--heldout N]\n"); return 1; }
173 int seed = atoi(argv[1]);
174 int tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000;
175 for (int i = 2; i < argc; i++) {
176 if (!strcmp(argv[i], "--heldout") && i + 1 < argc) nho = atoi(argv[++i]);
177 else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) {
178 ntier = 0;
179 for (char *p = strtok(argv[++i], ","); p && ntier < 16; p = strtok(NULL, ",")) tiers[ntier++] = atoi(p);
180 }
181 }
182 setvbuf(stdout, NULL, _IOLBF, 0);
183 build_world();
184 int maxt = 0;
185 for (int i = 0; i < ntier; i++) if (tiers[i] > maxt) maxt = tiers[i];
186 corpus_t full = gen_corpus(1000003ULL * seed + 1, maxt);
187 corpus_t ho = gen_corpus(1000003ULL * seed + 2, nho);
188
189 // Random hierarchy: words relabelled into classes with the gold class sizes (as level2's R).
190 int *rmap = malloc(sizeof(int) * V), *perm = malloc(sizeof(int) * V);
191 rng_t rr = { 777ULL * seed + 1 };
192 for (int w = 0; w < V; w++) perm[w] = w;
193 for (int w = V - 1; w > 0; w--) { int j = rpick(&rr, w + 1), t = perm[w]; perm[w] = perm[j]; perm[j] = t; }
194 for (int w = 0; w < V; w++) rmap[w] = primary[perm[w]];
195
196 tmask2 = (1 << 20) - 1;
197 types = malloc(sizeof(type_t) * (tmask2 + 1));
198
199 for (int ti = 0; ti < ntier; ti++) {
200 int tier = tiers[ti];
201 corpus_t tr = prefix(full, tier);
202 hier_t Hg, Hr;
203 build_hier(&Hg, &tr, &ho, NULL);
204 build_hier(&Hr, &tr, &ho, rmap);
205
206 // Part 1: held-out link F1 per scorer.
207 const char *pname[5] = { "W", "G", "S1", "S2", "BACKOFF" };
208 for (int mode = 0; mode <= NLEV; mode++) {
209 edge_ctx_t e = { &Hg, { Hg.ho[0], Hg.ho[1], Hg.ho[2], Hg.ho[3] }, mode };
210 link_t L = parse_eval(&ho, edge_level, &e, 1);
211 double p = L.pred ? (double) L.tp / L.pred : 0, r = L.gold ? (double) L.tp / L.gold : 0;
212 printf("P\t%d\t%d\t%s\t%.5f\t%.5f\t%.5f\n", seed, tier, pname[mode], p + r > 0 ? 2 * p * r / (p + r) : 0, p, r);
213 }
214
215 // Part 2: parse the training prefix three ways, collect G-class span types with their best structure.
216 int *sym[NLEV], *symr[NLEV];
217 for (int l = 0; l < NLEV; l++) {
218 sym[l] = malloc(sizeof(int) * tr.ntok); level_symbols(&tr, NULL, l, sym[l]);
219 symr[l] = malloc(sizeof(int) * tr.ntok); level_symbols(&tr, rmap, l, symr[l]);
220 }
221 int *hb = malloc(sizeof(int) * tr.ntok), *hgp = malloc(sizeof(int) * tr.ntok), *hrb = malloc(sizeof(int) * tr.ntok);
222 edge_ctx_t eb = { &Hg, { sym[0], sym[1], sym[2], sym[3] }, NLEV };
223 edge_ctx_t eg = { &Hg, { sym[0], sym[1], sym[2], sym[3] }, 1 };
224 edge_ctx_t er = { &Hr, { symr[0], symr[1], symr[2], symr[3] }, NLEV };
225 parse_heads(&tr, &eb, hb); parse_heads(&tr, &eg, hgp); parse_heads(&tr, &er, hrb);
226
227 memset(types, 0, sizeof(type_t) * (tmask2 + 1));
228 for (int s = 0; s < tr.ns; s++) {
229 int o = tr.off[s], n = tr.off[s + 1] - o;
230 for (int i = 0; i < n; i++) for (int len = 2; len <= LMAX && i + len <= n; len++) {
231 int j = i + len - 1;
232 type_t *t = type_get(type_key(tr.c + o, i, len), 1);
233 t->n += 1;
234 int sb = structure(hb, o, n, i, j), sg = structure(hgp, o, n, i, j), sr = structure(hrb, o, n, i, j);
235 if (sb > t->tier[0]) t->tier[0] = sb;
236 if (sg > t->tier[1]) t->tier[1] = sg;
237 if (sr > t->tier[2]) t->tier[2] = sr;
238 if (sg == 2) t->ncomp_g += 1;
239 }
240 }
241 size_t nt = 0;
242 for (size_t k = 0; k <= tmask2; k++) if (types[k].used) nt++;
243 rank_t *rk = malloc(sizeof(rank_t) * nt);
244
245 // Held-out gold span types.
246 size_t ng = 0, capg = 4096;
247 uint64_t *gkeys = malloc(sizeof(uint64_t) * capg);
248 int gi[MAXN], gj[MAXN];
249 for (int s = 0; s < ho.ns; s++) {
250 int k = gold_spans(&ho, s, gi, gj), o = ho.off[s];
251 for (int q = 0; q < k; q++) {
252 if (ng == capg) { capg *= 2; gkeys = realloc(gkeys, sizeof(uint64_t) * capg); }
253 gkeys[ng++] = type_key(ho.c + o, gi[q], gj[q] - gi[q] + 1);
254 }
255 }
256
257 const char *aname[5] = { "C-G", "HARD", "GRADED", "GRADED-G", "GRADED-R" };
258 for (int arm = 0; arm < 5; arm++) {
259 size_t m = 0;
260 for (size_t k = 0; k <= tmask2; k++) {
261 type_t *t = &types[k];
262 if (!t->used) continue;
263 if (arm == 1 && t->tier[1] < 2) continue; // HARD deletes non-assemblies
264 double k1 = arm == 0 ? t->n : arm == 1 ? t->ncomp_g : arm == 2 ? t->tier[0] : arm == 3 ? t->tier[1] : t->tier[2];
265 double k2 = arm >= 2 ? t->n : 0;
266 rk[m++] = (rank_t) { t, k1, k2 };
267 }
268 qsort(rk, m, sizeof(rank_t), by_key);
269 for (int b = 0; b < NBUD; b++) {
270 long B = fbudgets[b] < 0 || (size_t) fbudgets[b] > m ? (long) m : fbudgets[b];
271 // mark the first B types of this list (used = 2), count held-out gold spans whose type is marked
272 for (size_t q = 0; q < m; q++) rk[q].t->used = q < (size_t) B ? 2 : 1;
273 long hit = 0;
274 for (size_t g = 0; g < ng; g++) { type_t *t = type_get(gkeys[g], 0); if (t && t->used == 2) hit++; }
275 for (size_t q = 0; q < m; q++) rk[q].t->used = 1;
276 printf("R\t%d\t%d\t%s\t%ld\t%.5f\t%zu\n", seed, tier, aname[arm], fbudgets[b], ng ? (double) hit / ng : 0, m);
277 if (fbudgets[b] < 0) break;
278 }
279 }
280 free(rk); free(gkeys); free(hb); free(hgp); free(hrb);
281 for (int l = 0; l < NLEV; l++) { free(sym[l]); free(symr[l]); }
282 free_hier(&Hg); free_hier(&Hr);
283 }
284 return 0;
285}
experiments/fractal/run.sh +12 -0
……@@ -0,0 +1,12 @@
1#!/bin/sh
2# Full pre-registered run: 10 seeds x tiers 3K,10K,30K in parallel. Output: results/<stamp>/seed-N.tsv
3set -e
4cd "$(dirname "$0")"
5gcc -O2 -Wall -Wno-unused-function -o fractal fractal.c -lm 2>/dev/null
6out=results/$(date +%s)
7mkdir -p "$out"
8git rev-parse HEAD > "$out/commit.txt"
9sha256sum fractal.c ../across-cluster/ac.c > "$out/src.sha256"
10for s in 1 2 3 4 5 6 7 8 9 10; do ./fractal $s > "$out/seed-$s.tsv" & done
11wait
12echo "$out"