| … | … | @@ -0,0 +1,148 @@ |
| 1 | /* rung45irr.c — TEST: is division of labour blocked by REVERSIBILITY? (Law 2 applied to rung 4->5) |
| 2 | * |
| 3 | * rung45.c stalled, and rung45dup.c got specialists invading but full DoL plateauing at a second |
| 4 | * coordination valley. Law 2 (SYNTHESIS Part C) says why that might be: a boundary — or here a |
| 5 | * ROLE — that must be MAINTAINED is a strategy, and strategies get invaded. In rung45.c a |
| 6 | * specialist can mutate straight back to generalist (r = xs()%(F+1)), so specialisation is fully |
| 7 | * elective and the retreat to safe generality is always available. |
| 8 | * |
| 9 | * Life's specialists cannot go back: differentiation involves losing capability, and gene loss is |
| 10 | * one-way. So the one-knob test is whether making specialisation STRUCTURAL rather than elective |
| 11 | * stabilises DoL. |
| 12 | * |
| 13 | * REVERSIBLE (control, = rung45.c): with prob MUR, role := uniform(0..F) |
| 14 | * IRREVERSIBLE (test): with prob MUR, role := generalist ? uniform(0..F) |
| 15 | * : uniform(1..F) <- no way back |
| 16 | * |
| 17 | * NOT ENCODED: irreversibility removes the retreat-to-generalist escape, but leaves the OTHER |
| 18 | * failure route wide open — a compartment can still collapse to a monoculture of one specialist, |
| 19 | * lose a function and die. So the outcome is genuinely open: a ratchet could give division of |
| 20 | * labour, or could give monoculture and extinction. Viability is reported so which one happens is |
| 21 | * visible. |
| 22 | * |
| 23 | * build: cc -O2 -Wall -std=c11 -o rung45irr rung45irr.c -lm |
| 24 | */ |
| 25 | #include <math.h> |
| 26 | #include <stdio.h> |
| 27 | #include <stdlib.h> |
| 28 | |
| 29 | #define NCOMP 500 |
| 30 | #define GEN 400 |
| 31 | #define D 12 /* compartment size */ |
| 32 | #define F 3 /* functions (roles 1..F); role 0 = generalist */ |
| 33 | #define SSPEC 0.30 /* specialist replication advantage (the free-rider pressure) */ |
| 34 | #define EGEN 0.2 /* generalist efficiency per function */ |
| 35 | #define MUR 0.05 /* role mutation rate */ |
| 36 | |
| 37 | static unsigned long long RNG; |
| 38 | static unsigned long long xs(void){ RNG^=RNG<<13; RNG^=RNG>>7; RNG^=RNG<<17; return RNG; } |
| 39 | static double urand(void){ return (double)(xs()>>11)*(1.0/9007199254740992.0); } |
| 40 | static int binom(int n,double p){ int k=0; for(int i=0;i<n;i++) if(urand()<p) k++; return k; } |
| 41 | |
| 42 | static int IRREV=0, NOSEL=0; |
| 43 | static int comp[NCOMP][F+1]; /* role counts per compartment */ |
| 44 | static int dau[2*NCOMP][F+1]; static double cum[2*NCOMP]; |
| 45 | |
| 46 | static double viability_sel(const int *n); |
| 47 | static double viability(const int *n){ if(NOSEL) return 1.0; return viability_sel(n); } |
| 48 | static double viability_sel(const int *n){ |
| 49 | double v=1e9; |
| 50 | for(int k=1;k<=F;k++){ double cov=n[k]+EGEN*n[0]; if(cov<v) v=cov; } /* weakest-link: need ALL functions */ |
| 51 | return v; |
| 52 | } |
| 53 | |
| 54 | static void one_run(int irrev,int nosel,double *specf,double *dolf,double *viaf){ |
| 55 | IRREV=irrev; NOSEL=nosel; |
| 56 | for(int c=0;c<NCOMP;c++){ comp[c][0]=D; for(int k=1;k<=F;k++) comp[c][k]=0; } |
| 57 | for(int g=0;g<GEN;g++){ |
| 58 | double w[F+1]; w[0]=1.0; for(int k=1;k<=F;k++) w[k]=1.0+SSPEC; |
| 59 | for(int c=0;c<NCOMP;c++){ |
| 60 | double Z=0; for(int k=0;k<=F;k++) Z+=comp[c][k]*w[k]; if(Z<1e-12)Z=1e-12; |
| 61 | double pr[F+1],acc=0; for(int k=0;k<=F;k++){ acc+=comp[c][k]*w[k]/Z; pr[k]=acc; } |
| 62 | int m[F+1]={0}; |
| 63 | for(int i=0;i<2*D;i++){ double u=urand(); int r=0; while(r<F && u>pr[r]) r++; |
| 64 | if(urand()<MUR){ if(IRREV && r!=0) r=1+(int)(xs()%F); else r=(int)(xs()%(F+1)); } |
| 65 | m[r]++; } |
| 66 | 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; } |
| 67 | } |
| 68 | double tot=0; for(int i=0;i<2*NCOMP;i++){ tot+=viability(dau[i]); cum[i]=tot; } |
| 69 | if(tot<=0){ for(int c=0;c<NCOMP;c++) for(int k=0;k<=F;k++) comp[c][k]=0; break; } |
| 70 | for(int c=0;c<NCOMP;c++){ double r=urand()*tot; int lo=0,hi=2*NCOMP-1; |
| 71 | while(lo<hi){ int mid=(lo+hi)/2; if(cum[mid]<r) lo=mid+1; else hi=mid; } |
| 72 | for(int k=0;k<=F;k++) comp[c][k]=dau[lo][k]; } |
| 73 | } |
| 74 | long tot=0,spec=0; int dol=0,via=0; |
| 75 | for(int c=0;c<NCOMP;c++){ int s2=0; |
| 76 | for(int k=0;k<=F;k++){ tot+=comp[c][k]; if(k>0){ spec+=comp[c][k]; if(comp[c][k]>0) s2++; } } |
| 77 | if(s2==F) dol++; if(viability_sel(comp[c])>0.001) via++; } |
| 78 | *specf = tot? (double)spec/tot : 0; |
| 79 | *dolf = (double)dol/NCOMP; |
| 80 | *viaf = (double)via/NCOMP; |
| 81 | } |
| 82 | |
| 83 | int main(void){ |
| 84 | unsigned long long seeds[6]={0x9e3779b97f4a7c15ULL,0xd1b54a32d192ed03ULL,0x2545f4914f6cdd1dULL, |
| 85 | 0x123456789abcdefULL,0xfeedface12345678ULL,0x0badc0de0badc0deULL}; |
| 86 | int NS=6; |
| 87 | printf("=== RUNG 4->5 UNDER LAW 2: is division of labour blocked by REVERSIBILITY? ===\n"); |
| 88 | printf(" rung45.c let a specialist mutate straight back to generalist, so specialisation was\n"); |
| 89 | printf(" fully elective — and Law 2 says an elective role is a strategy and gets invaded.\n"); |
| 90 | printf(" IRREVERSIBLE removes only the way back; the monoculture-and-die route stays open.\n"); |
| 91 | printf(" start: every compartment = %d generalists; specialists replicate %.0f%% faster;\n",D,SSPEC*100); |
| 92 | printf(" a compartment needs all %d functions to be viable. %d comps, %d gens, %d seeds.\n\n",F,NCOMP,GEN,NS); |
| 93 | printf(" arm | spec frac | all-%d-roles | viable\n",F); |
| 94 | printf(" -------------+-----------+-------------+--------\n"); |
| 95 | double a[3]={0,0,0}, b[3]={0,0,0}, d[3]={0,0,0}, e[3]={0,0,0}; |
| 96 | for(int s=0;s<NS;s++){ double x,y,z; |
| 97 | RNG=seeds[s]; one_run(0,0,&x,&y,&z); a[0]+=x;a[1]+=y;a[2]+=z; |
| 98 | RNG=seeds[s]; one_run(1,0,&x,&y,&z); b[0]+=x;b[1]+=y;b[2]+=z; |
| 99 | RNG=seeds[s]; one_run(1,1,&x,&y,&z); d[0]+=x;d[1]+=y;d[2]+=z; |
| 100 | RNG=seeds[s]; one_run(0,1,&x,&y,&z); e[0]+=x;e[1]+=y;e[2]+=z; } |
| 101 | for(int i=0;i<3;i++){ a[i]/=NS; b[i]/=NS; d[i]/=NS; e[i]/=NS; } |
| 102 | printf(" REVERSIBLE | %.3f | %6.1f%% | %5.1f%%\n",a[0],100*a[1],100*a[2]); |
| 103 | printf(" IRREVERSIBLE | %.3f | %6.1f%% | %5.1f%%\n",b[0],100*b[1],100*b[2]); |
| 104 | printf(" ---- CONFOUND CHECK: same ratchet, selection REMOVED (flat viability) ----\n"); |
| 105 | printf(" REVERS, nosel| %.3f | %6.1f%% | --\n",e[0],100*e[1]); |
| 106 | printf(" IRREV, nosel| %.3f | %6.1f%% | --\n\n",d[0],100*d[1]); |
| 107 | printf(" irreversibility's DoL benefit WITH selection %+.1f%%, WITHOUT selection %+.1f%%\n", |
| 108 | 100*(b[1]-a[1]), 100*(d[1]-e[1])); |
| 109 | printf(" selection's effect on DoL: reversible %+.1f%%, irreversible %+.1f%%\n", |
| 110 | 100*(a[1]-e[1]), 100*(b[1]-d[1])); |
| 111 | |
| 112 | int dol_up = b[1]>a[1]+0.05, via_ok = b[2]>0.5, spec_up = b[0]>a[0]+0.05; |
| 113 | /* CORRECTED. b[1]-d[1] measures selection's contribution INSIDE the irreversible arm, not the |
| 114 | flux confound. The flux question is whether irreversibility's BENEFIT is bigger with selection |
| 115 | than without it: (irrev-rev | with sel) vs (irrev-rev | no sel). */ |
| 116 | double gain_sel = b[1]-a[1], gain_nosel = d[1]-e[1]; |
| 117 | int not_flux = gain_sel > gain_nosel + 0.05; |
| 118 | printf(" DoL higher when irreversible : %s (%.1f%% vs %.1f%%)\n", dol_up?"YES":"no",100*b[1],100*a[1]); |
| 119 | printf(" still viable : %s (%.1f%%)\n", via_ok?"YES":"NO — collapsed",100*b[2]); |
| 120 | printf(" specialist fraction up : %s (%.3f vs %.3f)\n\n", spec_up?"YES":"no",b[0],a[0]); |
| 121 | if(dol_up && via_ok && not_flux) |
| 122 | printf(" => REVERSIBILITY WAS THE BLOCKER, AND IT SURVIVES ITS CONFOUND CHECK. Irreversibility\n" |
| 123 | " raises DoL by %+.1f%% with selection but only %+.1f%% without, so roughly %.0f%% of the\n" |
| 124 | " gain is a genuine selection x irreversibility INTERACTION rather than ratchet flux.\n" |
| 125 | " The sharpest reading is in the last line: with REVERSIBLE roles selection makes DoL\n" |
| 126 | " WORSE (%+.1f%%) — it drives specialists back toward generality, which is Law 2's\n" |
| 127 | " invasion visible directly. With roles irreversible, the same selection HELPS (%+.1f%%).\n" |
| 128 | " Law 2 transfers from rung 1->2 to rung 4->5: an ELECTIVE role is a strategy and gets\n" |
| 129 | " invaded; a STRUCTURAL one cannot be. Rung 5 needs a ratchet, not a bargain.\n", |
| 130 | 100*gain_sel,100*gain_nosel,100.0*(gain_sel-gain_nosel)/gain_sel, |
| 131 | 100*(a[1]-e[1]),100*(b[1]-d[1])); |
| 132 | else if(dol_up && via_ok && !not_flux) |
| 133 | printf(" => IT IS THE RATCHET, NOT SELECTION. Irreversibility raises division of labour, but the\n" |
| 134 | " SAME rise appears with selection switched off (%.1f%% vs %.1f%%): a one-way\n" |
| 135 | " generalist->specialist flux accumulates specialists over %d generations whether or\n" |
| 136 | " not anything is being selected, and more specialists mechanically means more\n" |
| 137 | " compartments happen to hold all %d roles. The knob moved two variables again.\n", |
| 138 | 100*b[1],100*d[1],GEN,F); |
| 139 | else if(!via_ok) |
| 140 | printf(" => THE RATCHET KILLS IT. Irreversibility removes the retreat, and compartments collapse\n" |
| 141 | " to monoculture and lose a function instead. Specialisation without a way back is\n" |
| 142 | " fatal here — the second failure route the design deliberately left open.\n"); |
| 143 | else |
| 144 | printf(" => HONEST NULL. Irreversibility alone does not stabilise division of labour; the\n" |
| 145 | " coordination valley survives removing the retreat, so reversibility was not what\n" |
| 146 | " rung45.c was stalling on.\n"); |
| 147 | return 0; |
| 148 | } |