OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / basal-agent / spatial.c

141 lines · c ref:
/* spatial.c — can the ENVIRONMENT emergently supply templating? SPATIAL vs WELL-MIXED heredity.
 *
 * Every earlier model (gard..template) is WELL-MIXED: mature() fully stirs each pocket, so the only
 * heredity is whatever survives a random sample of the pool -> copy-number-limited (digigard/template).
 * The user's idea: make the environment part of the program. Give the pocket SPATIAL structure with
 * LOCAL physics, and divide by a GEOMETRIC cut. Then a spatially-organized parent hands each daughter
 * a COHERENT contiguous chunk that regrows truer than a random sample -- templating supplied by the
 * environment's geometry, with NO copy primitive coded anywhere.
 *
 * THE RAZOR (or this is just the encoding trap relocated): I code ONLY (a) local autocatalysis -- an
 * occupied site promotes its OWN clique in neighboring empty sites (the spatial version of the exact
 * within-clique catalysis used all along), and (b) a geometric split (daughter = a contiguous half,
 * regrown by the same local physics). I NEVER copy the parent's genome. Whether the module-set (an
 * emergent readout) transmits, and whether stable domains (spatial specialization) form, is MEASURED.
 *
 * Two arms, identical local physics and identical ~50% material retention -- ONLY the cut differs:
 *   SPATIAL : daughter = the contiguous left half of the parent lattice (domains preserved).
 *   MIXED   : daughter = a RANDOM half of the parent's tokens, scattered to random sites (domains broken).
 * Measured: (1) heredity signal (module-set bit-match daughter~parent minus ~random), (2) coexistence
 * (avg # modules present per pocket -- does space sustain specialists where stirring -> monoculture?),
 * (3) selection on module-0.
 *
 * build:  cc -O2 -Wall -std=c11 -o spatial spatial.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <string.h>

#define L    64          /* lattice sites per pocket (a ring) */
#define M    8           /* modules */
#define POCK 120
#define GEN  30
#define REG  (6*L)       /* local-dynamics updates when regrowing a daughter */
#define NUC   0.003      /* RARE de-novo nucleation (only into a fully empty neighborhood) */
#define EPS   0.01       /* tiny weight for a NON-adjacent module (near-forbids invasion) */
#define CAT   1.0        /* per-same-clique-neighbor catalysis weight (growth from existing domains) */
#define PD    0.02       /* per-update decay prob of an occupied site */
#define PRES  (L/(2*M))  /* module 'present' if its token count exceeds this (=4) */

static unsigned long long RNG;
static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; }
static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); }
static double randn(void){ double u=urand(),v=urand(); if(u<1e-300)u=1e-300; return sqrt(-2*log(u))*cos(6.283185307*v); }

static double w[M];               /* intrinsic module growth weights (asymmetry, like beta's randn) */
static void build_chem(void){ for(int m=0;m<M;m++) w[m]=exp(0.3*randn()); }

/* LOCAL dynamics: pick a site; occupied -> rare decay; empty -> GROW from occupied neighbors
   (local autocatalysis of adjacent cliques); de-novo nucleation only into a fully empty neighborhood,
   and rare. Absent modules cannot invade an occupied neighborhood -> module-presence is heritable. */
static void step(int*g){
	int i=(int)(xs()%L);
	if(g[i]>=0){ if(urand()<PD) g[i]=-1; return; }
	int lft=g[(i-1+L)%L], rgt=g[(i+1)%L];
	if(lft<0 && rgt<0){ if(urand()<NUC) g[i]=(int)(xs()%M); return; }  /* empty neighborhood: rare de novo */
	double wt[M], tot=0;
	for(int m=0;m<M;m++){ int nb=(lft==m)+(rgt==m); wt[m]=(nb? CAT*nb : EPS)*w[m]; tot+=wt[m]; }
	double u=urand()*tot, a=0; int c=M-1;
	for(int m=0;m<M;m++){ a+=wt[m]; if(u<=a){ c=m; break; } }
	g[i]=c;
}
static void regrow(int*g,int steps){ for(int t=0;t<steps;t++) step(g); }
static void genome(const int*g,int*gm){ int cnt[M]; memset(cnt,0,sizeof cnt); for(int i=0;i<L;i++) if(g[i]>=0) cnt[g[i]]++; for(int m=0;m<M;m++) gm[m]=(cnt[m]>PRES); }
static double bitmatch(const int*a,const int*b){ int e=0; for(int m=0;m<M;m++) if(a[m]==b[m]) e++; return (double)e/M; }
static int nmods(const int*gm){ int s=0; for(int m=0;m<M;m++) s+=gm[m]; return s; }

static int pk[POCK][L], nxt[POCK][L];
static double fit[POCK], cum[POCK];

