| … | … | @@ -0,0 +1,210 @@ |
| 1 | /* replisome.c — the closed genotype/phenotype loop. Can it bootstrap? |
| 2 | * |
| 3 | * WHERE THIS COMES FROM. genome.c v1/v2 gave templates a fair test and they failed twice, but the |
| 4 | * second failure exposed something I had been getting for free: TEMPLATE COPYING WAS FREE PHYSICS |
| 5 | * (child = par, perfect, no cost). In reality copying a genome faithfully needs a REPLICASE, which |
| 6 | * is itself a catalyst, which is itself specified by a template. That is the loop, and it is exactly |
| 7 | * rung 1→2. |
| 8 | * |
| 9 | * Two flaws in genome.c are fixed here: |
| 10 | * (1) a picked template ALWAYS became a catalyst when reading was on, so reading was a dead end for |
| 11 | * the template lineage — it could be useful or persist, never both. Now a picked template is |
| 12 | * READ with probability PREAD and otherwise COPIED TRUE, exactly as a catalyst is copied true. |
| 13 | * Reading is a FUNCTION templates provide, not a route by which they propagate. |
| 14 | * (2) template fidelity was free. Now it is CONDITIONAL on the compartment holding a catalyst of |
| 15 | * the replicase type: with one, templates copy at MU*RLO; without, at MU*RHI. |
| 16 | * |
| 17 | * THE RAZOR. This is where encoding the answer is easiest, so, precisely: |
| 18 | * SUPPLIED as physics: "a type-REPTYPE catalyst improves template fidelity in its compartment" — |
| 19 | * a function, the same shape as ribozyme.c's supplied fold→catalysis map. |
| 20 | * NOT SUPPLIED: any reward for being or holding a replicase. REPTYPE is EXCLUDED FROM THE TARGET |
| 21 | * SET, so it contributes nothing to payoff directly — it is pure overhead unless the template |
| 22 | * loop pays for itself. It is not seeded, not protected, not favoured in replication. Non-folders |
| 23 | * still replicate faster, so the parasite advantage is intact and the replicase is a PUBLIC GOOD |
| 24 | * within its compartment, free-ridable exactly as catalysis was in ribozyme.c. |
| 25 | * MEASURED: whether lineages acquire and HOLD the replicase, and whether function is higher for it. |
| 26 | * |
| 27 | * THE CONTROL, built to today's matched-condition lesson. Turning the replicase effect on also |
| 28 | * changes the average template mutation rate, which would confound any gain. So the control applies |
| 29 | * the IDENTICAL two-rate structure conditioned on a COIN FLIP per compartment instead of on |
| 30 | * replicase presence — same mutation-rate distribution, no type to select for. The realized |
| 31 | * low-fidelity fraction is reported for both arms so the match is visible rather than assumed. |
| 32 | * |
| 33 | * PRE-REGISTERED: |
| 34 | * R1 replicase frequency RISES above the control's. Lineages hold a type that pays them nothing |
| 35 | * directly, because it protects the templates that carry everything else. |
| 36 | * R2 target repertoire is HIGHER with the replicase effect than with the coin-flip control. |
| 37 | * R3 TEST OF THE TEST: catalysts and templates coexist, and the two arms' low-fidelity fractions |
| 38 | * are comparable. If they diverge sharply the comparison is confounded by mutation load. |
| 39 | * R4 the advantage should GROW with base mu — the replicase matters most where templates decay fast. |
| 40 | * |
| 41 | * HONEST PRIOR: partial. The replicase is a public good inside its compartment, so free-riding should |
| 42 | * bite exactly as it did in ribozyme.c — one level up. A null here is a real result. |
| 43 | * |
| 44 | * build: cc -O2 -Wall -std=c11 -o replicase replisome.c -lm |
| 45 | */ |
| 46 | #include <math.h> |
| 47 | #include <stdio.h> |
| 48 | #include <stdint.h> |
| 49 | |
| 50 | #define NFOLD 16 |
| 51 | #define NPAIR (NFOLD/2) |
| 52 | #define THETA 6 |
| 53 | #define NTYPE 8 |
| 54 | #define REPTYPE 0 /* the replicase type — excluded from the target set */ |
| 55 | #define NTARG 3 /* target types, drawn from types 1..7 */ |
| 56 | #define NCOMP 600 |
| 57 | #define CSZ 8 |
| 58 | #define GEN 200 |
| 59 | #define BETA 2.0 |
| 60 | #define PREAD 0.50 |
| 61 | #define RLO 0.25 /* template mutation multiplier WITH a replicase present */ |
| 62 | #define RHI 1.50 /* ... and without */ |
| 63 | |
| 64 | |
| 65 | static unsigned long long RNG; |
| 66 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 67 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 68 | |
| 69 | static int TARGET[NTYPE]; |
| 70 | static int phi(uint32_t s){ int c=0; for(int i=0;i<NPAIR;i++){ int a=(s>>i)&1,b=(s>>(NFOLD-1-i))&1; if(a!=b)c++; } return c; } |
| 71 | static int isfold(uint32_t s){ return phi(s)>=THETA; } |
| 72 | static int typeof_(uint32_t s){ return (int)((s>>NFOLD)&7); } |
| 73 | static uint32_t mut(uint32_t s,double mu){ for(int i=0;i<NFOLD+3;i++) if(urand()<mu) s^=(1u<<i); return s; } |
| 74 | |
| 75 | /* Each slot holds a PAIR of strands — a minimal physical complex, copied together (linkage). |
| 76 | PRIVATE: a template's fidelity is high only if ITS OWN partner is a replicase. |
| 77 | SHARED : a template's fidelity is high if ANY catalyst in the compartment is a replicase |
| 78 | (this is replicase.c's behaviour, which gave a complete null). |
| 79 | One knob: the SCALE at which the replicase's benefit is privatised. */ |
| 80 | static uint32_t A[NCOMP][CSZ], B[NCOMP][CSZ], nA[NCOMP][CSZ], nB[NCOMP][CSZ]; |
| 81 | static double cum[NCOMP]; |
| 82 | |
| 83 | static int payoff_of(int c){ |
| 84 | int seen[NTYPE]; for(int t=0;t<NTYPE;t++) seen[t]=0; |
| 85 | int d=0; |
| 86 | for(int k=0;k<CSZ;k++){ |
| 87 | if(isfold(A[c][k])){ int t=typeof_(A[c][k]); if(!seen[t]){ seen[t]=1; if(TARGET[t]) d++; } } |
| 88 | if(isfold(B[c][k])){ int t=typeof_(B[c][k]); if(!seen[t]){ seen[t]=1; if(TARGET[t]) d++; } } |
| 89 | } |
| 90 | return d; |
| 91 | } |
| 92 | static int is_rep(uint32_t s){ return isfold(s) && typeof_(s)==REPTYPE; } |
| 93 | static int comp_has_rep(int c){ for(int k=0;k<CSZ;k++) if(is_rep(A[c][k])||is_rep(B[c][k])) return 1; return 0; } |
| 94 | static double rep_freq_of(int c){ int n=0; for(int k=0;k<CSZ;k++){ n+=is_rep(A[c][k]); n+=is_rep(B[c][k]); } return (double)n/(2*CSZ); } |
| 95 | |
| 96 | /* mode 0 = PRIVATE (replisome scale), 1 = SHARED (compartment scale, = replicase.c) */ |
| 97 | static void run(int mode,double mu,double *rep_freq,double *pay_out,double *lofrac) |
| 98 | { |
| 99 | for(int t=0;t<NTYPE;t++) TARGET[t]=0; |
| 100 | for(int got=0;got<NTARG;){ int t=1+(int)(xs()%(NTYPE-1)); if(!TARGET[t]){ TARGET[t]=1; got++; } } |
| 101 | for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++){ |
| 102 | uint32_t v=0,w=0; |
| 103 | for(int i=0;i<NFOLD+3;i++){ if(urand()<0.5) v|=(1u<<i); if(urand()<0.5) w|=(1u<<i); } |
| 104 | A[c][k]=v; B[c][k]=w; |
| 105 | } |
| 106 | double losum=0; long lon=0; |
| 107 | |
| 108 | for(int g=0;g<GEN;g++){ |
| 109 | for(int c=0;c<NCOMP;c++){ |
| 110 | int shared_rep = comp_has_rep(c); |
| 111 | double tot=0,cw[CSZ]; |
| 112 | for(int k=0;k<CSZ;k++){ |
| 113 | double q = 2.0 + BETA*(1.0-(double)phi(A[c][k])/NPAIR) + BETA*(1.0-(double)phi(B[c][k])/NPAIR); |
| 114 | tot+=q; cw[k]=tot; |
| 115 | } |
| 116 | int mach=-1; for(int k=0;k<CSZ;k++){ if(isfold(A[c][k])){mach=k;break;} if(isfold(B[c][k])){mach=k;break;} } |
| 117 | for(int k=0;k<CSZ;k++){ |
| 118 | double r=urand()*tot; int lo=0,hi=CSZ-1; |
| 119 | while(lo<hi){ int mid=(lo+hi)/2; if(cw[mid]<r) lo=mid+1; else hi=mid; } |
| 120 | /* the PAIR is copied together — linkage, so a complex is inherited as a unit */ |
| 121 | int priv = is_rep(A[c][lo]) || is_rep(B[c][lo]); |
| 122 | int lo_fid = (mode==0)? priv : shared_rep; |
| 123 | losum+=lo_fid; lon++; |
| 124 | double tmu = mu*(lo_fid?RLO:RHI); |
| 125 | uint32_t src[2]={A[c][lo],B[c][lo]}, out[2]; |
| 126 | for(int h=0;h<2;h++){ |
| 127 | uint32_t par=src[h], child; double emu; |
| 128 | if(!isfold(par)){ |
| 129 | if(mach>=0 && urand()<PREAD){ |
| 130 | uint32_t fold = isfold(A[c][mach])? A[c][mach] : B[c][mach]; |
| 131 | child=(fold&((1u<<NFOLD)-1))|((uint32_t)typeof_(par)<<NFOLD); emu=mu; |
| 132 | } else { child=par; emu=tmu; } |
| 133 | } else { child=par; emu=mu; } |
| 134 | out[h]=mut(child,emu); |
| 135 | } |
| 136 | nA[c][k]=out[0]; nB[c][k]=out[1]; |
| 137 | } |
| 138 | } |
| 139 | for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++){ A[c][k]=nA[c][k]; B[c][k]=nB[c][k]; } |
| 140 | |
| 141 | double tot=0; |
| 142 | for(int c=0;c<NCOMP;c++){ tot += 0.05 + (double)payoff_of(c); cum[c]=tot; } |
| 143 | for(int c=0;c<NCOMP;c++){ |
| 144 | double r=urand()*tot; int lo=0,hi=NCOMP-1; |
| 145 | while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } |
| 146 | for(int k=0;k<CSZ;k++){ int q=(int)(xs()%CSZ); nA[c][k]=A[lo][q]; nB[c][k]=B[lo][q]; } |
| 147 | } |
| 148 | for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++){ A[c][k]=nA[c][k]; B[c][k]=nB[c][k]; } |
| 149 | } |
| 150 | double rf=0,pay=0; |
| 151 | for(int c=0;c<NCOMP;c++){ rf+=rep_freq_of(c); pay+=payoff_of(c); } |
| 152 | *rep_freq=rf/NCOMP; *pay_out=pay/NCOMP; *lofrac=losum/(double)lon; |
| 153 | } |
| 154 | |
| 155 | int main(void) |
| 156 | { |
| 157 | unsigned long long seeds[6]; |
| 158 | seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL; |
| 159 | for(int i=3;i<6;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1)); |
| 160 | int NS=6; |
| 161 | |
| 162 | printf("=== REPLISOME SCALE: does privatising the replicase to its own complex close the loop? ===\n"); |
| 163 | printf(" replicase.c gave a complete null: a replicase improves fidelity for EVERY template in its\n"); |
| 164 | printf(" compartment including free-riders', so within-compartment selection purges it. A boundary\n"); |
| 165 | printf(" can only privatise a good it can exclude non-contributors from, and everything inside a\n"); |
| 166 | printf(" compartment is inside it. So: shrink the scale.\n"); |
| 167 | printf(" Each slot is a PAIR copied together (linkage). PRIVATE = a pair gets high fidelity only if\n"); |
| 168 | printf(" ITS OWN partner is a replicase. SHARED = replicase.c's rule. One knob: the scale.\n"); |
| 169 | printf(" NOTE the confound direction: SHARED grants low-mutation more often at equal replicase\n"); |
| 170 | printf(" frequency, so SHARED enjoys LESS mutation load. If PRIVATE still wins, it wins against it.\n\n"); |
| 171 | |
| 172 | printf(" mu | PRIVATE rep-freq / payoff | SHARED rep-freq / payoff | lo-fid P / S\n"); |
| 173 | printf(" ------+---------------------------+--------------------------+-------------\n"); |
| 174 | double mus[]={0.005,0.01,0.02,0.04,0.08}; |
| 175 | int nM=5, w1=0, w2=0; double g0=0,g1=0; |
| 176 | for(int i=0;i<nM;i++){ |
| 177 | double a1=0,a2=0,a3=0,b1=0,b2=0,b3=0; |
| 178 | for(int s=0;s<NS;s++){ |
| 179 | double rf,pv,lf; |
| 180 | RNG=seeds[s]; run(0,mus[i],&rf,&pv,&lf); a1+=rf; a2+=pv; a3+=lf; |
| 181 | RNG=seeds[s]; run(1,mus[i],&rf,&pv,&lf); b1+=rf; b2+=pv; b3+=lf; |
| 182 | } |
| 183 | a1/=NS;a2/=NS;a3/=NS;b1/=NS;b2/=NS;b3/=NS; |
| 184 | printf(" %.3f | %.3f / %.2f | %.3f / %.2f | %.2f / %.2f\n", |
| 185 | mus[i],a1,a2,b1,b2,a3,b3); |
| 186 | if(a1>b1+0.01) w1++; |
| 187 | if(a2>b2+0.05) w2++; |
| 188 | if(i==0) g0=a1-b1; |
| 189 | g1=a1-b1; |
| 190 | } |
| 191 | printf("\n rep-freq = fraction of strands that are replicases; payoff = target types (max %d)\n\n",NTARG); |
| 192 | printf(" P1 PRIVATE holds more replicase than SHARED : %d/%d\n",w1,nM); |
| 193 | printf(" P2 PRIVATE payoff higher : %d/%d\n",w2,nM); |
| 194 | printf(" P3 replicase-frequency gap vs mu : %+.3f -> %+.3f\n\n",g0,g1); |
| 195 | |
| 196 | if(w1>=nM-1 && w2>=nM-1) |
| 197 | printf(" => SCALE IS THE ANSWER. Privatising the replicase to its own complex — and copying that\n" |
| 198 | " complex as a unit — lets lineages hold a catalyst that pays them nothing directly,\n" |
| 199 | " and carry more function for it. The same good, the same cost, the same free-riders:\n" |
| 200 | " only the SCOPE of the benefit changed, and that was the whole difference. It also\n" |
| 201 | " wins against the confound, since SHARED enjoys the lower mutation load.\n"); |
| 202 | else if(w1>=nM-1) |
| 203 | printf(" => REPLICASE HELD, FUNCTION NOT RAISED (%d/%d vs %d/%d). Privatisation retains the\n" |
| 204 | " replicase but the loop does not yet pay off in function. Partial.\n",w1,nM,w2,nM); |
| 205 | else |
| 206 | printf(" => HONEST NULL. Shrinking the scale of privatisation does not rescue the replicase\n" |
| 207 | " either. Three designs, three nulls: the genotype/phenotype loop does not bootstrap\n" |
| 208 | " in this family of models, and the missing ingredient is still not identified.\n"); |
| 209 | return 0; |
| 210 | } |