/* 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;
}