Adjudication · re-run on one machine · code & data

Does the graph neural network beat greedy?

A paper, two formal Comments and two Replies in Nature Machine Intelligence, and nobody moving. So I re-ran it: same host, same graphs, same seeds, both sides implemented from the published papers and code — the Reply’s GraphSAGE variant reconstructed from its text alone — with an answer key.

Corrections: this study has withdrawn two of its own claims — that the Replies’ objection had been “answered on its own terms” (D36, below), and a claimed disagreement with Krutský et al. that compared against their MaxCut rather than their MIS results (D38) — and records its numerical errata in D38. All are in DECISIONS.md.

THE CLAIM

What is actually on trial

“…the graph neural network optimizer performs on par or outperforms existing solvers, with the ability to scale beyond the state of the art to problems with millions of variables.” Schuetz, Brubaker & Katzgraber, Nature Machine Intelligence 4, 367 (2022)
“…a simple greedy algorithm, running in almost linear time, can find solutions for the MIS problem of much better quality than the GNN. The greedy algorithm is faster by a factor of 104…” Angelini & Ricci-Tersenghi, arXiv:2206.13211 (abstract); published as Nature Machine Intelligence 5, 29 (2023), whose own abstract words this differently

The measured object is the validity of a published sentence against an exact objective, not which model is better. That sentence contains two claims, and they are scored separately because they can have different answers.

Claim A · parity with existing solvers

Refuted

A degree-based greedy finds larger independent sets at every degree and every size tested, and at n = 10,000 does it about 204,208× faster on the same machine.

Claim B · scale to millions of variables

Upheld only after repair

Reachable — but not by the released example implementation, which builds a dense n×n matrix (3.6 TiB at n = 106); the paper does not describe its construction at that scale. With a mathematically identical sparse reformulation, a full run still projects to 6.4–8.1 days against greedy's 0.267 s.

Undisclosed · operating range

Fails above d ≈ 7

Past degree seven the network’s raw output is empty in every run (n = 1000, the released implementation’s configuration; a pre-registered re-run at the paper’s smaller embedding finds the same, 0/15 at d = 8, 12, 20, D39). The paper's own experiments sit at degrees three and five.


EVIDENCE

The head-to-head

Uniform random d-regular graphs. Quality is the approximation ratio against McKay's upper bound — the same metric the critique used, so these numbers sit directly beside the published ones. Every set below was verified independent and maximal by a separate checker.

“As published” and “published configuration” on this page mean the authors’ released example implementation’s setting, which uses an embedding of int(sqrt(n)) at every n and a patience of 100; the paper’s text uses int(cbrt(n)) below n = 105 (10 rather than 31 at n = 1000) and a patience of 103. A pre-registered re-run at that smaller embedding (D39) reproduces the ceiling — 0/15 non-empty raw outputs at d = 8, 12, 20, while d = 3 and 5 still escape in 10/10 runs. Every other result on this page uses the released setting.

