Skip to content

fix: restore exact Z2 symmetry and harden RBIM random streams - #28

Merged
marcohost33-maker merged 15 commits into
mainfrom
codex/qec-hardening-z2-seeds
Sep 10, 2026
Merged

fix: restore exact Z2 symmetry and harden RBIM random streams#28
marcohost33-maker merged 15 commits into
mainfrom
codex/qec-hardening-z2-seeds

Conversation

@marcohost33-maker

@marcohost33-maker marcohost33-maker commented Aug 16, 2026

Copy link
Copy Markdown
Owner

Anlass

Kritisches Audit nach Phase 6. Zwei voneinander getrennte Reproduzierbarkeits-/Korrektheitsrisiken wurden bestätigt:

  1. Der Majority-Tie-Break war nur im Mittel 50/50, aber nicht exakt Z2-äquivariant. Bei einem 2+2-Tie lieferte der Hash für s und -s dasselbe Vorzeichen, daher galt auf dem Tie-Pfad B(-s) != -B(s).
  2. Die historische RBIM-Baseline leitet an jedem p-Punkt Seeds arithmetisch als base_seed + d / base_seed + 10000 + d ab. 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ür s und -s gewählt; damit gilt konstruktiv B(-s) == -B(s).
  • Zusätzliche Eingangsvalidierung für Spinwerte und Resultatcontainer.
  • Numerisch stabilere Log-Gewichte in der 2x2-Enumeration.
  • Regressionstests für exakte Z2-Äquivarianz auf All-Tie- und gemischten Konfigurationen sowie die Invariante, dass der Tie-Output aus dem jeweiligen 2x2-Block stammt.

RBIM-Seed-Policy als additives Baseline-Inkrement

  • Neue Datei rbim_scan.py; die historische rbim_nishimori.py-Baseline bleibt unverändert und reproduzierbar.
  • Default seed_policy="independent": base_seed, p, L und Replikat-ID werden über NumPy SeedSequence gemischt; Bond- und Thermal-Streams werden anschließend mit spawn(2) getrennt.
  • Explizite Option seed_policy="common_random_numbers": p wird bewusst aus der Stream-Identität entfernt, sodass CRN nur noch absichtlich aktiviert werden kann.
  • Float-p wird bitgenau als float64-Wörter in die Seed-Identität aufgenommen; kein instabiles String-Hashing.
  • Tests für Reproduzierbarkeit, p-spezifische Unabhängigkeit, bewusstes CRN-Sharing, getrennte Bond/Thermal-Streams, L-/Replikat-Trennung, 1000 deterministisch kollisionsfreie Kindstreams, Eingabevalidierung und einen kleinen reproduzierbaren Scan-Smoke-Test.

Packaging / Repository-Metadaten

  • PEP-639/SPDX-Metadaten (license = "Apache-2.0", license-files = ["LICENSE"]).
  • Setuptools-Untergrenze >=77.0.3, passend zur PEP-639-Unterstützung.
  • Repository-/Issue-URLs und korrigierter Ruff-Kommentar.

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

  • Unabhängiger Invarianten-Stresstest: B(-s) == -B(s) für 100 zufällige 16x16-Konfigurationen; All-Tie-Quote +1 = 0.50001953125.
  • Erster CI-Lauf nach Z2-Fix: Surface-MWPM-Job PASS, zizmor PASS, Encoding Guard PASS, pip-audit PASS; Hauptjob stoppte ausschließlich an ruff format --check vor Pytest/Selftests.
  • Der Formatfehler wurde anschließend exakt nach Ruff-Ausgabe korrigiert; neue CI wurde durch die weiteren Commits neu gestartet.

Bewusst noch offen

  • Vollständige Finite-Size-Scaling-Pipeline mit mehreren L, Binder-Crossings, Disorder-Bootstrap und Scaling-Collapse.
  • Regeneration/versionierte Promotion der Phase-3b/4-Goldartefakte nach geändertem Tie-Break.
  • τ_int-Clamp-Konvention und G26-Zeiteinheiten.
  • Defensive Mixture, SNIS auf 2D/RBIM, DA×Diminishing Adaptation, MMD-Drift.
  • phenomenological/circuit-level noise über Stim DEM + PyMatching.

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.

@marcohost33-maker marcohost33-maker added the agent:codex PR authored by OpenAI Codex label Aug 16, 2026 — with ChatGPT Codex Connector
@marcohost33-maker marcohost33-maker changed the title fix: restore exact Z2 symmetry in RG blocking and harden packaging fix: restore exact Z2 symmetry and harden RBIM random streams Aug 16, 2026
marcohost33-maker and others added 6 commits August 16, 2026 22:54
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
@marcohost33-maker
marcohost33-maker marked this pull request as ready for review September 10, 2026 16:41

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread docs/ROADMAP.md Outdated
Comment thread src/adaptiverg_qec/ising2d.py Outdated
marcohost33-maker and others added 3 commits September 10, 2026 18:58
…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
@marcohost33-maker

Copy link
Copy Markdown
Owner Author

Antwort auf die beiden Review-Befunde — beide behoben

Stand 5cc14f0.

ising2d.py:228 — Validierung vor Cast (P2)

Der Befund war richtig und liess sich direkt reproduzieren: majority_block_b2([[1.5,-1.5],[1.5,-1.5]]) wurde akzeptiert und lieferte out=[[1]]. Der Cast machte ungueltige Werte gueltig, bevor die Pruefung sie sehen konnte.

Behoben: die Pruefung laeuft jetzt auf dem unveraenderten s_in = np.asarray(s), der Cast auf int64 folgt erst nach bestandener Pruefung; die Shape-/L-Pruefungen ebenso.

Nebenbefund aus derselben Zeile, der ohne den Fix unsichtbar geblieben waere: NaN und inf wurden bisher nur zufaellig abgewiesen — ueber einen undefinierten Cast mit RuntimeWarning, nicht ueber die Pruefung. Unter -W error::RuntimeWarning laufen sie jetzt sauber in die dokumentierte Fehlermeldung. Eine Kontrolle, die nur durch einen Nebeneffekt der Implementierung greift, ist keine Kontrolle.

Diskriminierung, beide Richtungen (Ledger DK-20260910T172058-2e05893c4b41, head 5cc14f0, provenienz_klasse VOLL, 2/2):

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

https://claude.ai/code/session_01VG6CeFJVgem6n4Tsam1onb

@marcohost33-maker
marcohost33-maker merged commit 5b10c9c into main Sep 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent:codex PR authored by OpenAI Codex

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant