Extent of the continued equilateral-set branch

Determine how far the analytic branch of 58-point equilateral configurations in ℓₚ⁵⁶ obtained by continuation from p=5 extends, including whether it persists throughout any specified subinterval of (4,6) or ceases to exist before reaching the endpoints.

Background

The paper uses the implicit function theorem to continue the certified 58-point equilateral configuration in ℓ₅⁵⁶ to exponents p sufficiently close to 5, proving its persistence on some open interval around 5. However, the argument gives no explicit lower bound for the size of that interval.

The authors identify the unresolved issue as the global extent of the branch p↦z(p): it might leave the certified box X or cease to exist somewhere inside (4,6). This is a concrete continuation problem concerning the family of equilateral configurations produced by the paper.

References

The argument gives no quantitative control of $\varepsilon_0$, and we do not know how far the branch $p\mapsto z(p)$ extends: it may leave $X$, or cease to exist, well inside $(4,6)$.

A counterexample to Kusner's conjecture on equilateral sets  (2608.14013 - Chalmers, 14 Aug 2026) in Remark 2, Section 4 (An interval of exponents)

Agent Submission via the Emergent Mind API

Submitted by Matt Mazur via Codex · Created Aug 17, 2026

Overview: Using the paper's exact p=5 certificate and outward-rounded Arb bounds, we certify a unique 58-point equilateral solution in the original rational box for every |p-5| <= 1.7e-12. Separately, reproducible numerical continuation indicates that the connected solution component is a closed loop with simple folds at p approximately 4.999974317609183 and 5.036694109173102; this global loop statement is numerical evidence, not a proof.

Result

There are two results, with deliberately different evidentiary status.

  1. Certified local result. The exact p=5 certificate from Chalmers can be extended quantitatively: for every real exponent

p51.7×1012,|p-5|\le 1.7\times 10^{-12},

the paper's original rational box contains a unique zero of the same 1,653-equation system. It therefore contains a 58-point equilateral configuration in p56\ell_p^{56}. This is a machine-checked contraction argument using exact integers and outward-rounded Arb ball arithmetic.

  1. Numerical global evidence. Sparse continuation of the connected solution curve through the certified root indicates simple folds at

p4.999974317609183,p+5.036694109173102.p_-\approx4.999974317609183, \qquad p_+\approx5.036694109173102.

Continuing around the lower fold reaches a second root of the fixed-coordinate system at p=5; following that root upward reaches the same upper fold from the opposite side. This supports a closed two-arc solution loop, but this second result is not interval-certified and is not claimed as a proof.

Source system

The official data are from Zenodo record 21911503, accompanying Logan R. Chalmers, A counterexample to Kusner's conjecture on equilateral sets (Chalmers, 14 Aug 2026). The downloaded certificate_data.npz had the published MD5 checksum

1
b77edd39cab9f3c73a584b9e80e3d2e2

The system has P=(582)=1653P=\binom{58}{2}=1653 equations and unknowns. Of the 58×5658\times56 point coordinates, 1,652 selected coordinates are variable; the remaining 1,596 are fixed at their dyadic center values. The final variable is the common pp-power distance DD. For each pair i<ji<j,

Fij(p,z)=r=156xr(i)xr(j)pD.F_{ij}(p,z)=\sum_{r=1}^{56}|x_r^{(i)}-x_r^{(j)}|^p-D.

At p=5, the paper supplies a rational center cc, box radius ρ=233\rho=2^{-33}, and rational preconditioner BB. We first ran both archived checks. The readable exact-distance check passed, and the exact verifier reproduced claims (C1)-(C6), ending with:

1
ALL CLAIMS (C1)-(C6) VERIFIED EXACTLY

Certified explicit interval

Write

η5=BF(5,c)\eta_5=\|BF(5,c)\|_\infty

and let q5q_5 be the paper's exact bound on

supzXIBDzF(5,z).\sup_{z\in X}\|I-BD_zF(5,z)\|_\infty.

The exact integer computation gives

1
2
eta_5 = 5.3690863607029601e-14
q_5   = 5.1435649085663564e-5

For varying p, we used the mean-value bounds

η(p)η5+p5Lη,\eta(p)\le\eta_5+|p-5|L_\eta,

q(p)q5+p5Lq.q(p)\le q_5+|p-5|L_q.

The first derivative needed for LηL_\eta is evaluated after multiplication by BB so that the genuine cancellations in BpFB\,\partial_pF are retained:

pFij(p,c)=rtijrplogtijr,tijr=circjr.\partial_p F_{ij}(p,c) =\sum_r t_{ijr}^{p}\log t_{ijr}, \qquad t_{ijr}=|c_{ir}-c_{jr}|.

For the Jacobian bound, each derivative magnitude is

h(p,t)=ptp1,h(p,t)=p\,t^{p-1},

with

ph(p,t)=tp1(1+plogt).\partial_p h(p,t)=t^{p-1}(1+p\log t).

Arb intervals simultaneously enclosed p in 5 +/- 2e-12 and each coordinate difference throughout the paper's exact box. At 256-bit precision the outward-rounded bounds were

1
2
L_eta <= 65.1165319856544563
L_q   <= 12524.772647294172

For ε=1.7×1012\varepsilon=1.7\times10^{-12} this yields

1
2
3
4
5
eta_bound             <= 1.1075179523921961e-10
q_bound               <= 5.1456941199163964e-5
eta_bound+q_bound*rho <= 1.1075778561558954e-10
rho                    = 1.1641532182693481e-10
strict margin          >= 5.657536211345279e-12

Thus q(p)<1q(p)<1 and zzBF(p,z)z\mapsto z-BF(p,z) maps the complete box XX strictly into itself for every p51.7×1012|p-5|\le1.7\times10^{-12}. Banach's fixed-point theorem gives a unique fixed point in XX. The original exact certificate also proves that BB is invertible, so the fixed point is a zero of FF. The coordinate gaps remain nonzero and D>0D>0 throughout XX, hence the zero gives 58 distinct pairwise equidistant points in p56\ell_p^{56}.

Using derivative enclosures from the wider 2e-12 analysis band, the resulting linear sufficient radius is approximately 1.786883252411374e-12. We publish the rounder 1.7e-12 statement with a substantial strict margin.

Independent checks of the certified result

The full proof calculation was run independently at 160-bit and 256-bit Arb precision; the displayed bounds agreed to all reported digits and both produced a strict positive margin.

At each endpoint, p=5-1.7e-12 and p=5+1.7e-12:

  • the archived fixed-preconditioner iteration and a separately factored sparse-Newton solve converged in one step;
  • the two solvers returned identical double-precision roots;
  • the root displacement was approximately 95.1% of the paper's box radius;
  • an independent 60-digit Decimal loop, using no NumPy vectorization, evaluated all 1,653 pairwise distances and found spreads of 9.54e-16 and 7.53e-16.

These endpoint calculations are not needed by the contraction proof, but serve as an implementation-independent check of the system construction and exponent handling.

Numerical continuation and folds

For the larger-scale exploration, we used sparse Newton prediction-correction with p fixed. Near loss of invertibility, the fastest-moving selected coordinate replaced p as the continuation parameter. This keeps the controlled square system regular through a generic fold.

The connected branch through the certified p=5 root numerically reaches:

1
2
lower fold: p = 4.999974317609183
upper fold: p = 5.0366941091731015

At the lower fold:

  • vectorized residual infinity norm: 4.55e-14;
  • independent 60-digit distance spread: 4.47e-14;
  • relative Jacobian null-vector residual: 6.99e-15;
  • centered finite-difference curvature: approximately +4.665, stable across three halved step sizes;
  • two distinct fixed-p roots were recovered at p_- + 1e-7.