nmethoddensityAR wall clocktrials
d = 3  upper bound ρUB = 0.45537 · 1RSB optimum AR = 0.99
1,000DGA (degree-based greedy)0.432600.950050 µs5
PI-GNN, as published0.418200.918413.745
null control: all-ones0.396600.8709103 µs5
GA (random greedy)0.378800.831953 µs5
10,000DGA (degree-based greedy)0.432700.9502579 µs5
PI-GNN, as published0.416120.9138118.35
null control: all-ones0.393860.8649473 µs5
GA (random greedy)0.374420.8222527 µs5
100,000DGA (degree-based greedy)0.432540.94990.006643
PI-GNN, as published0.415160.91172,1761
null control: all-ones0.395880.86940.0045633
GA (random greedy)0.375470.82450.0060743
d = 5  upper bound ρUB = 0.38443 · 1RSB optimum AR = 0.987
1,000DGA (degree-based greedy)0.353000.918277 µs5
PI-GNN, as published0.340000.884420.715
null control: all-ones0.328000.8532130 µs5
GA (random greedy)0.299000.777873 µs5
10,000DGA (degree-based greedy)0.356720.9279798 µs5
PI-GNN, as published0.341440.8882191.25
null control: all-ones0.326700.8498718 µs5
GA (random greedy)0.301900.7853727 µs5
100,000DGA (degree-based greedy)0.356650.92770.0090423
null control: all-ones0.326890.85030.0073533
GA (random greedy)0.301520.78430.0087723
d = 20  no published bound adopted; density reported
1,000DGA (degree-based greedy)0.17000—195 µs5
PI-GNN, as published0.13540—20.655
null control: all-ones0.15720—268 µs5
GA (random greedy)0.13820—186 µs5
10,000DGA (degree-based greedy)0.17324—0.0021035
PI-GNN, as published0.13956—272.75
null control: all-ones0.15880—0.002345
GA (random greedy)0.13956—0.002025
100,000DGA (degree-based greedy)0.17371—0.029873
null control: all-ones0.15873—0.030153
GA (random greedy)0.13948—0.028883

The replication holds: at d = 3, n = 10,000 (5 seeds each) the greedy lands at AR 0.9502 against the critique's published ≈0.95, and PI-GNN at 0.9138 against their ≈0.92. Both sides of their figure reproduce on independently written code.

0.800.850.900.95d = 3n = 10,0001RSB optimumDGA (greedy): AR 0.95020.950 DGA (greedy)PI-GNN (released config.): AR 0.91380.914 PI-GNN (released config.)null control: AR 0.86490.865 null controlrandom greedy: AR 0.82220.822 random greedyd = 5n = 10,000DGA (greedy): AR 0.92790.928 DGA (greedy)PI-GNN (released config.): AR 0.88820.888 PI-GNN (released config.)null control: AR 0.84980.850 null controlrandom greedy: AR 0.78530.785 random greedyapproximation ratio (independent-set density / McKay upper bound)
Approximation ratio by method at the largest size where PI-GNN has its full seed set (n on each row; the single n = 105 PI-GNN run is not plotted). The dashed line is the 1RSB replica-theory optimum — what a very good algorithm should approach.
CONTROL

The measurement nobody in the exchange ran

None of the five documents in the exchange — the original paper, two Comments and two Replies — separates what the network contributes from what the post-processing contributes. Both are needed to produce a reported score, and only one of them is the thing under test.

The separation is available from data already collected: compare the raw thresholded output against the score after repair and maximalization.

methoddn raw outputafter post-processing share added
PI-GNN31,000417418+0.3%
PI-GNN3100,00041,32141,516+0.5%
PI-GNN51,000332340+2.2%
PI-GNN201,0000135+100.0%
null: all-ones31,0001,000397—
null: all-ones3100,000100,00039,588—
null: all-ones51,0001,000328—
null: all-ones5100,000100,00032,689—
null: all-ones201,0001,000157—
null: all-ones20100,000100,00015,873—
Where the network works, the post-processing adds almost nothing — one node in 418 at d = 3. Where the network fails, the post-processing supplies the entire score: at d = 20 the raw output is empty, so the number reported as “PI-GNN” is produced entirely by a first-fit greedy pass over the node order.

This is the sharper form of the comparison, and it forecloses the obvious objection. A reader might suspect the repair-and-maximalize step flatters the network; the decomposition shows it contributes 0.3–2.2% where the network is working, so the measured gap to greedy is the network’s own.

A second control puts an all-ones bitstring — every node selected, carrying no information about the instance — through that same pipeline. It reaches AR 0.8649 at d = 3, n = 10,000 against the trained network’s 0.9138, and at d = 20, n = 10,000 it wins outright (0.15880 against 0.13956). One caveat stated plainly: on an all-ones input every node has the same conflict degree, so the repair pass reduces to iterated maximum-degree deletion — a real heuristic reading the graph. The bitstring carries no information; the pipeline does. That is why the decomposition above, not this control, is the primary evidence.

