Overview

Run 005 asks a simple question in plain terms: do the spacings between consecutive Riemann zeros behave like free randomness (Poisson), or like a repulsive process that keeps points apart (GOE/GUE from random-matrix theory)? We use the first 100,000 zeros, unfold spacings to unit mean, and compare the empirical curve against GOE and GUE (Wigner surmises). A Brody fit summarizes the “repulsion strength” with a single number β.

Run 005 — GUE comparison

Dataset
Odlyzko zeros6.gz; first 100,000 ordinates → 99,999 gaps; unfolded mean = 1.0000, σ ≈ 0.42925.
GOE (Wigner)
KS D ≈ 0.07811, p ≈ 0; AD ≈ 1645.87 (poor fit).
GUE (Wigner)
KS D ≈ 0.01557, p ≈ 1.7×10⁻²¹; AD ≈ 60.14 (closer fit).
Brody fit
β̂ ≈ 1.00 → strong level repulsion (non-Poisson).
Figures
ECDF vs GOE/GUE · Q–Q vs GUE · Histogram overlays · Brody profile
Metrics
r005_summary.json

With n≈10⁵, even tiny deviations are “significant”; the relative comparison (GUE ≪ GOE ≪ Poisson) is what matters conceptually.

Takeaways

  • Not Poisson: unfolded spacings show clear avoidance of tiny gaps.
  • Closer to GUE: ECDF/Q–Q align far better with GUE than GOE.
  • Repulsion quantified: Brody fit yields β̂ ≈ 1.00 (strong level repulsion).
  • Interpretation: spacing pattern looks like structured randomness that prevents collisions— consistent with growth that avoids overlaps.
  • Next steps: add long-range tests (Σ²(L), Δ₃(L)) and slide the window along the zeros to check stability.

Figure: ECDF vs GOE/GUE

ECDF of unfolded spacings vs GOE/GUE Wigner CDFs

Figure: Q–Q vs GUE

Q–Q plot vs GUE Wigner quantiles

Figure: Spacing PDF overlays

Histogram with Poisson, GOE, GUE, and Brody(β̂) overlays

Figure: Brody fit profile

Brody log-likelihood vs β (Δℓ)

Prime-driven growth

Spokes sprout ring-by-ring (prime-led build)
Spokes sprout ring-by-ring (prime-led build).
Prime-driven shape coordinates per ring (R=40)
Prime-driven shape coordinates per ring (R=40).

How the growth is controlled

Hex lattice expansion of primes across 60 rings
Accumulation across 60 rings (hex lattice expansion).
Wireframe scaffold and axes reference (R=40)
Wireframe scaffold / axes reference (R=40).

Implied universe shape (from prime-driven growth)

Universe-scale wedge/hex barrel implied by prime-driven growth
Macro geometry implied by prime-driven growth on the hex lattice.

Methods & Parameters

  • Zero set: Odlyzko zeros6.gz, first 100,000 ordinates.
  • Spacings: nearest-neighbor Δγ; unfolded by dividing by raw mean.
  • References: Wigner surmises (GOE/GUE) for nearest-neighbor spacing.
  • Tests: one-sample KS & Anderson–Darling against GOE/GUE CDFs.
  • Repulsion metric: Brody distribution (MLE over β ∈ [0,1]).

Results

  • Unfolded stats: mean = 1.0000, σ ≈ 0.42925 (n = 99,999).
  • KS (GOE, GUE): DGOE ≈ 0.07811; DGUE ≈ 0.01557.
  • AD (GOE, GUE):GOE ≈ 1645.87; A²GUE ≈ 60.14.
  • Brody β̂: ≈ 1.00 (strong repulsion; far from Poisson β=0).

Discussion

Why this test? A guiding idea in this project is that prime-driven structures might grow by placing new “events” in ways that avoid collisions. If positions were chosen with pure, uncorrelated randomness (a Poisson process), very small gaps would be common. By contrast, a repulsive process suppresses tiny gaps and spaces events out—much closer to how a complex system might grow without crashing into itself.