/* one run. mode 0=spatial cut, 1=mixed sample. returns heredity signal; writes coexistence + selection. */
static double run(int mode, double *coex, double *f_start, double *f_end){
	for(int p=0;p<POCK;p++){ for(int i=0;i<L;i++) pk[p][i]=-1;
		int present[M],np=0; for(int m=0;m<M;m++){present[m]=(urand()<0.5);if(present[m])np++;} if(!np){present[0]=1;}
		for(int i=0;i<L;i++) if(urand()<0.5){ int m; do{m=(int)(xs()%M);}while(!present[m]); pk[p][i]=m; }
		regrow(pk[p],REG); }

	double dig=0,cx=0; int hc=0,cc=0; double t0=0,t1=0;
	for(int g=0;g<GEN;g++){
		double tot=0; for(int p=0;p<POCK;p++){ int gm[M]; genome(pk[p],gm); fit[p]=0.3+(gm[0]?1.0:0.0); tot+=fit[p]; cum[p]=tot; }
		if(g==1){ double f=0; for(int p=0;p<POCK;p++){int gm[M];genome(pk[p],gm);f+=gm[0];} t0=f/POCK; }
		for(int p=0;p<POCK;p++){
			double r=urand()*tot; int lo=0,hi=POCK-1; while(lo<hi){int mid=(lo+hi)/2; if(cum[mid]<r)lo=mid+1; else hi=mid;} int par=lo;
			int D[L]; for(int i=0;i<L;i++) D[i]=-1;
			if(mode==0){
				/* SPATIAL: inherit the contiguous left half; right half empty. Domains preserved. */
				for(int i=0;i<L/2;i++) D[i]=pk[par][i];
			} else {
				/* MIXED: keep ~50% of the SAME tokens but scatter to random empty sites. Domains broken. */
				for(int i=0;i<L;i++) if(pk[par][i]>=0 && urand()<0.5){ int q; do{ q=(int)(xs()%L); }while(D[q]>=0); D[q]=pk[par][i]; }
			}
			regrow(D,REG);
			memcpy(nxt[p],D,sizeof D);
			if(g>GEN/2){ int rp=(int)(xs()%POCK); int gd[M],gpar[M],grn[M]; genome(D,gd); genome(pk[par],gpar); genome(pk[rp],grn);
				dig += bitmatch(gd,gpar)-bitmatch(gd,grn); cx += nmods(gd); hc++; cc++; }
		}
		memcpy(pk,nxt,sizeof pk);
	}
	{ double f=0; for(int p=0;p<POCK;p++){int gm[M];genome(pk[p],gm);f+=gm[0];} t1=f/POCK; }
	*coex = cc?cx/cc:0; *f_start=t0; *f_end=t1;
	return hc?dig/hc:0;
}

int main(void){
	printf("=== SPATIAL vs WELL-MIXED: can the environment's geometry supply templating? ===\n");
	printf("  Lattice L=%d, M=%d modules, local autocatalysis, geometric split. NO copy primitive.\n",L,M);
	printf("  Only difference between arms: SPATIAL keeps a contiguous half; MIXED keeps a random half.\n\n");

	unsigned long long seeds[]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL,0x123456789abcdefULL,0xfeedface12345678ULL};
	int NS=sizeof(seeds)/sizeof(seeds[0]);
	double hS=0,cS=0,dS=0, hM=0,cM=0,dM=0;
	for(int s=0;s<NS;s++){
		double c,a,b;
		RNG=seeds[s]; build_chem();  double h=run(0,&c,&a,&b); hS+=h; cS+=c; dS+=b-a;
		RNG=seeds[s]; build_chem();         h=run(1,&c,&a,&b); hM+=h; cM+=c; dM+=b-a;
	}
	hS/=NS;cS/=NS;dS/=NS; hM/=NS;cM/=NS;dM/=NS;

	printf("  (averaged over %d chemistry seeds)\n",NS);
	printf("  arm       | heredity | coexistence(#mods) | selection Delta\n");
	printf("  ----------+----------+--------------------+----------------\n");
	printf("  SPATIAL   |  %+.3f  |       %.2f         |     %+.3f\n", hS,cS, dS);
	printf("  MIXED     |  %+.3f  |       %.2f         |     %+.3f\n", hM,cM, dM);
	printf("\n");
	double s1S=dS,s0S=0,s1M=dM,s0M=0; (void)s1S;(void)s0S;(void)s1M;(void)s0M;

	int her_win  = (hS > hM + 0.02);
	int coex_win = (cS > cM + 0.2);
	int sel_win  = (dS > dM + 0.05);
	printf("  heredity   : spatial %s mixed (%+.3f vs %+.3f)  [whole module-set fidelity]\n", her_win?">":"~=/<", hS,hM);
	printf("  coexistence: spatial %s mixed (%.2f vs %.2f)      [modular-cage diversity]\n", coex_win?">":"~=/<", cS,cM);
	printf("  selection  : spatial %s mixed (%+.3f vs %+.3f)  [holding a favored trait]\n", sel_win?">":"~=/<", dS,dM);
	printf("\n");
	printf("  => TWO-SIDED result, honest:\n");
	if(!her_win && !coex_win)
		printf("     * TEMPLATING: NULL. Geometry does NOT supply faithful copying. A contiguous cut is only a\n       CORRELATED sampler of which modules are present -- no better (a hair worse) than a random draw,\n       because regrowth re-equilibrates to the inherited module-SET regardless of its arrangement. The\n       fidelity bottleneck is untouched; only templated COPYING (template.c) crosses it. Environment as\n       container (gardenv) and as geometry (here) both give container+selection, NOT the copy.\n");
	if(sel_win)
		printf("     * SELECTION: REAL. Spatial structure robustly amplifies selection on a trait (spatial\n       reciprocity): a favored module's domain colonizes and dominates a pocket, so between-pocket\n       selection fixes it far better than in the well-mixed arm. The environment-as-program helps the\n       SELECTION half of Darwinian evolution -- but heredity/fidelity, the actual bottleneck, was NOT\n       the half it helped. The 'modular cage' sharpens selection; it does not manufacture a template.\n");
	printf("\n  HONEST BOUND: local physics + a geometric cut were supplied; the null is not tuned away.\n");
	return 0;
}