A trained network scoring below the all-ones null control is not a hyperparameter problem: the next section gives the mechanism, and the one after it tests a pre-registered tuning budget, a post-hoc round, and the GraphSAGE + P = 10 configuration the Reply describes (as implemented here). At d = 20 none of them returns a non-empty raw output (0 of 57 runs).


MECHANISM

Why it fails at high degree: symmetry never breaks

Above degree seven the network's raw output — before any post-processing — is all zeros. My first explanation was wrong, and the way it was wrong is the finding.

I thought the culprit was the diagonal. The published loss is L(p) = pᵀQp with Qᵢᵢ = −1, a pure quadratic form with no linear term, so ∇L(0) = 0 exactly: the empty set is a critical point at every degree. Fixing that should rescue it. It does not. Changing the diagonal to −Σpᵢ removes the critical point, and d = 20 still returns nothing.

The real mechanism is visible when you compare the final training loss against the exact uniform optimum of the modified, linear-diagonal objective (a post-hoc change, not the published loss) — the value a network that learned nothing at all would reach:

armduniform p*closed-form L* (mean field) observed final lossoutcome
modified objective, lr 1e-430.1667-83.333-423.254symmetry-broken
modified objective, lr 1e-450.1000-50.000-341.256symmetry-broken
modified objective, lr 1e-4200.0250-12.500-12.499stuck at the mean field
modified objective, lr 1e-330.1667-83.333-421.949symmetry-broken
modified objective, lr 1e-350.1000-50.000-342.936symmetry-broken
modified objective, lr 1e-3200.0250-12.500-12.519stuck at the mean field
Under the modified objective at d = 20, the closed-form mean-field loss is −12.500 and the measurement is −12.499. Measured directly, the output hovers near the uniform value p* = 0.025 with 27–29% scatter and stays more than 6× below the 0.5 threshold. The network is trapped at the mean-field saddle — fluctuations form, but symmetry never breaks.

A caution this page’s first draft got wrong: matching L* does not by itself prove the configuration is uniform. The loss is second-order blind to zero-mean scatter around the stationary point, and two of the three seeds land slightly below L*, which no uniform configuration can do. The trapped-at-the-saddle claim therefore rests on measuring the output vector directly (mean p = 0.0261, standard deviation 27–29% of p*, maximum 0.074 — recorded in results/pstats.jsonl), not on the loss agreement alone.

For the published quadratic diagonal, that uniform solution happens to be p = 0. So “collapses to the empty set” and “stuck at the mean field” are the same event — the formulation simply makes the symmetric solution be nothing, and the fixed 0.5 threshold then reports it as nothing. This also explains why the rescue attempts tried here failed: learning rate, patience and a larger embedding (256) do not cause symmetry breaking. Neither does a smaller one: at the paper’s int(∛n) = 10 the raw output is empty in 15/15 runs at d = 8, 12, 20 (D39).