What we measured. We used the spacings between the first 100,000 nontrivial zeros of the Riemann zeta function (a standard proxy tightly linked to the primes). After rescaling to unit average spacing (“unfolding”), we compared the empirical distribution to three references:

  • Poisson (Exp(1)): baseline for uncorrelated randomness.
  • GOE Wigner: a repulsive law from random-matrix theory (orthogonal symmetry).
  • GUE Wigner: a stronger repulsive law (unitary symmetry) often seen in zeta-zero studies.

Main result. The unfolded zero spacings are emphatically not Poisson. They show level repulsion and align much more closely with the GUE prediction than with GOE (see “ECDF vs GOE/GUE” and the Q–Q figure). A simple repulsion score—the Brody fit—comes out at β̂ ≈ 1.00, indicating very strong avoidance of near-collisions.

How this relates to growth without collisions. If one imagines growth as “place the next point where it won’t collide,” then the data suggest the regulating mechanism isn’t free randomness; it’s structured randomness with correlations that actively keep points apart. In other words, the system behaves less like raindrops (Poisson) and more like particles with short-range repulsion. That kind of rule naturally supports branching or fractal-like expansion while avoiding overlaps.

About the lattice comparison. Earlier runs also compared the zero spacings to primes taken from simple quadratic “spokes” (n(r)=3r²−r+c, primes ≤ 10⁶). At that scale the spectral overlap was weak, which means those particular spokes don’t reproduce the zeros’ signature. This doesn’t rule out lattice-based mechanisms—it just says this first attempt isn’t the one.

Limitations & next steps. With very large samples, even tiny deviations produce microscopic p-values, so the relative fit (GUE ≪ GOE ≪ Poisson) is the meaningful comparison. Two natural follow-ups are: (1) add classic long-range diagnostics (number variance Σ²(L), spectral rigidity Δ₃(L)); (2) test whether the fit drifts with height by repeating this analysis in sliding windows along the zero sequence. Either way, the collision-avoidance story points firmly to a repulsive, GUE-like regime rather than Poisson randomness.

Plain-language takeaway: the spacing pattern of the zeta zeros looks like a system that deliberately keeps points from getting too close. That supports “growth without collisions,” but the mechanism is correlated (GUE-like), not free randomness.

Limitations

  • GUE/GUE surmises are approximations; finite-sample corrections and windowing effects can shift tail behavior.
  • Huge n makes tiny deviations “significant”; compare relative fit (GOE vs GUE vs Poisson) more than raw p-values.

Metrics — inline preview

Summary (JSON)

Loading…

Quantiles table (CSV)

Abstract

We test the idea that prime-driven growth avoids collisions by examining nearest-neighbor spacings of the first 100k nontrivial zeros (Odlyzko). After unfolding to mean 1, Poisson (Exp (1)) is strongly rejected by KS/CvM, while Wigner–Dyson surmises (GOE/GUE) match the short-range repulsion; a Brody fit provides a one-number summary of repulsion strength (β̂). Lattice “spoke” primes up to 10⁶ show small samples and weak/negative spectral overlap with zeros. Results support structured (repulsive) randomness rather than i.i.d. randomness; growth animations illustrate the intuition.

Key metrics — auto-filled

Unfolded spacings mean , σ , var
KS vs Poisson (Exp 1) D = , p =
KS vs GOE (Wigner) D = , p =
KS vs GUE (Wigner) D = , p =
Brody fit β̂ = (0 = Poisson, 1 ≈ Wigner-type)
AIC (lower is better) Poisson · GOE · GUE · Brody
Raw spacing mean (pre-unfold)

Source: metrics/r005_gue_tests_summary.json (populated by run005_make_all.py).

Reproducibility — Python (Run 005)

Generates ECDF vs GOE/GUE, Q–Q vs GUE, histogram overlays (Poisson/GOE/GUE/Brody), Brody profile, and r005_* metrics/data.

Environment

python -m venv .venv
source .venv/bin/activate  # Windows: .venv\Scripts\activate
pip install numpy pandas matplotlib

Script

# run005_make_all.py
# Produces: figures/r005_ecdf_vs_wigner.png, r005_qq_gue.png, r005_hist_overlays.png, r005_brody_ll.png
#           metrics/r005_summary.json
#           data/r005_brody_profile.csv, data/r005_quantiles.csv

