OverviewFilesCommitsIssuesDocsReleasesPeers

basal-agent / substrate.c

184 lines · c ref:
/* substrate.c — ONE representation for all five rungs. Milestone 1: does it express rung 1 and 2?
 *
 * WHY. SYNTHESIS's standing admission is that no rung ever produced the next, and the cold read of
 * Part C found why it is structural rather than accidental: EVERY RUNG HAS A DIFFERENT SUBSTRATE.
 * Bit strings, count vectors, reaction sets, gene-type lists, role counts. A rung's output is not
 * expressible as the next rung's input, so the ladder can only ever be narrated across disconnected
 * models. rafcomp.c got two rungs co-present by hand-translating one into the other's terms; doing
 * that for a whole ladder means one representation that expresses all of them.
 *
 * THE CONSTRAINT TONIGHT ADDED. genome.c, replicase.c and replisome.c all failed for one reason:
 * nothing in those models was expensive enough to be worth remembering, because payoff was scored on
 * the present configuration and any configuration was cheap to rediscover. linkage.c, the one that
 * worked, is the one whose payoff required a COMBINATION. So the substrate must make combinations
 * expensive to rediscover, or memory can never pay in it.
 *
 * THE CHOICE. A reaction fires only when a SPECIFIC SET of species is present — reactants AND a
 * catalyst. That is combinatorial by construction, and it already covers the ladder:
 *   rung 1  a self-sustaining reaction subset (a RAF)
 *   rung 2  a template: the reaction A -> A + A catalysed by A
 *   rung 3  species ARE sequences; mutation is a variant-producing reaction
 *   rung 4  a container is a sub-multiset copied as a unit — AND IS ITSELF A SPECIES
 *   rung 5  a specialist is a species participating in only part of the reaction set
 * The recursive move is the last-but-one: containers are species. That is what lets a rung's output
 * be the next rung's input instead of being hand-translated.
 *
 * MILESTONE 1 (this file) — the test of the test. A new substrate is worthless if it does not
 * faithfully express what we already have, so before anything else:
 *   S1 the engine must REPRODUCE rung1.c's percolation threshold. Same construction, same sweep. If
 *      the curve differs, the substrate does not express rung 1 and nothing built on it can be
 *      trusted.
 *   S2 a self-templating species (rung 2) must be EXPRESSIBLE in the same engine with no special
 *      case — just a reaction whose product is its own catalyst — and must self-sustain when its
 *      inputs are food.
 *   S3 both must be able to coexist in ONE network, which is the whole point: it is the precondition
 *      for rung 2 arising INSIDE a rung-1 system rather than being a separate model.
 * Milestones 2+ (containers-as-species, then rungs 3-5) are not attempted here. A substrate that
 * fails S1 is not worth extending.
 *
 * build:  cc -O2 -Wall -std=c11 -o substrate substrate.c -lm
 */
#include <math.h>
#include <stdio.h>
#include <string.h>

#define NSPEC 60           /* species; rung1.c used N=60 */
#define NFOOD 8            /* food species, always present; rung1.c used NF=8 */
#define NRXN  300          /* reactions; rung1.c used M=300 */
#define INST  60           /* network instances per point; rung1.c used INST=60 */

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

/* ---- THE TYPE. A reaction is (reactant a, reactant b) -> product, enabled by any catalyst.
   Species are integers. Everything in the ladder is meant to be expressed as species+reactions:
   a template is a reaction whose product catalyses itself; a container will be a species whose
   contents are a multiset (milestone 2). ---- */
typedef struct { int a, b, prod; unsigned char cat[NSPEC]; } Rxn;
static Rxn R[NRXN];
static int nrxn;
static unsigned char present[NSPEC], active[NRXN];

static void net_clear(void){ nrxn=0; }
static int  net_add(int a,int b,int prod){
	if(nrxn>=NRXN) return -1;
	R[nrxn].a=a; R[nrxn].b=b; R[nrxn].prod=prod;
	memset(R[nrxn].cat,0,sizeof R[nrxn].cat);
	return nrxn++;
}
static void net_catalyse(int j,int sp){ R[j].cat[sp]=1; }

/* rung1.c's construction, verbatim in shape: random reactions, each species catalyses each
   reaction with probability p. */
static void net_random_n(double p,int count){
	net_clear();
	if(count>NRXN) count=NRXN;
	for(int j=0;j<count;j++){
		int id=net_add(rint_(NSPEC),rint_(NSPEC),NFOOD+rint_(NSPEC-NFOOD));
		if(id<0) break;
		for(int m=0;m<NSPEC;m++) if(urand()<p) net_catalyse(id,m);
	}
}
static void net_random(double p){ net_random_n(p,NRXN); }

/* maxRAF by prune-to-fixpoint — rung1.c's algorithm, operating on the substrate's own type. */
static int max_raf(void){
	for(int j=0;j<nrxn;j++) active[j]=1;
	for(;;){
		for(int m=0;m<NSPEC;m++) present[m]=(m<NFOOD)?1:0;
		int grew=1;
		while(grew){ grew=0;
			for(int j=0;j<nrxn;j++){ if(!active[j]) continue;
				if(!present[R[j].a]||!present[R[j].b]||present[R[j].prod]) continue;
				int cz=0; for(int m=0;m<NSPEC;m++) if(R[j].cat[m]&&present[m]){cz=1;break;}
				if(cz){ present[R[j].prod]=1; grew=1; }
			}
		}
		int changed=0;
		for(int j=0;j<nrxn;j++){ if(!active[j]) continue;
			int sup=present[R[j].a]&&present[R[j].b];
			int cz=0; for(int m=0;m<NSPEC;m++) if(R[j].cat[m]&&present[m]){cz=1;break;}
			if(!(sup&&cz)){ active[j]=0; changed=1; }
		}
		if(!changed) break;
	}
	int n=0; for(int j=0;j<nrxn;j++) n+=active[j];
	return n;
}