0%25%50%75%100%357912162025d > 16: where the critics saidthe problem becomes hardd=3: 5/5 non-empty; Wilson 95% CI [0.57, 1.00]5/5d=4: 10/10 non-empty; Wilson 95% CI [0.72, 1.00]10/10d=5: 5/5 non-empty; Wilson 95% CI [0.57, 1.00]5/5d=6: 9/10 non-empty; Wilson 95% CI [0.60, 0.98]9/10d=7: 2/10 non-empty; Wilson 95% CI [0.06, 0.51]2/10d=8: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=9: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=10: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=11: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=12: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=13: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=14: 0/3 non-empty; Wilson 95% CI [0.00, 0.56]0/3d=15: 0/10 non-empty; Wilson 95% CI [0.00, 0.28]0/10d=16: 0/3 non-empty; Wilson 95% CI [0.00, 0.56]0/3d=18: 0/3 non-empty; Wilson 95% CI [0.00, 0.56]0/3d=20: 0/5 non-empty; Wilson 95% CI [0.00, 0.43]0/5d=25: 0/3 non-empty; Wilson 95% CI [0.00, 0.56]0/3the method stops working heredegree d of the random regular graphruns with a non-empty raw output
Fraction of runs whose raw network output is non-empty, n = 1000, the released implementation’s configuration (127 runs, one per degree and seed); bars are Wilson 95% intervals, and the annotation under each point is escapes/runs. Pooled across every degree d ≥ 8 the result is 0/87 runs produced a non-empty raw output (Wilson 95% CI [0.000, 0.042]) — no single degree row carries that claim on its own, and the pooled interval does. At the paper’s smaller int(cbrt(n)) = 10 embedding a pre-registered re-run finds the same ceiling: 0/15 non-empty at d = 8, 12, 20 (Wilson 95% CI [0.000, 0.204]), with 10/10 escaping at d = 3 and 5 (D39). The transition sits below the clustering transition at d > 16 that the critique proposed as the genuinely hard regime, so the method stops working well before the problem becomes hard.
DEFENCE

Testing the defence the authors actually published

A tuning budget was fixed in writing before any sweep ran — four learning rates × two penalty values × three seeds, at d = 3, 5 and 20 — followed by a separately-labelled post-hoc round with a bigger embedding, longer patience and the repaired objective.

That budget was aimed at the wrong target, and this page previously said otherwise. It was designed against the assumption that the Replies argue the network was under-tuned. Reading the Reply to Angelini (arXiv:2302.03602) shows the defence is two specific changes: “by simply setting P = 10 we find even further consistent improvements. Specifically, for d = 3 at P = 10 GraphSAGE achieves approximation ratios of AR ~ 0.947, on par with the DGA-based results…” — a different architecture and a penalty five times the published one (P = 10 against 2), well above the pre-registered grid, which stopped at P = 3 and never implemented GraphSAGE. The claim that the objection had been “answered on its own terms” is withdrawn.

dDGAPI-GNN published best pre-registeredbest post-hoc null controlnon-empty runs
30.94790.92230.9231lr 0.0001, penalty 30.9296modified_linear0.870424/24post-hoc 21/21
50.91480.88180.8870lr 0.0001, penalty 30.8931modified_linear_lr1e-30.850624/24post-hoc 21/21
200.169670.135000.13500lr 0.0001, penalty 20.13500posthoc_lr1e-30.157670/24post-hoc 0/21

On the same seeds 0–2 (every column above), the pre-registered grid buys at most 0.5 percentage points of AR, and the post-hoc round at most 1.1 — its best arms are the modified objective, a change to the loss rather than a tuned setting — and neither approaches the greedy. (The d = 20 column reports densities, as no published bound is adopted there.) At d = 20 every one of the 24 pre-registered runs and every one of the 21 post-hoc runs had an empty raw output: 0 non-empty results out of 45.

The Reply’s own configuration, run as described

GraphSAGE with a mean aggregator was implemented here (the Reply gives no code for it) and crossed with the penalty, giving the four arms below at n = 1000, seeds 0–2, with DGA on the same seeds.

configurationAR / density non-emptymean raw output
d = 3  approximation ratio
GCN, P = 2  (as published)0.92233/3419
GCN, P = 100.92893/3418
GraphSAGE, P = 20.85133/3388
GraphSAGE, P = 10  (the Reply’s configuration)0.87773/3400
DGA (greedy)  (same seeds 0–2)0.9479——
d = 5  approximation ratio
GCN, P = 2  (as published)0.88183/3333
GCN, P = 100.80731/3104
GraphSAGE, P = 20.81513/3313
GraphSAGE, P = 10  (the Reply’s configuration)0.82553/3217
DGA (greedy)  (same seeds 0–2)0.9148——
d = 20  density; no published bound adopted
GCN, P = 2  (as published)0.13500/30
GCN, P = 100.13500/30
GraphSAGE, P = 20.13500/30
GraphSAGE, P = 10  (the Reply’s configuration)0.13500/30
DGA (greedy)  (same seeds 0–2)0.1697——
The defence does not reach the high-degree failure at all: in every one of the 4 configurations — including GraphSAGE with P = 10, as the Reply specifies it — the raw output is empty at d = 20, 12 of 12 runs.

