/* main.c — Phase 1 with EMERGENT relations (MI-gated).
*
* Shows: (1) accuracy of ext / dir / marginals / emergent-relation models,
* (2) how many relations actually emerged (and which),
* (3) active vs random on the emergent model. */
#include "model.h"
#include "sensor.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static unsigned long long RNG;
static unsigned long long xorshift(void) {
RNG ^= RNG << 13; RNG ^= RNG >> 7; RNG ^= RNG << 17; return RNG;
}
static void shuffle(size_t *a, size_t n) {
for (size_t i = n; i > 1; i--) {
size_t j = (size_t)(xorshift() % i);
size_t t = a[i-1]; a[i-1] = a[j]; a[j] = t;
}
}
enum { M_EXT, M_DIR, M_MARG, M_REL };
static double eval(const Rel *r, const Corpus *c, const size_t *test, size_t nt, int mode) {
if (!nt) return 0.0;
size_t hit = 0;
double p[NUM_CLASSES];
for (size_t i = 0; i < nt; i++) {
const File *f = &c->files[test[i]];
if (mode == M_EXT) pred_ext (r, f->f_ext, p);
else if (mode == M_DIR) pred_dir (r, f->f_dir, p);
else if (mode == M_MARG) pred_marg(r, f->f_ext, f->f_dir, p);
else pred_rel (r, f->f_ext, f->f_dir, p);
if (argmax(p) == f->klass) hit++;
}
return (double)hit / (double)nt;
}
static const int CHK[] = {1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597};
#define NCHECK ((int)(sizeof CHK / sizeof CHK[0]))
static double run(Rel *r, Corpus *c, size_t *pool, size_t np,
const size_t *test, size_t nt, int active, double curve[NCHECK]) {
char *opened = calloc(np, 1);
int chk = 0;
for (size_t step = 0; step < np; step++) {
size_t pick;
if (active) {
double best = -1; size_t bi = 0;
for (size_t k = 0; k < np; k++) {
if (opened[k]) continue;
File *f = &c->files[pool[k]];
double g = infogain_pair(r, f->f_ext, f->f_dir);
if (g > best) { best = g; bi = k; }
}
pick = bi;
} else pick = step;
opened[pick] = 1;
File *f = &c->files[pool[pick]];
rel_update(r, f->f_ext, f->f_dir, f->klass);
while (chk < NCHECK && (int)(step+1) >= CHK[chk]) curve[chk++] = eval(r, c, test, nt, M_REL);
}
double fin = eval(r, c, test, nt, M_REL);
while (chk < NCHECK) curve[chk++] = fin;
free(opened);
return fin;
}
static int opens_to(const double curve[NCHECK], double ceil_acc, double frac) {
for (int k = 0; k < NCHECK; k++) if (curve[k] >= ceil_acc*frac) return CHK[k];
return -1;
}
int main(int argc, char **argv) {
if (argc < 2) { fprintf(stderr, "usage: %s <dir> [seed] [test_frac]\n", argv[0]); return 2; }
const char *root = argv[1];
RNG = (argc > 2) ? strtoull(argv[2], 0, 10) : 88172645463325252ULL;
if (!RNG) RNG = 88172645463325252ULL;
double test_frac = (argc > 3) ? atof(argv[3]) : 0.3;
Corpus c;
if (corpus_collect(&c, root)) { fprintf(stderr, "no files under %s\n", root); return 1; }
long cls[NUM_CLASSES] = {0};
for (size_t i = 0; i < c.count; i++) cls[sensor_open(&c, i)]++;
size_t *idx = malloc(c.count * sizeof *idx);
for (size_t i = 0; i < c.count; i++) idx[i] = i;
shuffle(idx, c.count);
size_t nt = (size_t)(test_frac * (double)c.count);
if (nt >= c.count) nt = c.count / 2;
size_t *test = idx, *pool = idx + nt, np = c.count - nt;
int maj = 0; for (int k = 1; k < NUM_CLASSES; k++) if (cls[k] > cls[maj]) maj = k;
size_t majhit = 0;
for (size_t i = 0; i < nt; i++) if (c.files[test[i]].klass == maj) majhit++;
printf("basal-agent phase 1 (EMERGENT relations, MI-gated) — root=%s\n", root);
printf("files=%zu exts=%zu dirs=%zu pool=%zu test=%zu\n",
c.count, c.ext.count, c.dir.count, np, nt);
printf("classes: SOURCE=%ld GENERATED=%ld majority-baseline=%.3f\n\n",
cls[0], cls[1], nt ? (double)majhit/nt : 0);
/* train once on full pool. */
Rel full; rel_init(&full, (int)c.ext.count, (int)c.dir.count, 0.5);
for (size_t i = 0; i < np; i++) {
File *f = &c.files[pool[i]];
rel_update(&full, f->f_ext, f->f_dir, f->klass);
}
/* count observed pairs and how many became relations. */
size_t npair = c.ext.count * c.dir.count, observed = 0, emerged = 0;
for (size_t p = 0; p < npair; p++) {
int f0 = (int)(p / c.dir.count), f1 = (int)(p % c.dir.count);
if (pair_seen(&full, f0, f1) > 0) {
observed++;
if (rel_emerged(&full, f0, f1)) emerged++;
}
}
double a_ext = eval(&full, &c, test, nt, M_EXT);
double a_dir = eval(&full, &c, test, nt, M_DIR);
double a_marg = eval(&full, &c, test, nt, M_MARG);
double a_rel = eval(&full, &c, test, nt, M_REL);
printf("held-out accuracy by model:\n");
printf(" ext only : %.3f\n", a_ext);
printf(" dir only : %.3f\n", a_dir);
printf(" marginals (no rel) : %.3f\n", a_marg);
printf(" EMERGENT relations : %.3f <- %+.3f over marginals\n\n", a_rel, a_rel - a_marg);
printf("relations emerged: %zu of %zu observed pairs (%.1f%%) [%zu possible]\n\n",
emerged, observed, observed ? 100.0*emerged/observed : 0.0, npair);
/* active vs random on the emergent model. */
double rc[NCHECK], ac[NCHECK];
Rel mr, ma;
rel_init(&mr, (int)c.ext.count, (int)c.dir.count, 0.5);
rel_init(&ma, (int)c.ext.count, (int)c.dir.count, 0.5);
size_t *pr = malloc(np * sizeof *pr); memcpy(pr, pool, np * sizeof *pr);
shuffle(pr, np);
double rf = run(&mr, &c, pr, np, test, nt, 0, rc);
double af = run(&ma, &c, pool, np, test, nt, 1, ac);
double ceil_acc = rf > af ? rf : af;
int r99 = opens_to(rc, ceil_acc, 0.99), a99 = opens_to(ac, ceil_acc, 0.99);
printf("active vs random: opens to 99%% ceiling RANDOM=%d ACTIVE=%d", r99, a99);
if (r99 > 0 && a99 > 0) printf(" (%.1fx fewer)", (double)r99/a99);
printf("\n\n");
/* the relations that formed, by information gain. */
printf("top emerged relations (the sections that formed):\n");
size_t *ord = malloc(npair * sizeof *ord);
size_t no = 0;
for (size_t p = 0; p < npair; p++) {
int f0 = (int)(p / c.dir.count), f1 = (int)(p % c.dir.count);
if (rel_emerged(&full, f0, f1)) ord[no++] = p;
}
const char *NM[] = {"SOURCE","GENERATED"};
for (int top = 0; top < 8 && (size_t)top < no; top++) {
size_t best = top;
for (size_t k = top+1; k < no; k++)
if (rel_gain(&full, ord[k]/c.dir.count, ord[k]%c.dir.count) >
rel_gain(&full, ord[best]/c.dir.count, ord[best]%c.dir.count)) best = k;
size_t t = ord[top]; ord[top] = ord[best]; ord[best] = t;
int f0 = (int)(ord[top]/c.dir.count), f1 = (int)(ord[top]%c.dir.count);
double pj[NUM_CLASSES];
pred_rel(&full, f0, f1, pj);
printf(" MI=%.2f (%-8s, %-18s) -> %-6s [n=%ld]\n",
rel_gain(&full, f0, f1), ext_name(&c, f0), dir_name(&c, f1),
NM[argmax(pj)], pair_seen(&full, f0, f1));
}
free(ord); free(idx); free(pr);
rel_free(&full); rel_free(&mr); rel_free(&ma);
corpus_free(&c);
return 0;
}