| … | … | @@ -0,0 +1,249 @@ |
| 1 | /* chemoton.c — compartments that GROW from their own chemistry's output. Does the stochastic |
| 2 | * corrector appear WITHOUT anyone decreeing group selection? |
| 3 | * |
| 4 | * WHERE THIS COMES FROM. byproduct.c applied the byproduct idea to the wrong half of what a |
| 5 | * compartment is. It coupled catalysis -> wall HEIGHT -> isolation, and measured that isolation |
| 6 | * alone does nothing: supplied walls at full height reach phi 5.65, no better than open space, while |
| 7 | * supplied walls PLUS between-site selection reach 10.78. So the barrier was never the active |
| 8 | * ingredient. What made compartments work is that they DIVIDE and are SELECTED on catalytic content |
| 9 | * -- and in ribozyme.c and byproduct.c that step was DECREED ("sample a parent site proportional to |
| 10 | * its mean phi"), never derived. |
| 11 | * |
| 12 | * THE FIX IS THE SAME IDEA, MOVED. A compartment divides when it grows big enough, so differential |
| 13 | * reproduction reduces to differential GROWTH. If the wall material is produced by the chemistry |
| 14 | * inside, growth rate is automatically a function of internal catalytic content: |
| 15 | * catalysis -> membrane material -> growth -> division |
| 16 | * A parasite produces no material, so a parasite-heavy compartment grows slowly and divides rarely. |
| 17 | * That yields the decreed rule as a CONSEQUENCE rather than an assertion. (This is Ganti's chemoton |
| 18 | * / the Szostak protocell coupling -- the field's standing answer, not a new idea. What is new here |
| 19 | * is testing it against our own measured claim that isolation is not enough.) |
| 20 | * |
| 21 | * AND A SECOND INGREDIENT byproduct.c LACKED: something LIMITING. There, sites were fixed at 600, so |
| 22 | * a dissolving region freed nothing and a thriving one had nowhere to expand. Decay without |
| 23 | * competition is not selection. Here MATERIAL IS CONSERVED: pool + compartment stores + units*cost |
| 24 | * is constant, uptake is rationed when demand exceeds supply, and dead units return their material. |
| 25 | * Compartments genuinely compete. |
| 26 | * |
| 27 | * WHAT IS SUPPLIED vs WHAT MUST EMERGE: |
| 28 | * SUPPLIED: the physical coupling activity -> material uptake, a division size, a per-unit death |
| 29 | * rate, and a conserved material pool. The division size is SWEPT, not picked. |
| 30 | * MUST EMERGE: differential reproduction of compartments. NOTHING samples parents by fitness. |
| 31 | * Compartments divide because they grew; they grew because they took up material; they took up |
| 32 | * material because their contents catalyzed. No group-selection step exists anywhere in the code. |
| 33 | * UNTOUCHED: the within-compartment tragedy. Parasites still replicate 3x faster and still take the |
| 34 | * growth slots, so a fast-growing compartment fills with the parasites its own growth carries. |
| 35 | * |
| 36 | * Chemistry identical to ribozyme.c / coupling.c / partition.c / signal.c / byproduct.c |
| 37 | * (phi = hairpin fold, parasites 3x faster, F0=0.85, F1=0.999, start phi ~5.99 = the neutral |
| 38 | * expectation 6.0). Reference: ribozyme well-mixed 5.99 -> 5.50; DECREED group selection at |
| 39 | * compartment size 8 -> 11.34, and it FAILS at size >= 16. |
| 40 | * |
| 41 | * PRE-REGISTERED: |
| 42 | * C1 the within-compartment tragedy still runs (parasites take growth slots) -- stated so that a |
| 43 | * rise cannot be explained by having quietly removed the problem. |
| 44 | * C2 phi RISES anyway, because catalyst-rich compartments divide faster. The corrector, emergent. |
| 45 | * C3 THE KEY TEST: the SIZE THRESHOLD reappears on its own. Sweeping division size, crossing at |
| 46 | * small sizes and failure at large ones -- matching ribozyme.c's decreed-selection result |
| 47 | * (works <= 8, fails >= 16). If growth-coupling really is the mechanism behind the decree, it |
| 48 | * must reproduce the decree's signature, not merely produce some rise. |
| 49 | * C4 CONTROL: uptake CONSTANT PER UNIT, independent of phi. Identical division, assortment, death |
| 50 | * and material limit; only the growth->catalysis coupling is cut. If phi rises here too, the |
| 51 | * effect is division-plus-assortment per se and C2/C3 mean nothing. |
| 52 | * C5 TEST OF THE TEST: do compartments actually divide and die, and does the population turn over? |
| 53 | * Report divisions and deaths per generation and the compartment count. If nothing divides, no |
| 54 | * compartment-level reproduction happened and a null says nothing. |
| 55 | * |
| 56 | * Do not tune to force a positive. A null with C5 passing is a real result: it would say the |
| 57 | * coupling is insufficient and the first unit of selection had to be supplied by the world. |
| 58 | * |
| 59 | * build: cc -O2 -Wall -std=c11 -o chemoton chemoton.c -lm |
| 60 | */ |
| 61 | #include <math.h> |
| 62 | #include <stdio.h> |
| 63 | #include <stdint.h> |
| 64 | #include <string.h> |
| 65 | |
| 66 | #define L 24 |
| 67 | #define PHIMAX (L/2) |
| 68 | #define F0 0.85 |
| 69 | #define F1 0.999 |
| 70 | #define BETA 2.0 |
| 71 | #define GEN 300 |
| 72 | |
| 73 | #define MAXCOMP 3000 |
| 74 | #define MAXSZ 48 |
| 75 | #define COSTUNIT 1.0 |
| 76 | #define TOTALMAT 6000.0 /* conserved: pool + stores + units*COSTUNIT */ |
| 77 | #define UPTAKE 0.55 |
| 78 | #define DEATH 0.10 |
| 79 | |
| 80 | static unsigned long long RNG; |
| 81 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 82 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 83 | |
| 84 | static int phi(uint32_t s){ int c=0; for(int i=0;i<PHIMAX;i++){ int a=(s>>i)&1,b=(s>>(L-1-i))&1; if(a!=b)c++; } return c; } |
| 85 | static uint32_t replicate(uint32_t t,double f){ uint32_t c=0; for(int i=0;i<L;i++){ int b=(t>>i)&1; int nb=(urand()<f)?(1-b):b; if(nb)c|=(1u<<i);} return c; } |
| 86 | |
| 87 | typedef struct { uint32_t u[MAXSZ]; int n; double mat; } Comp; |
| 88 | static Comp C[MAXCOMP]; |
| 89 | static int ncomp; |
| 90 | static double pool; |
| 91 | static double demand[MAXCOMP]; |
| 92 | |
| 93 | /* coupled=1: uptake tracks catalytic output. coupled=0: uptake is constant per unit (C4 control). */ |
| 94 | static double run(int coupled,int divsz,double *phi_start,double *ndiv,double *ndeath, |
| 95 | double *ncomp_end,double *msize,double *matcheck) |
| 96 | { |
| 97 | int seed_n = divsz/2 > 2 ? divsz/2 : 2; |
| 98 | ncomp = 600; |
| 99 | for(int c=0;c<ncomp;c++){ |
| 100 | C[c].n=seed_n; C[c].mat=0.0; |
| 101 | for(int k=0;k<seed_n;k++){ uint32_t v=0; for(int b=0;b<L;b++) if(urand()<0.5) v|=(1u<<b); C[c].u[k]=v; } |
| 102 | } |
| 103 | { double sp=0; long t=0; for(int c=0;c<ncomp;c++) for(int k=0;k<C[c].n;k++){ sp+=phi(C[c].u[k]); t++; } *phi_start=sp/t; } |
| 104 | { long units=0; for(int c=0;c<ncomp;c++) units+=C[c].n; pool = TOTALMAT - units*COSTUNIT; } |
| 105 | |
| 106 | double totdiv=0, totdeath=0; |
| 107 | |
| 108 | for(int g=0;g<GEN;g++){ |
| 109 | /* ---- uptake: demand from catalytic output, RATIONED when the pool cannot meet it ---- */ |
| 110 | double D=0; |
| 111 | for(int c=0;c<ncomp;c++){ |
| 112 | double out=0; |
| 113 | if(coupled){ for(int k=0;k<C[c].n;k++) out += (double)phi(C[c].u[k])/PHIMAX; } |
| 114 | else out = 0.5*C[c].n; /* control: same scale, blind to phi */ |
| 115 | demand[c]=UPTAKE*out; D+=demand[c]; |
| 116 | } |
| 117 | double frac = (D>0 && D>pool)? pool/D : 1.0; |
| 118 | for(int c=0;c<ncomp;c++){ double take=demand[c]*frac; C[c].mat+=take; pool-=take; } |
| 119 | |
| 120 | /* ---- growth: spend material on new units. Parasites still take the slots (C1). ---- */ |
| 121 | for(int c=0;c<ncomp;c++){ |
| 122 | if(C[c].n<=0) continue; |
| 123 | double a=0; for(int k=0;k<C[c].n;k++) a+=phi(C[c].u[k]); |
| 124 | double f=F0+(F1-F0)*((a/C[c].n)/PHIMAX); |
| 125 | while(C[c].mat>=COSTUNIT && C[c].n<MAXSZ){ |
| 126 | double tot=0,cum[MAXSZ]; |
| 127 | for(int k=0;k<C[c].n;k++){ tot+=1.0+BETA*(1.0-(double)phi(C[c].u[k])/PHIMAX); cum[k]=tot; } |
| 128 | double r=urand()*tot; int lo=0,hi=C[c].n-1; |
| 129 | while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } |
| 130 | C[c].u[C[c].n]=replicate(C[c].u[lo],f); C[c].n++; C[c].mat-=COSTUNIT; |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /* ---- per-unit death: material returns to the pool ---- */ |
| 135 | for(int c=0;c<ncomp;c++){ |
| 136 | for(int k=0;k<C[c].n;){ |
| 137 | if(urand()<DEATH){ C[c].u[k]=C[c].u[C[c].n-1]; C[c].n--; pool+=COSTUNIT; } |
| 138 | else k++; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | /* ---- division at threshold: random assortment, material split ---- */ |
| 143 | int base=ncomp; |
| 144 | for(int c=0;c<base;c++){ |
| 145 | if(C[c].n>=divsz && ncomp<MAXCOMP){ |
| 146 | Comp *p=&C[c], *q=&C[ncomp]; |
| 147 | q->n=0; q->mat=p->mat*0.5; p->mat*=0.5; |
| 148 | int w=0; |
| 149 | for(int k=0;k<p->n;k++){ |
| 150 | if(urand()<0.5) q->u[q->n++]=p->u[k]; |
| 151 | else p->u[w++]=p->u[k]; |
| 152 | } |
| 153 | p->n=w; ncomp++; totdiv++; |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | /* ---- compartment death when empty: stored material returns ---- */ |
| 158 | for(int c=0;c<ncomp;){ |
| 159 | if(C[c].n<=0){ pool+=C[c].mat; C[c]=C[ncomp-1]; ncomp--; totdeath++; } |
| 160 | else c++; |
| 161 | } |
| 162 | if(ncomp<=0) break; |
| 163 | } |
| 164 | |
| 165 | double sp=0; long t=0; |
| 166 | for(int c=0;c<ncomp;c++) for(int k=0;k<C[c].n;k++){ sp+=phi(C[c].u[k]); t++; } |
| 167 | double stored=0; for(int c=0;c<ncomp;c++) stored+=C[c].mat; |
| 168 | *ndiv=totdiv/GEN; *ndeath=totdeath/GEN; *ncomp_end=ncomp; |
| 169 | *msize = ncomp? (double)t/ncomp : 0; |
| 170 | *matcheck = pool+stored+t*COSTUNIT; |
| 171 | return t? sp/t : 0.0; |
| 172 | } |
| 173 | |
| 174 | int main(void) |
| 175 | { |
| 176 | unsigned long long seeds[6]; |
| 177 | seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL; |
| 178 | for(int i=3;i<6;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1)); |
| 179 | int NS=6; |
| 180 | |
| 181 | printf("=== CHEMOTON: compartments that grow from their own chemistry's output ===\n"); |
| 182 | printf(" catalysis -> material uptake -> growth -> division. NOTHING samples parents by fitness;\n"); |
| 183 | printf(" no group-selection step exists in the code. Material is CONSERVED (%.0f) and rationed,\n",TOTALMAT); |
| 184 | printf(" so compartments compete. Parasites still replicate %.0fx faster and still take growth slots.\n",1+BETA); |
| 185 | printf(" REFERENCE: ribozyme, with DECREED group selection, crosses at size 8 (11.34) and FAILS\n"); |
| 186 | printf(" at size >= 16. If growth-coupling is the mechanism behind that decree, the SIZE THRESHOLD\n"); |
| 187 | printf(" must reappear here on its own.\n\n"); |
| 188 | |
| 189 | printf(" div size | COUPLED phi | CONTROL phi | comps | mean sz | div/gen | death/gen | material\n"); |
| 190 | printf(" ---------+-------------+-------------+-------+---------+---------+-----------+---------\n"); |
| 191 | |
| 192 | int sizes[]={4,8,16,32}; |
| 193 | int NZ=(int)(sizeof(sizes)/sizeof(sizes[0])); |
| 194 | double start=0; int turned_over=0; |
| 195 | double co[8], ct[8]; |
| 196 | |
| 197 | for(int z=0;z<NZ;z++){ |
| 198 | double ec=0,ek=0,nd=0,nx=0,nc=0,ms=0,mc=0,ps=0; |
| 199 | for(int s=0;s<NS;s++){ |
| 200 | double p,d,x,n,m,mm; |
| 201 | RNG=seeds[s]; ec+=run(1,sizes[z],&p,&d,&x,&n,&m,&mm); nd+=d; nx+=x; nc+=n; ms+=m; mc+=mm; ps+=p; |
| 202 | RNG=seeds[s]; ek+=run(0,sizes[z],&p,&d,&x,&n,&m,&mm); |
| 203 | } |
| 204 | ec/=NS;ek/=NS;nd/=NS;nx/=NS;nc/=NS;ms/=NS;mc/=NS;ps/=NS; |
| 205 | start=ps; co[z]=ec; ct[z]=ek; |
| 206 | printf(" %2d | %5.2f | %5.2f | %5.0f | %5.2f | %6.1f | %6.1f | %.0f\n", |
| 207 | sizes[z],ec,ek,nc,ms,nd,nx,mc); |
| 208 | if(nd>1.0 && nx>1.0) turned_over=1; |
| 209 | } |
| 210 | printf("\n start phi (= neutral expectation) %.2f [material must stay ~%.0f]\n\n",start,TOTALMAT); |
| 211 | |
| 212 | int small_wins=0, large_fails=0, ctrl_flat=1; |
| 213 | for(int z=0;z<NZ;z++){ |
| 214 | if(sizes[z]<=8 && co[z]>start+0.5) small_wins++; |
| 215 | if(sizes[z]>=16 && co[z]<start+0.5) large_fails++; |
| 216 | if(ct[z]>start+0.5) ctrl_flat=0; |
| 217 | } |
| 218 | |
| 219 | printf(" C5 compartments divided AND died (turnover) : %s\n", turned_over?"YES":"NO — test did not run"); |
| 220 | printf(" C2 phi rose above neutral somewhere : %s\n", (co[0]>start+0.5||co[1]>start+0.5||co[2]>start+0.5||co[3]>start+0.5)?"YES":"NO"); |
| 221 | printf(" C3 SIZE THRESHOLD reappeared (small yes/large no): small %d/2, large %d/2\n", small_wins, large_fails); |
| 222 | printf(" C4 control (uptake blind to phi) stayed flat : %s\n\n", ctrl_flat?"YES":"NO — CONFOUNDED"); |
| 223 | |
| 224 | if(!turned_over) |
| 225 | printf(" => THE TEST DID NOT RUN. Compartments did not divide and die, so no compartment-level\n" |
| 226 | " reproduction existed to select on. Invalid experiment, not a null.\n"); |
| 227 | else if(!ctrl_flat) |
| 228 | printf(" => CONFOUNDED. The control rose too, so division-plus-assortment alone produces the\n" |
| 229 | " effect and the growth->catalysis coupling is not doing the work.\n"); |
| 230 | else if(small_wins==2 && large_fails==2) |
| 231 | printf(" => THE CORRECTOR EMERGED, THRESHOLD AND ALL. Compartment-level differential reproduction\n" |
| 232 | " arose with no group-selection step anywhere: compartments divided because they grew,\n" |
| 233 | " grew because they took up material, took up material because their contents catalyzed.\n" |
| 234 | " And it reproduces the DECREE'S SIGNATURE -- crossing at small division sizes, failing\n" |
| 235 | " at large -- which is what makes this the mechanism behind ribozyme.c's rule rather\n" |
| 236 | " than merely another way to get a rise.\n"); |
| 237 | else if(small_wins>0) |
| 238 | printf(" => PARTIAL. phi rises with the coupling and the control stays flat, so growth-coupling is\n" |
| 239 | " doing real work -- but the size threshold does not cleanly match the decreed version\n" |
| 240 | " (small %d/2, large %d/2). Report the shape as-is; do not claim the corrector.\n", |
| 241 | small_wins,large_fails); |
| 242 | else |
| 243 | printf(" => HONEST NULL, with turnover confirmed. Coupling growth to catalysis does NOT generate\n" |
| 244 | " usable compartment-level selection here: the within-compartment tragedy outruns the\n" |
| 245 | " between-compartment advantage at every division size. Ganti's coupling is necessary\n" |
| 246 | " but not sufficient, and the first unit of selection may have had to be supplied by\n" |
| 247 | " the world rather than built by the chemistry.\n"); |
| 248 | return 0; |
| 249 | } |