OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / replicase.c

213 lines · c ref:
/* replicase.c — the closed genotype/phenotype loop. Can it bootstrap?
 *
 * WHERE THIS COMES FROM. genome.c v1/v2 gave templates a fair test and they failed twice, but the
 * second failure exposed something I had been getting for free: TEMPLATE COPYING WAS FREE PHYSICS
 * (child = par, perfect, no cost). In reality copying a genome faithfully needs a REPLICASE, which
 * is itself a catalyst, which is itself specified by a template. That is the loop, and it is exactly
 * rung 1→2.
 *
 * Two flaws in genome.c are fixed here:
 *  (1) a picked template ALWAYS became a catalyst when reading was on, so reading was a dead end for
 *      the template lineage — it could be useful or persist, never both. Now a picked template is
 *      READ with probability PREAD and otherwise COPIED TRUE, exactly as a catalyst is copied true.
 *      Reading is a FUNCTION templates provide, not a route by which they propagate.
 *  (2) template fidelity was free. Now it is CONDITIONAL on the compartment holding a catalyst of
 *      the replicase type: with one, templates copy at MU*RLO; without, at MU*RHI.
 *
 * THE RAZOR. This is where encoding the answer is easiest, so, precisely:
 *  SUPPLIED as physics: "a type-REPTYPE catalyst improves template fidelity in its compartment" —
 *    a function, the same shape as ribozyme.c's supplied fold→catalysis map.
 *  NOT SUPPLIED: any reward for being or holding a replicase. REPTYPE is EXCLUDED FROM THE TARGET
 *    SET, so it contributes nothing to payoff directly — it is pure overhead unless the template
 *    loop pays for itself. It is not seeded, not protected, not favoured in replication. Non-folders
 *    still replicate faster, so the parasite advantage is intact and the replicase is a PUBLIC GOOD
 *    within its compartment, free-ridable exactly as catalysis was in ribozyme.c.
 *  MEASURED: whether lineages acquire and HOLD the replicase, and whether function is higher for it.
 *
 * THE CONTROL, built to today's matched-condition lesson. Turning the replicase effect on also
 * changes the average template mutation rate, which would confound any gain. So the control applies
 * the IDENTICAL two-rate structure conditioned on a COIN FLIP per compartment instead of on
 * replicase presence — same mutation-rate distribution, no type to select for. The realized
 * low-fidelity fraction is reported for both arms so the match is visible rather than assumed.
 *
 * PRE-REGISTERED:
 *  R1 replicase frequency RISES above the control's. Lineages hold a type that pays them nothing
 *     directly, because it protects the templates that carry everything else.
 *  R2 target repertoire is HIGHER with the replicase effect than with the coin-flip control.
 *  R3 TEST OF THE TEST: catalysts and templates coexist, and the two arms' low-fidelity fractions
 *     are comparable. If they diverge sharply the comparison is confounded by mutation load.
 *  R4 the advantage should GROW with base mu — the replicase matters most where templates decay fast.
 *
 * HONEST PRIOR: partial. The replicase is a public good inside its compartment, so free-riding should
 * bite exactly as it did in ribozyme.c — one level up. A null here is a real result.
 *
 * build:  cc -O2 -Wall -std=c11 -o replicase replicase.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <stdint.h>

#define NFOLD  16
#define NPAIR  (NFOLD/2)
#define THETA  6
#define NTYPE  8
#define REPTYPE 0                  /* the replicase type — excluded from the target set */
#define NTARG  3                   /* target types, drawn from types 1..7 */
#define NCOMP  600
#define CSZ    8
#define GEN    200
#define BETA   2.0
#define PREAD  0.50
#define RLO    0.25                /* template mutation multiplier WITH a replicase present */
#define RHI    1.50                /* ... and without */


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 TARGET[NTYPE];
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; }
static int isfold(uint32_t s){ return phi(s)>=THETA; }
static int typeof_(uint32_t s){ return (int)((s>>NFOLD)&7); }
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; }

static uint32_t cell[NCOMP][CSZ], ncell[NCOMP][CSZ];
static double cum[NCOMP];

static int payoff_of(const uint32_t *c){
	int seen[NTYPE]; for(int t=0;t<NTYPE;t++) seen[t]=0;
	int d=0;
	for(int k=0;k<CSZ;k++) if(isfold(c[k])){ int t=typeof_(c[k]); if(!seen[t]){ seen[t]=1; if(TARGET[t]) d++; } }
	return d;
}
static int has_replicase(const uint32_t *c){
	for(int k=0;k<CSZ;k++) if(isfold(c[k]) && typeof_(c[k])==REPTYPE) return 1;
	return 0;
}

