OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / ladder35.c

169 lines · c ref:
/* ladder35.c — MILESTONE 4: rungs 3 and 5 in the same type. No new machinery.
 *
 * The substrate now carries species, reactions, containers-as-species and counts (substrate.c,
 * container.c, counts.c), expressing rungs 1, 2 and 4 and reproducing rung1.c's threshold,
 * polymer.c's bootstrap problem, ribozyme.c's ordering and byproduct.c's isolation control.
 * The claim was that rungs 3 and 5 need no new type. This tests it.
 *
 *   rung 3  species ARE sequences. A replication reaction copies a species to itself; mutation is a
 *           variant-producing reaction to a Hamming neighbour. The rung-3 property is Eigen's ERROR
 *           CATASTROPHE — a sharp transition from localised on a master sequence to delocalised over
 *           sequence space, at mu_c ~ ln(sigma)/L.
 *   rung 5  a specialist is a species participating in only PART of the reaction set. Division of
 *           labour is complementary specialists whose union covers everything the container needs,
 *           none of which suffices alone.
 *
 * THE CHECK, as at every milestone: expressing something is worthless unless it reproduces the
 * result we already have.
 *   L3 the master-sequence fraction must show a SHARP fall with mutation rate, and the observed
 *      threshold must sit near ln(sigma)/L — rung3.c's exact quasispecies value.
 *   L5 specialists must sustain complementary coverage that no single species provides, and
 *      coverage must COLLAPSE when the requirement for complementarity is removed (the control).
 *
 * build:  cc -O2 -Wall -std=c11 -o ladder35 ladder35.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <string.h>

#define LBITS 8
#define NSEQ  (1<<LBITS)      /* 256 sequence-species — species ARE sequences */
#define POP   4000
#define GENS  300
#define SIGMA 4.0             /* master-sequence selective advantage */

#define NROLE 3
#define NSPC  12              /* rung-5 species */
#define NCOMP 400
#define CSZ   10

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 rint_(int n){ return (int)(xs()%(unsigned)n); }

/* ---------------- RUNG 3: species are sequences; mutation is a reaction to a neighbour --------- */
static int seqpop[NSEQ], nxtpop[NSEQ];
static double fit_of(int s){ return (s==0)? SIGMA : 1.0; }

static double rung3(double mu){
	memset(seqpop,0,sizeof seqpop);
	for(int i=0;i<POP;i++) seqpop[rint_(NSEQ)]++;
	for(int g=0;g<GENS;g++){
		double tot=0; static double cum[NSEQ];
		for(int s=0;s<NSEQ;s++){ tot += seqpop[s]*fit_of(s); cum[s]=tot; }
		memset(nxtpop,0,sizeof nxtpop);
		for(int i=0;i<POP;i++){
			double r=urand()*tot; int lo=0,hi=NSEQ-1;
			while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
			int child=lo;
			for(int b=0;b<LBITS;b++) if(urand()<mu) child^=(1<<b);  /* mutation = variant reaction */
			nxtpop[child]++;
		}
		memcpy(seqpop,nxtpop,sizeof seqpop);
	}
	return (double)seqpop[0]/POP;
}

/* ---------------- RUNG 5: a specialist participates in only PART of the reaction set ----------- */
static unsigned char covers[NSPC][NROLE];    /* which roles a species can perform */
static int comp[NCOMP][CSZ], ncomp2;

static void build_species(int generalists_allowed){
	for(int s=0;s<NSPC;s++){
		memset(covers[s],0,sizeof covers[s]);
		if(generalists_allowed && s<NROLE){ for(int r=0;r<NROLE;r++) covers[s][r]=1; }
		else covers[s][s%NROLE]=1;           /* specialist: exactly one role */
	}
}
static int coverage(const int *cell){
	int seen[NROLE]; memset(seen,0,sizeof seen);
	for(int k=0;k<CSZ;k++) for(int r=0;r<NROLE;r++) if(covers[cell[k]][r]) seen[r]=1;
	int c=0; for(int r=0;r<NROLE;r++) c+=seen[r];
	return c;
}
/* need_all=1: a container is viable only with ALL roles covered (complementarity required).
   need_all=0 (CONTROL): any single role suffices — complementarity is not demanded. */
