OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / basal-agent / linkage.c

192 lines · c ref:
/* linkage.c — rung 2 -> 3: do INDEPENDENT replicators become a LINKED sequence on their own?
 *
 * THE GAP THIS FILLS. The ladder was tested as 1->2 (open, 14+ experiments), 3->4 (rung34.c,
 * EMERGED), 4->5 (rung45.c, honest negative). 2->3 was never tested as an emergence: rung2.c is a
 * low-dimensional Eigen replicator-with-mutation and rung3.c is an exact quasispecies over Hamming
 * classes -- both equations SOLVED, separately. The transition between them is "a few forms become
 * a combinatorial form-space", i.e. Maynard Smith & Szathmary's independent-replicators ->
 * chromosomes.
 *
 * WHY IT MIGHT ESCAPE THE ARC'S WALL. coupling/partition/signal established that a boundary which
 * must be MAINTAINED cannot pay for itself: signal.c's investment collapses to the same low value
 * from every starting point (measured -- invasion, not a coordination trap). Linkage is a boundary
 * of a different kind. It is not maintained; it is STRUCTURAL. Linked genes share fate because they
 * are one string, and defection is not a strategy available to them. So the question is whether a
 * boundary nobody has to pay upkeep on can arise.
 *
 * THE TAUTOLOGY TRAP, AND THE CONTROL THAT AVOIDS IT. If a unit's fitness is computed from its own
 * genes, linkage privatizes BY DEFINITION and "linkage evolves when combination is valuable" merely
 * restates the setup. So everything here rests on arm B:
 *   arm A  combinatorial benefit computed from YOUR OWN genes      -> linkage can pay
 *   arm B  the same benefit computed from a RANDOM DRAW of the pool, of the same size
 *          -> length costs exactly as much, and buys exactly nothing
 * Length carries an identical replication penalty in both. If length rises in A and not in B, the
 * driver is fate-sharing and not the mere fact that longer units are scored on more genes.
 *
 * WHAT IS SUPPLIED vs WHAT MUST EMERGE:
 *  SUPPLIED: fusion and fission as mutations, a combinatorial fitness (>= KREQ distinct types), and
 *    a replication penalty for length. The penalty is SWEPT, not picked.
 *  MUST EMERGE: linkage itself. Every unit starts at LENGTH 1 -- fully independent replicators.
 *    Nothing rewards being long as such; long units replicate strictly slower.
 *  ABSENT: containers, compartments, space, group selection. Global Wright-Fisher over units.
 *
 * PRE-REGISTERED:
 *  L1 arm A: mean length RISES above 1 and the fraction of units reaching KREQ distinct types rises.
 *  L2 arm B (control): mean length stays at ~1 at every cost. If B rises too, the effect is an
 *     artifact of scoring longer units on more genes and L1 means nothing.
 *  L3 there is a length cost above which even A collapses to 1 -- linkage is not free.
 *  L4 TEST OF THE TEST: fusion/fission must actually generate length variation. If length is pinned
 *     at 1 or at MAXLEN regardless of arm, the test did not run and a null says nothing.
 *
 * build:  cc -O2 -Wall -std=c11 -o linkage linkage.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <stdint.h>

#define POP     4000
#define TYPES   8
#define MAXLEN  8
#define KREQ    4          /* distinct types needed for the combinatorial payoff */
#define BONUS   2.0
#define GEN     300
#define MUT     0.02       /* per-gene type mutation */
#define PFUSE   0.05
#define PFISS   0.05

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 uint8_t gene[POP][MAXLEN], ngene[POP][MAXLEN];
static int     len[POP], nlen[POP];
static double  wgt[POP], cum[POP];

static int distinct_of(const uint8_t*g,int n){ int seen[TYPES]; for(int t=0;t<TYPES;t++) seen[t]=0; int d=0; for(int i=0;i<n;i++) if(!seen[g[i]]){ seen[g[i]]=1; d++; } return d; }

