fix: restore exact Z2 symmetry and harden RBIM random streams - #28
Conversation
Ursache des roten CI-Laufs (Schritt 8 Pytest): das Selftest-Gate G32 hielt y_t/y_h gegen fest verdrahtete Zentralwerte, die unter dem ALTEN, nicht Z2-aequivarianten Majority-Tie-Break gemessen worden waren. Der Fix in ising2d.majority_block_b2 aendert die Blocking-Abbildung absichtlich; y_t/y_h sind Funktionale der geblockten Konfigurationen und verschieben sich zwangslaeufig (bis 1.7e-2 gegen atol=1e-7). Die alte Erwartung behauptete ab dem Fix, eine bewusste Aenderung der RG-Abbildung sei wirkungslos -- sie war falsch, nicht das Ergebnis. Erreicht wird das Gate ueber tests/test_cli_selftest.py (run_selftest ruft alle Gates), daher fiel Pytest und Schritt 9 wurde nur als Folge uebersprungen. Genau ein Gate fiel; G22/G27/G28 gegen das externe Onsager-Orakel blieben unveraendert PASS. - cli.py: Referenz unter dem Z2-aequivarianten Tie-Break neu erhoben, Herkunft und Herleitung im Docstring; die Zahl ist kein nachgezogener Messwert, sondern durch Symmetrie-Orakel, Onsager-Orakel und die Struktur-Invariante gestuetzt. - test_mcrg_multirg.py: die eigentliche Fix-3-Behauptung -- Jackknife- Blockgroesse bewegt NUR Fehlerbalken, nie Zentralwerte -- jetzt snapshot-frei und tie-break-unabhaengig geprueft, inklusive Nicht-Vakuitaets-Zusicherung, dass die beiden Politiken sich real unterscheiden. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
…Beispiel Der PR belegte B(-s) == -B(s) bisher an einzelnen Konfigurationen. Die Eigenschaft ist aber eine Invariante ueber ALLEN s; ein Beispiel-Test greift zu kurz. - exhaustive_2x2: majority_block_b2 wirkt blockweise und unabhaengig, die einzige Gittergroessen-Kopplung ist der Hash ueber (block_row, block_col). Ein Sweep ueber alle 16 moeglichen Blockinhalte, gekreuzt mit allen vier erreichbaren Selektorwerten, ist damit ein vollstaendiger Nachweis auf Blockebene -- 4096 Paare, davon 1536 auf dem Tie-Pfad. Mit Zusicherung, dass alle vier Selektoren und alle 6 Tie-Muster real durchlaufen wurden; ein Sweep, der den reparierten Pfad nie trifft, wuerde sonst ebenfalls bestehen. - randomized_large: dieselbe Invariante auf L=8/16/32 mit festem Generator, deckt die Hash-Indizierung ab; mit Untergrenze fuer die Zahl real getroffener Tie-Bloecke. - Positiv-Kontrolle: Z2-Aequivarianz allein ist auch von B(s)=s[0,0] erfuellt. Daher wird festgehalten, dass Bloecke mit echter Mehrheit hash-unabhaengig das Vorzeichen der Blocksumme liefern -- eine Tie-Regel, die alle Bloecke ueberschreibt, faellt hier durch. Keine neue Abhaengigkeit: hypothesis ist nicht im dev-Extra, der Sweep ist deterministisch erschoepfend. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
Der volle Grundlinien-Lauf (HEAD d18ff72: 5 failed, 377 passed, 31 skipped, 2193.97s, exit 1) zeigte einen fuenften Fehlschlag, den der Runner-Beleg des CI-Laufs vom 16. August nicht sichtbar gemacht hatte: test_fix3_central_values_unchanged trug dieselben hartkodierten Zentralwerte wie das Gate G32. Gleiche Ursache, gleiche Herleitung wie in G32: die Referenz stammt aus der Zeit vor dem Z2-aequivarianten Tie-Break; sie neu zu erheben ist kein Nachziehen an ein Ergebnis, sondern folgt daraus, dass die erzeugende RG-Abbildung absichtlich geaendert wurde. Belegt durch Symmetrie-Orakel, externes Onsager-Orakel und die snapshot-freie Struktur-Invariante. Geprueft: kein weiteres Vorkommen der alten Werte in src/ oder tests/. Die committeten Ergebnis-Artefakte unter results/ enthalten sie noch, werden aber von keinem Test und keinem CI-Schritt gelesen -- ihre Regeneration ist eine Promotions-Entscheidung und bleibt bewusst offen. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
…e behebt Block C ergab: die Seed-Umstellung ist im PR bereits nach Stand der Technik umgesetzt -- rbim_scan.py:92-95 nutzt SeedSequence(entropy) + spawn(2) statt arithmetischer Ableitung, p geht bitgenau als float64-Woerter in die Stream-Identitaet ein, und CRN ist nur ueber die benannte Option seed_policy="common_random_numbers" erreichbar. Nichts nachzubauen. Damit standen aber zwei Doku-Stellen im Widerspruch zum Code: AGENTS.md und docs/ROADMAP.md fuehrten den nicht-Z2-aequivarianten Tie-Break und die impliziten gemeinsamen Seeds weiter als bekannte Limitation. Beide Stellen sagen jetzt den Ist-Zustand -- einschliesslich dessen, was NICHT erledigt ist: die committeten Artefakte unter results/ tragen weiterhin die Vor-Fix-Zahlen. Zusaetzlich in der ROADMAP festgehalten, was beim Pruefen der unangetasteten Baseline auffiel: rbim_nishimori.py:474/480 leitet Bond-Seeds als base_seed + d und MCMC-Seeds als base_seed + 10000 + d ab. Ab n_disorder >= 10001 ueberlappen die beiden Familien exakt (nachgerechnet: bei base_seed=1000 nutzt d=10000 den Bond-Seed 11000, identisch mit dem MCMC-Seed von d=0); n_disorder ist nur gegen < 1 geprueft. Bewusst NICHT still gefixt -- der PR haelt die historische Baseline unveraendert, die Aenderung waere eine eigene Entscheidung. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 35a602e901
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
…sehen Review-Befund B2, reproduziert: majority_block_b2 castete die Eingabe in ising2d.py:228 nach int64, BEVOR die dokumentierte +/-1-Pruefung lief. Damit wurde eine Konfiguration aus 1.5/-1.5 stillschweigend zu 1/-1 abgeschnitten und akzeptiert -- gemessen lieferte sie out=[[1]] statt des dokumentierten Fehlers. Die Funktion blockte also veraenderte Daten, statt sie abzuweisen. Nebenbefund aus derselben Zeile: NaN und inf wurden zwar abgewiesen, aber nur zufaellig -- ueber einen undefinierten float->int64-Cast, der eine RuntimeWarning wirft, nicht ueber die Pruefung. Nach dem Fix laeuft der Fall unter -W error::RuntimeWarning sauber durch die dokumentierte Meldung. Die Pruefung arbeitet jetzt auf der unveraenderten Eingabe; der Cast folgt erst nach bestandener Pruefung. Das ist dieselbe Klasse, gegen die dieser PR insgesamt antritt: eine Kontrolle, die ins Leere greift. Tests: ein parametrisierter Negativfall ueber 1.5/-1.5, 0.5, 0.0, 2/-2, 0.9999999999, NaN und inf, der breit faengt und den TYP pruefet (ValueError plus die dokumentierte Meldung), damit ein Tod an einer fremden Ausnahme nicht als Erfolg durchgeht. Dazu eine Positiv-Kontrolle: float64, float32, int8 und rohe Listen mit gueltigen +/-1-Werten liefern weiter dasselbe Ergebnis -- ein Validator, der jede float-Eingabe ablehnt, bestuende sonst jeden Negativtest. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
…ht mehr gibt
Review-Befund B1. Ich hatte diese Artefakte als "von keinem Test gelesen"
abgetan -- das stimmt, trifft aber nicht den Punkt: results/README.md
deklariert sie ausdruecklich als reproduzierbare Gate-Logs und nennt je
einen Regenerationsbefehl. Ein Log, das eine Blocking-Regel beschreibt, die
es im selben Commit nicht mehr gibt, ist irrefuehrend, auch ohne Leser im
Testpfad. Weg (a) gewaehlt: regenerieren statt historisieren, damit die
Belege wieder aus diesem Commit reproduzierbar sind.
Betroffen sind genau drei Artefakte, nicht die zwei genannten. Ermittelt
statt geschaetzt: majority_block_b2 wird in src/ ausschliesslich von
mcrg_matrix und mcrg_multirg aufgerufen, dazu kommt der Gate-Log selbst.
Regeneriert aus diesem Stand, je mit gemessener Laufzeit:
python -m adaptiverg_qec.mcrg_matrix 18 s
python -m adaptiverg_qec.mcrg_multirg 135 s
python -m adaptiverg_qec.cli --selftest --json results/selftest.json
414 s, 45/45 PASS
Gegenprobe: die sechs Vor-Fix-Zentralwerte kommen im gesamten Arbeitsbaum
nicht mehr vor (grep ueber alles ausser .git/.venv, kein Treffer).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
Die Diskriminierungs-Probe (run_id DK-20260910T171938-7be6e1323152) stufte die Positiv-Kontrolle als "ROT DURCH ABSTURZ:ValueError -- kein Beleg" ein. Zu Recht: unter der Mutation "Validator lehnt jede float-Eingabe ab" starb der Test an der Ausnahme aus dem Aufruf, nicht an einer Zusicherung. Ein abgestuerzter Test ist nicht einzuordnen -- er beweist nicht, dass die geprueft Eigenschaft verletzt wurde, sondern nur, dass irgendetwas warf. Die Ablehnung wird jetzt in eine AssertionError mit Kontext uebersetzt (welche Variante, welcher Ausnahmetyp). Damit ist die Kontrolle als Beleg verwendbar. Dass mein eigener Test an genau der Schwaeche litt, die dieser PR bekaempft -- eine Kontrolle, die im Ernstfall nichts aussagt -- gehoert in den Verlauf. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb
Antwort auf die beiden Review-Befunde — beide behobenStand
|
| Mutation | test_..._rejects_non_spin_values_before_casting |
test_..._still_accepts_valid_spins_in_any_container |
|---|---|---|
| restauriert | exit 0 | exit 0 |
| Cast-vor-Pruefung wiederhergestellt | exit 1 | exit 0 |
| Validator lehnt jede float-Eingabe ab | exit 0 (blind) | exit 1 |
Die dritte Zeile ist der Grund, warum die Positiv-Kontrolle Pflicht ist: gegen einen Validator, der alles ablehnt, ist der Negativtest blind. Er waere gruen geblieben, und der Waechter waere trotzdem kaputt gewesen.
Der erste Ledger-Lauf war ehrlicherweise 1/2 — die Positiv-Kontrolle starb an einer Ausnahme statt an einer Zusicherung („ROT DURCH ABSTURZ:ValueError — kein Beleg") und war damit als Beleg wertlos. Sie uebersetzt die Ablehnung jetzt in eine AssertionError mit Variante und Ausnahmetyp.
docs/ROADMAP.md:152 — Evidenz-Artefakte regeneriert (P2)
Zugestimmt, und zwar gegen die eigene fruehere Einschaetzung: „von keinem Test gelesen" stimmt zwar, trifft aber den Punkt nicht — results/README.md deklariert diese Dateien ausdruecklich als reproduzierbare Gate-Logs mit Regenerationsbefehl. Ein Log, das eine Transformation beschreibt, die es im selben Commit nicht mehr gibt, ist irrefuehrend, auch wenn kein Test es liest.
Betroffen sind genau drei Artefakte, ermittelt statt geschaetzt (majority_block_b2 wird in src/ nur von mcrg_matrix und mcrg_multirg aufgerufen, dazu der Gate-Log selbst): phase3b-swendsen-matrix.json, phase4-wolff-multirg.json, selftest.json. Alle drei regeneriert.
Reproduzierbarkeit belegt, nicht behauptet: dieselben Befehle nach dem Commit erneut gefahren — phase3b und phase4 sind byte-identisch (erscheinen gar nicht im git diff), selftest.json unterscheidet sich ausschliesslich im Laufzeitfeld (elapsed_s), die Nutzlast ohne dieses Feld ist identisch, 45/45 PASS. Gegenprobe: die sechs Vor-Fix-Zentralwerte kommen im gesamten Arbeitsbaum nicht mehr vor.
Was ausserhalb dieses PR bleibt
Bei der Suche nach weiteren Stellen derselben Klasse (verengender Cast vor Wertpruefung) wurde keine zweite gefunden. Zwei Geschwisterstellen mit schwaecherem Problem sind bewusst unangetastet geblieben: rbim_nishimori.py:242 und wolff2d.py:106 casten Aufrufer-Daten nach int8 und haben gar keine Plus-Minus-Eins-Pruefung — das ist eine dokumentierte Vorbedingung ohne Kontrolle, eine andere Klasse als „Kontrolle greift ins Leere", und gehoert in einen eigenen PR.
Herkunft der Zahlen in diesem Kommentar
Selbst in diesem Lauf abgelesen: der PR-Head 5cc14f0 und der Status der acht bereits gruenen Pflicht-Kontexte.
Aus dem Bau-Lauf uebernommen, von mir nicht eigenhaendig nachgemessen: die Reproduktion out=[[1]], saemtliche Exit-Codes der Mutationstabelle, die Ledger-Kennungen, 45/45, die Byte-Identitaet der regenerierten Artefakte und die Laufzeiten. Der belastbarste Beleg bleibt der CI-Lauf dieses PR auf einem echten Runner.
🤖 Generated with Claude Code
Anlass
Kritisches Audit nach Phase 6. Zwei voneinander getrennte Reproduzierbarkeits-/Korrektheitsrisiken wurden bestätigt:
sund-sdasselbe Vorzeichen, daher galt auf dem Tie-PfadB(-s) != -B(s).base_seed + d/base_seed + 10000 + dab. Damit werden dieselben zugrunde liegenden Streams über verschiedene p-Punkte wiederverwendet. Das kann als Common-Random-Numbers-(CRN)-Design sinnvoll sein, darf aber nicht implizit passieren.Änderungen
Exakte Z2-Symmetrie
majority_block_b2: Tie wird jetzt durch deterministische Auswahl eines der vier Originalspins auf Basis eines SplitMix64-artigen Block-Hashes aufgelöst. Derselbe physische Input-Slot wird fürsund-sgewählt; damit gilt konstruktivB(-s) == -B(s).RBIM-Seed-Policy als additives Baseline-Inkrement
rbim_scan.py; die historischerbim_nishimori.py-Baseline bleibt unverändert und reproduzierbar.seed_policy="independent":base_seed,p,Lund Replikat-ID werden über NumPySeedSequencegemischt; Bond- und Thermal-Streams werden anschließend mitspawn(2)getrennt.seed_policy="common_random_numbers":pwird bewusst aus der Stream-Identität entfernt, sodass CRN nur noch absichtlich aktiviert werden kann.pwird bitgenau als float64-Wörter in die Seed-Identität aufgenommen; kein instabiles String-Hashing.Packaging / Repository-Metadaten
license = "Apache-2.0",license-files = ["LICENSE"]).>=77.0.3, passend zur PEP-639-Unterstützung.Methodische Begründung
NumPy empfiehlt
SeedSequence/spawn()bzw. hierarchisch gemischte Stream-IDs für reproduzierbare, mit sehr hoher Wahrscheinlichkeit nicht überlappende Zufallsströme. Die neue Implementierung macht die statistische Designentscheidung explizit: unabhängige Scanpunkte sind der Default; CRN ist eine opt-in Varianzreduktionsstrategie, deren Kovarianz in der Auswertung berücksichtigt werden muss.Für die nächste QEC-Stufe bleibt die etablierte Richtung Stim
DetectorErrorModel->PyMatching.Matching.from_detector_error_model(...); kein eigener circuit-level Decoder.Verifikation / Audit-Verlauf
B(-s) == -B(s)für 100 zufällige 16x16-Konfigurationen; All-Tie-Quote +1 = 0.50001953125.ruff format --checkvor Pytest/Selftests.Bewusst noch offen
Die PR bleibt Draft, bis der aktuelle vollständige CI-/Selftest-Lauf grün ist und die durch den Z2-Fix betroffenen Referenzartefakte bewusst neu bewertet bzw. versioniert wurden.