OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-layer-discovery commits / 0c796d3

experiments/level2: Test A-U — undirected FOREST/MST/MPG parses, uconn/ucomp rules, link F1 (l2 --undirected)
Smoke seed 1, 3K: R0 PASS (Test A lines unchanged), C5-U PASS (5002/5002). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
0c796d37395f40bb4c05d26b200ef94093594cfb
by plankatron <fleeky@gmail.com> · 2026-10-18T23:25:09.000Z

Changed files (3) +224 -3

experiments/level2/l2.c +158 -3
……@@ -114,6 +114,108 @@ static int complete (const int *head, int o, int n, int i, int j) {
114114 return 1;
115115}
116116
117// ---------------------------------------------------------------- Test A-U (PREREG-U.md): undirected parses
118
119#define MAXL 24
120typedef struct { int *lnk, *nl; } parse_t; // per token: in-sentence offsets of the tokens it is linked to
121
122static parse_t parse_alloc (int ntok) { parse_t P = { malloc(sizeof(int) * ntok * MAXL), malloc(sizeof(int) * ntok) }; return P; }
123static void parse_clear (parse_t *P, int o, int n) { for (int t = 0; t < n; t++) P->nl[o + t] = 0; }
124static int linked (const parse_t *P, int o, int a, int b) { for (int k = 0; k < P->nl[o + a]; k++) if (P->lnk[(o + a) * MAXL + k] == b) return 1; return 0; }
125static void add_link (parse_t *P, int o, int a, int b) {
126 if (a == b || linked(P, o, a, b) || P->nl[o + a] >= MAXL || P->nl[o + b] >= MAXL) return;
127 P->lnk[(o + a) * MAXL + P->nl[o + a]++] = b; P->lnk[(o + b) * MAXL + P->nl[o + b]++] = a;
128}
129
130static void links_from_heads (const corpus_t *C, const int *head, parse_t *P) {
131 for (int s = 0; s < C->ns; s++) {
132 int o = C->off[s], n = C->off[s + 1] - o;
133 parse_clear(P, o, n);
134 for (int t = 0; t < n; t++) if (head[o + t] >= 0) add_link(P, o, t, head[o + t]);
135 }
136}
137
138// Ungated ordered-pair MI over window counts; pairs never counted get -10.
139static double *pair_mi (const pairs_t *P) {
140 double *M = malloc(sizeof(double) * P->A * P->A);
141 for (int a = 0; a < P->A; a++) for (int b = 0; b < P->A; b++) {
142 double n = P->M[(size_t) a * P->A + b];
143 M[(size_t) a * P->A + b] = n > 0 ? log2(n * P->N / (P->R[a] * P->Cm[b])) : -10;
144 }
145 return M;
146}
147
148// MST: projective spanning tree (Eisner, root edges -1e6 so one root child); MPG: + non-crossing MI > 0 links.
149typedef struct { int i, j; double v; } cl_t;
150static int cl_cmp (const void *a, const void *b) {
151 const cl_t *x = a, *y = b;
152 if (x->v != y->v) return x->v > y->v ? -1 : 1;
153 return x->i != y->i ? x->i - y->i : x->j - y->j;
154}
155static void parse_u (const corpus_t *S, const double *MI, int A, int mpg, parse_t *P) {
156 int hd[MAXN + 1];
157 static cl_t cand[MAXN * W];
158 for (int s = 0; s < S->ns; s++) {
159 int o = S->off[s], n = S->off[s + 1] - o, m = n + 1, nc = 0;
160 for (int h = 0; h < m; h++) for (int d = 0; d < m; d++) sc[h][d] = NEG;
161 for (int d = 1; d < m; d++) sc[0][d] = -1e6;
162 for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++)
163 sc[i + 1][j + 1] = sc[j + 1][i + 1] = MI[(size_t) S->c[o + i] * A + S->c[o + j]];
164 eisner(m, hd);
165 parse_clear(P, o, n);
166 for (int d = 1; d < m; d++) if (hd[d] > 0) add_link(P, o, d - 1, hd[d] - 1);
167 if (!mpg) continue;
168 for (int i = 0; i < n; i++) for (int j = i + 1; j < n && j - i <= W; j++) {
169 double v = MI[(size_t) S->c[o + i] * A + S->c[o + j]];
170 if (v > 0 && !linked(P, o, i, j)) cand[nc++] = (cl_t) { i, j, v };
171 }
172 qsort(cand, nc, sizeof(cl_t), cl_cmp);
173 for (int c = 0; c < nc; c++) {
174 int i = cand[c].i, j = cand[c].j, ok = 1;
175 for (int a = 0; a < n && ok; a++) for (int k = 0; k < P->nl[o + a] && ok; k++) {
176 int b = P->lnk[(o + a) * MAXL + k];
177 if (a < b && ((a < i && i < b && b < j) || (i < a && a < j && j < b))) ok = 0;
178 }
179 if (ok) add_link(P, o, i, j);
180 }
181 }
182}
183
184// uconn: links inside [i, j] connect it. ucomp: uconn and at most one link leaves the span.
185static int uconn (const parse_t *P, int o, int i, int j) {
186 int seen[MAXN] = { 0 }, stack[MAXN], sp = 0, cnt = 1;
187 seen[i] = 1; stack[sp++] = i;
188 while (sp) {
189 int a = stack[--sp];
190 for (int k = 0; k < P->nl[o + a]; k++) {
191 int b = P->lnk[(o + a) * MAXL + k];
192 if (b >= i && b <= j && !seen[b]) { seen[b] = 1; cnt++; stack[sp++] = b; }
193 }
194 }
195 return cnt == j - i + 1;
196}
197static int ucomp (const parse_t *P, int o, int i, int j) {
198 if (!uconn(P, o, i, j)) return 0;
199 int cross = 0;
200 for (int a = i; a <= j; a++) for (int k = 0; k < P->nl[o + a]; k++) { int b = P->lnk[(o + a) * MAXL + k]; cross += b < i || b > j; }
201 return cross <= 1;
202}
203
204// Undirected link precision / recall / F1 of P on C against the gold tree.
205static void link_prf (const corpus_t *C, const parse_t *P, double *pr, double *rc, double *f1) {
206 long pred = 0, gold = 0, tp = 0;
207 for (int s = 0; s < C->ns; s++) {
208 int o = C->off[s], n = C->off[s + 1] - o;
209 for (int t = 0; t < n; t++) {
210 for (int k = 0; k < P->nl[o + t]; k++) pred += P->lnk[(o + t) * MAXL + k] > t;
211 int h = C->h[o + t];
212 if (h >= 0) { gold++; tp += linked(P, o, t, h); }
213 }
214 }
215 *pr = pred ? (double) tp / pred : 0; *rc = gold ? (double) tp / gold : 0;
216 *f1 = *pr + *rc > 0 ? 2 * *pr * *rc / (*pr + *rc) : 0;
217}
218
117219// ---------------------------------------------------------------- span-type table
118220
119221typedef struct { uint16_t s[LMAX]; uint8_t len, used; uint32_t cnum, hard, first; float soft; } span_t; // hard/first/soft: Test A-soft
……@@ -192,9 +294,14 @@ static int gold_spans (const corpus_t *C, int s, br_t *g, int *n_le6) {
192294
193295// ---------------------------------------------------------------- one arm (Test A: the search space)
194296
195enum { RULE_ALL, RULE_CONN, RULE_COMP };
297enum { RULE_ALL, RULE_CONN, RULE_COMP, RULE_UCONN, RULE_UCOMP };
196298
299static const parse_t *u_tr, *u_ho; // undirected parses for RULE_UCONN / RULE_UCOMP (set by the caller)
197300static int rule_ok (int rule, const int *head, int o, int n, int i, int j) {
301 if (rule == RULE_UCONN || rule == RULE_UCOMP) {
302 const parse_t *P = head == (const int *) u_tr ? u_tr : u_ho;
303 return rule == RULE_UCONN ? uconn(P, o, i, j) : ucomp(P, o, i, j);
304 }
198305 return rule == RULE_ALL || (rule == RULE_CONN ? connected(head, o, i, j) : complete(head, o, n, i, j));
199306}
200307
……@@ -354,16 +461,17 @@ static void run_soft (int seed, int tier, const char *src, const corpus_t *tr, c
354461// ---------------------------------------------------------------- main
355462
356463int main (int argc, char **argv) {
357 if (argc < 2) { fprintf(stderr, "usage: l2 <seed> [--tiers a,b] [--heldout N] [--mut-drop] [--jt prefix] [--soft [--soft-scale s]]\n"); return 1; }
464 if (argc < 2) { fprintf(stderr, "usage: l2 <seed> [--tiers a,b] [--heldout N] [--mut-drop] [--jt prefix] [--soft [--soft-scale s]] [--undirected]\n"); return 1; }
358465 int seed = atoi(argv[1]);
359466 int tiers[16] = { 3000, 10000, 30000 }, ntier = 3, nho = 2000;
360467 const char *jt_prefix = NULL;
361 int soft = 0; double soft_scale = 1.0;
468 int soft = 0, undirected = 0; double soft_scale = 1.0;
362469 for (int i = 2; i < argc; i++) {
363470 if (!strcmp(argv[i], "--mut-drop")) mut_drop = 1;
364471 else if (!strcmp(argv[i], "--heldout") && i + 1 < argc) nho = atoi(argv[++i]);
365472 else if (!strcmp(argv[i], "--jt") && i + 1 < argc) jt_prefix = argv[++i];
366473 else if (!strcmp(argv[i], "--soft")) soft = 1;
474 else if (!strcmp(argv[i], "--undirected")) undirected = 1;
367475 else if (!strcmp(argv[i], "--soft-scale") && i + 1 < argc) soft_scale = atof(argv[++i]);
368476 else if (!strcmp(argv[i], "--tiers") && i + 1 < argc) {
369477 ntier = 0;
……@@ -409,6 +517,22 @@ int main (int argc, char **argv) {
409517 if (!ok) return 2;
410518 }
411519
520 // C5-U (PREREG-U.md): with the gold tree as an undirected parse, every gold span is uconn and ucomp.
521 parse_t Ptr = { 0 }, Pho = { 0 };
522 if (undirected) {
523 Ptr = parse_alloc(full.ntok); Pho = parse_alloc(ho.ntok);
524 links_from_heads(&ho, ho.h, &Pho);
525 long g = 0, okc = 0, okp = 0;
526 br_t gb[MAXN];
527 for (int s = 0; s < ho.ns; s++) {
528 int o = ho.off[s], le6, ng = gold_spans(&ho, s, gb, &le6);
529 for (int b = 0; b < ng; b++) { g++; okc += uconn(&Pho, o, gb[b].i, gb[b].j); okp += ucomp(&Pho, o, gb[b].i, gb[b].j); }
530 }
531 int ok = okc == g && okp == g;
532 printf("GUARD\tC5-U\t%s\tgold=%ld\tuconn=%ld\tucomp=%ld\n", ok ? "PASS" : "FAIL", g, okc, okp);
533 if (!ok) return 2;
534 }
535
412536 int *str = malloc(sizeof(int) * full.ntok), *sho = malloc(sizeof(int) * ho.ntok);
413537 int *htr = malloc(sizeof(int) * full.ntok), *hho = malloc(sizeof(int) * ho.ntok);
414538 int *Rmap = malloc(sizeof(int) * V), *perm = malloc(sizeof(int) * V);
……@@ -444,6 +568,37 @@ int main (int argc, char **argv) {
444568 snprintf(nm, sizeof nm, "M-%s-comp", src_name[x]);
445569 run_arm(seed, tier, nm, RULE_COMP, &trs, &hos, &ho, htr, hho);
446570 }
571
572 // PREREG-U.md: undirected FOREST / MST / MPG parses, undirected unit rules, link F1 (sources G, L, R).
573 if (undirected) {
574 static const int usrc[3] = { SRC_G, SRC_L, SRC_R };
575 static const char *pname[3] = { "FOREST", "MST", "MPG" };
576 u_tr = &Ptr; u_ho = &Pho;
577 for (int xi = 0; xi < 3; xi++) {
578 int x = usrc[xi];
579 int A = layer1_symbols(x, &tr, seed, Lmap, Rmap, str);
580 layer1_symbols(x, &ho, seed, Lmap, Rmap, sho);
581 corpus_t trs = with_symbols(&tr, str), hos = with_symbols(&ho, sho);
582 arm_t a = { "l1", A, NULL, 1 };
583 pairs_t P = count_pairs(&a, &trs);
584 double *MI = pair_mi(&P);
585 free_pairs(&P);
586 for (int pi = 0; pi < 3; pi++) {
587 if (pi == 0) { layer1_parse(&trs, &hos, A, htr, hho); links_from_heads(&trs, htr, &Ptr); links_from_heads(&hos, hho, &Pho); }
588 else { parse_u(&trs, MI, A, pi == 2, &Ptr); parse_u(&hos, MI, A, pi == 2, &Pho); }
589 double pr, rc, f1;
590 link_prf(&hos, &Pho, &pr, &rc, &f1);
591 // link_prf reads gold heads from hos.h, which is the held-out gold tree (with_symbols only replaces c[]).
592 printf("P\t%d\t%d\t%s\t%s\t%.5f\t%.5f\t%.5f\n", seed, tier, src_name[x], pname[pi], pr, rc, f1);
593 char nm[32];
594 snprintf(nm, sizeof nm, "M-%s-uconn-%s", src_name[x], pname[pi]);
595 run_arm(seed, tier, nm, RULE_UCONN, &trs, &hos, &ho, (const int *) u_tr, (const int *) u_ho);
596 snprintf(nm, sizeof nm, "M-%s-ucomp-%s", src_name[x], pname[pi]);
597 run_arm(seed, tier, nm, RULE_UCOMP, &trs, &hos, &ho, (const int *) u_tr, (const int *) u_ho);
598 }
599 free(MI);
600 }
601 }
447602 free(Lmap);
448603
449604 // PREREG-soft.md: rank span types by assembly-ness over KPARSE perturbed parses (sources G and L).
experiments/level2/run-U.sh +12 -0
……@@ -0,0 +1,12 @@
1#!/bin/sh
2# PREREG-U full run: Test A + undirected parses and unit rules, 10 seeds x tiers 3K,10K,30K, in parallel.
3set -e
4cd "$(dirname "$0")"
5gcc -O2 -Wall -o l2 l2.c -lm 2>/dev/null
6out=results-U/$(date +%s)
7mkdir -p "$out"
8git rev-parse HEAD > "$out/commit.txt"
9sha256sum l2.c ../across-cluster/ac.c > "$out/src.sha256"
10for s in 1 2 3 4 5 6 7 8 9 10; do ./l2 $s --undirected > "$out/seed-$s.tsv" & done
11wait
12echo "$out"
experiments/level2/summarize-U.py +54 -0
……@@ -0,0 +1,54 @@
1#!/usr/bin/env python3
2# Score results-U/<stamp> against PREREG-U.md. Usage: summarize-U.py results-U/<stamp> results/<testA-stamp>
3import glob, math, os, re, sys
4T975 = {9: 2.262}
5def mean(x): return sum(x) / len(x)
6def ci(d):
7 n = len(d); m = mean(d); sd = math.sqrt(sum((v - m) ** 2 for v in d) / (n - 1))
8 h = T975.get(n - 1, 1.96) * sd / math.sqrt(n)
9 return m, m - h, m + h
10def fmt(c): return f'{c[0]:+.3f} [{c[1]:+.3f}, {c[2]:+.3f}]'
11A, P = {}, {}
12NEW = re.compile(r'uconn|ucomp|^P\t|C5-U')
13for f in glob.glob(os.path.join(sys.argv[1], 'seed-*.tsv')):
14 for line in open(f):
15 p = line.rstrip('\n').split('\t')
16 if p[0] == 'A': A[(int(p[2]), p[3], int(p[1]))] = (int(p[4]), float(p[5]), float(p[6]), float(p[7]))
17 elif p[0] == 'P': P[(int(p[2]), p[3], p[4], int(p[1]))] = tuple(map(float, p[5:8]))
18seeds = sorted({k[2] for k in A}); tiers = sorted({k[0] for k in A}); top = max(tiers)
19out = []
20def say(s=''): out.append(s); print(s)
21bad = 0; c5 = 0
22for s in seeds:
23 new_all = list(open(os.path.join(sys.argv[1], f'seed-{s}.tsv')))
24 c5 += any(l.startswith('GUARD\tC5-U\tPASS') for l in new_all)
25 bad += list(open(os.path.join(sys.argv[2], f'seed-{s}.tsv'))) != [l for l in new_all if not NEW.search(l)]
26say(f'R0: {"PASS" if bad == 0 else "FAIL"} — {len(seeds) - bad}/{len(seeds)} seeds reproduce Test A exactly')
27say(f'C5-U: {"PASS" if c5 == len(seeds) else "FAIL"} — {c5}/{len(seeds)} seeds')
28def a(t, m, i): return [A[(t, m, s)][i] for s in seeds]
29def da(t, m1, m2, i): return ci([p - q for p, q in zip(a(t, m1, i), a(t, m2, i))])
30def pf(t, x, p): return [P[(t, x, p, s)][2] for s in seeds]
31say('\n## Link precision / recall / F1 (mean, held-out)')
32for t in tiers:
33 say(f'{t}: ' + ' '.join(f'{x}-{p} ' + '/'.join(f'{mean([P[(t, x, p, s)][i] for s in seeds]):.3f}' for i in range(3)) for x in ['G', 'L', 'R'] for p in ['FOREST', 'MST', 'MPG']))
34say('\n## Means: types / instance recall / type recall / candidate precision')
35for t in tiers:
36 say(f'tier {t}')
37 for m in ['C-G', 'M-G-comp'] + [f'M-{x}-{r}-{p}' for x in ['G', 'L', 'R'] for p in ['FOREST', 'MST', 'MPG'] for r in ['uconn', 'ucomp']]:
38 say(f' {m:20s} {mean(a(t, m, 0)):8.0f} ' + ' '.join(f'{mean(a(t, m, i)):.3f}' for i in (1, 2, 3)))
39say(f'\n## Predictions (at {top} unless stated)')
40def verdict(n, ok, txt): say(f'{n}: {"PASS" if ok else "FAIL"} — {txt}')
41c = da(top, 'M-G-ucomp-FOREST', 'M-G-comp', 2); verdict('U1', c[1] > 0, f'type recall M-G-ucomp-FOREST − M-G-comp {fmt(c)}')
42for x in ['G', 'L']:
43 c = ci([p - q for p, q in zip(pf(top, x, 'MST'), pf(top, x, 'FOREST'))]); verdict(f'U2 {x}', c[1] > 0, f'link F1 MST − FOREST {fmt(c)}')
44best = 'MST' if mean(pf(top, 'G', 'MST')) >= mean(pf(top, 'G', 'FOREST')) else 'FOREST'
45m = f'M-G-ucomp-{best}'; r = mean(a(top, m, 2)) / mean(a(top, 'C-G', 2)); ty = mean(a(top, m, 0)) / mean(a(top, 'C-G', 0))
46verdict('U3', r >= 0.7 and ty <= 0.10, f'{m}: type recall {r:.3f} x C-G (bar >= 0.7), types {ty:.3f} x C-G (bar <= 0.10)')
47ok = True; cells = []
48for t in tiers:
49 for x in ['G', 'L']: c = da(t, f'M-{x}-ucomp-MST', 'M-R-ucomp-MST', 2); ok &= c[1] > 0; cells.append(f'{t} {x}: {c[0]:+.3f} [{c[1]:+.3f}]')
50verdict('U4', ok, 'type recall M-x-ucomp-MST − M-R-ucomp-MST ' + '; '.join(cells))
51ok = True; cells = []
52for t in tiers: c = da(t, 'M-G-ucomp-MST', 'C-G', 3); ok &= c[1] > 0; cells.append(f'{t}: {fmt(c)}')
53verdict('U5', ok, 'precision M-G-ucomp-MST − C-G ' + '; '.join(cells))
54open(os.path.join(sys.argv[1], 'summary.txt'), 'w').write('\n'.join(out) + '\n')