import gzip, os, json, math, numpy as np, pandas as pd
import matplotlib.pyplot as plt

run_base = "run005"
zeros_path = "zeros6.gz"
fig_dir = os.path.join(run_base, "figures")
met_dir = os.path.join(run_base, "metrics")
data_dir = os.path.join(run_base, "data")
os.makedirs(fig_dir, exist_ok=True); os.makedirs(met_dir, exist_ok=True); os.makedirs(data_dir, exist_ok=True)

# Load zeros and unfold
vals=[]
with gzip.open(zeros_path,"rt",encoding="utf-8",errors="ignore") as f:
    for line in f:
        s=line.strip()
        if not s: continue
        t=s.split()[0]
        try: v=float(t)
        except: continue
        vals.append(v)
        if len(vals)>=120_000: break
z = np.array(vals[:100_000], float)
sp_raw = np.diff(z)
sp = sp_raw / sp_raw.mean()

# GOE/GUE CDFs
def cdf_goe(s): return 1.0 - np.exp(-np.pi * s**2 / 4.0)
a = 4.0/np.pi; C = 32.0/(np.pi**2)
def cdf_gue(s):
    s = np.asarray(s); import math as _m
    erf = np.vectorize(_m.erf); sqrta = np.sqrt(a)
    term1 = np.sqrt(np.pi)*erf(sqrta*s)/(4.0*(a**1.5))
    term2 = s*np.exp(-a*s**2)/(2.0*a)
    return C*(term1-term2)

# KS/AD
def ks_one_sample(x,F):
    xs=np.sort(x); n=xs.size; Fx=F(xs); i=np.arange(1,n+1)
    D=max(np.max(i/n - Fx), np.max(Fx - (i-1)/n))
    t=(np.sqrt(n)+0.12+0.11/np.sqrt(n))*D; p=0.0
    for k in range(1,101): p += (-1)**(k-1)*np.exp(-2*(k*k)*(t*t))
    p=max(0.0, min(1.0, 2*p)); return float(D), float(p)
def anderson_darling(x,F):
    xs=np.sort(x); n=xs.size; Fx=np.clip(F(xs),1e-12,1-1e-12); i=np.arange(1,n+1)
    return float(-n - np.mean((2*i-1)*(np.log(Fx)+np.log(1.0-Fx[::-1]))))

ks_goe_D, ks_goe_p = ks_one_sample(sp, cdf_goe)
ks_gue_D, ks_gue_p = ks_one_sample(sp, cdf_gue)
ad_goe = anderson_darling(sp, cdf_goe)
ad_gue = anderson_darling(sp, cdf_gue)

# Brody fit
from math import gamma
def brody_b(beta): return gamma((beta+2.0)/(beta+1.0))**(beta+1.0)
def pdf_brody(s,beta):
    b=brody_b(beta); return (beta+1.0)*b*(s**beta)*np.exp(-b*(s**(beta+1.0)))
betas=np.linspace(0.0,1.0,101); s_pos=sp[sp>0]
ll=np.array([np.sum(np.log(b+1.0)+np.log(brody_b(b))+b*np.log(s_pos)-brody_b(b)*(s_pos**(b+1.0))) for b in betas])
beta_hat=float(betas[int(np.argmax(ll))])

# Figures
xs=np.sort(sp); ys=np.arange(1,xs.size+1)/xs.size
plt.figure(figsize=(7,4.2),dpi=150)
plt.step(xs,ys,where="post",label="ECDF (unfolded Δγ)")
plt.plot(xs,cdf_goe(xs),label="GOE CDF (Wigner)")
plt.plot(xs,cdf_gue(xs),label="GUE CDF (Wigner)")
plt.xlabel("s"); plt.ylabel("CDF"); plt.title("ECDF vs GOE/GUE — Run 005")
plt.legend(); plt.tight_layout(); plt.savefig(os.path.join(fig_dir,"r005_ecdf_vs_wigner.png")); plt.close()

