feat: add numeric and symbolic scripts
This commit is contained in:
parent
6ea7900b55
commit
e80b7c3582
38 changed files with 3314 additions and 0 deletions
183
scripts/symmetry_oracle.py
Normal file
183
scripts/symmetry_oracle.py
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
# symmetry_oracle.sage
|
||||
#
|
||||
# Zwei unabhaengige, computergestuetzte "Symmetrie-Orakel" fuer den vollen
|
||||
# Bloch-Tensor C(rho) eines Graphzustands, angewandt auf einen Schnitt S | S^c.
|
||||
# Verallgemeinert das von Hand gerechnete Ring-Graphzustand-Reflexions-Beispiel
|
||||
# (Section 6 des Papers) zu einem Werkzeug, das man auf beliebige Graphen mit
|
||||
# n <~ 6-8 Knoten anwenden kann.
|
||||
#
|
||||
# (A) Stabilisator-Mechanismus (Lemma "stabilizer-degeneracy"):
|
||||
# exakte GF(2)-symplektische Rechnung an den Stabilisatorerzeugern
|
||||
# K_v = X_v * prod_{u ~ v} Z_u. Liefert -- wenn die Injektivitaetshypothese
|
||||
# fuer psi erfuellt ist -- Singulaerwert und Vielfachheit von M_tilde_S(rho_H)
|
||||
# in geschlossener Form, ohne jede Numerik.
|
||||
#
|
||||
# (B) Schwache (cut-faktorisierende) Symmetrie-Hypothese (Prop. "block-diagonal"):
|
||||
# H = Stab_{Aut(G)}(S) (setwise), gefunden durch direkte Enumeration von
|
||||
# Aut(G) (fuer n<=6-8 unproblematisch). Die induzierte Permutationsdarstellung
|
||||
# von H auf S wird ueber die Charaktertafel in Aut-Irreduzible zerlegt --
|
||||
# das sagt voraus, in welche Isotypen-Bloecke M_S zerfaellt (nicht die
|
||||
# Singulaerwerte selbst, die bleiben zustandsabhaengig).
|
||||
#
|
||||
# Die beiden Mechanismen sind gemaess Remark "two-mechanisms" im Paper
|
||||
# unabhaengig und werden hier bewusst getrennt und gegeneinander gegengeprueft,
|
||||
# nicht kombiniert.
|
||||
#
|
||||
# Ausfuehren mit: sage symmetry_oracle.sage
|
||||
#
|
||||
# HINWEIS: Dieses Skript wurde ohne Zugriff auf eine laufende Sage-Instanz
|
||||
# geschrieben (reines Nachrechnen von Hand als Validierung, siehe unten). Die
|
||||
# Mathematik ist geprueft; falls eine einzelne Sage-Methode in eurer Version
|
||||
# anders heisst, sollte der eingebaute Konsistenz-Check (assert) das sofort
|
||||
# anzeigen statt still falsche Zahlen zu liefern.
|
||||
|
||||
from sage.all import *
|
||||
|
||||
|
||||
def graph_state_generator_matrix(G):
|
||||
"""
|
||||
n x 2n GF(2)-Matrix [I | A], deren Zeilen die symplektischen Vektoren der
|
||||
Stabilisatorerzeuger K_v = X_v * prod_{u ~ v} Z_u sind.
|
||||
Konvention: Spalten 0..n-1 = X-Anteile, n..2n-1 = Z-Anteile (wie im Paper,
|
||||
Lemma "code-support").
|
||||
"""
|
||||
n = G.num_verts()
|
||||
A = G.adjacency_matrix().change_ring(GF(2))
|
||||
I = identity_matrix(GF(2), n)
|
||||
return I.augment(A), n
|
||||
|
||||
|
||||
def restrict_columns(Gen, S, n):
|
||||
cols = sorted(S) + [n + v for v in sorted(S)]
|
||||
return Gen.matrix_from_columns(cols)
|
||||
|
||||
|
||||
def stabilizer_mechanism(G, S):
|
||||
"""Mechanismus (A), siehe Kopfkommentar."""
|
||||
Gen, n = graph_state_generator_matrix(G)
|
||||
S = list(S)
|
||||
Sc = [v for v in G.vertices() if v not in S]
|
||||
Gen_S = restrict_columns(Gen, S, n) # phi: Restriktion auf S (Quelle)
|
||||
Gen_Sc = restrict_columns(Gen, Sc, n) # psi: Restriktion auf S^c (Ziel)
|
||||
rank_phi, rank_psi = Gen_S.rank(), Gen_Sc.rank()
|
||||
ker_phi_dim, ker_psi_dim = n - rank_phi, n - rank_psi
|
||||
|
||||
result = {
|
||||
"S": S, "Sc": Sc, "n": n,
|
||||
"dim_ker_phi": ker_phi_dim, "dim_im_phi": rank_phi,
|
||||
"dim_ker_psi": ker_psi_dim, "dim_im_psi": rank_psi,
|
||||
"psi_injective": (ker_psi_dim == 0),
|
||||
}
|
||||
if result["psi_injective"]:
|
||||
dS, dSc = 2 ** len(S), 2 ** len(Sc) # Qubit-Fall, d=2 pro Partei
|
||||
ker_phi_size = 2 ** ker_phi_dim
|
||||
im_phi_size = 2 ** rank_phi
|
||||
mult = im_phi_size - 1
|
||||
sv = sqrt(QQ(ker_phi_size) / QQ((dS - 1) * (dSc - 1)))
|
||||
result["singular_value"] = sv
|
||||
result["multiplicity"] = mult
|
||||
result["nuclear_norm_contribution"] = mult * sv
|
||||
return result
|
||||
|
||||
|
||||
def weak_hypothesis_prediction(G, S):
|
||||
"""Mechanismus (B), siehe Kopfkommentar."""
|
||||
Aut = G.automorphism_group()
|
||||
S = list(S)
|
||||
S_frozen = frozenset(S)
|
||||
# H = Stab_{Aut(G)}(S) setwise, durch direkte Enumeration (robust, |Aut(G)|
|
||||
# ist fuer n<=6-8 klein genug, dass das kein Performanceproblem ist).
|
||||
stab_elements = [g for g in Aut if frozenset(g(v) for v in S) == S_frozen]
|
||||
H = PermutationGroup(stab_elements)
|
||||
|
||||
reps = H.conjugacy_classes_representatives()
|
||||
sizes = [len({h * g * h ** (-1) for h in H}) for g in reps]
|
||||
order = H.order()
|
||||
CT = H.character_table()
|
||||
|
||||
id_idx = [i for i, g in enumerate(reps) if g == H.one()][0]
|
||||
degrees = [CT[i, id_idx] for i in range(CT.nrows())]
|
||||
assert sum(d ** 2 for d in degrees) == order, \
|
||||
"Konsistenz-Check fehlgeschlagen: Summe der Quadrate der Irrep-Dimensionen != |H|."
|
||||
|
||||
perm_char = [sum(1 for v in S if g(v) == v) for g in reps]
|
||||
|
||||
decomposition = []
|
||||
for i in range(CT.nrows()):
|
||||
chi = [CT[i, j] for j in range(len(reps))]
|
||||
mult = sum(sizes[j] * perm_char[j] * chi[j].conjugate()
|
||||
for j in range(len(reps))) / order
|
||||
if mult != 0:
|
||||
decomposition.append((mult, degrees[i]))
|
||||
|
||||
# Zweiter, von der Spaltenreihenfolge unabhaengiger Konsistenz-Check:
|
||||
# sum_lambda m_lambda * dim(V_lambda) muss = dim(Perm(S)) = |S| sein.
|
||||
# Schlaegt dieser Check fehl, stimmt die Zuordnung reps <-> CT-Spalten
|
||||
# nicht ueberein (dann bitte melden statt der Ausgabe zu trauen).
|
||||
total_dim = sum(m * d for m, d in decomposition)
|
||||
assert total_dim == len(S), (
|
||||
f"Konsistenz-Check fehlgeschlagen: sum(m*dim) = {total_dim} != |S| = {len(S)}. "
|
||||
"Vermutlich Reihenfolge-Mismatch zwischen conjugacy_classes_representatives() "
|
||||
"und character_table()-Spalten -- bitte melden, dann fixen wir das gemeinsam."
|
||||
)
|
||||
|
||||
return {"H": H, "order": order, "decomposition": decomposition}
|
||||
|
||||
|
||||
def report(G, S, label):
|
||||
print("=" * 70)
|
||||
print(f"{label}: Schnitt S={sorted(S)} | S^c={[v for v in G.vertices() if v not in S]}")
|
||||
print("=" * 70)
|
||||
|
||||
stab = stabilizer_mechanism(G, S)
|
||||
print("\n-- (A) Stabilisator-Mechanismus --")
|
||||
print(f" dim ker(phi) = {stab['dim_ker_phi']}, dim im(phi) = {stab['dim_im_phi']}")
|
||||
print(f" dim ker(psi) = {stab['dim_ker_psi']}, dim im(psi) = {stab['dim_im_psi']}")
|
||||
if stab["psi_injective"]:
|
||||
sv, mult, contrib = stab["singular_value"], stab["multiplicity"], stab["nuclear_norm_contribution"]
|
||||
print(" psi injektiv -> Lemma greift exakt.")
|
||||
print(f" Singulaerwert = {sv} (numerisch {float(sv):.6f})")
|
||||
print(f" Vielfachheit = {mult}")
|
||||
print(f" Beitrag zur Nuklearnorm = {contrib} (numerisch {float(contrib):.6f})")
|
||||
else:
|
||||
print(" psi NICHT injektiv -> Lemma greift nicht direkt auf den vollen M_S-Block;")
|
||||
print(" der Ueberschuss sitzt in tieferen Sektoren (vgl. Diskussion des")
|
||||
print(" diagonalen Schnitts in Section 6 des Papers).")
|
||||
|
||||
weak = weak_hypothesis_prediction(G, S)
|
||||
print("\n-- (B) Schwache Symmetrie-Hypothese (Aut(G)-Stabilisator) --")
|
||||
print(f" H = Stab_Aut(G)(S), |H| = {weak['order']}")
|
||||
print(" Isotypenzerlegung von Perm(S) unter H (Multiplizitaet, Dimension):")
|
||||
for mult, deg in weak["decomposition"]:
|
||||
print(f" m={mult}, dim={deg} -> {mult} Kopie(n) eines {deg}-dim. Blocks,")
|
||||
print(" je x3 fuer die interne Pauli-Richtung (x,y,z)")
|
||||
print()
|
||||
|
||||
|
||||
# --- Validierung an einem bekannten Fall: der Ring-Graphzustand aus dem Paper ---
|
||||
|
||||
ring = Graph({0: [1, 3], 1: [0, 2], 2: [1, 3], 3: [0, 2]}) # 4-Zyklus 0-1-2-3-0
|
||||
|
||||
report(ring, [0, 1], "Ring-Graphzustand, 'benachbarter' Schnitt")
|
||||
report(ring, [0, 2], "Ring-Graphzustand, 'diagonaler' Schnitt")
|
||||
|
||||
# Erwartung (Tabelle zum Ring-Graphzustand im Paper, dort 1-indiziert als
|
||||
# {1,2}|{3,4} bzw. {1,3}|{2,4}, hier 0-indiziert):
|
||||
#
|
||||
# benachbarter Schnitt {0,1}|{2,3}:
|
||||
# (A) psi injektiv, Nuklearnorm-Beitrag = 5 <-- sollte exakt 5 ausgeben
|
||||
# (B) H = <(0 1)(2 3)> ~= Z_2, Perm(S) = trivial + sign, je Multiplizitaet 1
|
||||
# (das ist exakt die im Paper von Hand hergeleitete Zerlegung in die
|
||||
# symmetrische/antisymmetrische Kombination (e_x^(1) +- e_x^(2))/sqrt(2))
|
||||
#
|
||||
# diagonaler Schnitt {0,2}|{1,3}:
|
||||
# (A) psi NICHT injektiv, weil X_0 X_2 in H vollstaendig auf S getragen ist
|
||||
# -- passend zur Bemerkung im Paper, dass hier der volle Sektor nur
|
||||
# saettigt und der Ueberschuss aus tieferen Sektoren kommt.
|
||||
|
||||
# --- Eigenes Beispiel: hier einen n=5/6-Graphen eintragen ---
|
||||
eigener_graph = Graph({0: [1, 2], 1: [0, 2, 3], 2: [0, 1, 4], 3: [1, 4], 4: [2, 3]})
|
||||
report(eigener_graph, [0, 1], "eigenes Beispiel")
|
||||
|
||||
star = Graph({0: [1,2,3]}) # Stern: Zentrum 0, Blätter 1,2,3
|
||||
report(star, [1,2,3], "Stern, S = Blätter")
|
||||
Loading…
Add table
Add a link
Reference in a new issue