static double rung5(int need_all){
	ncomp2=NCOMP;
	for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++) comp[c][k]=rint_(NSPC);
	for(int g=0;g<200;g++){
		static int nxt[NCOMP][CSZ];
		double tot=0,cum[NCOMP];
		for(int c=0;c<NCOMP;c++){
			int cov=coverage(comp[c]);
			double v = need_all ? ((cov==NROLE)?1.0:0.02) : ((cov>=1)?1.0:0.02);
			tot+=v; 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++){
				int src=comp[lo][rint_(CSZ)];
				if(urand()<0.02) src=rint_(NSPC);
				nxt[c][k]=src;
			}
		}
		memcpy(comp,nxt,sizeof comp);
	}
	double full=0;
	for(int c=0;c<NCOMP;c++) if(coverage(comp[c])==NROLE) full++;
	return full/NCOMP;
}

int main(void)
{
	printf("=== MILESTONE 4: rungs 3 and 5 in the same type, no new machinery ===\n\n");

	/* ---- L3 ---- */
	double muc = log(SIGMA)/LBITS;
	printf("  L3 — rung 3: species ARE sequences, mutation is a variant-producing reaction.\n");
	printf("       Eigen predicts an error catastrophe near mu_c = ln(sigma)/L = ln(%.0f)/%d = %.3f\n\n",SIGMA,LBITS,muc);
	printf("        mu    | master-sequence fraction\n");
	printf("       -------+-------------------------\n");
	double mus[7]={0.02,0.08,0.14,0.17,0.22,0.30,0.45};
	double v[7];
	for(int i=0;i<7;i++){
		double a=0; for(int s=0;s<4;s++){ RNG=0x9e3779b97f4a7c15ULL+(unsigned long long)(i*97+s)*0x2545F4914F6CDD1DULL; a+=rung3(mus[i]); }
		v[i]=a/4;
		printf("        %.2f  |         %.4f          %s\n",mus[i],v[i], (mus[i]<muc)?"(below mu_c)":"(above)");
	}
	/* Locate the threshold as the CROSSING of a low master fraction, not the biggest absolute drop.
	   On a monotone decreasing curve the largest absolute drop always sits where the curve is
	   highest, so it locates the steep start, not the catastrophe — the same wrong-statistic error
	   as linkage.c's "B stays near 1" bar. Interpolate where the master fraction crosses 0.05. */
	double where=-1;
	for(int i=0;i+1<7;i++){
		if(v[i]>=0.05 && v[i+1]<0.05){
			double t=(v[i]-0.05)/(v[i]-v[i+1]);
			where=mus[i]+t*(mus[i+1]-mus[i]); break;
		}
	}
	int l3 = (v[0]>0.3) && (v[6]<0.05) && where>0 && fabs(where-muc)<0.06;
	printf("\n       master fraction crosses 0.05 at mu = %.3f   vs Eigen prediction %.3f  (diff %+.3f)\n",where,muc,where-muc);
	printf("       => %s\n\n", l3?"LOCALISED below, DELOCALISED above, transition near ln(sigma)/L — rung 3 expressed."
	                            :"does NOT reproduce the error catastrophe — rung 3 is not expressed.");

	/* ---- L5 ---- */
	printf("  L5 — rung 5: a specialist participates in only PART of the reaction set.\n");
	printf("       Division of labour = complementary specialists whose union covers what one cannot.\n\n");
	build_species(0);
	double spec_need = rung5(1), spec_ctrl = rung5(0);
	build_species(1);
	double gen_need = rung5(1);
	printf("        arm                                   | containers with ALL %d roles\n",NROLE);
	printf("        --------------------------------------+---------------------------\n");
	printf("        specialists, complementarity REQUIRED |          %.3f\n",spec_need);
	printf("        specialists, requirement REMOVED (ctl)|          %.3f\n",spec_ctrl);
	printf("        generalists available                 |          %.3f\n",gen_need);
	int l5 = (spec_need>0.5) && (spec_ctrl < spec_need-0.1);
	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."
	                              :"coverage does not behave as rung 5 — not expressed.");

	printf("  MILESTONE 4 %s\n", (l3&&l5)?"PASSED — rungs 1,2,3,4,5 all live in one type."
	                                     :"NOT PASSED — see which check failed above.");
	printf("  Still scaffolding: nothing here CROSSES a rung. It removes the reason crossings could\n");
	printf("  not compose, which is what the whole ladder has lacked.\n");
	return 0;
}