/* mode 0 = arm A (own genes). mode 1 = arm B control (same-size random draw from the pool). */
static void run(int mode,double lcost,double *len_end,double *frac_k,double *len_max,double *len_min)
{
	for(int i=0;i<POP;i++){ len[i]=1; gene[i][0]=(uint8_t)(xs()%TYPES); }

	for(int g=0;g<GEN;g++){
		/* control draw size is the POPULATION MEAN length, NOT the individual's own length, so in
		   arm B length carries the identical cost and buys literally nothing. Drawing len[i] genes
		   (the first attempt) left length buying the payoff by another route -- a confounded control. */
		int dsz;
		{ double m=0; for(int i=0;i<POP;i++) m+=len[i]; dsz=(int)(m/POP+0.5); if(dsz<1)dsz=1; if(dsz>MAXLEN)dsz=MAXLEN; }
		double tot=0;
		for(int i=0;i<POP;i++){
			int d;
			if(mode==0) d=distinct_of(gene[i],len[i]);
			else {
				uint8_t draw[MAXLEN];
				for(int k=0;k<dsz;k++){ int u=(int)(xs()%POP); int q=(int)(xs()%(unsigned long long)len[u]); draw[k]=gene[u][q]; }
				d=distinct_of(draw,dsz);
			}
			double fit = 1.0 + ((d>=KREQ)? BONUS : 0.0);
			double pen = 1.0/(1.0+lcost*(len[i]-1));      /* longer replicates strictly slower */
			double w = fit*pen;
			tot+=w; cum[i]=tot; wgt[i]=w;
		}
		for(int i=0;i<POP;i++){
			double r=urand()*tot; int lo=0,hi=POP-1;
			while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; }
			int n=len[lo];
			uint8_t tmp[2*MAXLEN];
			for(int k=0;k<n;k++) tmp[k]=gene[lo][k];

			if(urand()<PFUSE && n<MAXLEN){                /* FUSION: concatenate another unit */
				int o=(int)(xs()%POP); int m=len[o];
				for(int k=0;k<m && n<MAXLEN;k++) tmp[n++]=gene[o][k];
			}
			if(urand()<PFISS && n>1){                      /* FISSION: keep a contiguous piece */
				int cut=1+(int)(xs()%(unsigned long long)(n-1));
				if(urand()<0.5){ n=cut; }
				else { for(int k=0;k<n-cut;k++) tmp[k]=tmp[cut+k]; n=n-cut; }
			}
			for(int k=0;k<n;k++) if(urand()<MUT) tmp[k]=(uint8_t)(xs()%TYPES);
			nlen[i]=n; for(int k=0;k<n;k++) ngene[i][k]=tmp[k];
		}
		for(int i=0;i<POP;i++){ len[i]=nlen[i]; for(int k=0;k<len[i];k++) gene[i][k]=ngene[i][k]; }
	}

	double sl=0; int nk=0, mx=0, mn=MAXLEN;
	for(int i=0;i<POP;i++){
		sl+=len[i];
		if(len[i]>mx) mx=len[i];
		if(len[i]<mn) mn=len[i];
		if(distinct_of(gene[i],len[i])>=KREQ) nk++;
	}
	*len_end=sl/POP; *frac_k=(double)nk/POP; *len_max=mx; *len_min=mn;
}

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

	printf("=== LINKAGE (rung 2 -> 3): do independent replicators concatenate on their own? ===\n");
	printf("  Every unit STARTS AT LENGTH 1. Fusion/fission are mutations. Payoff needs %d distinct\n",KREQ);
	printf("  of %d types in one unit. Longer units replicate STRICTLY SLOWER. No compartments,\n",TYPES);
	printf("  no space, no group selection; global Wright-Fisher over units.\n");
	printf("  arm A = payoff from YOUR OWN genes.  arm B = same payoff from a same-size RANDOM POOL\n");
	printf("  DRAW of the POPULATION-MEAN size: length costs identically and buys NOTHING in B.\n\n");

	printf("  length cost | A mean len | A frac>=%d | A len range | B mean len | B frac>=%d\n",KREQ,KREQ);
	printf("  ------------+------------+-----------+-------------+------------+-----------\n");
	double costs[]={0.00,0.02,0.05,0.10,0.20,0.40,0.80};
	int NC=(int)(sizeof(costs)/sizeof(costs[0]));
	int roseA=0, varied=0, sepcount=0, ncmp=0; double bestlen=0, bestcost=-1, minratio=1e9;

	for(int c=0;c<NC;c++){
		double la=0,fa=0,lb=0,fb=0,mx=0,mn=0;
		for(int s=0;s<NS;s++){
			double L,F,M,N; RNG=seeds[s]; run(0,costs[c],&L,&F,&M,&N); la+=L; fa+=F; mx+=M; mn+=N;
			RNG=seeds[s]; run(1,costs[c],&L,&F,&M,&N); lb+=L; fb+=F;
		}
		la/=NS;fa/=NS;lb/=NS;fb/=NS;mx/=NS;mn/=NS;
		printf("     %.2f     |    %.2f    |   %.3f   |   %.1f-%.1f    |    %.2f    |   %.3f\n",
			costs[c],la,fa,mn,mx,lb,fb);
		/* An absolute bar on B is the wrong statistic: this is a TWO-ARM comparison, and the
		   question is whether length pays MORE when the genes are your own. Judge the separation,
		   and only where cost bites (cost 0 has nothing selecting against length) and where A has
		   not itself collapsed (above the ceiling neither arm sustains length). */
		if(costs[c]>0.0 && la>1.5){
			roseA=1; ncmp++;
			double ratio = (lb>0)? la/lb : 1e9;
			if(ratio<minratio) minratio=ratio;
			if(la-lb>1.0) sepcount++;
		}
		if(mx-mn>=2) varied=1;
		if(la>bestlen){ bestlen=la; bestcost=costs[c]; }
	}
	printf("\n");

	printf("  L4 TEST OF THE TEST — did length actually vary? %s\n", varied?"YES":"NO — test did not run");
	printf("  L1 arm A length rose above 1?                   %s (best %.2f at cost %.2f)\n", roseA?"YES":"NO",bestlen,bestcost);
	printf("  L2 A exceeds control B where cost bites?        %d/%d costs, min A/B ratio %.2f\n", sepcount,ncmp,minratio);
	printf("\n");

	if(!varied)
		printf("  => THE TEST DID NOT RUN. Fusion/fission never generated real length variation, so\n"
		       "     nothing here speaks to whether linkage can emerge. Invalid experiment, not a null.\n");
	else if(roseA && ncmp>0 && sepcount==ncmp && minratio>2.0)
		printf("  => LINKAGE EMERGED, AND THE CONTROL RULES OUT THE TAUTOLOGY. Units start independent and\n"
		       "     concatenate on their own despite a strict replication penalty for length, while the\n"
		       "     pool-draw control -- identical cost, no fate-sharing -- stays at length 1. This is a\n"
		       "     boundary that is STRUCTURAL rather than maintained: nobody pays upkeep, and defection\n"
		       "     is not an available strategy because linked genes cannot replicate at different rates.\n"
		       "     Contrast signal.c, where the maintained boundary collapsed from EVERY starting point.\n");
	else if(roseA && sepcount<ncmp)
		printf("  => CONFOUNDED. Length rose in the control too, so the effect is that longer units are\n"
		       "     scored on more genes, not fate-sharing. Claim nothing about linkage.\n");
	else
		printf("  => HONEST NULL. Linkage did not emerge at any cost: the replication penalty for length\n"
		       "     outweighs the combinatorial payoff throughout, so independent replicators stay\n"
		       "     independent. A structural boundary is no easier to originate than a maintained one.\n");
	return 0;
}