OverviewFilesCommitsIssuesDocsReleasesPeers

hypercore-kb / basal-agent / rung45irr.c

149 lines · c ref:
/* rung45irr.c — TEST: is division of labour blocked by REVERSIBILITY? (Law 2 applied to rung 4->5)
 *
 * rung45.c stalled, and rung45dup.c got specialists invading but full DoL plateauing at a second
 * coordination valley. Law 2 (SYNTHESIS Part C) says why that might be: a boundary — or here a
 * ROLE — that must be MAINTAINED is a strategy, and strategies get invaded. In rung45.c a
 * specialist can mutate straight back to generalist (r = xs()%(F+1)), so specialisation is fully
 * elective and the retreat to safe generality is always available.
 *
 * Life's specialists cannot go back: differentiation involves losing capability, and gene loss is
 * one-way. So the one-knob test is whether making specialisation STRUCTURAL rather than elective
 * stabilises DoL.
 *
 *   REVERSIBLE   (control, = rung45.c): with prob MUR, role := uniform(0..F)
 *   IRREVERSIBLE (test):                with prob MUR, role := generalist ? uniform(0..F)
 *                                                              : uniform(1..F)   <- no way back
 *
 * NOT ENCODED: irreversibility removes the retreat-to-generalist escape, but leaves the OTHER
 * failure route wide open — a compartment can still collapse to a monoculture of one specialist,
 * lose a function and die. So the outcome is genuinely open: a ratchet could give division of
 * labour, or could give monoculture and extinction. Viability is reported so which one happens is
 * visible.
 *
 * build:  cc -O2 -Wall -std=c11 -o rung45irr rung45irr.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#define NCOMP 500
#define GEN   400
#define D     12          /* compartment size */
#define F     3           /* functions (roles 1..F); role 0 = generalist */
#define SSPEC 0.30        /* specialist replication advantage (the free-rider pressure) */
#define EGEN  0.2         /* generalist efficiency per function */
#define MUR   0.05        /* role mutation rate */

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 binom(int n,double p){ int k=0; for(int i=0;i<n;i++) if(urand()<p) k++; return k; }

static int IRREV=0, NOSEL=0;
static int comp[NCOMP][F+1];              /* role counts per compartment */
static int dau[2*NCOMP][F+1]; static double cum[2*NCOMP];

static double viability_sel(const int *n);
static double viability(const int *n){ if(NOSEL) return 1.0; return viability_sel(n); }
static double viability_sel(const int *n){
	double v=1e9;
	for(int k=1;k<=F;k++){ double cov=n[k]+EGEN*n[0]; if(cov<v) v=cov; }  /* weakest-link: need ALL functions */
	return v;
}

static void one_run(int irrev,int nosel,double *specf,double *dolf,double *viaf){
	IRREV=irrev; NOSEL=nosel;
	for(int c=0;c<NCOMP;c++){ comp[c][0]=D; for(int k=1;k<=F;k++) comp[c][k]=0; }
	for(int g=0;g<GEN;g++){
		double w[F+1]; w[0]=1.0; for(int k=1;k<=F;k++) w[k]=1.0+SSPEC;
		for(int c=0;c<NCOMP;c++){
			double Z=0; for(int k=0;k<=F;k++) Z+=comp[c][k]*w[k]; if(Z<1e-12)Z=1e-12;
			double pr[F+1],acc=0; for(int k=0;k<=F;k++){ acc+=comp[c][k]*w[k]/Z; pr[k]=acc; }
			int m[F+1]={0};
			for(int i=0;i<2*D;i++){ double u=urand(); int r=0; while(r<F && u>pr[r]) r++;
				if(urand()<MUR){ if(IRREV && r!=0) r=1+(int)(xs()%F); else r=(int)(xs()%(F+1)); }
				m[r]++; }
			for(int k=0;k<=F;k++){ int d1=binom(m[k],0.5); dau[2*c][k]=d1; dau[2*c+1][k]=m[k]-d1; }
		}
		double tot=0; for(int i=0;i<2*NCOMP;i++){ tot+=viability(dau[i]); cum[i]=tot; }
		if(tot<=0){ for(int c=0;c<NCOMP;c++) for(int k=0;k<=F;k++) comp[c][k]=0; break; }
		for(int c=0;c<NCOMP;c++){ double r=urand()*tot; int lo=0,hi=2*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<=F;k++) comp[c][k]=dau[lo][k]; }
	}
	long tot=0,spec=0; int dol=0,via=0;
	for(int c=0;c<NCOMP;c++){ int s2=0;
		for(int k=0;k<=F;k++){ tot+=comp[c][k]; if(k>0){ spec+=comp[c][k]; if(comp[c][k]>0) s2++; } }
		if(s2==F) dol++; if(viability_sel(comp[c])>0.001) via++; }
	*specf = tot? (double)spec/tot : 0;
	*dolf  = (double)dol/NCOMP;
	*viaf  = (double)via/NCOMP;
}