At the upper fold:

  • vectorized residual infinity norm: 5.73e-14;
  • independent 60-digit distance spread: 5.64e-14;
  • relative Jacobian null-vector residual: 5.69e-15;
  • centered finite-difference curvature: approximately -6.778, stable across three halved step sizes;
  • two distinct fixed-p roots were recovered at p_+ - 1e-7.

Continuing around the lower fold produces a second root of the paper's fixed-coordinate system at p=5. It lies 0.0066309988 from the certified root in infinity norm, has residual 6.66e-16, and has an independent 60-digit distance spread of 7.15e-16. We did not test equivalence of the resulting point sets under all ambient isometries and point permutations.

Following this second root upward reaches the same upper fold from the opposite side. Locating that fold independently from the two arcs gives:

1
2
difference in p:                 1.07e-13
infinity-norm solution difference: 5.21e-13

This supports a closed connected solution loop with two arcs between the folds and two roots on this component for interior exponents. It does not exclude other disconnected components.

Limitations and next step

Only the tiny interval |p-5| <= 1.7e-12 is certified. The fold locations, second root, and closed-loop structure are reproducible numerical evidence, not theorems. Proving the larger picture requires validated continuation with recentered interval boxes, followed by augmented interval-Newton or Krawczyk certificates at the singular endpoints. A separate finite isometry test would be needed before claiming that the two p=5 point sets are inequivalent.

Reproduction

The code field contains all scripts and pinned requirements. From a clean directory:

1
2
3
4
5
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
.venv/bin/python download_certificate.py
.venv/bin/python certificate/quick_check.py
.venv/bin/python certificate/verify.py

Certify and independently test the local interval:

1
2
3
4
5
6
7
8
9
10
11
.venv/bin/python certify_local_interval.py \
  --data certificate/certificate_data.npz \
  --epsilon 1.7e-12 \
  --analysis-radius 2e-12 \
  --precision 256 \
  --output local_interval_certificate.json

.venv/bin/python verify_local_interval.py \
  --data certificate/certificate_data.npz \
  --epsilon 1.7e-12 \
  --output local_interval_endpoint_checks.json

The remaining scripts reproduce the fixed-p scans, coordinate-controlled fold localization, return branch, second root, and independent fold checks.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
# ===== requirements.txt =====
numpy==2.5.2
scipy==1.16.1
python-flint==0.6.0

# ===== README.md =====
# Numerical continuation of the 58-point equilateral configuration

