| … | … | @@ -0,0 +1,273 @@ |
| 1 | /* rafcomp.c — TEST B: chain rung 1 into rung 4. Does compartment-level selection still work when |
| 2 | * catalytic quality is a COLLECTIVE closure property instead of a per-unit one? |
| 3 | * |
| 4 | * WHY. SYNTHESIS's standing admission is that no rung ever produced the next — every rung is an |
| 5 | * isolated island. chemoton.c derives compartment-level selection from growth coupling, but it still |
| 6 | * SUPPLIES phi as a per-unit fold->catalysis map. rung1.c separately shows autocatalytic sets (RAFs) |
| 7 | * self-organising from random chemistry. This chains them: catalytic output is read off an EMERGENT |
| 8 | * RAF rather than a supplied fold rule, putting rung 1 and rung 4 in ONE model. |
| 9 | * |
| 10 | * THE REASON IT MIGHT FAIL, AND WHY THAT IS THE POINT. phi is intrinsic to a unit: a compartment's |
| 11 | * quality is the average of its units' qualities, so assortment shuffles good units around without |
| 12 | * destroying what makes them good. RAF membership is NOT intrinsic — it is a property of the SET. |
| 13 | * A reaction is in the RAF only if its reactants are producible and it is catalyzed by something |
| 14 | * producible, all within the set present. So splitting a compartment can DESTROY the closure that |
| 15 | * made the parent productive, even though every individual reaction survives. |
| 16 | * |
| 17 | * That collides head-on with Test A, which measured that ASSORTMENT VARIANCE is what purges |
| 18 | * parasites (propagule beats fission by +1.14 phi at matched realized size). Variance is needed to |
| 19 | * purge parasites; variance destroys collective closure. Test B asks which wins. |
| 20 | * |
| 21 | * WHAT IS SUPPLIED vs WHAT MUST EMERGE: |
| 22 | * SUPPLIED: a random catalytic reaction network (rung1.c's given chemistry), a food set, the |
| 23 | * growth/division/death machinery from chemoton.c, and a conserved material pool. Catalysis |
| 24 | * density is SWEPT. |
| 25 | * MUST EMERGE: the RAF itself (nothing designates which reactions are "good" — closure is computed, |
| 26 | * not assigned), and compartment-level differential reproduction (nothing samples parents by |
| 27 | * fitness; compartments divide because they grew). |
| 28 | * UNTOUCHED: the tragedy. Reactions OUTSIDE the RAF are cheap — they carry no catalytic obligation |
| 29 | * — so they replicate faster, exactly as low-phi parasites did. |
| 30 | * |
| 31 | * PRE-REGISTERED: |
| 32 | * T1 TEST OF THE TEST: do RAFs form inside compartments at all? Report mean maxRAF size against the |
| 33 | * random-subset baseline. If no compartment ever closes a RAF, nothing was selected on and a |
| 34 | * null says nothing. |
| 35 | * T2 coupled uptake raises mean RAF size above the random baseline. Compartment selection survives |
| 36 | * the move from per-unit to collective quality. |
| 37 | * T3 CONTROL: uptake blind to RAF size, everything else identical. Must stay at baseline. |
| 38 | * T4 THE REVERSAL: propagule (high variance) should be WORSE than fission here, opposite to Test A, |
| 39 | * because a single-unit bottleneck cannot carry a closure. If propagule still wins, collective |
| 40 | * function is more robust to assortment than expected and Test A's finding generalises. |
| 41 | * |
| 42 | * Do not tune to force a positive. A null with T1 passing is a real result: it would say the ladder |
| 43 | * does not chain, and that chemoton's success depended on quality being per-unit. |
| 44 | * |
| 45 | * build: cc -O2 -Wall -std=c11 -o rafcomp rafcomp.c -lm |
| 46 | */ |
| 47 | #include <math.h> |
| 48 | #include <stdio.h> |
| 49 | #include <stdint.h> |
| 50 | #include <string.h> |
| 51 | |
| 52 | /* ---- reaction network (rung1.c shape, scaled so per-compartment RAF is cheap to compute) ---- */ |
| 53 | #define NMOL 30 |
| 54 | #define NFOOD 6 |
| 55 | #define MRXN 60 |
| 56 | |
| 57 | /* ---- compartment machinery (chemoton.c shape) ---- */ |
| 58 | #define MAXCOMP 2400 |
| 59 | #define MAXSZ 24 |
| 60 | #define GEN 200 |
| 61 | #define BETA 2.0 |
| 62 | #define COSTUNIT 1.0 |
| 63 | #define TOTALMAT 4000.0 |
| 64 | #define UPTAKE 0.55 |
| 65 | #define DEATH 0.10 |
| 66 | #define MUTR 0.02 /* chance a copied reaction is replaced by a random one */ |
| 67 | |
| 68 | static unsigned long long RNG; |
| 69 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 70 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 71 | static int rint_(int n){ return (int)(xs()%(unsigned)n); } |
| 72 | |
| 73 | static int r1[MRXN],r2[MRXN],prod[MRXN]; |
| 74 | static unsigned char cat[MRXN][NMOL]; |
| 75 | |
| 76 | static void build_net(double p){ |
| 77 | for(int j=0;j<MRXN;j++){ |
| 78 | r1[j]=rint_(NMOL); r2[j]=rint_(NMOL); |
| 79 | prod[j]=NFOOD+rint_(NMOL-NFOOD); |
| 80 | for(int m=0;m<NMOL;m++) cat[j][m]=(urand()<p)?1:0; |
| 81 | } |
| 82 | } |
| 83 | |
| 84 | /* maxRAF over a SUBSET of reactions (prune-to-fixpoint, rung1.c's algorithm). |
| 85 | inraf[] is filled with 1 for reactions surviving in the closure. Returns its size. */ |
| 86 | static int max_raf_subset(const int *rx,int n,unsigned char *inraf) |
| 87 | { |
| 88 | unsigned char act[MAXSZ], W[NMOL]; |
| 89 | for(int i=0;i<n;i++) act[i]=1; |
| 90 | for(;;){ |
| 91 | for(int m=0;m<NMOL;m++) W[m]=(m<NFOOD)?1:0; |
| 92 | int grew=1; |
| 93 | while(grew){ grew=0; |
| 94 | for(int i=0;i<n;i++){ if(!act[i]) continue; int j=rx[i]; |
| 95 | if(!W[r1[j]]||!W[r2[j]]||W[prod[j]]) continue; |
| 96 | int cz=0; for(int m=0;m<NMOL;m++) if(cat[j][m]&&W[m]){cz=1;break;} |
| 97 | if(cz){ W[prod[j]]=1; grew=1; } |
| 98 | } |
| 99 | } |
| 100 | int changed=0; |
| 101 | for(int i=0;i<n;i++){ if(!act[i]) continue; int j=rx[i]; |
| 102 | int sup=W[r1[j]]&&W[r2[j]]; |
| 103 | int cz=0; for(int m=0;m<NMOL;m++) if(cat[j][m]&&W[m]){cz=1;break;} |
| 104 | if(!(sup&&cz)){ act[i]=0; changed=1; } |
| 105 | } |
| 106 | if(!changed) break; |
| 107 | } |
| 108 | int c=0; for(int i=0;i<n;i++){ inraf[i]=act[i]; c+=act[i]; } |
| 109 | return c; |
| 110 | } |
| 111 | |
| 112 | typedef struct { int rx[MAXSZ]; unsigned char inraf[MAXSZ]; int n; int raf; double mat; } Comp; |
| 113 | static Comp C[MAXCOMP]; |
| 114 | static int ncomp; |
| 115 | static double pool; |
| 116 | static double demand[MAXCOMP]; |
| 117 | |
| 118 | static double run(int coupled,int divsz,int propagule,double *raf_end,double *ncomp_end, |
| 119 | double *msize,double *ndiv,double *matcheck) |
| 120 | { |
| 121 | int seed_n = divsz/2>2?divsz/2:2; |
| 122 | ncomp=600; |
| 123 | for(int c=0;c<ncomp;c++){ |
| 124 | C[c].n=seed_n; C[c].mat=0; |
| 125 | for(int k=0;k<seed_n;k++) C[c].rx[k]=rint_(MRXN); |
| 126 | } |
| 127 | { long u=0; for(int c=0;c<ncomp;c++) u+=C[c].n; pool=TOTALMAT-u*COSTUNIT; } |
| 128 | double totdiv=0; |
| 129 | |
| 130 | for(int g=0;g<GEN;g++){ |
| 131 | for(int c=0;c<ncomp;c++) C[c].raf = max_raf_subset(C[c].rx,C[c].n,C[c].inraf); |
| 132 | |
| 133 | double D=0; |
| 134 | for(int c=0;c<ncomp;c++){ |
| 135 | double out = coupled ? (double)C[c].raf : 0.5*C[c].n; /* control: blind to closure */ |
| 136 | demand[c]=UPTAKE*out; D+=demand[c]; |
| 137 | } |
| 138 | double frac=(D>0&&D>pool)?pool/D:1.0; |
| 139 | for(int c=0;c<ncomp;c++){ double t=demand[c]*frac; C[c].mat+=t; pool-=t; } |
| 140 | |
| 141 | /* growth: reactions OUTSIDE the RAF are cheap and replicate faster (the tragedy) */ |
| 142 | for(int c=0;c<ncomp;c++){ |
| 143 | while(C[c].mat>=COSTUNIT && C[c].n<MAXSZ && C[c].n>0){ |
| 144 | double tot=0,cum[MAXSZ]; |
| 145 | for(int k=0;k<C[c].n;k++){ tot += 1.0 + BETA*(C[c].inraf[k]?0.0:1.0); cum[k]=tot; } |
| 146 | double r=urand()*tot; int lo=0,hi=C[c].n-1; |
| 147 | while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } |
| 148 | int nj = (urand()<MUTR)? rint_(MRXN) : C[c].rx[lo]; |
| 149 | C[c].rx[C[c].n]=nj; C[c].inraf[C[c].n]=0; C[c].n++; C[c].mat-=COSTUNIT; |
| 150 | } |
| 151 | } |
| 152 | for(int c=0;c<ncomp;c++) |
| 153 | for(int k=0;k<C[c].n;){ |
| 154 | if(urand()<DEATH){ C[c].rx[k]=C[c].rx[C[c].n-1]; C[c].inraf[k]=C[c].inraf[C[c].n-1]; C[c].n--; pool+=COSTUNIT; } |
| 155 | else k++; |
| 156 | } |
| 157 | |
| 158 | int base=ncomp; |
| 159 | for(int c=0;c<base;c++){ |
| 160 | if(C[c].n>=divsz && ncomp<MAXCOMP){ |
| 161 | Comp *p=&C[c],*q=&C[ncomp]; |
| 162 | if(propagule){ |
| 163 | int pick=rint_(p->n); double sh=p->mat/(double)p->n; |
| 164 | q->n=0; q->rx[q->n]=p->rx[pick]; q->inraf[q->n]=0; q->n++; q->mat=sh; p->mat-=sh; |
| 165 | p->rx[pick]=p->rx[p->n-1]; p->inraf[pick]=p->inraf[p->n-1]; p->n--; |
| 166 | } else { |
| 167 | q->n=0; q->mat=p->mat*0.5; p->mat*=0.5; int w=0; |
| 168 | for(int k=0;k<p->n;k++){ |
| 169 | if(urand()<0.5){ q->rx[q->n]=p->rx[k]; q->inraf[q->n]=p->inraf[k]; q->n++; } |
| 170 | else { p->rx[w]=p->rx[k]; p->inraf[w]=p->inraf[k]; w++; } |
| 171 | } |
| 172 | p->n=w; |
| 173 | } |
| 174 | ncomp++; totdiv++; |
| 175 | } |
| 176 | } |
| 177 | for(int c=0;c<ncomp;){ if(C[c].n<=0){ pool+=C[c].mat; C[c]=C[ncomp-1]; ncomp--; } else c++; } |
| 178 | if(ncomp<=0) break; |
| 179 | } |
| 180 | |
| 181 | double sr=0; long u=0; double stored=0; |
| 182 | for(int c=0;c<ncomp;c++){ |
| 183 | C[c].raf=max_raf_subset(C[c].rx,C[c].n,C[c].inraf); |
| 184 | sr+=C[c].raf; u+=C[c].n; stored+=C[c].mat; |
| 185 | } |
| 186 | *raf_end = ncomp? sr/ncomp : 0; |
| 187 | *ncomp_end=ncomp; *msize= ncomp? (double)u/ncomp : 0; |
| 188 | *ndiv=totdiv/GEN; *matcheck=pool+stored+u*COSTUNIT; |
| 189 | return *raf_end; |
| 190 | } |
| 191 | |
| 192 | /* baseline: expected maxRAF of a RANDOM subset of the same size, same network */ |
| 193 | static double random_baseline(int n,int reps) |
| 194 | { |
| 195 | double s=0; int rx[MAXSZ]; unsigned char ir[MAXSZ]; |
| 196 | for(int t=0;t<reps;t++){ for(int k=0;k<n;k++) rx[k]=rint_(MRXN); s+=max_raf_subset(rx,n,ir); } |
| 197 | return s/reps; |
| 198 | } |
| 199 | |
| 200 | int main(void) |
| 201 | { |
| 202 | unsigned long long seeds[5]; |
| 203 | seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL; |
| 204 | for(int i=3;i<5;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1)); |
| 205 | int NS=5; |
| 206 | |
| 207 | printf("=== TEST B: rung 1 chained into rung 4 — collective quality instead of per-unit ===\n"); |
| 208 | printf(" Catalytic output = size of the maxRAF of the reactions a compartment actually holds.\n"); |
| 209 | printf(" Nothing designates which reactions are good: closure is COMPUTED, not assigned.\n"); |
| 210 | printf(" Reactions outside the RAF carry no catalytic obligation, so they replicate %.0fx faster.\n",1+BETA); |
| 211 | printf(" THE TENSION: Test A found assortment VARIANCE purges parasites (+1.14 at matched size).\n"); |
| 212 | printf(" But a RAF is a property of the SET, so variance should DESTROY closure. Which wins?\n\n"); |
| 213 | |
| 214 | printf(" cat.dens | divsz | rand base | FISSION raf | fis ctrl | PROPAGULE raf | prop ctrl | mean sz\n"); |
| 215 | printf(" ---------+-------+-----------+-------------+----------+---------------+-----------+--------\n"); |
| 216 | |
| 217 | double dens[]={0.04,0.08,0.12}; |
| 218 | int sizes[]={8,16}; |
| 219 | int any_raf=0, coupled_wins=0, ctrl_flat=1, ncmp=0, prop_better=0, fis_better=0; |
| 220 | |
| 221 | for(int d=0;d<3;d++){ |
| 222 | for(int z=0;z<2;z++){ |
| 223 | double bf=0,bc=0,pf=0,pc=0,ms=0,base=0,mc=0,nd=0,nc=0; |
| 224 | for(int s=0;s<NS;s++){ |
| 225 | double re,n,m,dv,mm; |
| 226 | RNG=seeds[s]; build_net(dens[d]); |
| 227 | base+=random_baseline(sizes[z]/2>2?sizes[z]/2:2, 200); |
| 228 | RNG=seeds[s]; build_net(dens[d]); bf+=run(1,sizes[z],0,&re,&n,&m,&dv,&mm); ms+=m; mc+=mm; nd+=dv; nc+=n; |
| 229 | RNG=seeds[s]; build_net(dens[d]); bc+=run(0,sizes[z],0,&re,&n,&m,&dv,&mm); |
| 230 | RNG=seeds[s]; build_net(dens[d]); pf+=run(1,sizes[z],1,&re,&n,&m,&dv,&mm); |
| 231 | RNG=seeds[s]; build_net(dens[d]); pc+=run(0,sizes[z],1,&re,&n,&m,&dv,&mm); |
| 232 | } |
| 233 | bf/=NS;bc/=NS;pf/=NS;pc/=NS;ms/=NS;base/=NS;mc/=NS;nd/=NS;nc/=NS; |
| 234 | printf(" %.2f | %2d | %5.2f | %5.2f | %5.2f | %5.2f | %5.2f | %5.2f\n", |
| 235 | dens[d],sizes[z],base,bf,bc,pf,pc,ms); |
| 236 | if(bf>0.2||pf>0.2) any_raf=1; |
| 237 | ncmp++; |
| 238 | if(bf>base+0.3||pf>base+0.3) coupled_wins++; |
| 239 | if(bc>base+0.3||pc>base+0.3) ctrl_flat=0; |
| 240 | if(pf>bf+0.2) prop_better++; |
| 241 | if(bf>pf+0.2) fis_better++; |
| 242 | } |
| 243 | } |
| 244 | printf("\n"); |
| 245 | printf(" T1 RAFs formed inside compartments at all : %s\n", any_raf?"YES":"NO — test did not run"); |
| 246 | printf(" T2 coupled beats the random-subset baseline : %d/%d cells\n", coupled_wins,ncmp); |
| 247 | printf(" T3 CONTROL (uptake blind to closure) flat : %s\n", ctrl_flat?"YES":"NO — CONFOUNDED"); |
| 248 | printf(" T4 REVERSAL: propagule better %d / fission better %d\n\n", prop_better, fis_better); |
| 249 | |
| 250 | if(!any_raf) |
| 251 | printf(" => THE TEST DID NOT RUN. No compartment ever closed a RAF, so there was nothing to\n" |
| 252 | " select on. Invalid experiment, not a null. Raise catalysis density or compartment size.\n"); |
| 253 | else if(!ctrl_flat) |
| 254 | printf(" => CONFOUNDED. The closure-blind control also beats baseline, so the rise is not\n" |
| 255 | " selection on collective function.\n"); |
| 256 | else if(coupled_wins==0) |
| 257 | printf(" => HONEST NULL. RAFs form, but compartment selection does NOT raise closure above the\n" |
| 258 | " random baseline. The ladder does NOT chain: chemoton's success depended on catalytic\n" |
| 259 | " quality being a PER-UNIT property, and it does not survive the move to collective\n" |
| 260 | " closure — assortment destroys the very thing selection would act on.\n"); |
| 261 | else if(fis_better>prop_better) |
| 262 | printf(" => CHAINS, AND TEST A REVERSES. Compartment selection survives collective quality\n" |
| 263 | " (%d/%d cells beat baseline), but FISSION now beats the propagule bottleneck\n" |
| 264 | " (%d vs %d) — the opposite of Test A. A single-unit bottleneck cannot carry a\n" |
| 265 | " closure, so the variance that purges parasites also destroys collective function.\n" |
| 266 | " The optimal assortment is a TRADE-OFF, not a maximum.\n", coupled_wins,ncmp,fis_better,prop_better); |
| 267 | else |
| 268 | printf(" => CHAINS, AND TEST A GENERALISES. Compartment selection survives collective quality\n" |
| 269 | " (%d/%d beat baseline) and the propagule bottleneck still wins (%d vs %d), so RAF\n" |
| 270 | " closure is more robust to assortment than predicted. Report as-is.\n", |
| 271 | coupled_wins,ncmp,prop_better,fis_better); |
| 272 | return 0; |
| 273 | } |