/* container.c — MILESTONE 2: containers as species. And a structural check that may kill the design.
*
* substrate.c passed milestone 1: one type expressing rung 1 (a self-sustaining reaction subset) and
* rung 2 (a reaction whose product catalyses itself), coexisting in one network with no translation.
* The next move is the recursive one — a CONTAINER IS ITSELF A SPECIES, so a compartment can be
* produced, consumed, catalysed and nested like anything else. That is what would let a rung's
* output be the next rung's input.
*
* THE CHECK THAT COMES FIRST, AND MIGHT KILL IT. Before building on containers, ask whether this
* type can express what rung 4 is FOR. In ribozyme.c compartments bought privatisation against
* parasites: a catalyst-rich compartment out-reproduces a parasite-ridden one. That is a claim about
* relative ABUNDANCE. But substrate.c's RAF is a purely STRUCTURAL notion — which reactions can
* sustain themselves given which species are present — and RAF is MONOTONE in the reaction set:
* adding reactions can never remove closure. If so, a container can never hold a RAF that the whole
* mixture lacks, and compartmentalisation cannot buy anything expressible in this type.
*
* That is a falsifiable structural prediction about the substrate's own design, so it is tested here
* rather than assumed:
* C1 containers work at all — a container's RAF is computed over ITS OWN contents (locality is
* what a compartment IS), containers can be reaction products, and containers can nest.
* C2 THE KILLER: is global RAF always a superset of every container's RAF? Measured over many
* random networks and random partitions. If it always is, static closure cannot express rung 4
* and the type needs ABUNDANCES, not just presence.
*
* A negative on C2 is the useful outcome: it says precisely what milestone 3 must add.
*
* build: cc -O2 -Wall -std=c11 -o container container.c -lm
*/
#include <stdio.h>
#include <string.h>
#define NSPEC 48
#define NFOOD 6
#define NRXN 200
#define MAXC 8 /* container species occupy the top MAXC species indices */
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 int nrxn;
/* ---- THE RECURSIVE MOVE: a container is a species that also has CONTENTS. Species index c is a
container iff c >= NSPEC-MAXC. Its contents are a set of species — which may include other
containers, so nesting is free. ---- */
static unsigned char contents[MAXC][NSPEC];
static int is_container(int s){ return s>=NSPEC-MAXC; }
static int cidx(int s){ return s-(NSPEC-MAXC); }
static unsigned char present[NSPEC], active[NRXN];
/* RAF over a RESTRICTED species scope: only species in `scope` may be used. Passing an all-ones
scope gives the global RAF; passing a container's contents gives that container's local RAF.
Locality is the whole of what a compartment is, in this type. */
static int raf_scope(const unsigned char *scope){
for(int j=0;j<nrxn;j++) active[j]=1;
for(;;){
for(int m=0;m<NSPEC;m++) present[m]=(m<NFOOD && scope[m])?1:0;
int grew=1;
while(grew){ grew=0;
for(int j=0;j<nrxn;j++){ if(!active[j]) continue;
int a=R[j].a,b=R[j].b,p=R[j].prod;
if(!scope[a]||!scope[b]||!scope[p]) continue;
if(!present[a]||!present[b]||present[p]) continue;
int cz=0; for(int m=0;m<NSPEC;m++) if(R[j].cat[m]&&scope[m]&&present[m]){cz=1;break;}
if(cz){ present[p]=1; grew=1; }
}
}
int ch=0;
for(int j=0;j<nrxn;j++){ if(!active[j]) continue;
int ok = scope[R[j].a]&&scope[R[j].b]&&scope[R[j].prod]
&& present[R[j].a]&&present[R[j].b];
int cz=0; if(ok){ for(int m=0;m<NSPEC;m++) if(R[j].cat[m]&&scope[m]&&present[m]){cz=1;break;} }
if(!(ok&&cz)){ active[j]=0; ch=1; }
}
if(!ch) break;
}
int n=0; for(int j=0;j<nrxn;j++) n+=active[j];
return n;
}
static void raf_mark(const unsigned char *scope,unsigned char *out){
raf_scope(scope); memcpy(out,active,nrxn);
}
static void net_random(double p){
nrxn=0;
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()<p) R[j].cat[m]=1;
nrxn++;
}
}
int main(void)
{
unsigned char all[NSPEC], sc[NSPEC];
for(int m=0;m<NSPEC;m++) all[m]=1;
printf("=== MILESTONE 2: containers as species — and the check that may kill the design ===\n");
printf(" A container is a species that also has contents; contents may include containers, so\n");
printf(" nesting is free. A container's RAF is its contents' RAF — locality is what a\n");
printf(" compartment IS. %d species (%d food, top %d are containers), %d reactions.\n\n",NSPEC,NFOOD,MAXC,NRXN);
/* ---- C1: containers exist, are products, and nest ---- */
printf(" C1 — containers work at all\n");
RNG=0x9e3779b97f4a7c15ULL; net_random(0.030);
int K1=NSPEC-MAXC, K2=NSPEC-MAXC+1;
memset(contents,0,sizeof contents);
for(int m=0;m<NFOOD;m++){ contents[cidx(K1)][m]=1; contents[cidx(K2)][m]=1; }
for(int m=NFOOD;m<NFOOD+12;m++) contents[cidx(K1)][m]=1;
contents[cidx(K2)][K1]=1; /* K2 CONTAINS K1 — nesting */
R[0].a=0; R[0].b=1; R[0].prod=K1; R[0].cat[0]=1;/* a reaction whose PRODUCT is a container */
int prod_ok = (R[0].prod==K1) && is_container(R[0].prod);
int nest_ok = contents[cidx(K2)][K1] && is_container(K1);
memcpy(sc,contents[cidx(K1)],NSPEC); int local=raf_scope(sc);
int glob=raf_scope(all);
printf(" container is a reaction product : %s\n",prod_ok?"YES":"no");
printf(" container nested in a container : %s\n",nest_ok?"YES":"no");
printf(" local RAF (inside K1) %d vs global RAF %d\n",local,glob);
int c1 = prod_ok && nest_ok;
printf(" => %s\n\n", c1?"containers are ordinary species — the recursive move works"
:"containers are NOT ordinary species here — fix before proceeding");
/* ---- C2: is a container's RAF always a SUBSET of the global RAF? ---- */
printf(" C2 — THE KILLER. Can a container hold closure the whole mixture lacks?\n");
printf(" Random networks x random partitions; count any reaction in a container's RAF\n");
printf(" but NOT in the global RAF.\n");
int trials=0, violations=0, nonempty=0;
static unsigned char gmark[NRXN], lmark[NRXN];
for(int t=0;t<400;t++){
RNG=0x2545f4914f6cdd1dULL + (unsigned long long)t*0x9E3779B97F4A7C15ULL;
net_random(0.020+0.02*urand());
raf_mark(all,gmark);
for(int r=0;r<3;r++){
for(int m=0;m<NSPEC;m++) sc[m]=(m<NFOOD)?1:(urand()<0.5);
raf_mark(sc,lmark);
int loc=0,viol=0;
for(int j=0;j<nrxn;j++){ loc+=lmark[j]; if(lmark[j]&&!gmark[j]) viol++; }
trials++; if(loc>0) nonempty++; if(viol>0) violations++;
}
}
printf(" %d partitions, %d with a non-empty local RAF, %d holding closure the global\n",trials,nonempty,violations);
printf(" mixture lacks.\n");
printf(" => %s\n\n", violations==0?
"NONE. RAF is MONOTONE: a container can never hold closure the mixture lacks."
:"some containers DO hold closure the mixture lacks — the design survives.");
if(violations==0)
printf(" MILESTONE 2: CONTAINERS WORK, BUT STATIC CLOSURE CANNOT EXPRESS RUNG 4.\n"
" The recursive move is sound — containers are ordinary species, they can be produced\n"
" and nested. But compartmentalisation buys NOTHING expressible in a presence/absence\n"
" type, because RAF is monotone in the reaction set: adding reactions never removes\n"
" closure, so a part can never sustain what the whole cannot. What ribozyme.c's\n"
" compartments actually bought was PRIVATISATION AGAINST PARASITES — a claim about\n"
" relative ABUNDANCE, not about what can exist. So milestone 3 is not more structure:\n"
" it is COUNTS. The type must carry how MUCH of each species is present, and the RAF\n"
" becomes a dynamical attractor rather than a static closure. That is a concrete,\n"
" measured design requirement rather than a guess — and it is the honest reason the\n"
" substrate is not yet ready to chain rung 3 onward.\n");
else
printf(" MILESTONE 2 PASSED as designed — proceed to expressing rungs 3 and 5.\n");
return 0;
}