/* mode 0 = REPLICASE (fidelity conditioned on holding one), 1 = CONTROL (same structure, coin flip) */
static void run(int mode,double mu,double pflip,double *rep_freq,double *pay_out,double *tmpl,double *lofrac)
{
	for(int t=0;t<NTYPE;t++) TARGET[t]=0;
	for(int got=0;got<NTARG;){ int t=1+(int)(xs()%(NTYPE-1)); if(!TARGET[t]){ TARGET[t]=1; got++; } }
	for(int c=0;c<NCOMP;c++)
		for(int k=0;k<CSZ;k++){ uint32_t v=0; for(int i=0;i<NFOLD+3;i++) if(urand()<0.5) v|=(1u<<i); cell[c][k]=v; }

	double losum=0; long lon=0;

	for(int g=0;g<GEN;g++){
		for(int c=0;c<NCOMP;c++){
			int lo_fid = (mode==0)? has_replicase(cell[c]) : (urand()<pflip);
			losum += lo_fid; lon++;
			double tmu = mu*(lo_fid?RLO:RHI);
			double tot=0,cw[CSZ];
			for(int k=0;k<CSZ;k++){ tot += 1.0 + BETA*(1.0-(double)phi(cell[c][k])/NPAIR); cw[k]=tot; }
			int mach=-1; for(int k=0;k<CSZ;k++) if(isfold(cell[c][k])){ mach=k; break; }
			for(int k=0;k<CSZ;k++){
				double r=urand()*tot; int lo=0,hi=CSZ-1;
				while(lo<hi){ int mid=(lo+hi)/2; if(cw[mid]<r) lo=mid+1; else hi=mid; }
				uint32_t par=cell[c][lo], child; double emu;
				if(!isfold(par)){
					if(mach>=0 && urand()<PREAD){                  /* READ: template -> catalyst */
						child=(cell[c][mach]&((1u<<NFOLD)-1))|((uint32_t)typeof_(par)<<NFOLD);
						emu=mu;                                    /* the new catalyst is a catalyst */
					} else { child=par; emu=tmu; }                 /* COPY TRUE, at template fidelity */
				} else { child=par; emu=mu; }                      /* catalyst -> catalyst */
				ncell[c][k]=mut(child,emu);
			}
		}
		for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++) cell[c][k]=ncell[c][k];

		double tot=0;
		for(int c=0;c<NCOMP;c++){ tot += 0.05 + (double)payoff_of(cell[c]); cum[c]=tot; }
		for(int c=0;c<NCOMP;c++){
			double r=urand()*tot; int lo=0,hi=NCOMP-1;
			while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
			for(int k=0;k<CSZ;k++) ncell[c][k]=cell[lo][(int)(xs()%CSZ)];
		}
		for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++) cell[c][k]=ncell[c][k];
	}

	long nt=0; double rp=0,pay=0;
	for(int c=0;c<NCOMP;c++){
		rp += has_replicase(cell[c]); pay += payoff_of(cell[c]);
		for(int k=0;k<CSZ;k++) if(!isfold(cell[c][k])) nt++;
	}
	*rep_freq=rp/NCOMP; *pay_out=pay/NCOMP;
	*tmpl=(double)nt/((double)NCOMP*CSZ); *lofrac=losum/(double)lon;
}