q=np.linspace(0.001,0.999,2000)
def inv_cdf(F,qvals,smax=6.0):
    out=np.empty_like(qvals)
    for i,qq in enumerate(qvals):
        lo,hi=0.0,smax
        for _ in range(60):
            mid=0.5*(lo+hi)
            if F(mid)

Reproducibility — PowerShell (Run 005)

Windows PowerShell 5+/PowerShell 7+. Uses .NET Charting for PNGs; dependency-free.

# run005_make_all.ps1
# Outputs: figures/r005_*.png, metrics/r005_summary.json, data/r005_*.csv
param(
  [string]$ZerosPath = "zeros6.gz",
  [string]$RunBase = "run005",
  [int]$Take = 100000,
  [int]$ParseCap = 120000
)
$ErrorActionPreference = "Stop"
Add-Type -AssemblyName System.IO.Compression.FileSystem
Add-Type -AssemblyName System.Windows.Forms
Add-Type -AssemblyName System.Windows.Forms.DataVisualization

function Ensure-Dir($p){ $d=[IO.Path]::GetDirectoryName($p); if($d -and -not(Test-Path $d)){ New-Item -ItemType Directory $d | Out-Null } }

function Read-Zeros([string]$gz,[int]$cap){
  $fs=[IO.File]::OpenRead($gz); $gzs=New-Object IO.Compression.GZipStream($fs,[IO.Compression.CompressionMode]::Decompress)
  $sr=New-Object IO.StreamReader($gzs); $vals=New-Object 'System.Collections.Generic.List[double]'
  while(-not $sr.EndOfStream -and $vals.Count -lt $cap){
    $line=$sr.ReadLine().Trim(); if($line.Length -eq 0){ continue }
    $tok=$line.Split((@(' ', "`t")),[StringSplitOptions]::RemoveEmptyEntries)[0]
    [double]$v=0; if([double]::TryParse($tok,[ref]$v)){ $vals.Add($v) }
  }; $sr.Close(); $gzs.Close(); $fs.Close(); ,$vals.ToArray()
}
function Diff([double[]]$a){ $n=$a.Length-1; $d=New-Object double[] $n; for($i=0;$i -lt $n;$i++){ $d[$i]=$a[$i+1]-$a[$i] }; ,$d }
function Unfold([double[]]$s){ $m=($s|Measure-Object -Average).Average; if($m -eq 0 -or [double]::IsNaN($m)){ throw "Bad mean" }
  $u=New-Object double[] $s.Length; for($i=0;$i -lt $s.Length;$i++){ $u[$i]=$s[$i]/$m }; @{ u=$u; m=$m } }

# GOE/GUE
function CDF-GOE([double[]]$s){ ,($s | ForEach-Object { 1.0 - [math]::Exp(-[math]::PI * $_ * $_ / 4.0) }) }
function CDF-GUE([double[]]$s){
  $a=4.0/[math]::PI; $C=32.0/([math]::PI*[math]::PI)
  ,($s | ForEach-Object {
    $sq=[math]::Sqrt($a)*$_
    $term1=[math]::Sqrt([math]::PI)*[math]::Erf($sq)/(4.0*([math]::Pow($a,1.5)))
    $term2=$_*[math]::Exp(-$a*$_*$_)/(2.0*$a)
    $C*($term1-$term2)
  })
}
function KS([double[]]$x,[scriptblock]$F){
  $xs=$x|Sort-Object; $n=$xs.Length; $Fx=& $F $xs
  $Dplus=0.0; $Dminus=0.0
  for($i=1;$i -le $n;$i++){ $Dplus=[math]::Max($Dplus,  $i/$n - $Fx[$i-1]); $Dminus=[math]::Max($Dminus, $Fx[$i-1] - ($i-1)/$n) }
  $D=[math]::Max($Dplus,$Dminus); $t=([math]::Sqrt($n)+0.12+0.11/[math]::Sqrt($n))*$D
  $p=0.0; for($k=1;$k -le 100;$k++){ $p += ((-1.0)**($k-1))*[math]::Exp(-2.0*$k*$k*$t*$t) }; $p=[math]::Max(0.0,[math]::Min(1.0,2*$p))
  return @{D=$D; p=$p}
}
function AD([double[]]$x,[scriptblock]$F){
  $xs=$x|Sort-Object; $n=$xs.Length; $Fx=& $F $xs | ForEach-Object { [math]::Min([math]::Max($_,1e-12),1-1e-12) }
  $A2=-$n; for($i=1;$i -le $n;$i++){ $u=(2*$i-1)/(2.0*$n); $A2+= (($Fx[$i-1]-$u)*($Fx[$i-1]-$u)) } ; return $A2
}

