OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / src / main.c

178 lines · c ref:
/* 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;
}