int main(void)
{
	unsigned long long seeds[6];
	seeds[0]=0x9e3779b97f4a7c15ULL; seeds[1]=0xd1b54a32d192ed03ULL; seeds[2]=0x2545f4914f6cdd1dULL;
	for(int i=3;i<6;i++) seeds[i]=0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(i+1));
	int NS=6;

	printf("=== REPLICASE: can the genotype/phenotype loop close on itself? ===\n");
	printf("  A type-%d catalyst improves TEMPLATE fidelity in its compartment (x%.2f vs x%.2f).\n",REPTYPE,RLO,RHI);
	printf("  It is EXCLUDED from the target set, so it pays nothing directly — pure overhead unless\n");
	printf("  the template loop pays for itself. Not seeded, not protected; non-folders still replicate\n");
	printf("  %.0fx faster, so the replicase is a PUBLIC GOOD inside its compartment and free-ridable.\n",1+BETA);
	printf("  CONTROL applies the identical two-rate structure by COIN FLIP instead — same mutation\n");
	printf("  distribution, no type to select for. %d comps x %d, %d gens, %d seeds.\n\n",NCOMP,CSZ,GEN,NS);

	printf("    mu   | REPLICASE: rep-freq / payoff / tmpl | CONTROL: rep-freq / payoff | lo-fid frac R/C\n");
	printf("   ------+-------------------------------------+----------------------------+----------------\n");
	double mus[]={0.005,0.01,0.02,0.04,0.08};
	int nM=5, r1=0, r2=0, matched=1; double g0=0,g1=0;

	for(int i=0;i<nM;i++){
		/* TWO PASS, so the control is matched rather than guessed: run the replicase arm, measure the
		   fraction of compartment-generations that actually got the low rate, then give the control a
		   coin flip at exactly that probability. The first version guessed PFLIP=0.60 from a uniform-type
		   estimate; selection purges type 0 (it pays nothing) so the real figure is 0.03-0.13, and the
		   control was running at a fraction of the mutation load. R3 caught it. */
		double a1=0,a2=0,a3=0,a4=0, b1=0,b2=0,b4=0;
		double lf_meas[8];
		for(int s=0;s<NS;s++){
			double rf,pv,tm,lf;
			RNG=seeds[s]; run(0,mus[i],0.0,&rf,&pv,&tm,&lf); a1+=rf; a2+=pv; a3+=tm; a4+=lf; lf_meas[s]=lf;
		}
		for(int s=0;s<NS;s++){
			double rf,pv,tm,lf;
			RNG=seeds[s]; run(1,mus[i],lf_meas[s],&rf,&pv,&tm,&lf); b1+=rf; b2+=pv; b4+=lf;
		}
		a1/=NS;a2/=NS;a3/=NS;a4/=NS; b1/=NS;b2/=NS;b4/=NS;
		printf("   %.3f |      %.3f / %.2f / %.3f       |     %.3f / %.2f        |  %.2f / %.2f\n",
			mus[i],a1,a2,a3,b1,b2,a4,b4);
		if(a1>b1+0.02) r1++;
		if(a2>b2+0.05) r2++;
		if(fabs(a4-b4)>0.03) matched=0;
		if(i==0) g0=a2-b2;
		g1=a2-b2;
	}
	printf("\n   rep-freq = fraction of compartments holding a replicase; payoff = target types present (max %d)\n\n",NTARG);

	printf("   R3 low-fidelity fractions comparable   : %s\n", matched?"YES":"NO — CONFOUNDED by mutation load");
	printf("   R1 replicase held above control        : %d/%d mutation rates\n",r1,nM);
	printf("   R2 payoff higher than control          : %d/%d mutation rates\n",r2,nM);
	printf("   R4 advantage grows with mu             : %+.2f at mu=%.3f -> %+.2f at mu=%.3f\n\n",g0,mus[0],g1,mus[nM-1]);

	if(!matched)
		printf("   => CONFOUNDED. The arms did not experience comparable mutation loads, so any difference\n"
		       "      is the load and not the loop.\n");
	else if(r1>=nM-1 && r2>=nM-1)
		printf("   => THE LOOP CLOSED. Lineages hold a catalyst type that pays them NOTHING directly,\n"
		       "      because it protects the templates that carry everything that does pay — and they\n"
		       "      carry more function for it than the matched control. Genotype and phenotype became\n"
		       "      mutually dependent without either being rewarded for it. That is rung 1->2's\n"
		       "      architecture, bootstrapped rather than installed.\n");
	else if(r2>=nM-1)
		printf("   => FUNCTION UP, REPLICASE NOT HELD (%d/%d vs %d/%d). The fidelity boost helps where it\n"
		       "      happens to occur, but lineages do not retain the replicase — free-riding inside the\n"
		       "      compartment wins, exactly as ribozyme.c found one level down. The loop does not close.\n",r2,nM,r1,nM);
	else
		printf("   => HONEST NULL. The replicase is not held and function is not raised. A catalyst that\n"
		       "      pays only by protecting templates cannot pay for itself here: it is a public good\n"
		       "      inside its own compartment and is free-ridden away. The genotype/phenotype loop\n"
		       "      needs something this model does not have.\n");
	return 0;
}