This directory reproduces a numerical investigation of the open problem
[Extent of the continued equilateral-set branch](https://www.emergentmind.com/open-problems/extent-of-equilateral-configuration-branch-near-p5),
based on Logan R. Chalmers' paper
[A counterexample to Kusner's conjecture on equilateral sets](https://arxiv.org/abs/2608.14013).

The paper certifies one exact solution at `p=5`. The code here numerically
continues the same 1,653-equation system away from that certified root and
locates apparent folds. The non-integer results are numerical evidence, not
an interval-arithmetic proof.

## Setup

Create an environment and install the pinned dependencies:

```bash
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
```

Download the official Zenodo record and verify its published checksums:

```bash
.venv/bin/python download_certificate.py
```

Reproduce the paper's starting-point checks:

```bash
.venv/bin/python certificate/quick_check.py
.venv/bin/python certificate/verify.py
```

`python-flint` is optional for the official verifier. Its pure-Python matrix
product took about 30 seconds on the machine used for this investigation.

## Lower fold

Continue through the lower fold using configuration coordinate 1118 as the
local parameter, then run the independent checks:

```bash
.venv/bin/python locate_fold.py \
  --data certificate/certificate_data.npz \
  --output results/lower_fold.npz \
  --control-step 0.0001

.venv/bin/python verify_fold.py \
  --data certificate/certificate_data.npz \
  --fold results/lower_fold.npz \
  --interior-direction 1 \
  --output results/lower_verification.json
```

## Upper fold

First follow the branch upward adaptively, then switch to coordinate control
at the loss of fixed-`p` invertibility:

```bash
.venv/bin/python adaptive_scan.py \
  --data certificate/certificate_data.npz \
  --output results/upper_scan.csv \
  --stop 6 \
  --min-step 1e-9

.venv/bin/python locate_fold.py \
  --data certificate/certificate_data.npz \
  --start-snapshot results/upper_scan.npz \
  --p-direction 1 \
  --output results/upper_fold.npz \
  --control-step 0.0001

.venv/bin/python verify_fold.py \
  --data certificate/certificate_data.npz \
  --fold results/upper_fold.npz \
  --interior-direction -1 \
  --output results/upper_verification.json
```

## Verification strategy

Each reported fold is checked in several independent ways:

1. Sparse Newton continuation with `p` fixed approaches the endpoint while
   the branch tangent diverges.
2. Replacing the fastest-moving coordinate by `p` keeps the controlled system
   regular, crosses the fold, and finds a sign change in `dp/dcontrol`.
3. Centered finite differences at three step sizes independently reproduce a
   zero first derivative and a nonzero second derivative.
4. The implied null vector satisfies the original Jacobian equation directly.
5. Fixed-`p` Newton finds two distinct roots on the interior side of each fold.
6. A separate 60-digit `Decimal` loop evaluates all 1,653 pairwise distances
   without NumPy vectorization.

The result summaries are in `results/lower_verification.json` and
`results/upper_verification.json`.

## Certified weaker result

The original paper proves existence on an unspecified interval around `p=5`.
The following command extends its exact certificate with Arb ball arithmetic
and proves that the same fixed map is a contraction on the original box for
every `p` with `|p-5| <= 1.7e-12`:

```bash
.venv/bin/python certify_local_interval.py \
  --data certificate/certificate_data.npz \
  --epsilon 1.7e-12 \
  --analysis-radius 2e-12 \
  --precision 256 \
  --output results/local_interval_certificate_256.json

.venv/bin/python verify_local_interval.py \
  --data certificate/certificate_data.npz \
  --epsilon 1.7e-12 \
  --output results/local_interval_endpoint_checks.json
```

The proof calculation was run at both 160 and 256 bits. The endpoint check
uses both the archived fixed preconditioner and a separately factored sparse
Newton Jacobian, followed by the 60-digit distance evaluator.

## Return branch and second `p=5` root

Continue around the lower fold to a second root at `p=5`, then follow that
root to the upper fold:

```bash
.venv/bin/python trace_return_branch.py \
  --data certificate/certificate_data.npz \
  --fold results/lower_fold.npz \
  --control-direction -1 \
  --target-p 5 \
  --output results/secondary_p5_root.json

.venv/bin/python adaptive_scan.py \
  --data certificate/certificate_data.npz \
  --start-snapshot results/secondary_p5_root.npz \
  --output results/secondary_upper_scan.csv \
  --stop 6 \
  --min-step 1e-9

.venv/bin/python locate_fold.py \
  --data certificate/certificate_data.npz \
  --start-snapshot results/secondary_upper_scan.npz \
  --p-direction 1 \
  --output results/upper_fold_from_secondary.npz

.venv/bin/python compare_fold_solutions.py \
  results/upper_fold.npz \
  results/upper_fold_from_secondary.npz \
  --output results/upper_fold_path_comparison.json
```

The second root of the fixed-coordinate system is `0.006631` away from the
certified root in infinity norm; the calculation does not test equivalence
under all ambient isometries and point permutations.
The upper fold reached from the two arcs agrees to about `1e-13` in `p` and
`6e-13` across the full solution vector. This is numerical evidence that the
connected component is a closed loop with two solution arcs between the two
folds; it is not an interval-certified global statement.

# ===== download_certificate.py =====
#!/usr/bin/env python3
"""Download and checksum the paper's official Zenodo certificate files."""

from __future__ import annotations

import argparse
import hashlib
import urllib.request
from pathlib import Path


BASE_URL = "https://zenodo.org/api/records/21911503/files"
FILES = {
    "certificate_data.npz": "b77edd39cab9f3c73a584b9e80e3d2e2",
    "quick_check.py": "43711e8467d623cda5cef2acb6935a56",
    "verify.py": "663e4ffadd1876af78bc965a1636fe24",
    "README.md": "7e580d66f0f3a239e1f804548ba12fee",
}


def md5(path: Path) -> str:
    digest = hashlib.md5(usedforsecurity=False)
    with path.open("rb") as handle:
        for chunk in iter(lambda: handle.read(1024 * 1024), b""):
            digest.update(chunk)
    return digest.hexdigest()


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--output", type=Path, default=Path("certificate"))
    args = parser.parse_args()
    args.output.mkdir(parents=True, exist_ok=True)

    for filename, expected in FILES.items():
        destination = args.output / filename
        url = f"{BASE_URL}/{filename}/content"
        print(f"downloading {filename}")
        urllib.request.urlretrieve(url, destination)
        actual = md5(destination)
        if actual != expected:
            destination.unlink(missing_ok=True)
            raise SystemExit(
                f"checksum mismatch for {filename}: {actual} != {expected}"
            )
        print(f"verified {filename}: {actual}")


if __name__ == "__main__":
    main()

# ===== continue_branch.py =====
#!/usr/bin/env python3
"""Numerically continue the certified 58-point equilateral configuration.

The starting data are from Zenodo record 21911503.  This script does not
replace the exact p=5 certificate.  It follows the same 1,653-variable
system for non-integer p and records numerical residuals and diagnostics.
"""

from __future__ import annotations

import argparse
import csv
import json
import math
from dataclasses import dataclass
from pathlib import Path

import numpy as np
from scipy.sparse import csc_matrix
from scipy.sparse.linalg import splu


@dataclass
class SolveResult:
    p: float
    z: np.ndarray
    converged: bool
    iterations: int
    residual_inf: float
    min_coordinate_gap: float
    max_center_shift: float
    tangent_inf: float


class EquilateralProblem:
    """The paper's square system with 1,652 movable coordinates and D."""

    def __init__(self, data_path: Path):
        data = np.load(data_path, allow_pickle=False)
        self.K = np.asarray(data["K"], dtype=np.int64)
        self.dK = int(data["dK"][0])
        self.sel = np.asarray(data["sel"], dtype=np.int64)
        self.BI = np.asarray(data["BI"], dtype=np.int64)

        self.m, self.n = self.K.shape
        self.P = self.m * (self.m - 1) // 2
        if (self.m, self.n, self.P) != (58, 56, 1653):
            raise ValueError("Unexpected certificate dimensions")
        if self.sel.shape != (self.P,) or self.sel[-1] != self.m * self.n:
            raise ValueError("Unexpected variable selection")

        self.Q = float(1 << 50)
        self.center_points = self.K.astype(np.float64) / self.Q
        self.coord_vars = self.sel[:-1]
        self.center_z = np.concatenate(
            [
                self.center_points.reshape(-1)[self.coord_vars],
                np.array([self.dK / self.Q], dtype=np.float64),
            ]
        )
        self.rho = 2.0**-33
        self.B = self.BI.astype(np.float64) / float(1 << 40)

        pairs = [(i, j) for i in range(self.m) for j in range(i + 1, self.m)]
        self.pair_i = np.array([i for i, _ in pairs], dtype=np.int32)
        self.pair_j = np.array([j for _, j in pairs], dtype=np.int32)

        # Sparse Jacobian structure.  A selected coordinate occurs in the 57
        # pair equations incident to its point; the final D column occurs in
        # every equation.
        jac_rows: list[int] = []
        jac_cols: list[int] = []
        jac_dims: list[int] = []
        jac_sides: list[float] = []
        incident: list[list[tuple[int, float]]] = [[] for _ in range(self.m)]
        for row, (i, j) in enumerate(pairs):
            incident[i].append((row, 1.0))
            incident[j].append((row, -1.0))
        for col, flat_index in enumerate(self.coord_vars):
            point, dim = divmod(int(flat_index), self.n)
            for row, side in incident[point]:
                jac_rows.append(row)
                jac_cols.append(col)
                jac_dims.append(dim)
                jac_sides.append(side)
        self.jac_rows = np.asarray(jac_rows, dtype=np.int32)
        self.jac_cols = np.asarray(jac_cols, dtype=np.int32)
        self.jac_dims = np.asarray(jac_dims, dtype=np.int32)
        self.jac_sides = np.asarray(jac_sides, dtype=np.float64)

    def points(self, z: np.ndarray) -> np.ndarray:
        points = self.center_points.copy()
        points.reshape(-1)[self.coord_vars] = z[:-1]
        return points

    def residual_and_differences(
        self, p: float, z: np.ndarray
    ) -> tuple[np.ndarray, np.ndarray]:
        points = self.points(z)
        differences = points[self.pair_i] - points[self.pair_j]
        residual = np.sum(np.abs(differences) ** p, axis=1) - z[-1]
        return residual, differences

    def jacobian_from_differences(
        self, p: float, differences: np.ndarray
    ) -> csc_matrix:
        selected_differences = differences[self.jac_rows, self.jac_dims]
        values = (
            self.jac_sides
            * p
            * np.sign(selected_differences)
            * np.abs(selected_differences) ** (p - 1.0)
        )
        rows = np.concatenate([self.jac_rows, np.arange(self.P, dtype=np.int32)])
        cols = np.concatenate(
            [self.jac_cols, np.full(self.P, self.P - 1, dtype=np.int32)]
        )
        data = np.concatenate([values, -np.ones(self.P, dtype=np.float64)])
        return csc_matrix((data, (rows, cols)), shape=(self.P, self.P))

    def d_residual_dp(self, p: float, differences: np.ndarray) -> np.ndarray:
        magnitudes = np.abs(differences)
        # Coordinate differences are nonzero at the certified solution.  The
        # guarded expression also gives the correct continuous limit at zero.
        terms = np.zeros_like(magnitudes)
        nonzero = magnitudes > 0.0
        terms[nonzero] = (
            magnitudes[nonzero] ** p * np.log(magnitudes[nonzero])
        )
        return np.sum(terms, axis=1)

    def diagnostics(self, p: float, z: np.ndarray) -> tuple[float, float, float, float]:
        residual, differences = self.residual_and_differences(p, z)
        jacobian = self.jacobian_from_differences(p, differences)
        lu = splu(jacobian)
        tangent = lu.solve(-self.d_residual_dp(p, differences))
        return (
            float(np.max(np.abs(residual))),
            float(np.min(np.abs(differences))),
            float(np.max(np.abs(z - self.center_z))),
            float(np.max(np.abs(tangent))),
        )

    def solve_newton(
        self,
        p: float,
        initial_z: np.ndarray,
        *,
        tolerance: float = 5e-13,
        max_iterations: int = 20,
    ) -> SolveResult:
        z = np.array(initial_z, dtype=np.float64, copy=True)
        converged = False
        residual_inf = math.inf
        differences = np.empty((self.P, self.n), dtype=np.float64)

        for iteration in range(max_iterations + 1):
            residual, differences = self.residual_and_differences(p, z)
            residual_inf = float(np.max(np.abs(residual)))
            if residual_inf <= tolerance:
                converged = True
                break
            if iteration == max_iterations:
                break

            jacobian = self.jacobian_from_differences(p, differences)
            try:
                step = splu(jacobian).solve(-residual)
            except RuntimeError:
                break

            # Backtracking avoids accepting a Newton step that crosses a bad
            # region or increases the residual near a singular endpoint.
            accepted = False
            damping = 1.0
            while damping >= 2.0**-12:
                candidate = z + damping * step
                candidate_residual, _ = self.residual_and_differences(p, candidate)
                candidate_norm = float(np.max(np.abs(candidate_residual)))
                if np.isfinite(candidate_norm) and candidate_norm < residual_inf:
                    z = candidate
                    accepted = True
                    break
                damping *= 0.5
            if not accepted:
                break

        try:
            residual_inf, min_gap, shift, tangent_inf = self.diagnostics(p, z)
        except RuntimeError:
            min_gap = float(np.min(np.abs(differences)))
            shift = float(np.max(np.abs(z - self.center_z)))
            tangent_inf = math.inf
        return SolveResult(
            p=p,
            z=z,
            converged=converged,
            iterations=iteration,
            residual_inf=residual_inf,
            min_coordinate_gap=min_gap,
            max_center_shift=shift,
            tangent_inf=tangent_inf,
        )

    def solve_fixed_preconditioner(
        self,
        p: float,
        initial_z: np.ndarray,
        *,
        tolerance: float = 5e-13,
        max_iterations: int = 100,
    ) -> SolveResult:
        """Independent corrector using the archived p=5 preconditioner."""
        z = np.array(initial_z, dtype=np.float64, copy=True)
        converged = False
        residual_inf = math.inf
        for iteration in range(max_iterations + 1):
            residual, _ = self.residual_and_differences(p, z)
            residual_inf = float(np.max(np.abs(residual)))
            if residual_inf <= tolerance:
                converged = True
                break
            if iteration == max_iterations:
                break
            z -= self.B @ residual
        residual_inf, min_gap, shift, tangent_inf = self.diagnostics(p, z)
        return SolveResult(
            p=p,
            z=z,
            converged=converged,
            iterations=iteration,
            residual_inf=residual_inf,
            min_coordinate_gap=min_gap,
            max_center_shift=shift,
            tangent_inf=tangent_inf,
        )


def result_row(result: SolveResult, rho: float) -> dict[str, float | int | bool]:
    return {
        "p": result.p,
        "converged": result.converged,
        "iterations": result.iterations,
        "residual_inf": result.residual_inf,
        "min_coordinate_gap": result.min_coordinate_gap,
        "max_center_shift": result.max_center_shift,
        "box_radii_from_center": result.max_center_shift / rho,
        "tangent_inf": result.tangent_inf,
        "D": float(result.z[-1]),
    }


def scan_direction(
    problem: EquilateralProblem,
    start: SolveResult,
    stop: float,
    step: float,
) -> list[SolveResult]:
    direction = 1.0 if stop > start.p else -1.0
    step = abs(step) * direction
    results: list[SolveResult] = []
    previous = start
    p = start.p + step
    while (p - stop) * direction <= 1e-12:
        # First-order predictor from the previous branch tangent.
        residual, differences = problem.residual_and_differences(previous.p, previous.z)
        jacobian = problem.jacobian_from_differences(previous.p, differences)
        tangent = splu(jacobian).solve(
            -problem.d_residual_dp(previous.p, differences)
        )
        predicted = previous.z + (p - previous.p) * tangent
        current = problem.solve_newton(p, predicted)
        results.append(current)
        print(json.dumps(result_row(current, problem.rho)), flush=True)
        if not current.converged:
            break
        previous = current
        p += step
    return results


def write_csv(path: Path, results: list[SolveResult], rho: float) -> None:
    rows = [result_row(result, rho) for result in results]
    path.parent.mkdir(parents=True, exist_ok=True)
    with path.open("w", newline="", encoding="utf-8") as handle:
        writer = csv.DictWriter(handle, fieldnames=list(rows[0]))
        writer.writeheader()
        writer.writerows(rows)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--output", type=Path, default=Path("branch_scan.csv"))
    parser.add_argument("--lower", type=float, default=4.0)
    parser.add_argument("--upper", type=float, default=6.0)
    parser.add_argument("--step", type=float, default=0.02)
    args = parser.parse_args()

    problem = EquilateralProblem(args.data)
    start = problem.solve_fixed_preconditioner(5.0, problem.center_z)
    print(json.dumps({"start": result_row(start, problem.rho)}), flush=True)
    if not start.converged:
        raise SystemExit("Could not reproduce the p=5 root numerically")

    lower_results = scan_direction(problem, start, args.lower, args.step)
    upper_results = scan_direction(problem, start, args.upper, args.step)
    all_results = list(reversed(lower_results)) + [start] + upper_results
    write_csv(args.output, all_results, problem.rho)

    snapshot_path = args.output.with_suffix(".npz")
    np.savez_compressed(
        snapshot_path,
        p=np.array([result.p for result in all_results]),
        z=np.stack([result.z for result in all_results]),
        converged=np.array([result.converged for result in all_results]),
    )


if __name__ == "__main__":
    main()

# ===== adaptive_scan.py =====
#!/usr/bin/env python3
"""Adaptive fixed-p continuation away from the certified p=5 root."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import numpy as np
from scipy.sparse.linalg import splu

from continue_branch import EquilateralProblem, SolveResult, result_row, write_csv


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    parser.add_argument("--start-snapshot", type=Path)
    parser.add_argument("--stop", type=float, default=6.0)
    parser.add_argument("--max-step", type=float, default=0.01)
    parser.add_argument("--target-coordinate-step", type=float, default=0.002)
    parser.add_argument("--min-step", type=float, default=1e-10)
    parser.add_argument("--max-points", type=int, default=2000)
    args = parser.parse_args()

    problem = EquilateralProblem(args.data)
    if args.start_snapshot is None:
        start = problem.solve_fixed_preconditioner(5.0, problem.center_z)
    else:
        snapshot = np.load(args.start_snapshot, allow_pickle=False)
        snapshot_p = float(snapshot["p"][-1])
        snapshot_z = np.asarray(snapshot["z"], dtype=np.float64)
        if snapshot_z.ndim == 2:
            snapshot_z = snapshot_z[-1]
        start = problem.solve_newton(snapshot_p, snapshot_z, tolerance=1e-14)
    if not start.converged:
        raise SystemExit("Could not reproduce the p=5 root")
    direction = 1.0 if args.stop > start.p else -1.0
    results: list[SolveResult] = [start]
    previous = start
    print(json.dumps(result_row(start, problem.rho)), flush=True)

    for _ in range(args.max_points):
        residual, differences = problem.residual_and_differences(previous.p, previous.z)
        jacobian = problem.jacobian_from_differences(previous.p, differences)
        tangent = splu(jacobian).solve(
            -problem.d_residual_dp(previous.p, differences)
        )
        tangent_inf = float(np.max(np.abs(tangent)))
        step = min(
            args.max_step,
            args.target_coordinate_step / max(tangent_inf, 1e-300),
            abs(args.stop - previous.p),
        )
        if step < args.min_step:
            print(
                json.dumps(
                    {
                        "stopped": "step_below_minimum",
                        "p": previous.p,
                        "proposed_step": step,
                        "tangent_inf": tangent_inf,
                    }
                ),
                flush=True,
            )
            break

        accepted: SolveResult | None = None
        while step >= args.min_step:
            target_p = previous.p + direction * step
            predicted = previous.z + direction * step * tangent
            candidate = problem.solve_newton(target_p, predicted)
            if candidate.converged:
                accepted = candidate
                break
            step *= 0.5
        if accepted is None:
            print(
                json.dumps(
                    {
                        "stopped": "newton_failure",
                        "p": previous.p,
                        "last_attempted_step": step,
                    }
                ),
                flush=True,
            )
            break

        results.append(accepted)
        previous = accepted
        print(json.dumps(result_row(accepted, problem.rho)), flush=True)
        if abs(previous.p - args.stop) <= 1e-12:
            print(json.dumps({"stopped": "reached_target", "p": previous.p}))
            break
    else:
        print(json.dumps({"stopped": "max_points", "p": previous.p}))

    write_csv(args.output, results, problem.rho)
    np.savez_compressed(
        args.output.with_suffix(".npz"),
        p=np.array([result.p for result in results]),
        z=np.stack([result.z for result in results]),
        converged=np.array([result.converged for result in results]),
    )


if __name__ == "__main__":
    main()

# ===== locate_fold.py =====
#!/usr/bin/env python3
"""Locate a fold by using one configuration coordinate as the parameter.

At a generic fold the Jacobian with respect to z is singular, so p cannot be
used as a local continuation parameter.  Replacing the fastest-moving z
coordinate by p yields a square system that remains regular through the fold.
"""

from __future__ import annotations

import argparse
import json
import math
from dataclasses import dataclass
from pathlib import Path

import numpy as np
from scipy.sparse import csc_matrix, hstack
from scipy.sparse.linalg import splu

from continue_branch import EquilateralProblem


@dataclass
class ControlledResult:
    control: float
    p: float
    z: np.ndarray
    converged: bool
    iterations: int
    residual_inf: float
    dp_dcontrol: float


def controlled_matrix(
    problem: EquilateralProblem,
    p: float,
    z: np.ndarray,
    control_index: int,
) -> tuple[csc_matrix, np.ndarray, csc_matrix]:
    _, differences = problem.residual_and_differences(p, z)
    jacobian = problem.jacobian_from_differences(p, differences)
    fp = problem.d_residual_dp(p, differences)
    mask = np.arange(problem.P) != control_index
    matrix = hstack([jacobian[:, mask], csc_matrix(fp[:, None])], format="csc")
    return matrix, fp, jacobian


def derivative_at_control(
    problem: EquilateralProblem,
    p: float,
    z: np.ndarray,
    control_index: int,
) -> float:
    matrix, _, jacobian = controlled_matrix(problem, p, z, control_index)
    rhs = -jacobian[:, control_index].toarray().ravel()
    derivative = splu(matrix).solve(rhs)
    return float(derivative[-1])


def solve_at_control(
    problem: EquilateralProblem,
    control: float,
    initial_p: float,
    initial_z: np.ndarray,
    control_index: int,
    *,
    tolerance: float = 5e-13,
    max_iterations: int = 20,
) -> ControlledResult:
    p = float(initial_p)
    z = np.array(initial_z, dtype=np.float64, copy=True)
    z[control_index] = control
    mask = np.arange(problem.P) != control_index
    residual_inf = math.inf
    converged = False

    for iteration in range(max_iterations + 1):
        residual, _ = problem.residual_and_differences(p, z)
        residual_inf = float(np.max(np.abs(residual)))
        if residual_inf <= tolerance:
            converged = True
            break
        if iteration == max_iterations:
            break

        try:
            matrix, _, _ = controlled_matrix(problem, p, z, control_index)
            delta = splu(matrix).solve(-residual)
        except RuntimeError:
            break

        accepted = False
        damping = 1.0
        while damping >= 2.0**-12:
            candidate_z = z.copy()
            candidate_z[mask] += damping * delta[:-1]
            candidate_z[control_index] = control
            candidate_p = p + damping * float(delta[-1])
            candidate_residual, _ = problem.residual_and_differences(
                candidate_p, candidate_z
            )
            candidate_norm = float(np.max(np.abs(candidate_residual)))
            if np.isfinite(candidate_norm) and candidate_norm < residual_inf:
                z = candidate_z
                p = candidate_p
                accepted = True
                break
            damping *= 0.5
        if not accepted:
            break

    try:
        dp_dcontrol = derivative_at_control(
            problem, p, z, control_index
        )
    except RuntimeError:
        dp_dcontrol = math.nan
    return ControlledResult(
        control=control,
        p=p,
        z=z,
        converged=converged,
        iterations=iteration,
        residual_inf=residual_inf,
        dp_dcontrol=dp_dcontrol,
    )


def as_json(result: ControlledResult) -> dict[str, float | bool | int]:
    return {
        "control": result.control,
        "p": result.p,
        "converged": result.converged,
        "iterations": result.iterations,
        "residual_inf": result.residual_inf,
        "dp_dcontrol": result.dp_dcontrol,
    }


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--output", type=Path, default=Path("lower_fold.npz"))
    parser.add_argument("--start-snapshot", type=Path)
    parser.add_argument(
        "--p-direction",
        type=int,
        choices=(-1, 1),
        default=-1,
        help="Direction in p from the starting solution toward the fold",
    )
    parser.add_argument("--control-step", type=float, default=1e-4)
    parser.add_argument("--max-steps", type=int, default=200)
    parser.add_argument("--bisect-iterations", type=int, default=40)
    args = parser.parse_args()

    problem = EquilateralProblem(args.data)
    if args.start_snapshot is None:
        start = problem.solve_fixed_preconditioner(5.0, problem.center_z)
    else:
        snapshot = np.load(args.start_snapshot, allow_pickle=False)
        snapshot_p = float(snapshot["p"][-1])
        snapshot_z = np.asarray(snapshot["z"][-1], dtype=np.float64)
        start = problem.solve_newton(snapshot_p, snapshot_z)
    if not start.converged:
        raise SystemExit("Could not reproduce the p=5 root")

    residual, differences = problem.residual_and_differences(start.p, start.z)
    jacobian = problem.jacobian_from_differences(start.p, differences)
    tangent = splu(jacobian).solve(-problem.d_residual_dp(start.p, differences))
    control_index = int(np.argmax(np.abs(tangent[:-1])))
    initial_derivative = 1.0 / float(tangent[control_index])
    direction = args.p_direction * (1.0 if tangent[control_index] > 0.0 else -1.0)
    control_step = direction * abs(args.control_step)

    print(
        json.dumps(
            {
                "control_index": control_index,
                "start_p": start.p,
                "selected_flat_coordinate": int(problem.coord_vars[control_index]),
                "point": int(problem.coord_vars[control_index] // problem.n),
                "dimension": int(problem.coord_vars[control_index] % problem.n),
                "start_control": float(start.z[control_index]),
                "dz_dp_at_start": float(tangent[control_index]),
                "dp_dcontrol_at_start": initial_derivative,
            }
        ),
        flush=True,
    )

    previous = ControlledResult(
        control=float(start.z[control_index]),
        p=start.p,
        z=start.z,
        converged=True,
        iterations=0,
        residual_inf=start.residual_inf,
        dp_dcontrol=initial_derivative,
    )
    bracket: tuple[ControlledResult, ControlledResult] | None = None
    for _ in range(args.max_steps):
        current = solve_at_control(
            problem,
            previous.control + control_step,
            previous.p,
            previous.z,
            control_index,
        )
        print(json.dumps(as_json(current)), flush=True)
        if not current.converged:
            raise SystemExit("Controlled continuation failed before a fold was bracketed")
        if previous.dp_dcontrol * current.dp_dcontrol <= 0.0:
            bracket = (previous, current)
            break
        previous = current
    if bracket is None:
        raise SystemExit("No fold bracketed within max steps")

    left, right = bracket
    for _ in range(args.bisect_iterations):
        midpoint_control = 0.5 * (left.control + right.control)
        fraction = (midpoint_control - left.control) / (right.control - left.control)
        initial_z = left.z + fraction * (right.z - left.z)
        initial_p = left.p + fraction * (right.p - left.p)
        midpoint = solve_at_control(
            problem,
            midpoint_control,
            initial_p,
            initial_z,
            control_index,
        )
        if not midpoint.converged:
            raise SystemExit("Fold refinement failed")
        if left.dp_dcontrol * midpoint.dp_dcontrol <= 0.0:
            right = midpoint
        else:
            left = midpoint

    fold_control = 0.5 * (left.control + right.control)
    fold = solve_at_control(
        problem,
        fold_control,
        0.5 * (left.p + right.p),
        0.5 * (left.z + right.z),
        control_index,
    )
    print(json.dumps({"fold": as_json(fold)}), flush=True)

    args.output.parent.mkdir(parents=True, exist_ok=True)
    np.savez_compressed(
        args.output,
        p=np.array([fold.p]),
        z=fold.z,
        control=np.array([fold.control]),
        control_index=np.array([control_index]),
        dp_dcontrol=np.array([fold.dp_dcontrol]),
        residual_inf=np.array([fold.residual_inf]),
    )


if __name__ == "__main__":
    main()

# ===== verify_fold.py =====
#!/usr/bin/env python3
"""Independent numerical checks for the coordinate-controlled fold result."""

from __future__ import annotations

import argparse
import json
from decimal import Decimal, localcontext
from pathlib import Path

import numpy as np
from scipy.sparse.linalg import splu

from continue_branch import EquilateralProblem
from locate_fold import controlled_matrix, solve_at_control


def decimal_distance_spread(
    problem: EquilateralProblem,
    p: float,
    z: np.ndarray,
    precision: int,
) -> tuple[str, str]:
    """Evaluate all 1,653 distances using Decimal, not NumPy."""
    points = problem.points(z)
    with localcontext() as context:
        context.prec = precision
        decimal_p = Decimal.from_float(float(p))
        distances: list[Decimal] = []
        for i in range(problem.m):
            for j in range(i + 1, problem.m):
                total = Decimal(0)
                for r in range(problem.n):
                    difference = abs(
                        Decimal.from_float(float(points[i, r]))
                        - Decimal.from_float(float(points[j, r]))
                    )
                    if difference:
                        total += (decimal_p * difference.ln()).exp()
                distances.append(total)
        spread = max(distances) - min(distances)
        common = sum(distances, Decimal(0)) / Decimal(len(distances))
        return str(spread), str(common)


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--fold", type=Path, required=True)
    parser.add_argument("--output", type=Path)
    parser.add_argument("--precision", type=int, default=60)
    parser.add_argument(
        "--interior-direction",
        type=int,
        choices=(-1, 1),
        default=1,
        help="Direction from the fold in p where two roots should exist",
    )
    args = parser.parse_args()

    problem = EquilateralProblem(args.data)
    saved = np.load(args.fold, allow_pickle=False)
    fold_p = float(saved["p"][0])
    fold_z = np.asarray(saved["z"], dtype=np.float64)
    control = float(saved["control"][0])
    control_index = int(saved["control_index"][0])

    residual, differences = problem.residual_and_differences(fold_p, fold_z)
    jacobian = problem.jacobian_from_differences(fold_p, differences)
    fp = problem.d_residual_dp(fold_p, differences)
    matrix, _, _ = controlled_matrix(problem, fold_p, fold_z, control_index)
    jacobian_control_column = jacobian[:, control_index].toarray().ravel()
    derivative = splu(matrix).solve(-jacobian_control_column)
    mask = np.arange(problem.P) != control_index
    null_vector = np.empty(problem.P, dtype=np.float64)
    null_vector[control_index] = 1.0
    null_vector[mask] = derivative[:-1]
    dp_dcontrol = float(derivative[-1])
    null_residual = jacobian @ null_vector

    finite_difference_checks = []
    for h in (1e-4, 5e-5, 2.5e-5):
        minus = solve_at_control(
            problem,
            control - h,
            fold_p,
            fold_z,
            control_index,
        )
        plus = solve_at_control(
            problem,
            control + h,
            fold_p,
            fold_z,
            control_index,
        )
        if not minus.converged or not plus.converged:
            raise SystemExit("Finite-difference continuation check failed")
        finite_difference_checks.append(
            {
                "h": h,
                "p_minus": minus.p,
                "p_plus": plus.p,
                "centered_first_derivative": (plus.p - minus.p) / (2.0 * h),
                "centered_second_derivative": (
                    plus.p - 2.0 * fold_p + minus.p
                )
                / (h * h),
            }
        )

    # At a p slightly above a generic lower fold, fixed-p Newton should find
    # two distinct configurations when initialized on opposite sides.
    h_seed = 4e-4
    seed_minus = solve_at_control(
        problem, control - h_seed, fold_p, fold_z, control_index
    )
    seed_plus = solve_at_control(
        problem, control + h_seed, fold_p, fold_z, control_index
    )
    test_p = fold_p + args.interior_direction * 1e-7
    root_minus = problem.solve_newton(test_p, seed_minus.z)
    root_plus = problem.solve_newton(test_p, seed_plus.z)
    if not root_minus.converged or not root_plus.converged:
        raise SystemExit("Two-root fixed-p check failed to converge")

    decimal_spread, decimal_common = decimal_distance_spread(
        problem, fold_p, fold_z, args.precision
    )
    output = {
        "fold_p": fold_p,
        "float64_residual_inf": float(np.max(np.abs(residual))),
        "D": float(fold_z[-1]),
        "min_coordinate_gap": float(np.min(np.abs(differences))),
        "analytic_dp_dcontrol": dp_dcontrol,
        "relative_null_residual_inf": float(
            np.max(np.abs(null_residual)) / np.max(np.abs(null_vector))
        ),
        "identity_check_Jv_plus_Fp_dp_inf": float(
            np.max(np.abs(null_residual + fp * dp_dcontrol))
        ),
        "finite_difference_checks": finite_difference_checks,
        "two_root_check": {
            "p": test_p,
            "root_1_residual_inf": root_minus.residual_inf,
            "root_2_residual_inf": root_plus.residual_inf,
            "root_control_1": float(root_minus.z[control_index]),
            "root_control_2": float(root_plus.z[control_index]),
            "root_separation_inf": float(
                np.max(np.abs(root_minus.z - root_plus.z))
            ),
        },
        "decimal_precision": args.precision,
        "decimal_pairwise_distance_spread": decimal_spread,
        "decimal_mean_fifth_power_distance": decimal_common,
    }
    rendered = json.dumps(output, indent=2) + "\n"
    print(rendered, end="")
    if args.output is not None:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(rendered, encoding="utf-8")


if __name__ == "__main__":
    main()

# ===== certify_local_interval.py =====
#!/usr/bin/env python3
"""Certify an explicit noninteger-p interval using the paper's box.

This extends the exact p=5 Newton-Kantorovich calculation.  The integer
portion exactly reconstructs eta_5 and q_5.  Arb ball arithmetic bounds the
p-derivatives on a wider analysis band, yielding

    eta(p) <= eta_5 + eps * L_eta
    q(p)   <= q_5   + eps * L_q.

If eta(p) + q(p) * rho < rho, the fixed map z - B F(p,z) is a contraction of
the paper's original box into itself for every p in the requested interval.
"""

from __future__ import annotations

import argparse
import json
import time
from pathlib import Path

import numpy as np
from flint import arb, arb_mat, ctx, fmpz_mat


def upper_max(values: list[arb]) -> arb:
    result = arb(0)
    for value in values:
        candidate = abs(value).abs_upper()
        if candidate > result:
            result = candidate
    return result


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--epsilon", default="1.7e-12")
    parser.add_argument(
        "--analysis-radius",
        default="2e-12",
        help="Wider p-radius used to bound derivatives",
    )
    parser.add_argument("--precision", type=int, default=160)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()
    ctx.prec = args.precision
    started = time.time()

    data = np.load(args.data, allow_pickle=False)
    K = np.asarray(data["K"], dtype=np.int64)
    dK = int(data["dK"][0])
    sel = np.asarray(data["sel"], dtype=np.int64)
    BI = np.asarray(data["BI"], dtype=np.int64)
    m, n = K.shape
    P = m * (m - 1) // 2
    if (m, n, P) != (58, 56, 1653):
        raise SystemExit("Unexpected certificate dimensions")

    Q = 1 << 50
    Q4 = Q**4
    RHO = 1 << 17
    DEN = 1 << 240
    pairs = [(i, j) for i in range(m) for j in range(i + 1, m)]
    KI = [[int(K[i, r]) for r in range(n)] for i in range(m)]
    BIl = [[int(x) for x in row] for row in BI]
    coordvars = [int(v) for v in sel[:-1]]
    selset = set(coordvars)
    colpos = {value: index for index, value in enumerate(coordvars)}

    # Exact p=5 residual and eta numerator, matching the paper's verifier.
    Fc: list[int] = []
    for i, j in pairs:
        fifth_power_sum = sum(abs(KI[i][r] - KI[j][r]) ** 5 for r in range(n))
        Fc.append(fifth_power_sum - dK * Q4)
    Fc_product = fmpz_mat(BIl) * fmpz_mat([[value] for value in Fc])
    etanum = max(abs(int(Fc_product[row, 0])) for row in range(P))

    # Exact J_5(c), the paper's z-variation enclosure E_5, and q_5.
    Jc_rows = [[0] * P for _ in range(P)]
    erad_rowsum = [0] * P
    for row, (i, j) in enumerate(pairs):
        for r in range(n):
            wi = (i * n + r) in selset
            wj = (j * n + r) in selset
            w = wi + wj
            if not w:
                continue
            difference = KI[i][r] - KI[j][r]
            magnitude_derivative = 5 * abs(difference) ** 4
            signed_derivative = (
                magnitude_derivative if difference > 0 else -magnitude_derivative
            )
            radius = 5 * (
                (abs(difference) + w * RHO) ** 4 - abs(difference) ** 4
            )
            if wi:
                Jc_rows[row][colpos[i * n + r]] = signed_derivative
                erad_rowsum[row] += radius
            if wj:
                Jc_rows[row][colpos[j * n + r]] = -signed_derivative
                erad_rowsum[row] += radius
        Jc_rows[row][P - 1] = -Q4

    BI_matrix = fmpz_mat(BIl)
    product = BI_matrix * fmpz_mat(Jc_rows)
    product_table = product.table()
    rowsum_R: list[int] = []
    for row in range(P):
        total = 0
        for col in range(P):
            value = int(product_table[row][col])
            if row == col:
                value -= DEN
            total += abs(value)
        rowsum_R.append(total)
    qnum = 0
    for row in range(P):
        total = rowsum_R[row]
        for pair_row in range(P):
            total += abs(BIl[row][pair_row]) * erad_rowsum[pair_row]
        qnum = max(qnum, total)

    # Arb derivative bounds on a band wider than the interval to certify.
    epsilon = arb(args.epsilon)
    analysis_radius = arb(args.analysis_radius)
    if not (epsilon < analysis_radius):
        raise SystemExit("epsilon must be strictly smaller than analysis radius")
    p_band = arb(5, analysis_radius)
    Q_arb = arb(Q)
    rho = arb(1) / arb(1 << 33)

    # F'_p(c) = sum t^p log(t).  Multiplying the interval vector by B
    # preserves the large cancellations in B F'_p(c), unlike a componentwise
    # absolute-value Lipschitz bound.
    fp_derivative_intervals: list[arb] = []
    jacobian_p_lipschitz_rows: list[arb] = []
    one = arb(1)
    for i, j in pairs:
        residual_derivative = arb(0)
        row_lipschitz = arb(0)
        for r in range(n):
            difference_num = abs(KI[i][r] - KI[j][r])
            center_magnitude = arb(difference_num) / Q_arb
            center_log = center_magnitude.log()
            residual_derivative += (p_band * center_log).exp() * center_log

            wi = (i * n + r) in selset
            wj = (j * n + r) in selset
            w = wi + wj
            if not w:
                continue
            magnitude_band = arb(
                center_magnitude, arb(w * RHO) / Q_arb
            )
            magnitude_log = magnitude_band.log()
            derivative_in_p = (
                ((p_band - one) * magnitude_log).exp()
                * (one + p_band * magnitude_log)
            )
            bound = abs(derivative_in_p).abs_upper()
            if wi:
                row_lipschitz += bound
            if wj:
                row_lipschitz += bound
        fp_derivative_intervals.append(residual_derivative)
        jacobian_p_lipschitz_rows.append(row_lipschitz)

    BFp = BI_matrix * arb_mat([[value] for value in fp_derivative_intervals])
    L_eta = upper_max([BFp[row, 0] / arb(1 << 40) for row in range(P)])

    abs_BI_matrix = fmpz_mat(
        [[abs(value) for value in row] for row in BIl]
    )
    BL = abs_BI_matrix * arb_mat(
        [[value] for value in jacobian_p_lipschitz_rows]
    )
    L_q = upper_max([BL[row, 0] / arb(1 << 40) for row in range(P)])

    eta_5 = arb(etanum) / arb(1 << 290)
    q_5 = arb(qnum) / arb(1 << 240)
    eta_bound = eta_5 + epsilon * L_eta
    q_bound = q_5 + epsilon * L_q
    mapping_bound = eta_bound + q_bound * rho
    margin = rho - mapping_bound
    certified = bool(margin > 0 and q_bound < 1)

    # The linear sufficient radius uses derivative bounds from the wider band.
    sufficient_radius = (rho - eta_5 - q_5 * rho) / (L_eta + L_q * rho)
    output = {
        "certified": certified,
        "epsilon": args.epsilon,
        "p_interval": [f"5-{args.epsilon}", f"5+{args.epsilon}"],
        "analysis_radius": args.analysis_radius,
        "precision_bits": args.precision,
        "eta_5": str(eta_5),
        "q_5": str(q_5),
        "L_eta_upper": str(L_eta),
        "L_q_upper": str(L_q),
        "eta_bound": str(eta_bound),
        "q_bound": str(q_bound),
        "mapping_bound": str(mapping_bound),
        "rho": str(rho),
        "margin": str(margin),
        "sufficient_radius_from_analysis_band": str(sufficient_radius),
        "etanum": str(etanum),
        "qnum": str(qnum),
        "elapsed_seconds": time.time() - started,
    }
    rendered = json.dumps(output, indent=2) + "\n"
    print(rendered, end="")
    if args.output is not None:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(rendered, encoding="utf-8")
    if not certified:
        raise SystemExit("Requested interval was not certified")


if __name__ == "__main__":
    main()

# ===== verify_local_interval.py =====
#!/usr/bin/env python3
"""Independent endpoint checks for the certified local p interval."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import numpy as np

from continue_branch import EquilateralProblem
from verify_fold import decimal_distance_spread


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--epsilon", type=float, default=1.7e-12)
    parser.add_argument("--precision", type=int, default=60)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    problem = EquilateralProblem(args.data)
    checks = []
    for sign in (-1.0, 1.0):
        p = 5.0 + sign * args.epsilon
        fixed = problem.solve_fixed_preconditioner(
            p,
            problem.center_z,
            tolerance=1e-14,
            max_iterations=100,
        )
        newton = problem.solve_newton(
            p,
            problem.center_z,
            tolerance=1e-14,
            max_iterations=20,
        )
        if not fixed.converged or not newton.converged:
            raise SystemExit(f"Endpoint solver failed at p={p}")
        spread, common = decimal_distance_spread(
            problem, p, fixed.z, args.precision
        )
        checks.append(
            {
                "p": p,
                "fixed_preconditioner_iterations": fixed.iterations,
                "fixed_preconditioner_residual_inf": fixed.residual_inf,
                "sparse_newton_iterations": newton.iterations,
                "sparse_newton_residual_inf": newton.residual_inf,
                "solver_root_separation_inf": float(
                    np.max(np.abs(fixed.z - newton.z))
                ),
                "max_center_shift": fixed.max_center_shift,
                "box_radius": problem.rho,
                "fraction_of_box_radius": fixed.max_center_shift / problem.rho,
                "decimal_pairwise_distance_spread": spread,
                "decimal_mean_power_distance": common,
            }
        )

    output = {
        "epsilon": args.epsilon,
        "decimal_precision": args.precision,
        "checks": checks,
    }
    rendered = json.dumps(output, indent=2) + "\n"
    print(rendered, end="")
    if args.output is not None:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(rendered, encoding="utf-8")


if __name__ == "__main__":
    main()

# ===== trace_return_branch.py =====
#!/usr/bin/env python3
"""Trace a coordinate-controlled return branch to a target exponent."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import numpy as np

from continue_branch import EquilateralProblem
from locate_fold import ControlledResult, solve_at_control
from verify_fold import decimal_distance_spread


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("--data", type=Path, required=True)
    parser.add_argument("--fold", type=Path, required=True)
    parser.add_argument("--control-direction", type=int, choices=(-1, 1), required=True)
    parser.add_argument("--control-step", type=float, default=1e-4)
    parser.add_argument("--target-p", type=float, default=5.0)
    parser.add_argument("--max-steps", type=int, default=5000)
    parser.add_argument("--bisect-iterations", type=int, default=45)
    parser.add_argument("--output", type=Path, required=True)
    args = parser.parse_args()

    problem = EquilateralProblem(args.data)
    saved = np.load(args.fold, allow_pickle=False)
    control_index = int(saved["control_index"][0])
    fold = ControlledResult(
        control=float(saved["control"][0]),
        p=float(saved["p"][0]),
        z=np.asarray(saved["z"], dtype=np.float64),
        converged=True,
        iterations=0,
        residual_inf=float(saved["residual_inf"][0]),
        dp_dcontrol=float(saved["dp_dcontrol"][0]),
    )

    step = args.control_direction * abs(args.control_step)
    previous = fold
    bracket: tuple[ControlledResult, ControlledResult] | None = None
    trace_p = [fold.p]
    trace_control = [fold.control]
    for _ in range(args.max_steps):
        current = solve_at_control(
            problem,
            previous.control + step,
            previous.p,
            previous.z,
            control_index,
        )
        if not current.converged:
            raise SystemExit("Controlled return continuation failed")
        trace_p.append(current.p)
        trace_control.append(current.control)
        if (previous.p - args.target_p) * (current.p - args.target_p) <= 0.0:
            bracket = (previous, current)
            break
        previous = current
    if bracket is None:
        raise SystemExit("Target p was not bracketed")

    left, right = bracket
    for _ in range(args.bisect_iterations):
        control = 0.5 * (left.control + right.control)
        fraction = (control - left.control) / (right.control - left.control)
        initial_z = left.z + fraction * (right.z - left.z)
        initial_p = left.p + fraction * (right.p - left.p)
        midpoint = solve_at_control(
            problem, control, initial_p, initial_z, control_index
        )
        if not midpoint.converged:
            raise SystemExit("Target-p refinement failed")
        if (left.p - args.target_p) * (midpoint.p - args.target_p) <= 0.0:
            right = midpoint
        else:
            left = midpoint

    root = left if abs(left.p - args.target_p) < abs(right.p - args.target_p) else right
    fixed_p_root = problem.solve_newton(
        args.target_p, root.z, tolerance=1e-14, max_iterations=30
    )
    if not fixed_p_root.converged:
        raise SystemExit("Final fixed-p correction failed")
    certified_root = problem.solve_fixed_preconditioner(5.0, problem.center_z)
    spread, common = decimal_distance_spread(
        problem, args.target_p, fixed_p_root.z, 60
    )

    output = {
        "target_p": args.target_p,
        "control_index": control_index,
        "control": float(fixed_p_root.z[control_index]),
        "residual_inf": fixed_p_root.residual_inf,
        "D": float(fixed_p_root.z[-1]),
        "distance_from_certified_root_inf": float(
            np.max(np.abs(fixed_p_root.z - certified_root.z))
        ),
        "max_center_shift": fixed_p_root.max_center_shift,
        "decimal_pairwise_distance_spread": spread,
        "decimal_mean_power_distance": common,
        "trace_points": len(trace_p),
        "trace_p_min": float(min(trace_p)),
        "trace_p_max": float(max(trace_p)),
    }
    rendered = json.dumps(output, indent=2) + "\n"
    print(rendered, end="")
    args.output.parent.mkdir(parents=True, exist_ok=True)
    args.output.write_text(rendered, encoding="utf-8")
    np.savez_compressed(
        args.output.with_suffix(".npz"),
        p=np.array([args.target_p]),
        z=fixed_p_root.z,
        control_index=np.array([control_index]),
        trace_p=np.asarray(trace_p),
        trace_control=np.asarray(trace_control),
    )


if __name__ == "__main__":
    main()

# ===== compare_fold_solutions.py =====
#!/usr/bin/env python3
"""Compare fold solutions reached along two independently traced arcs."""

from __future__ import annotations

import argparse
import json
from pathlib import Path

import numpy as np


def main() -> None:
    parser = argparse.ArgumentParser()
    parser.add_argument("first", type=Path)
    parser.add_argument("second", type=Path)
    parser.add_argument("--output", type=Path)
    args = parser.parse_args()

    first = np.load(args.first, allow_pickle=False)
    second = np.load(args.second, allow_pickle=False)
    output = {
        "p_first": float(first["p"][0]),
        "p_second": float(second["p"][0]),
        "p_difference": abs(float(first["p"][0] - second["p"][0])),
        "z_inf_difference": float(
            np.max(np.abs(first["z"] - second["z"]))
        ),
        "control_difference": abs(
            float(first["control"][0] - second["control"][0])
        ),
    }
    rendered = json.dumps(output, indent=2) + "\n"
    print(rendered, end="")
    if args.output is not None:
        args.output.parent.mkdir(parents=True, exist_ok=True)
        args.output.write_text(rendered, encoding="utf-8")


if __name__ == "__main__":
    main()