int main(void)
{
	printf("=== SUBSTRATE, MILESTONE 1: does one representation express rung 1 AND rung 2? ===\n");
	printf("  Species are integers; a reaction is (a,b)->prod enabled by any present catalyst.\n");
	printf("  Nothing is special-cased per rung. %d species (%d food), %d reactions.\n\n",NSPEC,NFOOD,NRXN);

	/* ---- S1: reproduce rung1.c's percolation threshold ---- */
	printf("  S1 — reproduce rung1.c's threshold (its published curve in the right column)\n");
	printf("     %-8s %10s %10s   %s\n","p","maxRAF","P(RAF>0)","rung1.c");
	const char *ref[6]={"~0 (empty)","~0","rising","rising","system-spanning","system-spanning"};
	double ps[6]={0.006,0.012,0.021,0.030,0.042,0.054};
	int ok_low=0, ok_high=0;
	for(int i=0;i<6;i++){
		double sz=0; int nz=0;
		for(int t=0;t<INST;t++){
			RNG=0x9e3779b97f4a7c15ULL + (unsigned long long)(i*10007+t)*0x2545F4914F6CDD1DULL;
			net_random(ps[i]); int s=max_raf(); sz+=s; if(s>0) nz++;
		}
		double m=sz/INST, f=(double)nz/INST;
		printf("     %-8.4f %10.1f %10.2f   %s\n",ps[i],m,f,ref[i]);
		if(i==0 && m<1.0) ok_low=1;
		if(i==5 && m>20.0) ok_high=1;
	}
	printf("     => %s\n\n", (ok_low&&ok_high)?
		"MATCHES: empty below, system-spanning above — rung 1 is expressed faithfully."
		:"DIVERGES from rung1.c — the substrate does not express rung 1. Stop here.");

	/* ---- S2: a template is just a reaction that catalyses itself. No special case. ---- */
	printf("  S2 — express rung 2 (a template) in the SAME type: a reaction whose product is its\n");
	printf("       own catalyst. Food-only reactants, so it needs nothing but the food set.\n");
	net_clear();
	int T=NFOOD;                                   /* the template species */
	int t1=net_add(0,1,T); net_catalyse(t1,T);     /* food + food -> T, catalysed by T */
	int self=max_raf();
	printf("       template alone, uncatalysed start : maxRAF %d  (expected 0 — needs a seed)\n",self);
	net_catalyse(t1,0);                            /* also catalysed by a food species = a seed */
	int seeded=max_raf();
	printf("       with a food-species seed          : maxRAF %d  (expected 1 — self-sustains)\n",seeded);
	int s2 = (self==0 && seeded==1);
	printf("     => %s\n\n", s2?
		"EXPRESSED: rung 2 is a reaction shape, not a new model. And it reproduces the known\n        bootstrap problem — a self-catalysing template cannot start itself without a seed."
		:"NOT EXPRESSED as expected — check the encoding before building on it.");

	/* ---- S3: both in ONE network, which is the precondition for chaining ---- */
	printf("  S3 — rung 1 and rung 2 in ONE network (the point: rung 2 could arise INSIDE rung 1)\n");
	int both=0, tin=0;
	{
		/* BUG FIXED: net_random filled all NRXN slots, so the following net_add returned -1 and
		   active[-1] was read — undefined behaviour, reported as "no". Leave a slot free. */
		int hits=0, tries=8;
		for(int t=0;t<tries;t++){
			RNG=0xfeedface12345678ULL + (unsigned long long)t*0x9E3779B97F4A7C15ULL;
			net_random_n(0.030,NRXN-1);
			int j=net_add(0,1,T);
			if(j<0){ printf("       reaction table full — cannot express both\n"); break; }
			net_catalyse(j,T); net_catalyse(j,0);
			int n=max_raf();
			if(active[j] && n>1) hits++;
			if(t==0) printf("       mixed network maxRAF %d, template reaction in the RAF: %s\n",n,active[j]?"YES":"no");
		}
		printf("       across %d seeds, template retained in the RAF: %d\n",tries,hits);
		tin=(hits>0); both=(hits>=tries-1);
	}
	printf("     => %s\n\n", both?
		"COEXIST: a self-templating species sits inside an autocatalytic set, in one type, with no\n        translation between them. That is the precondition the ladder has never had."
		:"did NOT coexist in this instance — retry across seeds before concluding.");

	printf("  MILESTONE 1 %s\n", (ok_low&&ok_high&&s2&&both)?"PASSED":"NOT PASSED");
	printf("  Next, and NOT attempted here: containers as species (rung 4), which is the recursive\n");
	printf("  move that lets a compartment be produced, counted and selected like anything else —\n");
	printf("  and is what would let a rung's output actually BE the next rung's input.\n");
	return 0;
}