At d = 3, P = 10 moves the GCN slightly in the direction the Reply claims (0.9223 to 0.9289) and still falls short of greedy’s 0.9479 on the same seeds. At d = 5 it makes matters markedly worse (0.8818 to 0.8073, with a non-empty raw output in only 1/3 runs), which the Reply does not mention. These arms were run at d = 3, 5 and 20 only.

One result here is a failure to reproduce, and is reported as nothing more. The Reply states GraphSAGE at P = 10 reaches AR ~ 0.947 for d = 3. This implementation reaches 0.8777 — below its own GCN. The likeliest explanation is that this GraphSAGE is not equivalent to theirs: the Reply gives no architecture detail and no code for it. That is a gap in what can be checked from the published record, not evidence against their number.

The Reply also answers the high-degree benchmark on relevance rather than capability: it calls the d > 16 instances “an interesting academic exercise” but is “not convinced about its practical usefulness”, because real problems are not random regular graphs. That is a different objection from the one measured here. This page concerns random d-regular graphs only, where the ceiling sits at d ≈ 7 (raw output empty in every run from d = 8), below the regime the Reply calls academic; it says nothing about structured real-world graphs.


TIME

The speed gap, actually measured

The published 104 figure was never a controlled measurement. The critique timed its greedy on the authors' own laptop and read the network's times off Figure 5 of the paper it was criticising — different hardware, different conditions. Everything below is one machine, one job at a time, nothing concurrent.

100 µs1 ms10 ms100 ms1 s10 s100 s17 min1,00010,000100,000PI-GNN n=1000: 13.74sPI-GNN n=10000: 118.3sPI-GNN n=100000: 2176sPI-GNNDGA (greedy) n=1000: 4.98e-05sDGA (greedy) n=10000: 0.0005792sDGA (greedy) n=100000: 0.00664sDGA (greedy)275,927×204,208×327,659×problem size n (d = 3)wall clock, this host
Wall clock at d = 3, Apple M1 Pro. Graph loading is excluded for both methods; data-structure construction is included for both. The GNN figure covers build, training and post-processing — this study’s own repair pass, not the updated post-processing the Reply reports (see Claim B).
SCALE

Claim B: a million variables

The reference implementation builds a dense torch.zeros(n, n) QUBO matrix. At n = 1,000,000 that is 1.0e+12 float32 entries — 3.6 TiB. The full paper never describes the QUBO construction used at that scale — the word “dense” does not appear in it — so this is a property of the released example implementation, not of a published method description. The sparse reformulation used here is mathematically identical and is what makes any test at this scale possible — a deliberate improvement in the claim’s favour.

nms / epoch× per decadelocal exponentembedding
1,0001.10——31×15
10,0007.987.3×0.86100×50
100,000163.2220.4×1.31316×158
1,000,00047,387.71290.3×2.461000×500

The paper sets dim_embedding = int(sqrt(n)) only for n ≥ 105, and int(cbrt(n)) below that; the released notebook, and this study, use int(sqrt(n)) at every n (the embedding column above). From n = 105 up the two agree: dim_embedding = int(sqrt(n)), hidden = dim/2 makes the first layer a dense (n × dim) @ (dim × hidden) matmul, so arithmetic grows as n²/2 regardless of how sparse the graph is. Measured time does not track that cleanly: it grew more slowly than that arithmetic below n = 105, and about 3× faster than it in the last decade, where the 14.9 GiB working set goes memory-bound.