# load data
$vals=Read-Zeros $ZerosPath $ParseCap
if($vals.Length -lt $Take){ throw "Parsed only $($vals.Length)" }
$z=$vals[0..($Take-1)]; $spRaw=Diff $z; $u=Unfold $spRaw; $sp=$u.u

# tests
$ksGoe=KS $sp { param($x) CDF-GOE $x }
$ksGue=KS $sp { param($x) CDF-GUE $x }
$adGoe=AD $sp { param($x) CDF-GOE $x }
$adGue=AD $sp { param($x) CDF-GUE $x }

# plots (ECDF vs GOE/GUE)
$sorted=$sp|Sort-Object; $n=$sorted.Length; $y=1..$n | ForEach-Object { $_/$n }
$FxGoe = CDF-GOE $sorted; $FxGue = CDF-GUE $sorted
$ch=New-Object System.Windows.Forms.DataVisualization.Charting.Chart; $ch.Width=800; $ch.Height=480
$area=New-Object System.Windows.Forms.DataVisualization.Charting.ChartArea; $ch.ChartAreas.Add($area)
$S1=New-Object System.Windows.Forms.DataVisualization.Charting.Series; $S1.ChartType='FastLine'; $S1.BorderWidth=2
for($i=0;$i -lt $n;$i++){ [void]$S1.Points.AddXY([double]$sorted[$i],[double]$y[$i]) }
$S2=New-Object System.Windows.Forms.DataVisualization.Charting.Series; $S2.ChartType='FastLine'; $S2.BorderWidth=2
for($i=0;$i -lt $n;$i++){ [void]$S2.Points.AddXY([double]$sorted[$i],[double]$FxGoe[$i]) }
$S3=New-Object System.Windows.Forms.DataVisualization.Charting.Series; $S3.ChartType='FastLine'; $S3.BorderWidth=2
for($i=0;$i -lt $n;$i'){ [void]$S3.Points.AddXY([double]$sorted[$i],[double]$FxGue[$i]) }
$ch.Series.Add($S1); $ch.Series.Add($S2); $ch.Series.Add($S3)
$ch.Titles.Add("ECDF vs GOE/GUE — Run 005") | Out-Null
Ensure-Dir "$RunBase\figures\r005_ecdf_vs_wigner.png"; $ch.SaveImage("$RunBase\figures\r005_ecdf_vs_wigner.png",'Png')

# metrics JSON
@{
  raw_mean_spacing_first100k = ($spRaw|Measure-Object -Average).Average
  unfolded_mean = ($sp|Measure-Object -Average).Average
  unfolded_std  = [math]::Sqrt((($sp|ForEach-Object { ($_-($sp|Measure-Object -Average).Average)**2 })|Measure-Object -Average).Average)
  ks_goe_D = $ksGoe.D; ks_goe_p = $ksGoe.p
  ks_gue_D = $ksGue.D; ks_gue_p = $ksGue.p
  ad_goe = $adGoe; ad_gue = $adGue
} | ConvertTo-Json | Set-Content -Path "$RunBase\metrics\r005_summary.json" -Encoding UTF8

Write-Host "Run005 complete."

Reproducibility — PHP (CLI, Run 005)

CLI: php run005_make_all.php zeros6.gz. Writes r005_summary.json and a simple inline-SVG report.

<?php
// run005_make_all.php
$zeros = $argv[1] ?? 'zeros6.gz';
$run = 'run005';
@mkdir("$run/metrics",0777,true);
@mkdir("$run/figures",0777,true);
@mkdir("$run/data",0777,true);

// parse first ~120k; use first 100k
$vals=[]; $f=gzopen($zeros,'r') or die("open fail");
while(!gzeof($f) && count($vals)<120000){
  $line=trim(gzgets($f)); if($line==='') continue;
  $tok=preg_split('/\s+/',$line)[0];
  if(is_numeric($tok)) $vals[]=(float)$tok;
} gzclose($f);
if(count($vals)<100000) die("parsed ".count($vals));

$z=array_slice($vals,0,100000); $spRaw=[];
for($i=0;$i<count($z)-1;$i++){ $spRaw[]=$z[$i+1]-$z[$i]; }
$meanRaw=array_sum($spRaw)/count($spRaw);
$sp=array_map(fn($v)=>$v/$meanRaw,$spRaw);

// GOE/GUE CDFs
function cdf_goe($s){ return 1.0 - exp(-M_PI*$s*$s/4.0); }
function cdf_gue($s){
  $a=4.0/M_PI; $C=32.0/(M_PI*M_PI);
  $term1 = sqrt(M_PI)*erf(sqrt($a)*$s)/(4.0*pow($a,1.5));
  $term2 = $s*exp(-$a*$s*$s)/(2.0*$a);
  return $C*($term1-$term2);
}
function ks($x,$F){
  sort($x); $n=count($x); $Dplus=0.0; $Dminus=0.0;
  for($i=1;$i<=$n;$i++){
    $Fx = $F($x[$i-1]);
    $Dplus=max($Dplus,$i/$n - $Fx);
    $Dminus=max($Dminus,$Fx - ($i-1)/$n);
  }
  $D=max($Dplus,$Dminus);
  $t=(sqrt($n)+0.12+0.11/sqrt($n))*$D; $p=0.0;
  for($k=1;$k<=100;$k++) $p += ((-1)**($k-1))*exp(-2*$k*$k*$t*$t);
  $p=max(0.0,min(1.0,2.0*$p)); return [$D,$p];
}
function ad($x,$F){
  sort($x); $n=count($x); $A2=-$n;
  for($i=1;$i<=$n;$i++){
    $Fx=max(min($F($x[$i-1]),1-1e-12),1e-12);
    $u=(2*$i-1)/(2.0*$n); $A2 += ($Fx-$u)*($Fx-$u);
  } return $A2;
}

[$ksGoeD,$ksGoeP]=ks($sp,'cdf_goe');
[$ksGueD,$ksGueP]=ks($sp,'cdf_gue');
$adGoe=ad($sp,'cdf_goe'); $adGue=ad($sp,'cdf_gue');

file_put_contents("$run/metrics/r005_summary.json", json_encode([
  "raw_mean_spacing_first100k"=>$meanRaw,
  "unfolded_mean"=>array_sum($sp)/count($sp),
  "ks_goe_D"=>$ksGoeD,"ks_goe_p"=>$ksGoeP,
  "ks_gue_D"=>$ksGueD,"ks_gue_p"=>$ksGueP,
  "ad_goe"=>$adGoe,"ad_gue"=>$adGue
], JSON_PRETTY_PRINT));

echo "Run005 complete\n";

Reproducibility — C# (.NET 6 + ScottPlot, Run 005)

Setup

dotnet new console -o Run005Cs
cd Run005Cs
dotnet add package ScottPlot --version 5.0.19

Program.cs

using System.IO.Compression;
using ScottPlot;

string zerosPath = args.Length > 0 ? args[0] : "zeros6.gz";
string runBase = "run005";
Directory.CreateDirectory(Path.Combine(runBase,"metrics"));
Directory.CreateDirectory(Path.Combine(runBase,"figures"));
Directory.CreateDirectory(Path.Combine(runBase,"data"));

// Parse zeros
List<double> vals = new();
using (var fs = File.OpenRead(zerosPath))
using (var gz = new GZipStream(fs, CompressionMode.Decompress))
using (var sr = new StreamReader(gz))
{
    string? line;
    while ((line = sr.ReadLine()) != null && vals.Count < 120_000)
    {
        line = line.Trim();
        if (line.Length == 0) continue;
        var tok = line.Split((char[])null, StringSplitOptions.RemoveEmptyEntries)[0];
        if (double.TryParse(tok, out double v)) vals.Add(v);
    }
}
if (vals.Count < 100_000) throw new Exception($"Parsed only {vals.Count} zeros");
double[] z = vals.Take(100_000).ToArray();
double[] spRaw = z.Skip(1).Zip(z, (a,b) => a-b).Select(x=>(double)x).ToArray();
double meanRaw = spRaw.Average();
double[] sp = spRaw.Select(v => v/meanRaw).ToArray();

// GOE/GUE CDFs
double[] CdfGoe(double[] s) => s.Select(v => 1.0 - Math.Exp(-Math.PI*v*v/4.0)).ToArray();
double[] CdfGue(double[] s) {
    double a = 4.0/Math.PI, C = 32.0/(Math.PI*Math.PI);
    return s.Select(v => {
        double term1 = Math.Sqrt(Math.PI)*Erf(Math.Sqrt(a)*v)/(4.0*Math.Pow(a,1.5));
        double term2 = v*Math.Exp(-a*v*v)/(2.0*a);
        return C*(term1-term2);
    }).ToArray();
}
static double Erf(double x){ // Abramowitz-Stegun approximation
    // For simplicity in sample code; use MathNet.Numerics for production
    double sign = Math.Sign(x); x = Math.Abs(x);
    double a1=0.254829592,a2=-0.284496736,a3=1.421413741,a4=-1.453152027,a5=1.061405429,p=0.3275911;
    double t=1.0/(1.0+p*x);
    double y=1.0-((((a5*t+a4)*t+a3)*t+a2)*t+a1)*t*Math.Exp(-x*x);
    return sign*y;
}

// KS
(double D, double P) Ks(double[] x, Func<double[],double[]> F){
    double[] xs = x.OrderBy(v=>v).ToArray(); int n=xs.Length;
    double[] Fx = F(xs);
    double Dplus=0, Dminus=0;
    for(int i=1;i<=n;i++){
        Dplus  = Math.Max(Dplus,  i/(double)n - Fx[i-1]);
        Dminus = Math.Max(Dminus, Fx[i-1] - (i-1)/(double)n);
    }
    double D = Math.Max(Dplus,Dminus);
    double t = (Math.Sqrt(n)+0.12+0.11/Math.Sqrt(n))*D;
    double p=0; for(int k=1;k<=100;k++) p += Math.Pow(-1,k-1)*Math.Exp(-2*k*k*t*t);
    p = Math.Max(0, Math.Min(1, 2*p));
    return (D,p);
}
var (ksGoeD, ksGoeP) = Ks(sp, CdfGoe);
var (ksGueD, ksGueP) = Ks(sp, CdfGue);

// ECDF vs GOE/GUE
{
    double[] xs = sp.OrderBy(v=>v).ToArray();
    double[] ys = Enumerable.Range(1, xs.Length).Select(i => i/(double)xs.Length).ToArray();
    double[] FxGoe = CdfGoe(xs);
    double[] FxGue = CdfGue(xs);
    var plt = new ScottPlot.Plot(800,480);
    plt.Add.SignalXY(xs, ys);
    plt.Add.SignalXY(xs, FxGoe);
    plt.Add.SignalXY(xs, FxGue);
    plt.Title("ECDF vs GOE/GUE — Run 005");
    plt.XLabel("s"); plt.YLabel("CDF");
    plt.SavePng(Path.Combine(runBase,"figures","r005_ecdf_vs_wigner.png"),800,480);
}

// Metrics JSON
File.WriteAllText(Path.Combine(runBase,"metrics","r005_summary.json"),
    System.Text.Json.JsonSerializer.Serialize(new {
        raw_mean_spacing_first100k = meanRaw,
        unfolded_mean = sp.Average(),
        ks_goe_D = ksGoeD, ks_goe_p = ksGoeP,
        ks_gue_D = ksGueD, ks_gue_p = ksGueP
    }, new System.Text.Json.JsonSerializerOptions{ WriteIndented=true })
);

Console.WriteLine("Run005 complete.");

Run

dotnet run -- zeros6.gz