OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / genome.c

216 lines · c ref:
/* genome.c — does a system OFFLOAD its information into a cheap non-folding molecule?
 *            (the parasite-as-proto-genome test)
 *
 * WHY THIS, AND WHY NOW. fidfloor.c's division-size sweep found the two demands on a compartment are
 * irreconcilable in one parameter: purging parasites needs SMALL compartments (ribozyme.c,
 * chemoton.c, Test A), maintaining sequence information needs LARGE ones, and the sweep showed no
 * optimum anywhere — SELECT − MIXED is negative and monotone at every division size and fidelity.
 * So the way out cannot be a better compartment size. It has to be to stop asking one molecule to do
 * both jobs: put the memory somewhere that does not have to fold.
 *
 * And that carrier is already in every model we have built. A parasite is a cheap, fast-replicating,
 * NON-FOLDING strand — which is a precise description of a gene. Genes do not do chemistry. They get
 * copied. So rung 1→2 may not be catalysts acquiring heredity; it may be catalysts DOMESTICATING a
 * parasite, precisely because it is cheap to copy and carries no structural constraint.
 *
 * THE STRUCTURAL PREREQUISITE. Offloading needs something to offload TO, so fold and information must
 * be separable. A strand is 16 FOLD bits (φ = complementary pairs, 0..8) plus 3 TYPE bits. A strand
 * that folds (φ ≥ THETA) is a CATALYST of its type; one that does not is a TEMPLATE carrying only
 * type. Both mutate at the same per-bit rate — but a template has 3 bits to protect and a catalyst
 * has 19, which is the whole hypothesis: the same information costs fewer constrained bits when it
 * is not also load-bearing structure.
 *
 * THE RAZOR (issue #6 — this is where encoding the answer is easiest, so state it precisely):
 *  SUPPLIED as physics, outcome left open: a template can be READ — an existing catalyst's fold bits
 *    plus a template's type bits make a new catalyst of that type. That is transcription/translation
 *    in miniature, and it is exactly the kind of coupling ribozyme.c supplied as its fold→catalysis
 *    premise. It is a mechanism, not a reward.
 *  NOT SUPPLIED: any benefit to holding templates. Templates fold nothing, contribute nothing
 *    directly, and cost material like everything else. They keep the parasite advantage — non-folders
 *    replicate faster (weight 1 + BETA·(1 − φ/PHIMAX)) — so they can still free-ride and take over.
 *    Nothing says a lineage should keep them, read them, or protect them.
 *  MEASURED: whether template fraction RISES and whether functional diversity is higher because of it.
 *
 * WHAT IS ALSO SUPPLIED, AND FLAGGED: between-compartment selection is DECREED here (sample parents ∝
 * productivity, reseed by assortment), as in ribozyme.c. chemoton.c showed that step is derivable;
 * re-deriving it inside this model would confound the one new variable, so it is handed in
 * deliberately. This experiment is about offloading, not about where compartment selection comes from.
 *
 * PRE-REGISTERED:
 *  G1 READ OFF: templates are purged — they contribute nothing and are pure parasites.
 *  G2 READ ON: template fraction RISES and distinct catalyst types is HIGHER than with reading off.
 *     Information moved off the folded molecule onto the cheap one, unforced.
 *  G3 CONTROL — SCRAMBLED: reading works, but a template's type bits are replaced by random bits when
 *     read, so the mechanism exists and carries NO information. If templates still rise here, the
 *     effect is the reading machinery, not the information, and G2 means nothing.
 *  G4 TEST OF THE TEST: catalysts and templates must coexist. If either goes extinct in every arm
 *     there was no offloading to observe and a null says nothing.
 *  G5 The advantage should GROW with mutation rate μ — a template protects 3 bits where a catalyst
 *     protects 19, so the gap should widen exactly where folds get fragile.
 *
 * build:  cc -O2 -Wall -std=c11 -o genome genome.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <stdint.h>

#define NFOLD  16                  /* fold bits: pairs (i, NFOLD-1-i), i in 0..7 */
#define NPAIR  (NFOLD/2)           /* max fold score = 8 */
#define THETA  6                   /* folds (is a catalyst) if phi >= THETA */
#define NTYPE  8                   /* 3 type bits */
#define NCOMP  600
#define CSZ    8                   /* small compartments: the regime the corrector needs */
#define GEN    200
#define PREAD  0.50                /* chance a picked template is READ rather than copied true */
#define BETA   2.0                 /* non-folders replicate faster — the parasite advantage stays */

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); }

