OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / chain12.c

160 lines · c ref:
/* chain12.c — THE QUESTION: does rung 1 PRODUCE rung 2?
 *
 * SYNTHESIS's oldest admission, unchanged since the arc began: "No rung ever emerged from the rung
 * below. Each was a separate model, and the ladder was a story told across disconnected pieces."
 * rafcomp.c got two rungs CO-PRESENT by hand-translating one into the other's terms. The substrate
 * milestones removed the structural reason that was necessary — all five rungs now live in one type.
 * So for the first time the question is askable in one model rather than narrated across two.
 *
 * THE TEST. Run a random catalytic network in the counts substrate at a density where a RAF exists
 * (rung 1 — a self-sustaining set, which rung1.c showed self-organises). Then ask whether
 * SELF-TEMPLATING species are preferentially maintained: a species X is self-templating when some
 * reaction produces X and X catalyses that same reaction. That is exactly rung 2 — a form that
 * templates copies of itself — expressed in the same type, with no separate model.
 *
 *   If self-templaters end up ENRICHED relative to their share of the network, the autocatalytic set
 *   is preferentially sustaining the seeds of heredity: rung 1 produces rung 2.
 *   If they end at their chance share, rung 1 sustains itself and nothing more, and the ladder's
 *   oldest admission stands.
 *
 * THE GUARD, and it is what makes a positive believable. "Enriched relative to chance" needs the
 * right null, and the wrong one is easy: self-templating reactions are catalysed BY DEFINITION, so
 * they are more likely to fire than an average reaction whether or not anything is being selected.
 * The control therefore uses a SCRAMBLED network with the identical number of self-templating
 * reactions and the identical catalysis density, but with the self-templating property assigned to
 * RANDOM species instead of the ones that carry it. Same firing advantage, no relationship between
 * being self-templating and being sustained. That is the four-check tally applied at design time:
 * the knob must move one variable.
 *
 * A null here is the expected outcome and a real result — it would say the substrate makes the
 * question askable without making the answer yes.
 *
 * build:  cc -O2 -Wall -std=c11 -o chain12 chain12.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <string.h>

#define NSPEC 40
#define NFOOD 6
#define NRXN  160
#define TOTAL 3000
#define ROUNDS 20000
static double PCAT_RUN=0.004;

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

typedef struct { int a,b,prod; unsigned char cat[NSPEC]; } Rxn;
static Rxn R[NRXN];
static unsigned char selftem[NSPEC];      /* species X: some reaction makes X and X catalyses it */

/* scramble=0: self-templating is whatever the network actually has.
   scramble=1: the SAME COUNT of self-templating reactions, but the property is reassigned to random
   species — identical firing advantage, no link between being self-templating and being sustained. */
static void net_random(int scramble){
	memset(selftem,0,sizeof selftem);
	for(int j=0;j<NRXN;j++){
		R[j].a=rint_(NSPEC); R[j].b=rint_(NSPEC); R[j].prod=NFOOD+rint_(NSPEC-NFOOD);
		memset(R[j].cat,0,sizeof R[j].cat);
		for(int m=0;m<NSPEC;m++) if(urand()<PCAT_RUN) R[j].cat[m]=1;
	}
	/* find the genuine self-templaters */
	int hits[NRXN], nh=0;
	for(int j=0;j<NRXN;j++) if(R[j].cat[R[j].prod]) hits[nh++]=j;
	if(!scramble){
		for(int i=0;i<nh;i++) selftem[R[hits[i]].prod]=1;
	} else {
		/* keep the same number of self-templating reactions, but point the property elsewhere:
		   the reaction still catalyses itself (same firing advantage) while the SPECIES we count as
		   self-templating is drawn at random. */
		for(int i=0;i<nh;i++) selftem[NFOOD+rint_(NSPEC-NFOOD)]=1;
	}
}

static int n[NSPEC];
static void seed(void){ memset(n,0,sizeof n); for(int i=0;i<TOTAL;i++) n[rint_(NSPEC)]++; }