Measured, not projected: after 1,503 s of training at n = 106 — 36 epochs, 0.27% of the schedule this model needs — PI-GNN’s output repairs to 0.37470. The all-ones null control sits at 0.39527, and DGA reached 0.43295 in 0.2671 seconds.

That run was stopped by its wall-clock budget, not by convergence, so it is a lower bound on time-to-parity and nothing more. It does not show the network would never converge. Its raw output fell from 137,531 nodes (epoch 3) to 220 (epoch 36) with the loss still positive, which resembles the high-degree collapse. A traced n = 104 run (one seed) shows the same dip, to 8 nodes at epoch 450, then recovers to 4,097 by epoch 3,000; the n = 106 run stopped far earlier in its schedule than that minimum, so the decay is consistent with the normal early transient (D34). The decay rate depends strongly on n (raw-size ratio 0.95 / 0.56 / 0.0016 over the first 36 epochs at n = 103 / 104 / 106), and that is not explained here.

Projection, and labelled as one. Epochs-to-convergence is stable across three decades (12,361 / 14,812 / 13,321; the n = 105 figure is a single run), so the epoch count transfers. Using the counts measured at the two sizes nearest 106 (13,321 at n = 105, 14,812 at n = 104) and the two measured per-epoch costs at n = 106 (41.75 s from the budgeted run, 47.39 s from the probe), a full run at the released implementation’s configuration would take 6.4–8.1 days, against DGA’s 0.2671 s on the same instance — a factor of roughly 2.1–2.6 million×. The critique claimed 104; this same-host figure is ~208–263× larger, because their 104 compared their own laptop’s greedy against GPU timings read off Figure 5 of the paper they were criticising. Every GNN cost at n = 106 is CPU-only: the faster-device rule (D12) was never executed at this size, an unexecuted protocol step (D33).

The Reply to Angelini reports an updated post-processing (its scaling improves from ~n2.0 to ~n1.0, total run time from ~n1.7 to ~n0.8). The timings on this page are of the original released implementation, followed by this study’s own repair pass (code/repair.c); the updated routine is not tested here. The projection above counts training epochs only.

CALIBRATION

Is the yardstick sound?

The approximation ratios above are measured against McKay's upper bound, which is not tight. To check the scale is sane I solved small instances to proven optimality with an integer program.

dnexact density / ρUBstatus
31000.440000.9662proven optimal
32000.447000.9816proven optimal
51000.370000.9625proven optimal
52000.372000.9677time limit — lower bound only
201000.18000—proven optimal
202000.18000—time limit — lower bound only

Proven optima sit at 0.96–0.98 of the bound at these sizes, consistent with the 1RSB asymptotic value of 0.990. Runs that hit the time limit are reported as lower bounds, not optima.


HONESTY

What would overturn this