/* strand = 16 fold bits (0..15) + 3 type bits (16..18) */
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 int TARGET[NTYPE];          /* world-supplied: which types are worth having (payoff mode 1) */
static int NTARG=4;
static uint32_t cell[NCOMP][CSZ], ncell[NCOMP][CSZ];
static double cum[NCOMP];

/* payoff 0 = DIVERSITY: any distinct catalyst types count (genome.c's original).
   payoff 1 = TARGET:    only types in a world-supplied target set count.
   The difference is the whole point: under DIVERSITY any repertoire is as good as any other, so
   there is nothing worth remembering and a dice-roll beats a memory. Under TARGET a SPECIFIC
   configuration beats a random one, so faithful reading has something to be faithful TO. */
static int repertoire_mode(const uint32_t *c,int payoff){
	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(payoff==0||TARGET[t]) d++; } }
	return d;
}
static int repertoire(const uint32_t *c){ return repertoire_mode(c,0); }

/* mode 0 = READ OFF, 1 = READ ON, 2 = SCRAMBLED control (reads, but type bits randomised) */
static double run(int mode,double mu,int payoff,double *tmpl_frac,double *cat_frac,double *rep_out)
{
	/* world-supplied target: a random NTARG-subset of the types, redrawn per run so no particular
	   set is special-cased. Same shape as polymer.c's random target motif. */
	for(int t=0;t<NTYPE;t++) TARGET[t]=0;
	for(int got=0;got<NTARG;){ int t=(int)(xs()%NTYPE); 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; }

	for(int g=0;g<GEN;g++){
		/* ---- within compartment: replication. Non-folders are faster (parasite advantage). ---- */
		for(int c=0;c<NCOMP;c++){
			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; }
			/* index of some catalyst in this compartment, if any — the generic reading machinery */
			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;
				if(mode!=0 && !isfold(par) && mach>=0 && urand()<PREAD){
					/* READ (template -> catalyst): a catalyst's fold bits + this template's type bits.
					   v1/v2 made this the ONLY thing a template could do, so reading was a dead end for
					   the template lineage — it could be useful or persist, never both. Real genomes are
					   copied AS genomes and SEPARATELY read. Now reading happens with probability PREAD
					   and otherwise the template is copied as a template, exactly as a catalyst is
					   copied as a catalyst. Both classes propagate only by copying; reading is a
					   FUNCTION templates provide, not an extra route to propagate. */
					uint32_t ty = (mode==2) ? (uint32_t)(xs()&7) : (uint32_t)typeof_(par);
					child = (cell[c][mach] & ((1u<<NFOLD)-1)) | (ty<<NFOLD);
				} else child = par;                      /* copy true: catalyst->catalyst, template->template */
				ncell[c][k]=mut(child,mu);
			}
		}
		for(int c=0;c<NCOMP;c++) for(int k=0;k<CSZ;k++) cell[c][k]=ncell[c][k];

		/* ---- between compartment: DECREED selection on functional repertoire (flagged, not derived) ---- */
		double tot=0;
		for(int c=0;c<NCOMP;c++){ tot += 0.05 + (double)repertoire_mode(cell[c],payoff); 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,nc=0; double rp=0;
	for(int c=0;c<NCOMP;c++){
		rp += repertoire_mode(cell[c],payoff);
		for(int k=0;k<CSZ;k++){ if(isfold(cell[c][k])) nc++; else nt++; }
	}
	*tmpl_frac=(double)nt/((double)NCOMP*CSZ);
	*cat_frac =(double)nc/((double)NCOMP*CSZ);
	*rep_out  = rp/NCOMP;
	return *rep_out;
}

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("=== PARASITE AS PROTO-GENOME v2: does the PAYOFF decide whether memory pays? ===\n");
	printf("  v1 found reading raises function (+0.92 at high mu) but SCRAMBLED reading — random type\n");
	printf("  instead of the template's — BEAT faithful reading everywhere. Diagnosis: the payoff\n");
	printf("  rewarded DIVERSITY, and randomness supplies diversity better than fidelity does. A world\n");
	printf("  where any repertoire is as good as any other has nothing worth remembering.\n");
	printf("  v2 adds a TARGET payoff: only %d of %d types count, drawn at random per run. Now a\n",4,NTYPE);
	printf("  SPECIFIC configuration beats a random one. PREDICTION: the control INVERTS.\n\n");

	double mus[]={0.005,0.01,0.02,0.04,0.08};
	int nM=5;
	const char *pn[2]={"DIVERSITY (v1)","TARGET (v2)"};
	int inverted=0, div_scr_wins=0, tgt_faith_wins=0;

	for(int pay=0;pay<2;pay++){
		printf("  --- payoff = %s ---\n",pn[pay]);
		printf("    mu   | READ OFF | READ ON (faithful) | SCRAMBLED | faithful - scrambled\n");
		printf("   ------+----------+--------------------+-----------+---------------------\n");
		for(int i=0;i<nM;i++){
			double r0=0,r1=0,r2=0;
			for(int s=0;s<NS;s++){
				double tf,cf,rp;
				RNG=seeds[s]; run(0,mus[i],pay,&tf,&cf,&rp); r0+=rp;
				RNG=seeds[s]; run(1,mus[i],pay,&tf,&cf,&rp); r1+=rp;
				RNG=seeds[s]; run(2,mus[i],pay,&tf,&cf,&rp); r2+=rp;
			}
			r0/=NS;r1/=NS;r2/=NS;
			double d=r1-r2;
			printf("   %.3f |   %.2f   |        %.2f        |   %.2f    |       %+.3f\n",mus[i],r0,r1,r2,d);
			if(pay==0 && d<-0.02) div_scr_wins++;
			if(pay==1 && d>0.02) tgt_faith_wins++;
		}
		printf("\n");
	}
	inverted = (div_scr_wins>=4 && tgt_faith_wins>=4);

	printf("   under DIVERSITY, scrambled beats faithful : %d/%d mutation rates\n",div_scr_wins,nM);
	printf("   under TARGET,    faithful beats scrambled : %d/%d mutation rates\n\n",tgt_faith_wins,nM);

	if(inverted)
		printf("   => THE CONTROL INVERTS, AND THE PAYOFF IS WHAT DECIDES. The identical mechanism, the\n"
		       "      identical molecules, the identical parasite advantage — and whether carrying\n"
		       "      information on a cheap non-folding strand pays depends entirely on whether the world\n"
		       "      rewards a SPECIFIC configuration or merely variety. Memory is worthless in a world\n"
		       "      that will take any answer, and selected for in one that will not. That is a\n"
		       "      condition on the ENVIRONMENT for the genotype/phenotype split, not on the chemistry.\n");
	else if(tgt_faith_wins>=4)
		printf("   => FAITHFUL WINS UNDER TARGET (%d/%d) but the diversity contrast did not come out as\n"
		       "      cleanly (%d/%d). The direction supports the diagnosis; report the asymmetry as-is.\n",
		       tgt_faith_wins,nM,div_scr_wins,nM);
	else
		printf("   => HONEST NULL. Even with a target payoff, faithful reading does NOT beat scrambled.\n"
		       "      The diagnosis from v1 was wrong: giving the world something specific to want is not\n"
		       "      what makes memory pay, and the missing ingredient is still unidentified.\n");
	return 0;
}