| … | … | @@ -0,0 +1,237 @@ |
| 1 | /* gardphi.c — Phi_r probe on OUR GARD (gardenv.c). The only model of ours that is actually GARD. |
| 2 | * |
| 3 | * WHY THIS ONE MATTERS. ribophi.c and rung34phi.c tested Pigozzi & Levin's causal-emergence claim |
| 4 | * (arXiv 2607.28250) by ANALOGY, in models that are not GARD. Both returned nulls, and the honest |
| 5 | * caveat in both was the same: different model, so the disagreement could sit anywhere. gardenv.c |
| 6 | * IS the GARD family -- token types, a catalysis matrix beta, accretion, split-and-regrow, |
| 7 | * compositional (not sequence) inheritance. So this probe can speak to their result directly. |
| 8 | * |
| 9 | * MATCHING THEIR SETUP, not ours: |
| 10 | * - SINGLE LINEAGE. The paper traces one assembly, fissioning and continuing from one daughter. |
| 11 | * gardenv.c normally runs 200 pockets with between-pocket selection; here selection is OFF and |
| 12 | * one lineage is followed, because the paper is explicit that GARD "is essentially a stochastic |
| 13 | * model of events before natural selection takes place". |
| 14 | * - MOLECULAR TIME, not generations. They compute Phi_r over molecular steps. ribophi.c sampled |
| 15 | * once per generation and that coarseness was part of why it could not host the test. Here the |
| 16 | * composition is sampled every SAMPLE join/leave events, giving ~9000 points per run. |
| 17 | * - THEIR SELF-REPLICATOR CRITERION. "Recurring compositions inherited across generations": find |
| 18 | * the most recurring composition, then a timestep is in-self-replicator if its cosine to that |
| 19 | * composition exceeds a threshold. A run enters and leaves self-replication over its lifetime. |
| 20 | * |
| 21 | * DIMENSION REDUCTION (declared, because it is a real deviation). They use Ng=100 parts and an |
| 22 | * unspecified minimum-information-bipartition search; exhaustive search over 100 parts is |
| 23 | * impossible, so they must use a heuristic they do not describe. We reduce N=40 token types to 8 |
| 24 | * coordinates -- the 7 most abundant types plus an "other" bin -- chosen from the FIRST 25% of each |
| 25 | * run only, so no information from the predicted portion leaks into the coordinate choice. Then |
| 26 | * CLR, drop one, 7 parts, exhaustive bipartition (63) as in the other probes. |
| 27 | * |
| 28 | * PRE-REGISTERED: |
| 29 | * R1 (their Fig 3/4, the headline): Phi_r is HIGHER during timesteps in self-replication than |
| 30 | * outside it. Per run by Mann-Whitney; report how many runs are significantly positive. |
| 31 | * R2 (their Fig 5): mean Phi_r over the FIRST 25% of a run predicts the fraction of the REMAINING |
| 32 | * 75% spent in self-replication, across runs (Spearman). |
| 33 | * R3 CONTROL for R2, the one that can kill it: the same prediction from a raw compositional |
| 34 | * statistic over the same first 25% (mean drift = 1 - cosine between consecutive samples). |
| 35 | * Phi_r earns its keep only if it beats this. NOTE R1 is not circular -- Phi_r is not derived |
| 36 | * from the label -- but R2's control must be, since the label itself is compositional. |
| 37 | * R4 TEST OF THE TEST: does self-replication actually occur and fluctuate? If a run is in-replicator |
| 38 | * ~always or ~never, there is nothing to predict and that run is uninformative. Report the |
| 39 | * distribution of in-replicator fraction before reading anything else. |
| 40 | * |
| 41 | * Prior from our own arc: gardenv.c measured heredity ~0 in this chemistry -- composomes do not |
| 42 | * breed true. So compositions may satisfy the paper's similarity criterion while transmitting |
| 43 | * nothing. If R1 holds while heredity is ~0, that is not a contradiction of the paper; it locates |
| 44 | * precisely what their "self-replication" is tracking. |
| 45 | * |
| 46 | * build: cc -O2 -Wall -std=c11 -o gardphi gardphi.c -lm |
| 47 | */ |
| 48 | #include <math.h> |
| 49 | #include <stdio.h> |
| 50 | #include <string.h> |
| 51 | #include "phir.h" |
| 52 | |
| 53 | /* ---------- verbatim from gardenv.c ---------- */ |
| 54 | #define N 40 |
| 55 | #define S 150 |
| 56 | #define GEN 60 |
| 57 | #define MAT 1500 |
| 58 | #define PCAT 0.15 |
| 59 | #define RHO0 0.4 |
| 60 | |
| 61 | /* ---------- probe ---------- */ |
| 62 | #define SAMPLE 10 /* sample the composition every SAMPLE molecular events */ |
| 63 | #define MAXT 12000 |
| 64 | #define NB 8 |
| 65 | #define P (NB-1) |
| 66 | #define WINL 200 /* Phi_r window, in samples */ |
| 67 | #define STRIDE 50 |
| 68 | #define NRUN 30 |
| 69 | #define SIMTHR 0.90 /* cosine to the most recurring composition = "in self-replicator" */ |
| 70 | |
| 71 | static unsigned long long RNG; |
| 72 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 73 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 74 | static double randnv(void){ double u=urand(),v=urand(); if(u<1e-300)u=1e-300; return sqrt(-2*log(u))*cos(6.283185307*v); } |
| 75 | |
| 76 | static double bta[N][N], rate[N]; |
| 77 | static void build_chem(void){ for(int i=0;i<N;i++)for(int j=0;j<N;j++) bta[i][j]=(i!=j&&urand()<PCAT)? exp(1.8*randnv())*0.30 : 0.0; } |
| 78 | static void recompute(const int*n){ for(int i=0;i<N;i++){ double r=RHO0; for(int j=0;j<N;j++) r+=bta[i][j]*n[j]; rate[i]=r; } } |
| 79 | static int pick_add(void){ double t=0; for(int i=0;i<N;i++)t+=rate[i]; double u=urand()*t,a=0; for(int i=0;i<N;i++){a+=rate[i];if(u<=a)return i;} return N-1; } |
| 80 | static int pick_rem(const int*n,int sz){ double u=urand()*sz,a=0; for(int j=0;j<N;j++){a+=n[j];if(u<=a)return j;} return N-1; } |
| 81 | static void addm(int*n,int i){ n[i]++; for(int k=0;k<N;k++) rate[k]+=bta[k][i]; } |
| 82 | static void remm(int*n,int j){ n[j]--; for(int k=0;k<N;k++) rate[k]-=bta[k][j]; } |
| 83 | static double cosv(const double*a,const double*b){ double d=0,na=0,nb=0; for(int i=0;i<N;i++){ d+=a[i]*b[i]; na+=a[i]*a[i]; nb+=b[i]*b[i]; } if(na<=0||nb<=0)return 0; return d/(sqrt(na)*sqrt(nb)); } |
| 84 | |
| 85 | static double traj[MAXT][N]; |
| 86 | static int ntraj; |
| 87 | |
| 88 | static void snap(const int*n){ if(ntraj>=MAXT) return; for(int i=0;i<N;i++) traj[ntraj][i]=n[i]; ntraj++; } |
| 89 | |
| 90 | /* mature + grow, sampling composition in MOLECULAR time (the paper's substrate) */ |
| 91 | static void mature_s(int*n,int sz){ recompute(n); for(int t=0;t<MAT;t++){ int j=pick_rem(n,sz); remm(n,j); addm(n,pick_add()); if(t%SAMPLE==0) snap(n); } } |
| 92 | static void grow_to_s(int*n,int*sz,int tgt){ recompute(n); int c=0; while(*sz<tgt){ addm(n,pick_add()); (*sz)++; if((c++)%SAMPLE==0) snap(n); } } |
| 93 | |
| 94 | /* one lineage: accrete, mature, fission, continue from a daughter. NO selection. */ |
| 95 | static void run_lineage(void) |
| 96 | { |
| 97 | int n[N]; memset(n,0,sizeof n); |
| 98 | for(int s=0;s<S;s++) n[(int)(xs()%N)]++; |
| 99 | ntraj=0; |
| 100 | mature_s(n,S); |
| 101 | for(int g=0;g<GEN;g++){ |
| 102 | int D[N]; memset(D,0,sizeof D); int ds=0; |
| 103 | for(int i=0;i<N;i++) for(int c=0;c<n[i];c++) if(urand()<0.5){ D[i]++; ds++; } |
| 104 | if(ds<1){ D[0]=1; ds=1; } |
| 105 | grow_to_s(D,&ds,S); |
| 106 | mature_s(D,S); |
| 107 | memcpy(n,D,sizeof D); |
| 108 | } |
| 109 | } |
| 110 | |
| 111 | int main(void) |
| 112 | { |
| 113 | phir_seed(0x9e3779b97f4a7c15ULL); |
| 114 | printf("=== Phi_r on OUR GARD (gardenv.c chemistry): single lineage, no selection ===\n"); |
| 115 | printf(" %d runs, %d tokens, %d generations, composition sampled every %d molecular events.\n",NRUN,N,GEN,SAMPLE); |
| 116 | printf(" Self-replicator = cosine to the most recurring composition > %.2f (their criterion).\n",SIMTHR); |
| 117 | printf(" Coordinates: 7 most abundant token types + other, chosen from the FIRST 25%% only.\n\n"); |
| 118 | |
| 119 | double earlyPhi[NRUN], lateFrac[NRUN], earlyDrift[NRUN], inFrac[NRUN]; |
| 120 | int nrun=0, nsig_pos=0, nsig_neg=0, ntested=0; |
| 121 | |
| 122 | for(int r=0;r<NRUN;r++){ |
| 123 | RNG = 0x9e3779b97f4a7c15ULL ^ (0x100000001b3ULL*(unsigned long long)(r+1)); |
| 124 | for(int k=0;k<5;k++) xs(); |
| 125 | build_chem(); |
| 126 | run_lineage(); |
| 127 | if(ntraj < 4*WINL) continue; |
| 128 | |
| 129 | int q1 = ntraj/4; /* first 25% */ |
| 130 | |
| 131 | /* --- coordinates from the first 25% only: 7 most abundant types + other --- */ |
| 132 | double ab[N]; for(int i=0;i<N;i++) ab[i]=0; |
| 133 | for(int t=0;t<q1;t++) for(int i=0;i<N;i++) ab[i]+=traj[t][i]; |
| 134 | int top[NB-1]; |
| 135 | { int used[N]; for(int i=0;i<N;i++) used[i]=0; |
| 136 | for(int s=0;s<NB-1;s++){ int bi=-1; double bv=-1; |
| 137 | for(int i=0;i<N;i++) if(!used[i] && ab[i]>bv){ bv=ab[i]; bi=i; } |
| 138 | top[s]=bi; used[bi]=1; } } |
| 139 | |
| 140 | static double clr[MAXT][P]; |
| 141 | for(int t=0;t<ntraj;t++){ |
| 142 | double bins[NB]; for(int b=0;b<NB;b++) bins[b]=0; |
| 143 | double tot=0; for(int i=0;i<N;i++) tot+=traj[t][i]; |
| 144 | for(int s=0;s<NB-1;s++) bins[s]=traj[t][top[s]]; |
| 145 | double used=0; for(int s=0;s<NB-1;s++) used+=bins[s]; |
| 146 | bins[NB-1]=tot-used; |
| 147 | phir_clr(bins,NB,clr[t]); |
| 148 | } |
| 149 | |
| 150 | /* --- most recurring composition: biggest cosine>0.85 cluster over the whole run --- */ |
| 151 | double cent[N]; int bestcount=-1; |
| 152 | { |
| 153 | int NPROBE=60; |
| 154 | for(int a=0;a<NPROBE;a++){ |
| 155 | int idx=(int)((double)a/NPROBE*ntraj); |
| 156 | int cnt=0; for(int t=0;t<ntraj;t+=5) if(cosv(traj[idx],traj[t])>0.85) cnt++; |
| 157 | if(cnt>bestcount){ bestcount=cnt; memcpy(cent,traj[idx],sizeof cent); } |
| 158 | } |
| 159 | } |
| 160 | static int lab[MAXT]; |
| 161 | int nin=0; |
| 162 | for(int t=0;t<ntraj;t++){ lab[t] = (cosv(traj[t],cent)>SIMTHR); nin+=lab[t]; } |
| 163 | inFrac[nrun]=(double)nin/ntraj; |
| 164 | |
| 165 | /* --- Phi_r trajectory in molecular time --- */ |
| 166 | static double pairs[MAXT][2*P]; |
| 167 | static double pv[MAXT]; static int pc[MAXT]; int nw=0; |
| 168 | for(int st=0; st+WINL<=ntraj; st+=STRIDE){ |
| 169 | int n2=WINL-1; |
| 170 | for(int t=0;t<n2;t++) for(int i=0;i<P;i++){ pairs[t][i]=clr[st+t][i]; pairs[t][P+i]=clr[st+t+1][i]; } |
| 171 | int ok; double v=phi_r(&pairs[0][0],n2,P,&ok); |
| 172 | if(ok){ pv[nw]=v; pc[nw]=st+WINL/2; nw++; } |
| 173 | } |
| 174 | if(nw<8) continue; |
| 175 | |
| 176 | /* --- R1: Phi_r inside vs outside self-replication (per run) --- */ |
| 177 | double insv[MAXT], outv[MAXT]; int ni=0,no=0; |
| 178 | for(int i=0;i<nw;i++){ if(lab[pc[i]]) insv[ni++]=pv[i]; else outv[no++]=pv[i]; } |
| 179 | if(ni>=4 && no>=4){ |
| 180 | ntested++; |
| 181 | double p=mannwhitney_p(insv,ni,outv,no); |
| 182 | double mi=meanv(insv,ni), mo=meanv(outv,no); |
| 183 | if(p<0.05){ if(mi>mo) nsig_pos++; else nsig_neg++; } |
| 184 | } |
| 185 | |
| 186 | /* --- R2/R3: early Phi_r and early drift vs later self-replication --- */ |
| 187 | { |
| 188 | double sPhi=0; int cPhi=0; |
| 189 | for(int i=0;i<nw;i++) if(pc[i]<q1){ sPhi+=pv[i]; cPhi++; } |
| 190 | earlyPhi[nrun] = cPhi? sPhi/cPhi : NAN; |
| 191 | double d=0; int cd=0; |
| 192 | for(int t=1;t<q1;t++){ d += 1.0-cosv(traj[t-1],traj[t]); cd++; } |
| 193 | earlyDrift[nrun] = cd? d/cd : NAN; |
| 194 | int late=0,lc=0; |
| 195 | for(int t=q1;t<ntraj;t++){ late+=lab[t]; lc++; } |
| 196 | lateFrac[nrun] = lc? (double)late/lc : NAN; |
| 197 | } |
| 198 | nrun++; |
| 199 | } |
| 200 | |
| 201 | /* ---------- R4 first: did self-replication happen and fluctuate? ---------- */ |
| 202 | printf(" R4 TEST OF THE TEST — in-self-replicator fraction across %d runs:\n",nrun); |
| 203 | { |
| 204 | double f[NRUN]; int nf=0, degenerate=0; |
| 205 | for(int i=0;i<nrun;i++){ f[nf++]=inFrac[i]; if(inFrac[i]<0.02||inFrac[i]>0.98) degenerate++; } |
| 206 | printf(" mean %.3f, median %.3f, min %.3f, max %.3f | degenerate runs (always/never): %d/%d\n", |
| 207 | meanv(f,nf), median(f,nf), f[0], f[nf-1], degenerate, nrun); |
| 208 | double mn=1,mx=0; for(int i=0;i<nf;i++){ if(f[i]<mn)mn=f[i]; if(f[i]>mx)mx=f[i]; } |
| 209 | printf(" range %.3f .. %.3f\n",mn,mx); |
| 210 | if(degenerate>nrun/2) |
| 211 | printf(" => MOSTLY DEGENERATE: little to predict. Treat everything below as uninformative.\n"); |
| 212 | else |
| 213 | printf(" => self-replication occurs and fluctuates; the test is live.\n"); |
| 214 | } |
| 215 | |
| 216 | /* ---------- R1 ---------- */ |
| 217 | printf("\n R1 (their headline): Phi_r higher DURING self-replication than outside?\n"); |
| 218 | printf(" runs tested %d | significantly HIGHER %d | significantly LOWER %d | n.s. %d\n", |
| 219 | ntested,nsig_pos,nsig_neg,ntested-nsig_pos-nsig_neg); |
| 220 | |
| 221 | /* ---------- R2 / R3 ---------- */ |
| 222 | printf("\n R2/R3: early (first 25%%) predictor vs later self-replication fraction, across runs\n"); |
| 223 | { |
| 224 | double a[NRUN],b[NRUN],c[NRUN]; int m=0; |
| 225 | for(int i=0;i<nrun;i++){ |
| 226 | if(isnan(earlyPhi[i])||isnan(lateFrac[i])||isnan(earlyDrift[i])) continue; |
| 227 | a[m]=earlyPhi[i]; b[m]=lateFrac[i]; c[m]=earlyDrift[i]; m++; |
| 228 | } |
| 229 | double r1=spearman(a,b,m), r2=spearman(c,b,m); |
| 230 | printf(" n=%d runs\n",m); |
| 231 | printf(" R2 early Phi_r vs later self-replication : rho = %+.3f (p=%.3g)\n",r1,spearman_p(r1,m)); |
| 232 | printf(" R3 early drift vs later self-replication : rho = %+.3f (p=%.3g) <- CONTROL\n",r2,spearman_p(r2,m)); |
| 233 | printf(" => Phi_r earns its keep only if |rho| exceeds the control's.\n"); |
| 234 | } |
| 235 | printf("\n=== verdict read off R1-R4 ===\n"); |
| 236 | return 0; |
| 237 | } |