int main(void){
	unsigned long long seeds[6]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL,
	                             0x123456789abcdefULL,0xfeedface12345678ULL,0x0badc0de0badc0deULL};
	int NS=6;
	printf("=== RUNG 4->5 UNDER LAW 2: is division of labour blocked by REVERSIBILITY? ===\n");
	printf("  rung45.c let a specialist mutate straight back to generalist, so specialisation was\n");
	printf("  fully elective — and Law 2 says an elective role is a strategy and gets invaded.\n");
	printf("  IRREVERSIBLE removes only the way back; the monoculture-and-die route stays open.\n");
	printf("  start: every compartment = %d generalists; specialists replicate %.0f%% faster;\n",D,SSPEC*100);
	printf("  a compartment needs all %d functions to be viable. %d comps, %d gens, %d seeds.\n\n",F,NCOMP,GEN,NS);
	printf("   arm          | spec frac | all-%d-roles | viable\n",F);
	printf("   -------------+-----------+-------------+--------\n");
	double a[3]={0,0,0}, b[3]={0,0,0}, d[3]={0,0,0}, e[3]={0,0,0};
	for(int s=0;s<NS;s++){ double x,y,z;
		RNG=seeds[s]; one_run(0,0,&x,&y,&z); a[0]+=x;a[1]+=y;a[2]+=z;
		RNG=seeds[s]; one_run(1,0,&x,&y,&z); b[0]+=x;b[1]+=y;b[2]+=z;
		RNG=seeds[s]; one_run(1,1,&x,&y,&z); d[0]+=x;d[1]+=y;d[2]+=z;
		RNG=seeds[s]; one_run(0,1,&x,&y,&z); e[0]+=x;e[1]+=y;e[2]+=z; }
	for(int i=0;i<3;i++){ a[i]/=NS; b[i]/=NS; d[i]/=NS; e[i]/=NS; }
	printf("   REVERSIBLE   |   %.3f   |   %6.1f%%    | %5.1f%%\n",a[0],100*a[1],100*a[2]);
	printf("   IRREVERSIBLE |   %.3f   |   %6.1f%%    | %5.1f%%\n",b[0],100*b[1],100*b[2]);
	printf("   ---- CONFOUND CHECK: same ratchet, selection REMOVED (flat viability) ----\n");
	printf("   REVERS,  nosel|   %.3f   |   %6.1f%%    |   --\n",e[0],100*e[1]);
	printf("   IRREV,   nosel|   %.3f   |   %6.1f%%    |   --\n\n",d[0],100*d[1]);
	printf("   irreversibility's DoL benefit WITH selection %+.1f%%, WITHOUT selection %+.1f%%\n",
		100*(b[1]-a[1]), 100*(d[1]-e[1]));
	printf("   selection's effect on DoL: reversible %+.1f%%, irreversible %+.1f%%\n",
		100*(a[1]-e[1]), 100*(b[1]-d[1]));

	int dol_up = b[1]>a[1]+0.05, via_ok = b[2]>0.5, spec_up = b[0]>a[0]+0.05;
	/* CORRECTED. b[1]-d[1] measures selection's contribution INSIDE the irreversible arm, not the
	   flux confound. The flux question is whether irreversibility's BENEFIT is bigger with selection
	   than without it: (irrev-rev | with sel) vs (irrev-rev | no sel). */
	double gain_sel = b[1]-a[1], gain_nosel = d[1]-e[1];
	int not_flux = gain_sel > gain_nosel + 0.05;
	printf("   DoL higher when irreversible : %s (%.1f%% vs %.1f%%)\n", dol_up?"YES":"no",100*b[1],100*a[1]);
	printf("   still viable                 : %s (%.1f%%)\n", via_ok?"YES":"NO — collapsed",100*b[2]);
	printf("   specialist fraction up       : %s (%.3f vs %.3f)\n\n", spec_up?"YES":"no",b[0],a[0]);
	if(dol_up && via_ok && not_flux)
		printf("   => REVERSIBILITY WAS THE BLOCKER, AND IT SURVIVES ITS CONFOUND CHECK. Irreversibility\n"
		       "      raises DoL by %+.1f%% with selection but only %+.1f%% without, so roughly %.0f%% of the\n"
		       "      gain is a genuine selection x irreversibility INTERACTION rather than ratchet flux.\n"
		       "      The sharpest reading is in the last line: with REVERSIBLE roles selection makes DoL\n"
		       "      WORSE (%+.1f%%) — it drives specialists back toward generality, which is Law 2's\n"
		       "      invasion visible directly. With roles irreversible, the same selection HELPS (%+.1f%%).\n"
		       "      Law 2 transfers from rung 1->2 to rung 4->5: an ELECTIVE role is a strategy and gets\n"
		       "      invaded; a STRUCTURAL one cannot be. Rung 5 needs a ratchet, not a bargain.\n",
		       100*gain_sel,100*gain_nosel,100.0*(gain_sel-gain_nosel)/gain_sel,
		       100*(a[1]-e[1]),100*(b[1]-d[1]));
	else if(dol_up && via_ok && !not_flux)
		printf("   => IT IS THE RATCHET, NOT SELECTION. Irreversibility raises division of labour, but the\n"
		       "      SAME rise appears with selection switched off (%.1f%% vs %.1f%%): a one-way\n"
		       "      generalist->specialist flux accumulates specialists over %d generations whether or\n"
		       "      not anything is being selected, and more specialists mechanically means more\n"
		       "      compartments happen to hold all %d roles. The knob moved two variables again.\n",
		       100*b[1],100*d[1],GEN,F);
	else if(!via_ok)
		printf("   => THE RATCHET KILLS IT. Irreversibility removes the retreat, and compartments collapse\n"
		       "      to monoculture and lose a function instead. Specialisation without a way back is\n"
		       "      fatal here — the second failure route the design deliberately left open.\n");
	else
		printf("   => HONEST NULL. Irreversibility alone does not stabilise division of labour; the\n"
		       "      coordination valley survives removing the retreat, so reversibility was not what\n"
		       "      rung45.c was stalling on.\n");
	return 0;
}