static void react(void){
	for(int t=0;t<ROUNDS;t++){
		int tot=0; for(int m=0;m<NSPEC;m++) tot+=n[m];
		if(tot<2) return;
		int pa=rint_(tot),pb=rint_(tot),a=-1,b=-1,acc=0;
		for(int m=0;m<NSPEC;m++){ acc+=n[m]; if(a<0&&pa<acc)a=m; if(b<0&&pb<acc)b=m; }
		if(a<0||b<0) continue;
		int j0=rint_(NRXN);
		for(int d=0;d<NRXN;d++){
			int j=(j0+d)%NRXN;
			if(!((R[j].a==a&&R[j].b==b)||(R[j].a==b&&R[j].b==a))) continue;
			int cz=0; for(int m=0;m<NSPEC;m++) if(R[j].cat[m]&&n[m]>0){cz=1;break;}
			if(!cz) break;
			int ev=rint_(tot),e=-1; acc=0;
			for(int m=0;m<NSPEC;m++){ acc+=n[m]; if(ev<acc){ e=m; break; } }
			if(e>=NFOOD && n[e]>0) n[e]--;
			n[R[j].prod]++;
			break;
		}
	}
}

/* share of sustained abundance that is self-templating, and the chance share it is compared to */
static void measure(double *got,double *chance){
	int tot=0,st=0;
	for(int m=NFOOD;m<NSPEC;m++){ tot+=n[m]; if(selftem[m]) st+=n[m]; }
	*got = tot? (double)st/tot : 0.0;
	int ns=0; for(int m=NFOOD;m<NSPEC;m++) if(selftem[m]) ns++;
	*chance = (double)ns/(NSPEC-NFOOD);       /* their share of the species pool */
}

int main(void)
{
	printf("=== REPLICATION: does the rung1->rung2 enrichment hold across seeds and catalysis density? ===\n");
	printf("  First run: 1.433x real vs 1.046x scrambled, n=25 of 60 seeds, one PCAT. Promising and\n");
	printf("  unreplicated. Here: 200 seeds per cell and a PCAT sweep. The control is a PERMUTATION —\n");
	printf("  both arms share the seed so network and dynamics are byte-identical; only which species\n");
	printf("  are LABELLED self-templating differs.\n");
	printf("  Also reported: how many networks contained any self-templater, since the rest are\n");
	printf("  excluded and that conditioning should be visible rather than hidden.\n\n");

	double pcats[4]={0.002,0.004,0.008,0.016};
	int seeds=200, held=0, tested=0;
	printf("   PCAT  | networks w/ self-tem | REAL enrich | SCRAMBLED | difference\n");
	printf("   ------+----------------------+-------------+-----------+-----------\n");
	for(int pi=0;pi<4;pi++){
		PCAT_RUN=pcats[pi];
		double eg=0,ec=0; int ng=0,nc=0;
		for(int s=0;s<seeds;s++){
			double g,ch;
			RNG=0x9e3779b97f4a7c15ULL+(unsigned long long)(pi*7919+s)*0x2545F4914F6CDD1DULL;
			net_random(0); seed(); react(); measure(&g,&ch);
			if(ch>0.01){ eg+=g/ch; ng++; }
			RNG=0x9e3779b97f4a7c15ULL+(unsigned long long)(pi*7919+s)*0x2545F4914F6CDD1DULL;
			net_random(1); seed(); react(); measure(&g,&ch);
			if(ch>0.01){ ec+=g/ch; nc++; }
		}
		if(ng) eg/=ng;
		if(nc) ec/=nc;
		double d=eg-ec;
		printf("   %.3f |     %3d of %3d       |    %.3fx   |   %.3fx  |   %+.3f\n",
			pcats[pi],ng,seeds,eg,ec,d);
		if(ng>=seeds/10){ tested++; if(d>0.10) held++; }
	}
	printf("\n   held (difference > 0.10) in %d of %d cells with enough networks\n\n",held,tested);
	if(tested==0)
		printf("   => TOO FEW NETWORKS CONTAINED A SELF-TEMPLATER AT ANY DENSITY. Nothing measured.\n");
	else if(held==tested)
		printf("   => IT HOLDS. The enrichment survives 200 seeds per cell across the whole catalysis\n"
		       "      sweep, against a permutation null on byte-identical dynamics. The first result was\n"
		       "      not a small-n artefact.\n");
	else if(held>0)
		printf("   => PARTIAL: holds in %d of %d cells. Report the density dependence as-is; the effect\n"
		       "      is real somewhere and absent elsewhere, which is itself informative.\n",held,tested);
	else
		printf("   => IT DOES NOT HOLD. At 200 seeds per cell the enrichment does not separate from its\n"
		       "      permutation control anywhere in the sweep. The first result was a small-n artefact\n"
		       "      and chain12's claim must be RETRACTED — the ladder's oldest admission stands.\n");
	return 0;
}