OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / counts.c

172 lines · c ref:
/* counts.c — MILESTONE 3: abundances. Can the substrate now express what rung 4 is FOR?
 *
 * container.c settled milestone 2 with a decisive structural negative: containers ARE ordinary
 * species (products, nestable, local scope) but RAF is MONOTONE in the reaction set, so across 1200
 * partitions ZERO containers held closure the whole mixture lacked. A presence/absence type cannot
 * express compartmentalisation, because what compartments buy is privatisation against parasites —
 * a claim about relative ABUNDANCE, not about what can exist.
 *
 * So the type gains counts. A container holds n[species] rather than a bit, reactions consume and
 * produce molecules, and the "RAF" becomes a dynamical attractor: which species are MAINTAINED,
 * not which could exist.
 *
 * PARASITES ARE NOT ADDED — they fall out. In a random network some species catalyse reactions and
 * some catalyse nothing. The latter still get produced and still occupy capacity. That is exactly
 * ribozyme.c's parasite, arising from the network rather than being declared.
 *
 * THE CHECK, and it is the same discipline as milestone 1: a new type is worthless if it does not
 * reproduce what we already have.
 *   M1 the dynamics sustain anything at all — catalyst fraction must not simply collapse to 0 or
 *      saturate at 1 in every arm, or there is nothing to compare.
 *   M2 THE TEST: does WELL-MIXED lose to COMPARTMENTS + between-compartment selection, as
 *      ribozyme.c found (5.99 -> 5.50 well-mixed vs -> 11.34 at compartment size 8)? If the new
 *      type reproduces that ordering, it can express rung 4 and the substrate is ready to chain.
 *      If compartments do NOT win, counts were not the missing piece either.
 *   M3 CONTROL: compartments with selection REMOVED (reseed from a random parent rather than a
 *      catalyst-rich one). Isolates compartment-level selection from compartmentalisation per se —
 *      the distinction byproduct.c had to learn the hard way.
 *
 * Total molecules are held EQUAL across arms so the comparison is not confounded by population size,
 * per the four-check tally.
 *
 * build:  cc -O2 -Wall -std=c11 -o counts counts.c -lm
 */
#include <stdio.h>
#include <string.h>

#define NSPEC 40
#define NFOOD 6
#define NRXN  160
#define TOTAL 4800         /* molecules, identical in every arm */
#define STEPS 300
#define PCAT  0.004

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 int rint_(int n){ return (int)(xs()%(unsigned)n); }

typedef struct { int a,b,prod; unsigned char cat[NSPEC]; } Rxn;
static Rxn R[NRXN];
static unsigned char catalyses[NSPEC];      /* does this species catalyse ANY reaction? */

static void net_random(void){
	memset(catalyses,0,sizeof catalyses);
	for(int j=0;j<NRXN;j++){
		R[j].a=rint_(NSPEC); R[j].b=rint_(NSPEC); R[j].prod=NFOOD+rint_(NSPEC-NFOOD);
		memset(R[j].cat,0,sizeof R[j].cat);
		for(int m=0;m<NSPEC;m++) if(urand()<PCAT){ R[j].cat[m]=1; catalyses[m]=1; }
	}
}

/* a container is a count vector. one step: pick two molecules, fire a matching catalysed reaction,
   produce the product, and evict a random molecule to hold capacity. */
static void react(int *n,int cap,int steps){
	for(int t=0;t<steps;t++){
		int tot=0; for(int m=0;m<NSPEC;m++) tot+=n[m];
		if(tot<2) return;
		int pa=rint_(tot), pb=rint_(tot), a=-1,b=-1,acc=0;
		for(int m=0;m<NSPEC;m++){ acc+=n[m]; if(a<0&&pa<acc) a=m; if(b<0&&pb<acc) b=m; }
		if(a<0||b<0) continue;
		int j0=rint_(NRXN);
		for(int d=0;d<NRXN;d++){
			int j=(j0+d)%NRXN;
			if(!((R[j].a==a&&R[j].b==b)||(R[j].a==b&&R[j].b==a))) continue;
			int cz=0; for(int m=0;m<NSPEC;m++) if(R[j].cat[m]&&n[m]>0){cz=1;break;}
			if(!cz) break;
			int ev=rint_(tot),e=-1; acc=0;
			for(int m=0;m<NSPEC;m++){ acc+=n[m]; if(ev<acc){ e=m; break; } }
			if(e>=0 && n[e]>0 && e>=NFOOD) n[e]--;            /* food is buffered, not evicted */
			n[R[j].prod]++;
			(void)cap;
			break;
		}
	}
}
static double catfrac(const int *n){
	int tot=0,c=0;
	for(int m=0;m<NSPEC;m++){ tot+=n[m]; if(catalyses[m]) c+=n[m]; }
	return tot? (double)c/tot : 0.0;
}

static int pool[512][NSPEC];

