| … | … | @@ -0,0 +1,168 @@ |
| 1 | /* ladder35.c — MILESTONE 4: rungs 3 and 5 in the same type. No new machinery. |
| 2 | * |
| 3 | * The substrate now carries species, reactions, containers-as-species and counts (substrate.c, |
| 4 | * container.c, counts.c), expressing rungs 1, 2 and 4 and reproducing rung1.c's threshold, |
| 5 | * polymer.c's bootstrap problem, ribozyme.c's ordering and byproduct.c's isolation control. |
| 6 | * The claim was that rungs 3 and 5 need no new type. This tests it. |
| 7 | * |
| 8 | * rung 3 species ARE sequences. A replication reaction copies a species to itself; mutation is a |
| 9 | * variant-producing reaction to a Hamming neighbour. The rung-3 property is Eigen's ERROR |
| 10 | * CATASTROPHE — a sharp transition from localised on a master sequence to delocalised over |
| 11 | * sequence space, at mu_c ~ ln(sigma)/L. |
| 12 | * rung 5 a specialist is a species participating in only PART of the reaction set. Division of |
| 13 | * labour is complementary specialists whose union covers everything the container needs, |
| 14 | * none of which suffices alone. |
| 15 | * |
| 16 | * THE CHECK, as at every milestone: expressing something is worthless unless it reproduces the |
| 17 | * result we already have. |
| 18 | * L3 the master-sequence fraction must show a SHARP fall with mutation rate, and the observed |
| 19 | * threshold must sit near ln(sigma)/L — rung3.c's exact quasispecies value. |
| 20 | * L5 specialists must sustain complementary coverage that no single species provides, and |
| 21 | * coverage must COLLAPSE when the requirement for complementarity is removed (the control). |
| 22 | * |
| 23 | * build: cc -O2 -Wall -std=c11 -o ladder35 ladder35.c -lm |
| 24 | */ |
| 25 | #include <math.h> |
| 26 | #include <stdio.h> |
| 27 | #include <string.h> |
| 28 | |
| 29 | #define LBITS 8 |
| 30 | #define NSEQ (1<<LBITS) /* 256 sequence-species — species ARE sequences */ |
| 31 | #define POP 4000 |
| 32 | #define GENS 300 |
| 33 | #define SIGMA 4.0 /* master-sequence selective advantage */ |
| 34 | |
| 35 | #define NROLE 3 |
| 36 | #define NSPC 12 /* rung-5 species */ |
| 37 | #define NCOMP 400 |
| 38 | #define CSZ 10 |
| 39 | |
| 40 | static unsigned long long RNG; |
| 41 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 42 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 43 | static int rint_(int n){ return (int)(xs()%(unsigned)n); } |
| 44 | |
| 45 | /* ---------------- RUNG 3: species are sequences; mutation is a reaction to a neighbour --------- */ |
| 46 | static int seqpop[NSEQ], nxtpop[NSEQ]; |
| 47 | static double fit_of(int s){ return (s==0)? SIGMA : 1.0; } |
| 48 | |
| 49 | static double rung3(double mu){ |
| 50 | memset(seqpop,0,sizeof seqpop); |
| 51 | for(int i=0;i<POP;i++) seqpop[rint_(NSEQ)]++; |
| 52 | for(int g=0;g<GENS;g++){ |
| 53 | double tot=0; static double cum[NSEQ]; |
| 54 | for(int s=0;s<NSEQ;s++){ tot += seqpop[s]*fit_of(s); cum[s]=tot; } |
| 55 | memset(nxtpop,0,sizeof nxtpop); |
| 56 | for(int i=0;i<POP;i++){ |
| 57 | double r=urand()*tot; int lo=0,hi=NSEQ-1; |
| 58 | while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } |
| 59 | int child=lo; |
| 60 | for(int b=0;b<LBITS;b++) if(urand()<mu) child^=(1<<b); /* mutation = variant reaction */ |
| 61 | nxtpop[child]++; |
| 62 | } |
| 63 | memcpy(seqpop,nxtpop,sizeof seqpop); |
| 64 | } |
| 65 | return (double)seqpop[0]/POP; |
| 66 | } |
| 67 | |
| 68 | /* ---------------- RUNG 5: a specialist participates in only PART of the reaction set ----------- */ |
| 69 | static unsigned char covers[NSPC][NROLE]; /* which roles a species can perform */ |
| 70 | static int comp[NCOMP][CSZ], ncomp2; |
| 71 | |
| 72 | static void build_species(int generalists_allowed){ |
| 73 | for(int s=0;s<NSPC;s++){ |
| 74 | memset(covers[s],0,sizeof covers[s]); |
| 75 | if(generalists_allowed && s<NROLE){ for(int r=0;r<NROLE;r++) covers[s][r]=1; } |
| 76 | else covers[s][s%NROLE]=1; /* specialist: exactly one role */ |
| 77 | } |
| 78 | } |
| 79 | static int coverage(const int *cell){ |
| 80 | int seen[NROLE]; memset(seen,0,sizeof seen); |
| 81 | for(int k=0;k<CSZ;k++) for(int r=0;r<NROLE;r++) if(covers[cell[k]][r]) seen[r]=1; |
| 82 | int c=0; for(int r=0;r<NROLE;r++) c+=seen[r]; |
| 83 | return c; |
| 84 | } |
| 85 | /* need_all=1: a container is viable only with ALL roles covered (complementarity required). |
| 86 | need_all=0 (CONTROL): any single role suffices — complementarity is not demanded. */ |
| 87 | static double rung5(int need_all){ |
| 88 | ncomp2=NCOMP; |
| 89 | for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++) comp[c][k]=rint_(NSPC); |
| 90 | for(int g=0;g<200;g++){ |
| 91 | static int nxt[NCOMP][CSZ]; |
| 92 | double tot=0,cum[NCOMP]; |
| 93 | for(int c=0;c<NCOMP;c++){ |
| 94 | int cov=coverage(comp[c]); |
| 95 | double v = need_all ? ((cov==NROLE)?1.0:0.02) : ((cov>=1)?1.0:0.02); |
| 96 | tot+=v; cum[c]=tot; |
| 97 | } |
| 98 | for(int c=0;c<NCOMP;c++){ |
| 99 | double r=urand()*tot; int lo=0,hi=NCOMP-1; |
| 100 | while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } |
| 101 | for(int k=0;k<CSZ;k++){ |
| 102 | int src=comp[lo][rint_(CSZ)]; |
| 103 | if(urand()<0.02) src=rint_(NSPC); |
| 104 | nxt[c][k]=src; |
| 105 | } |
| 106 | } |
| 107 | memcpy(comp,nxt,sizeof comp); |
| 108 | } |
| 109 | double full=0; |
| 110 | for(int c=0;c<NCOMP;c++) if(coverage(comp[c])==NROLE) full++; |
| 111 | return full/NCOMP; |
| 112 | } |
| 113 | |
| 114 | int main(void) |
| 115 | { |
| 116 | printf("=== MILESTONE 4: rungs 3 and 5 in the same type, no new machinery ===\n\n"); |
| 117 | |
| 118 | /* ---- L3 ---- */ |
| 119 | double muc = log(SIGMA)/LBITS; |
| 120 | printf(" L3 — rung 3: species ARE sequences, mutation is a variant-producing reaction.\n"); |
| 121 | printf(" Eigen predicts an error catastrophe near mu_c = ln(sigma)/L = ln(%.0f)/%d = %.3f\n\n",SIGMA,LBITS,muc); |
| 122 | printf(" mu | master-sequence fraction\n"); |
| 123 | printf(" -------+-------------------------\n"); |
| 124 | double mus[7]={0.02,0.08,0.14,0.17,0.22,0.30,0.45}; |
| 125 | double v[7]; |
| 126 | for(int i=0;i<7;i++){ |
| 127 | double a=0; for(int s=0;s<4;s++){ RNG=0x9e3779b97f4a7c15ULL+(unsigned long long)(i*97+s)*0x2545F4914F6CDD1DULL; a+=rung3(mus[i]); } |
| 128 | v[i]=a/4; |
| 129 | printf(" %.2f | %.4f %s\n",mus[i],v[i], (mus[i]<muc)?"(below mu_c)":"(above)"); |
| 130 | } |
| 131 | /* Locate the threshold as the CROSSING of a low master fraction, not the biggest absolute drop. |
| 132 | On a monotone decreasing curve the largest absolute drop always sits where the curve is |
| 133 | highest, so it locates the steep start, not the catastrophe — the same wrong-statistic error |
| 134 | as linkage.c's "B stays near 1" bar. Interpolate where the master fraction crosses 0.05. */ |
| 135 | double where=-1; |
| 136 | for(int i=0;i+1<7;i++){ |
| 137 | if(v[i]>=0.05 && v[i+1]<0.05){ |
| 138 | double t=(v[i]-0.05)/(v[i]-v[i+1]); |
| 139 | where=mus[i]+t*(mus[i+1]-mus[i]); break; |
| 140 | } |
| 141 | } |
| 142 | int l3 = (v[0]>0.3) && (v[6]<0.05) && where>0 && fabs(where-muc)<0.06; |
| 143 | printf("\n master fraction crosses 0.05 at mu = %.3f vs Eigen prediction %.3f (diff %+.3f)\n",where,muc,where-muc); |
| 144 | printf(" => %s\n\n", l3?"LOCALISED below, DELOCALISED above, transition near ln(sigma)/L — rung 3 expressed." |
| 145 | :"does NOT reproduce the error catastrophe — rung 3 is not expressed."); |
| 146 | |
| 147 | /* ---- L5 ---- */ |
| 148 | printf(" L5 — rung 5: a specialist participates in only PART of the reaction set.\n"); |
| 149 | printf(" Division of labour = complementary specialists whose union covers what one cannot.\n\n"); |
| 150 | build_species(0); |
| 151 | double spec_need = rung5(1), spec_ctrl = rung5(0); |
| 152 | build_species(1); |
| 153 | double gen_need = rung5(1); |
| 154 | printf(" arm | containers with ALL %d roles\n",NROLE); |
| 155 | printf(" --------------------------------------+---------------------------\n"); |
| 156 | printf(" specialists, complementarity REQUIRED | %.3f\n",spec_need); |
| 157 | printf(" specialists, requirement REMOVED (ctl)| %.3f\n",spec_ctrl); |
| 158 | printf(" generalists available | %.3f\n",gen_need); |
| 159 | int l5 = (spec_need>0.5) && (spec_ctrl < spec_need-0.1); |
| 160 | printf("\n => %s\n\n", l5?"complementary coverage is sustained, and COLLAPSES when not required —\n division of labour is a reaction-participation pattern, not a new type." |
| 161 | :"coverage does not behave as rung 5 — not expressed."); |
| 162 | |
| 163 | printf(" MILESTONE 4 %s\n", (l3&&l5)?"PASSED — rungs 1,2,3,4,5 all live in one type." |
| 164 | :"NOT PASSED — see which check failed above."); |
| 165 | printf(" Still scaffolding: nothing here CROSSES a rung. It removes the reason crossings could\n"); |
| 166 | printf(" not compose, which is what the whole ladder has lacked.\n"); |
| 167 | return 0; |
| 168 | } |