Stated plainly, because a verdict that cannot be attacked is not worth much:

  • The port. The original code is DGL 0.5.3 / torch 1.7.1 and will not run on current hardware, so I ported it to plain PyTorch. The repository’s test checks the port’s forward math against the reference’s, re-derived inline — the loss to machine precision against the true asymmetric Q, the convolution to float32 epsilon — and an adversarial audit found it kills the natural mutations of that math except a row-normalisation mutant, because its normalisation check is vacuous on d-regular graphs (D26), and the GraphSAGE layer is untested, because no reference implementation of it exists. The training loop is outside that proof: its fidelity rests on line-by-line replication of the reference’s bookkeeping and on six fresh reruns that matched the recorded results (D26); training is not bit-deterministic, so re-executing a seeded cell can shift its final loss and epoch count slightly (D38). One reporting deviation exists and favours the network: the port keeps the larger of the best and final bitstrings, where the reference keeps best only.
  • The threshold’s location is measured, not derived. The mean-field analysis says where a non-breaking network lands; it does not say why symmetry breaking fails only above d ≈ 6–7 — the uniform point is a strict saddle at every degree.
  • Charity where it counts. Every difference from the reference favours the network: a sparse loss it does not have, and a repair-and-maximalize step it does not perform. The greedy gets none of that. One protocol choice cuts slightly the other way: in the device benchmark at n = 105 the GPU (MPS) was 7% faster than the CPU at d = 3 and 1% slower at d = 5, and every GNN run used the CPU (D14); the check was never repeated at n = 106, an unexecuted protocol step.
  • The mean-field result is a closed form, checked against measurement, not a fit. −12.500 is L* = −n/(2Pd) under the modified objective, derived after that arm had run (D17) and compared with the measured −12.499. Nothing in it is fitted to the data, which makes it the easiest claim here to check.
  • Cells deliberately not run. PI-GNN at d = 5 and d = 20 for n = 105, and at d = 5 for n = 106, were cut on wall-clock grounds after the smaller sizes had already settled those cases — 2.4 hours of machine time for results predictable to three digits, or for a collapse already 10/10 consistent with a closed-form explanation. Their baselines were measured. Saying so beats leaving the gap implicit.
  • Prior art. Much of this is anticipated. The null-control idea is Böther et al., ICLR 2022 (arXiv:2201.10494), for a different method; both failure modes are named by Ichikawa, NeurIPS 2024 (arXiv:2309.16965); the density transition is Krutský et al., ECAI 2025 (arXiv:2507.13703). Their MIS tables put the baseline at zero at d = 10, as here (0/10 non-empty); an earlier version of this repository claimed a disagreement at d = 10 by comparing with their MaxCut result, and that claim is withdrawn (D38). What this study adds on that axis is resolution: its degree grid places the MIS transition at d ≈ 6–7, between their grid points 5 and 10.
  • What I did not test. MaxCut — Boettcher's concurrent critique targets that, and this study is MIS only. Nor does any of this show that graph neural networks cannot do combinatorial optimisation. It shows that this relaxation, with this projection, does not.
METHOD

Reproduce it

559 measurement records on one Apple M1 Pro (10 core, 32 GB). Greedy baselines are C with a bucket queue, so the timing comparison is not distorted by a slow baseline. Instances are uniform random d-regular graphs, verified simple and exactly regular at generation. Seeds control the graph, the network initialisation and the tie-break together.

# Python 3.12
pip install -r requirements.txt
cc -O3 -march=native -o code/greedy code/greedy.c     # drop -march=native if your compiler rejects it
cc -O3 -march=native -o code/repair code/repair.c
python code/test_port.py            # run first — forward-math port-equivalence check; needs nothing else
python code/analyze.py              # summary tables from the committed results
python code/build_report.py && python code/make_site.py      # regenerate report.html and site/
python code/fetch_refs.py           # optional: the papers + the reference implementation into refs/
# To re-measure rather than re-read, write to a NEW file — drivers skip cells already in --out:
python code/runner.py --out results/rerun_phase1.jsonl --d 3 5 20 --n 1000 10000 --seeds 0 1 2 3 4
# collapse_sweep.py, escape_prob.py, escape_d10plus.py and reply_defence.py take no --out: each
# appends to a fixed file in results/ and skips cells already there, so move that file aside first.
# mv -n never overwrites an earlier move-aside; the published files are tracked by git, so
# `git checkout -- results/` restores them.
mkdir -p results.published
mv -n results/collapse.jsonl results/escape.jsonl results.published/
python code/collapse_sweep.py && python code/escape_prob.py && python code/escape_d10plus.py   # escape table: 121 of its 127 runs (the other 6 are phase-1 runs, re-measured by runner.py above)
mv -n results/reply_defence.jsonl results.published/ && python code/reply_defence.py             # the Reply’s defence, GraphSAGE + P=10 (D37)

Code, the raw results/*.jsonl and the decision log are at github.com/ryanonline1234/gnn-vs-greedy; its README carries the same commands.