/* mode 0 = WELL-MIXED (one container of TOTAL), 1 = COMPARTMENTS + selection, 2 = COMPARTMENTS, no
   selection (reseed from a RANDOM parent). Molecule count identical in every arm. */
static double run(int mode,int csz){
	int ncomp = (mode==0)? 1 : TOTAL/csz;
	int cap   = (mode==0)? TOTAL : csz;
	if(ncomp>512) ncomp=512;
	for(int c=0;c<ncomp;c++){
		memset(pool[c],0,sizeof pool[c]);
		for(int i=0;i<cap;i++) pool[c][rint_(NSPEC)]++;
	}
	int rounds = (mode==0)? STEPS*8 : STEPS/4;
	for(int g=0;g<20;g++){
		for(int c=0;c<ncomp;c++) react(pool[c],cap,rounds);
		if(mode==0) continue;
		static int nxt[512][NSPEC];
		double tot=0,cum[512];
		for(int c=0;c<ncomp;c++){ tot += 0.01 + catfrac(pool[c]); cum[c]=tot; }
		for(int c=0;c<ncomp;c++){
			int par;
			if(mode==1){ double r=urand()*tot; int lo=0,hi=ncomp-1;
				while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } par=lo; }
			else par=rint_(ncomp);                     /* M3 control: selection removed */
			memset(nxt[c],0,sizeof nxt[c]);
			int tt=0; for(int m=0;m<NSPEC;m++) tt+=pool[par][m];
			for(int i=0;i<cap;i++){                    /* random assortment from the parent */
				if(tt<1) break;
				int p=rint_(tt),acc=0;
				for(int m=0;m<NSPEC;m++){ acc+=pool[par][m]; if(p<acc){ nxt[c][m]++; break; } }
			}
		}
		for(int c=0;c<ncomp;c++) memcpy(pool[c],nxt[c],sizeof nxt[c]);
	}
	double s=0; for(int c=0;c<ncomp;c++) s+=catfrac(pool[c]);
	return s/ncomp;
}

int main(void)
{
	unsigned long long seeds[5]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL,
	                             0x123456789abcdefULL,0xfeedface12345678ULL};
	int NS=5;
	printf("=== MILESTONE 3: counts. Can the substrate now express what rung 4 is FOR? ===\n");
	printf("  container.c: RAF is monotone, so a presence/absence type cannot express\n");
	printf("  compartmentalisation — 0 of 1200 containers held closure the mixture lacked. The type\n");
	printf("  now carries ABUNDANCES; parasites (species that catalyse nothing) fall out of the\n");
	printf("  random network rather than being declared. %d molecules in EVERY arm.\n\n",TOTAL);
	printf("  catalyst fraction (parasites are the rest); ribozyme.c: well-mixed LOSES to size-8\n\n");
	printf("   comp size | WELL-MIXED | COMPARTMENTS+sel | COMPARTMENTS, no sel\n");
	printf("   ----------+------------+------------------+---------------------\n");
	int sizes[3]={8,16,48}, wins=0, ctrl_ok=1; double wm=0;
	for(int z=0;z<3;z++){
		double a=0,b=0,c=0;
		for(int s=0;s<NS;s++){
			RNG=seeds[s]; net_random(); a+=run(0,sizes[z]);
			RNG=seeds[s]; net_random(); b+=run(1,sizes[z]);
			RNG=seeds[s]; net_random(); c+=run(2,sizes[z]);
		}
		a/=NS;b/=NS;c/=NS; wm=a;
		printf("      %2d     |   %.3f    |      %.3f       |        %.3f\n",sizes[z],a,b,c);
		if(b>a+0.02) wins++;
		if(c>b-0.01) ctrl_ok=0;         /* selection must beat no-selection, or it is not selection */
	}
	printf("\n   M1 dynamics sustain something         : %s\n", (wm>0.02&&wm<0.98)?"YES":"NO — degenerate, nothing to compare");
	printf("   M2 compartments beat well-mixed       : %d/3 sizes\n",wins);
	printf("   M3 selection beats no-selection       : %s\n\n", ctrl_ok?"YES":"NO — compartmentalisation alone");
	if(!(wm>0.02&&wm<0.98))
		printf("   => DEGENERATE. The dynamics collapse or saturate, so nothing is being compared.\n"
		       "      Counts are in the type but the reactor is wrong. Invalid, not a null.\n");
	else if(wins>=2 && ctrl_ok)
		printf("   => THE TYPE NOW EXPRESSES RUNG 4. Compartments beat well-mixed, and selection beats\n"
		       "      compartmentalisation alone — the ordering ribozyme.c found, reproduced in a\n"
		       "      general substrate that also expresses rungs 1 and 2. Counts were the missing piece.\n");
	else
		printf("   => HONEST NULL. Counts alone do not reproduce ribozyme.c's ordering here, so the\n"
		       "      missing piece is not merely abundance. The substrate expresses rungs 1 and 2 and\n"
		       "      still cannot express what compartments are for.\n");
	return 0;
}