Reproduction code for "How slowly must you pump an anomalous soliton?"
(pvjohnston.com, 2026-07-17)

Model: Tao, Wang & Xu, Nat. Commun. (2026), doi:10.1038/s41467-026-73460-y
Requires numpy only. No scipy, no torch, no GPU.

VALIDATION (run first; reproduces Table 1)
  python3 soliton_model.py    # k-space vs real-space spectrum, Chern numbers, gaps
  python3 wannier.py          # Wannier centres; the flat band at theta=pi

MAIN RESULT (Tables 2 and 3, and the abstract numbers)
  python3 converge.py normal  # writes conv_normal.jsonl
  python3 converge.py anom1   # case 1 (g=-1), writes conv_anom1.jsonl
  python3 converge.py anom2   # case 2 (g=1),  writes conv_anom2.jsonl
  python3 build_tables.py     # rebuilds Tables 2 & 3 from the three conv_*.jsonl

  Each converge.py run is convergence-gated: it halves the time step
  (0.02 -> 0.01 -> 0.005 -> 0.0025) until two successive steps agree to
  within 0.02 in displacement, and flags any period that does not.
  This is the fix for an earlier under-resolved dt=0.03 scan.

SUPPORTING CHECKS
  python3 sensitivity4.py     # perturbation test at the excursion periods T=8800, 9600
  python3 evolve4.py          # 4th-order Yoshida dt-convergence at fixed T
  python3 dtconv.py           # Strang-vs-Yoshida step-size comparison (imports scan2.py);
                              #   reproduces the -0.2694 / -0.2542 / -2.7950 Strang values
  python3 gate.py             # branch-following displacements (naive theta-stepping)

Note: use evolve4 (4th-order Yoshida), not evolve (2nd-order Strang). Strang
error grows as T*dt^2 -- it is worst in exactly the long-period limit the
physics lives in.
