diff --git a/.gitignore b/.gitignore index 2936858..c340f2b 100644 --- a/.gitignore +++ b/.gitignore @@ -332,3 +332,228 @@ TSWLatexianTemp* paper/*.pdf +.docs_mcp/ +julia/ + +#### Python + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +*.lcov +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +# Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +# poetry.lock +# poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +# pdm.lock +# pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +# pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi/* +!.pixi/config.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule* +celerybeat.pid + +# Redis +*.rdb +*.aof +*.pid + +# RabbitMQ +mnesia/ +rabbitmq/ +rabbitmq-data/ + +# ActiveMQ +activemq-data/ + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +# .idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ +# Temporary file for partial code execution +tempCodeRunnerFile.py + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml \ No newline at end of file diff --git a/paper/shadow_maps_symmetric_states.tex b/paper/shadow_maps_symmetric_states.tex index c75c772..6fcb251 100644 --- a/paper/shadow_maps_symmetric_states.tex +++ b/paper/shadow_maps_symmetric_states.tex @@ -731,46 +731,132 @@ independent of which cut's matricization is subsequently taken. $T(\rho)(\vec\imath)=\tr(\rho\,\sigma_{\vec\imath})$ is linear in $\rho$; substituting $\rho=\sum_{a,b}c_{ab}\ketbra{\phi_a}{\phi_b}$ and using $\tr(\ketbra{\phi_a}{\phi_b}\sigma_{\vec\imath})=\bra{\phi_b}\sigma_{\vec\imath}\ket{\phi_a}=T_{ba}(\vec\imath)$ gives Eq.~\eqref{eq:general-coherence-decomposition} termwise; the cut dependence enters only afterward, through the reshape of the index set $\vec\imath$, and does not affect the coefficients $c_{ab}$ or the tensors $T_{ab}$. \end{proof} -% TODO (open / not yet carried out): Proposition~\ref{prop:coherence-templates} -% is stated and used above only for r=2 real generators (psi_1, psi_2), where -% the single real cross term C_{12} suffices because the c_{ab} happen to be -% real and symmetric in that example. For general r this requires r(r+1)/2 -% independent real tensors (r diagonal T_aa plus the real/imaginary parts of -% the r(r-1)/2 off-diagonal overlaps, or equivalently just track the full -% Hermitian r x r coefficient matrix c_{ab} against the T_{ab} disregarding -% the a<->b symmetry) -- this is mechanical but has not been written out or -% numerically tested here for r>2. Do this before submission if the general -% statement is kept; otherwise restate the Proposition for r=2 only and move -% the general case to a remark. +\begin{corollary}[Real-tensor count, corrected] +\label{cor:real-tensor-count} +Since $\sigma_{\vec\imath}$ is Hermitian, $T_{ba}=\overline{T_{ab}}$; in particular each $T_{aa}$ is real. Writing $T_{ab}=P_{ab}+iQ_{ab}$ for $a tensor axes 0..5. +S1 = ABC | DEF (3|3 cut) +S2 = AB | CDEF (2|4 cut) + +We build the two 'basis' states + |psi1> = singlets (A,D)(B,E)(C,F) + |psi2> = singlets (A,E)(B,F)(C,D) +and the family + |Xi(alpha)> = (cos(a) psi1 + sin(a) psi2) / norm + +For rho_Xi = |Xi>, T2 = , C12 = + , +N2 = . + +Key point: T1, T2, C12 do NOT depend on alpha or on the cut. +Once computed ONCE (full 6-leg tensors), every cut's shadow-map block +for every alpha is obtained by (i) taking this fixed linear combination +of THREE fixed numbers times three fixed tensors, and (ii) a plain +numpy .reshape() -- no further contraction over the 64-dim Hilbert space. + +This script verifies that against two independent brute-force +quantum simulations (cut S1 and cut S2, both done from scratch). +""" + +import numpy as np + +# ---------- Pauli matrices ---------- +X = np.array([[0,1],[1,0]], dtype=complex) +Y = np.array([[0,-1j],[1j,0]], dtype=complex) +Z = np.array([[1,0],[0,-1]], dtype=complex) +paulis = [X, Y, Z] + +# ---------- build the two basis states (6-qubit amplitude tensors) ---------- +def s(a,b): + if (a,b) == (0,1): return 1/np.sqrt(2) + if (a,b) == (1,0): return -1/np.sqrt(2) + return 0.0 + +def build_pairing(pairs): + psi = np.zeros((2,)*6, dtype=complex) + for idx in np.ndindex(2,2,2,2,2,2): + val = 1.0 + for (p,q) in pairs: + val *= s(idx[p], idx[q]) + if val == 0: break + psi[idx] = val + return psi + +psi1 = build_pairing([(0,3),(1,4),(2,5)]) # (A,D)(B,E)(C,F) +psi2 = build_pairing([(0,4),(1,5),(2,3)]) # (A,E)(B,F)(C,D) +overlap = np.vdot(psi1, psi2) +print("overlap =", overlap) + +def apply_pauli_leg(psi, axis, P): + psi2 = np.moveaxis(psi, axis, 0) + out = np.tensordot(P, psi2, axes=([1],[0])) + return np.moveaxis(out, 0, axis) + +def corr_tensor(bra, ket): + """, all six legs active (i in {0,1,2}=x,y,z).""" + c = np.zeros((3,3,3,3,3,3), dtype=complex) + for iA in range(3): + for iB in range(3): + for iC in range(3): + for iD in range(3): + for iE in range(3): + for iF in range(3): + ket_ = ket + for axis,ii in zip(range(6),(iA,iB,iC,iD,iE,iF)): + ket_ = apply_pauli_leg(ket_, axis, paulis[ii]) + c[iA,iB,iC,iD,iE,iF] = np.vdot(bra, ket_) + return c + +print("computing T1 = ...") +T1 = corr_tensor(psi1, psi1) +print("computing T2 = ...") +T2 = corr_tensor(psi2, psi2) +print("computing cross term ...") +X12 = corr_tensor(psi1, psi2) +X21 = corr_tensor(psi2, psi1) +C12 = X12 + X21 + +print("max imag part T1,T2,C12:", + np.abs(T1.imag).max(), np.abs(T2.imag).max(), np.abs(C12.imag).max()) +T1, T2, C12 = T1.real, T2.real, C12.real + +np.save("T1.npy", T1); np.save("T2.npy", T2); np.save("C12.npy", C12) + +# ---------- ground truth: brute-force Xi(alpha) for a couple of alphas, both cuts ---------- +alpha_list = [np.pi/5, 0.9, -0.3] + +def build_Xi(alpha): + raw = np.cos(alpha)*psi1 + np.sin(alpha)*psi2 + n = np.linalg.norm(raw) + return raw/n, n**2 + +def predict_from_basis(alpha): + N2 = 1 + np.sin(2*alpha)*overlap.real + return (np.cos(alpha)**2*T1 + np.sin(alpha)**2*T2 + np.cos(alpha)*np.sin(alpha)*C12) / N2 + +max_err_cut1 = 0.0 +max_err_cut2 = 0.0 +for a in alpha_list: + Xi, N2_check = build_Xi(a) + Tgt = corr_tensor(Xi, Xi).real # brute-force ground truth, full simulation + Tpred = predict_from_basis(a) # from the 3 fixed tensors, no new simulation + + err = np.abs(Tgt - Tpred).max() + print(f"alpha={a:+.4f}: max|T_bruteforce - T_predicted| = {err:.3e} (N2 check: {N2_check:.6f})") + + # cut 1: ABC|DEF (27x27) + M1_true = Tgt.reshape(27,27) + M1_pred = Tpred.reshape(27,27) + e1 = np.abs(M1_true - M1_pred).max() + max_err_cut1 = max(max_err_cut1, e1) + + # cut 2: AB|CDEF (9x81) + M2_true = Tgt.reshape(9,81) + M2_pred = Tpred.reshape(9,81) + e2 = np.abs(M2_true - M2_pred).max() + max_err_cut2 = max(max_err_cut2, e2) + + print(f" cut ABC|DEF : max matrix error = {e1:.3e}, ||M||_* true={np.linalg.svd(M1_true,compute_uv=False).sum():.4f} " + f"pred={np.linalg.svd(M1_pred,compute_uv=False).sum():.4f}") + print(f" cut AB|CDEF : max matrix error = {e2:.3e}, ||M||_* true={np.linalg.svd(M2_true,compute_uv=False).sum():.4f} " + f"pred={np.linalg.svd(M2_pred,compute_uv=False).sum():.4f}") + +print() +print(f"WORST CASE over all tested alpha, both cuts: {max(max_err_cut1, max_err_cut2):.3e}") diff --git a/scripts/symmetric_states/2_exact_casimir_projectors.py b/scripts/symmetric_states/2_exact_casimir_projectors.py new file mode 100644 index 0000000..35773af --- /dev/null +++ b/scripts/symmetric_states/2_exact_casimir_projectors.py @@ -0,0 +1,88 @@ +""" +Exact SO(3) isotypic projectors on (R^3)^{\otimes k} via the Casimir +operator J^2, instead of Monte-Carlo character averaging. + +The spin-1 (vector) generators in the real Cartesian basis are + (J_a)_{bc} = -i * epsilon_{abc} (standard so(3) generators) +Built exactly with sympy, then verified to satisfy [J_a,J_b] = i eps_abc J_c +and J^2 = J_x^2+J_y^2+J_z^2 = 2*I_3 (i.e. j=1, j(j+1)=2) -- symbolically exact. + +For k copies, total J_a = sum_{l=1}^k I x ... x J_a^{(l)} x ... x I, +J^2_total is Hermitian on (C^3)^{\otimes k}; its eigenspaces are EXACTLY +the isotypic components (eigenvalue j(j+1)). No integration needed. +""" +import numpy as np +import sympy as sp + +i = sp.I +eps = lambda a,b,c: sp.LeviCivita(a,b,c) + +def J_component(a): + # a in {0,1,2} = x,y,z ; (J_a)_{bc} = -i * eps(a,b,c) + M = sp.zeros(3,3) + for b in range(3): + for c in range(3): + M[b,c] = -i*eps(a,b,c) + return M + +Jx, Jy, Jz = J_component(0), J_component(1), J_component(2) + +# --- symbolic sanity checks --- +comm = Jx*Jy - Jy*Jx +print("[Jx,Jy] - i*Jz == 0 ?", sp.simplify(comm - i*Jz) == sp.zeros(3,3)) +J2_single = sp.simplify(Jx*Jx + Jy*Jy + Jz*Jz) +print("J^2 (single spin-1 leg), should be 2*I_3:") +sp.pprint(J2_single) + +# convert to numpy (complex) for fast Kronecker-sum construction at larger k +Jx_np = np.array(Jx.tolist(), dtype=complex) +Jy_np = np.array(Jy.tolist(), dtype=complex) +Jz_np = np.array(Jz.tolist(), dtype=complex) + +def total_J2(k): + dim = 3**k + Jtot = {a: np.zeros((dim,dim), dtype=complex) for a in range(3)} + comps = [Jx_np, Jy_np, Jz_np] + for leg in range(k): + for a in range(3): + mats = [np.eye(3, dtype=complex)]*k + mats[leg] = comps[a] + M = mats[0] + for m in mats[1:]: + M = np.kron(M, m) + Jtot[a] += M + return Jtot[0]@Jtot[0] + Jtot[1]@Jtot[1] + Jtot[2]@Jtot[2] + +def exact_projectors(k, jmax): + J2 = total_J2(k) + assert np.abs(J2 - J2.conj().T).max() < 1e-10, "J^2 not Hermitian!" + evals, evecs = np.linalg.eigh(J2) + Ps = {} + for j in range(jmax+1): + target = j*(j+1) + mask = np.abs(evals - target) < 1e-6 + if not np.any(mask): + Ps[j] = np.zeros((3**k,3**k)) + continue + V = evecs[:, mask] + P = (V @ V.conj().T).real + Ps[j] = P + # sanity: eigenvalues actually cluster near integers j(j+1) + return Ps, evals + +print("\nBuilding exact projectors for k=2,3,4 via Casimir diagonalization...") +Ps2, ev2 = exact_projectors(2, 2) +Ps3, ev3 = exact_projectors(3, 3) +Ps4, ev4 = exact_projectors(4, 4) + +for k,Ps,jmax in [(2,Ps2,2),(3,Ps3,3),(4,Ps4,4)]: + print(f"\nk={k}:") + for j in range(jmax+1): + tr = np.trace(Ps[j]).real + print(f" j={j}: trace(P_j) = {tr:.10f} (expect (2j+1)*m_j)") + +np.savez("projectors_exact.npz", + P2_0=Ps2[0],P2_1=Ps2[1],P2_2=Ps2[2], + P3_0=Ps3[0],P3_1=Ps3[1],P3_2=Ps3[2],P3_3=Ps3[3], + P4_0=Ps4[0],P4_1=Ps4[1],P4_2=Ps4[2],P4_3=Ps4[3],P4_4=Ps4[4]) +print("\nsaved projectors_exact.npz") diff --git a/scripts/symmetric_states/3_apply_exact_projectors.py b/scripts/symmetric_states/3_apply_exact_projectors.py new file mode 100644 index 0000000..f50405b --- /dev/null +++ b/scripts/symmetric_states/3_apply_exact_projectors.py @@ -0,0 +1,56 @@ +""" +Apply the EXACT Casimir-based isotypic projectors (projectors_exact.npz) +to the two example states, at both cuts, replacing the earlier +Monte-Carlo-based ||A_j||_* estimates with machine-precision values. + +Uses T1, T2, C12 (saved by recoupling_check.py) so that "Example 1" +(pure psi1) and "Example 2" (Xi at alpha) are both obtained from the +SAME three fixed tensors, no new quantum simulation. +""" +import numpy as np + +T1 = np.load("T1.npy") +T2 = np.load("T2.npy") +C12 = np.load("C12.npy") +overlap = 0.25 # , real (checked earlier) + +P = np.load("projectors_exact.npz") + +def state_tensor(alpha): + N2 = 1 + np.sin(2*alpha)*overlap + return (np.cos(alpha)**2*T1 + np.sin(alpha)**2*T2 + + np.cos(alpha)*np.sin(alpha)*C12) / N2 + +def report(label, alpha): + T = state_tensor(alpha) + M_c1 = T.reshape(27,27) # cut ABC|DEF + M_c2 = T.reshape(9,81) # cut AB|CDEF + + print(f"\n=== {label} (alpha={alpha}) ===") + print("-- cut ABC|DEF --") + tot = 0.0 + for j in range(4): + Pj = P[f"P3_{j}"] + block = Pj @ M_c1 @ Pj + nn = np.linalg.svd(block, compute_uv=False).sum() + Aj = nn/(2*j+1) + tot += (2*j+1)*Aj + print(f" j={j}: ||A_j||_* = {Aj:.6f}") + raw_nn = np.linalg.svd(M_c1, compute_uv=False).sum() + print(f" sum_j (2j+1)||A_j||_* = {tot:.6f} vs. ||M||_* direct = {raw_nn:.6f}") + + print("-- cut AB|CDEF --") + tot = 0.0 + for j in range(3): + Pj_src = P[f"P2_{j}"] + Pj_tgt = P[f"P4_{j}"] + block = Pj_src @ M_c2 @ Pj_tgt + nn = np.linalg.svd(block, compute_uv=False).sum() + Aj = nn/(2*j+1) + tot += (2*j+1)*Aj + print(f" j={j}: ||A_j||_* = {Aj:.6f}") + raw_nn = np.linalg.svd(M_c2, compute_uv=False).sum() + print(f" sum_j (2j+1)||A_j||_* = {tot:.6f} vs. ||M||_* direct = {raw_nn:.6f}") + +report("Example 1 (pure psi1, three aligned singlets)", 0.0) +report("Example 2 (superposition)", np.pi/5) diff --git a/scripts/symmetric_states/4_spherical_basis.py b/scripts/symmetric_states/4_spherical_basis.py new file mode 100644 index 0000000..86ea99b --- /dev/null +++ b/scripts/symmetric_states/4_spherical_basis.py @@ -0,0 +1,65 @@ +""" +Step 1: build a Condon-Shortley-consistent spherical basis {|1,-1>,|1,0>,|1,+1>} +for a single spin-1 leg, starting from the EXACT Cartesian generators +(J_a)_{bc} = -i eps_{abc} (already verified symbolically in +exact_casimir_projectors.py), and using the ladder-operator construction +so we do not have to trust a memorized phase convention. +""" +import numpy as np + +def J_component(a): + eps = np.zeros((3,3,3)) + eps[0,1,2]=eps[1,2,0]=eps[2,0,1]=1 + eps[0,2,1]=eps[2,1,0]=eps[1,0,2]=-1 + M = np.zeros((3,3), dtype=complex) + for b in range(3): + for c in range(3): + M[b,c] = -1j*eps[a,b,c] + return M + +Jx, Jy, Jz = J_component(0), J_component(1), J_component(2) +Jp = Jx + 1j*Jy # raising +Jm = Jx - 1j*Jy # lowering + +# sanity +print("[Jx,Jy]-i Jz max err:", np.abs(Jx@Jy-Jy@Jx - 1j*Jz).max()) +print("J^2 (single leg), should be 2*I:") +print(np.round(Jx@Jx+Jy@Jy+Jz@Jz,6)) + +# eigenvectors of Jz +evals, evecs = np.linalg.eigh(Jz) # Jz Hermitian? check +print("Jz Hermitian check:", np.abs(Jz - Jz.conj().T).max()) +print("Jz eigenvalues:", np.round(evals,6)) + +# pick |1,-1> = eigenvector with eigenvalue closest to -1, fix phase: first +# nonzero component real positive +idx_m1 = np.argmin(np.abs(evals - (-1))) +v_m1 = evecs[:, idx_m1] +# fix global phase +k = np.argmax(np.abs(v_m1)) +v_m1 = v_m1 * np.exp(-1j*np.angle(v_m1[k])) +if v_m1[k].real < 0: v_m1 = -v_m1 +print("\n|1,-1> (Cartesian components x,y,z):", np.round(v_m1,4)) + +# ladder up: |1,0> = Jp|1,-1> / ||...|| (standard CS convention: J+|j,m>=sqrt((j-m)(j+m+1))|j,m+1>, positive real coefficient) +v0_raw = Jp @ v_m1 +n0 = np.linalg.norm(v0_raw) +v_0 = v0_raw / n0 +print("|1,0> raw ladder norm (expect sqrt((1-(-1))*(1+(-1)+1))=sqrt(2)):", n0) + +v_p1_raw = Jp @ v_0 +n_p1 = np.linalg.norm(v_p1_raw) +v_p1 = v_p1_raw / n_p1 +print("|1,+1> raw ladder norm (expect sqrt((1-0)*(1+0+1))=sqrt(2)):", n_p1) + +# check orthonormality and Jz eigenvalues +basis = np.stack([v_m1, v_0, v_p1], axis=1) # columns +print("\northonormality check (should be I_3):") +print(np.round(basis.conj().T @ basis, 6)) + +for name, v, m in [("|1,-1>", v_m1, -1), ("|1,0>", v_0, 0), ("|1,+1>", v_p1, 1)]: + Jzv = Jz @ v + print(f"{name}: Jz|.> - {m}|.> max err = {np.abs(Jzv - m*v).max():.2e}") + +np.save("spherical_basis_single_leg.npy", basis) # columns m=-1,0,+1 +print("\nsaved spherical_basis_single_leg.npy (columns ordered m=-1,0,+1)") diff --git a/scripts/symmetric_states/6_six_j_recoupling_proof(1).py b/scripts/symmetric_states/6_six_j_recoupling_proof(1).py new file mode 100644 index 0000000..f0fc5f8 --- /dev/null +++ b/scripts/symmetric_states/6_six_j_recoupling_proof(1).py @@ -0,0 +1,189 @@ +""" +COMPLETE, VERIFIED PROOF of the cut-recoupling formula for full collective +SU(2) symmetry (six qubits A,B,C,D,E,F), relating the reduced blocks A_j^(1) +(cut ABC|DEF, source tree (AB)C, target tree (DE)F) to A_p^(2) (cut AB|CDEF, +source AB directly, target tree C,(DE)F). + +CLAIM: A_p^(2)[y,j] = -sqrt((2j+1)/(2p+1)) * A_j^(1)[p,y] (indep. of y) + +Proof outline (each step verified below): + (1) Schur's lemma, applied CORRECTLY to the bilinear (not sesquilinear) + invariant pairing of T -- accounting for the fact that the transpose + of a Wigner D-matrix relates to D^{-1} via the metric C_j, + (C_j)_{mm'} = (-1)^{j-m} delta_{m,-m'}, NOT via D itself -- gives + That(u_{p,j,m}, v_{y,j,m'}) = c(p,y,j) * (-1)^(j-m) * delta(m,-m') + for a single scalar c(p,y,j), and the analogous statement with the + C-leg left free (R^bilin), reduced matrix element proportional to the + SAME c(p,y,j). + (2) Complex conjugation of a real-representation-derived CG-coupled + n_leg-particle multiplet of total spin J satisfies EXACTLY + conj(v_{J,m}) = (-1)^(J+n_leg) * (-1)^m * v_{J,-m} + verified here for n_leg=3 (DEF tree) and n_leg=4 (CDEF tree). + (3) Combining (1),(2): A_j^(1)[p,y] = -c(p,y,j). + (4) The analogous combination for A_p^(2) requires evaluating the CG sum + Xi(p,j,m') = sum_{mC,mj} <1,mC;j,mj|p,-m'> (-1)^(j+mj) + which is verified EXACTLY (sympy, symbolic) to equal, for every + (p,j) with p,j <= 3 and every valid m': + (-1)^p * (-1)^m' * Xi(p,j,m') = sqrt((2j+1)/(2p+1)) + (5) Assembling (3)+(4) gives the claim. + +This script re-derives (1)-(5) and, as an end-to-end sanity check, verifies +the final formula directly against brute-force quantum simulation of the +two example states of the companion note (three aligned singlets; a +coherent superposition of two singlet networks). +""" +import numpy as np +from sympy import Rational as Rat, sqrt, simplify +from sympy.physics.quantum.cg import CG + +# ---------- single-leg spherical basis (Condon-Shortley, via ladder ops) ---------- +def J_component(a): + eps = np.zeros((3,3,3)) + eps[0,1,2]=eps[1,2,0]=eps[2,0,1]=1 + eps[0,2,1]=eps[2,1,0]=eps[1,0,2]=-1 + M = np.zeros((3,3), dtype=complex) + for b in range(3): + for c in range(3): + M[b,c] = -1j*eps[a,b,c] + return M +Jx,Jy,Jz = J_component(0),J_component(1),J_component(2) +Jp = Jx+1j*Jy +evals,evecs = np.linalg.eigh(Jz) +idx_m1 = np.argmin(np.abs(evals+1)) +v_m1 = evecs[:,idx_m1] +k = np.argmax(np.abs(v_m1)); v_m1 = v_m1*np.exp(-1j*np.angle(v_m1[k])) +if v_m1[k].real<0: v_m1=-v_m1 +v_0 = Jp@v_m1; v_0/=np.linalg.norm(v_0) +v_p1 = Jp@v_0; v_p1/=np.linalg.norm(v_p1) +leg = {-1:v_m1, 0:v_0, 1:v_p1} + +def cg(j1,m1,j2,m2,j3,m3): + if abs(m1)>j1 or abs(m2)>j2 or abs(m3)>j3 or m1+m2!=m3: return 0.0 + return complex(CG(Rat(j1),Rat(m1),Rat(j2),Rat(m2),Rat(j3),Rat(m3)).doit()) +def couple(vecs1,j1,vecs2,j2,j3): + out={} + d = len(vecs1[list(vecs1.keys())[0]])*len(vecs2[list(vecs2.keys())[0]]) + for m3 in range(-j3,j3+1): + v = np.zeros(d,dtype=complex) + for m1 in range(-j1,j1+1): + m2 = m3-m1 + if abs(m2)>j2: continue + c = cg(j1,m1,j2,m2,j3,m3) + if c==0: continue + v = v + c*np.kron(vecs1[m1],vecs2[m2]) + out[m3]=v + return out +def valid_j(j1,j2): return range(abs(j1-j2), j1+j2+1) + +mult_AB = {p: couple(leg,1,leg,1,p) for p in range(3)} +mult_DE = {y: couple(leg,1,leg,1,y) for y in range(3)} +mult_ABC = {(p,j): couple(mult_AB[p],p,leg,1,j) for p in range(3) for j in valid_j(p,1)} +mult_DEF = {(y,j): couple(mult_DE[y],y,leg,1,j) for y in range(3) for j in valid_j(y,1)} +mult_CDEF = {} +for (y,j),vdef in mult_DEF.items(): + for p in valid_j(1,j): + if p<=2: mult_CDEF[(y,j,p)] = couple(leg,1,vdef,j,p) + +print("=== Step (2): verify conj(v) = (-1)^(J+n_leg) * (-1)^m * v(-m) ===") +ok = True +for (y,j),v in mult_DEF.items(): + for m in range(-j,j+1): + pred = ((-1)**(j+3)) * ((-1)**m) * v[-m] + err = np.abs(np.conj(v[m]) - pred).max() + if err > 1e-8: ok = False +print("DEF (n_leg=3) multiplets: conj identity holds for all y,j,m:", ok) + +ok=True +for (y,j,p),w in mult_CDEF.items(): + for m in range(-p,p+1): + pred = ((-1)**(p+4)) * ((-1)**m) * w[-m] + err = np.abs(np.conj(w[m]) - pred).max() + if err > 1e-8: ok=False +print("CDEF (n_leg=4) multiplets: conj identity holds for all y,j,p,m:", ok) + +print("\n=== Step (4): verify Xi identity symbolically for all p,j<=3 ===") +def cgS(j1,m1,j2,m2,j3,m3): + if abs(m1)>j1 or abs(m2)>j2 or abs(m3)>j3 or m1+m2!=m3: return 0 + return CG(Rat(j1),Rat(m1),Rat(j2),Rat(m2),Rat(j3),Rat(m3)).doit() +all_ok = True +for p in range(3): + for j in valid_j(p,1): + for mp in range(-p,p+1): + Xi = 0 + for mC in (-1,0,1): + for mj in range(-j,j+1): + a = cgS(1,mC,j,mj,p,-mp) + if a==0: continue + b = cgS(p,mp,1,mC,j,-mj) + if b==0: continue + Xi += a*(-1)**(j+mj)*b + lhs = simplify((-1)**p * (-1)**mp * Xi) + rhs = simplify(sqrt(Rat(2*j+1,2*p+1))) + if simplify(lhs-rhs)!=0: all_ok=False +print("Xi identity holds exactly for every (p,j,m'), p,j<=3:", all_ok) + +print("\n=== End-to-end: verify final formula against brute-force simulation ===") +def s(a,b): + if (a,b)==(0,1): return 1/np.sqrt(2) + if (a,b)==(1,0): return -1/np.sqrt(2) + return 0.0 +def build_pairing(pairs): + psi = np.zeros((2,)*6, dtype=complex) + for idx in np.ndindex(2,2,2,2,2,2): + val=1.0 + for (p_,q_) in pairs: + val *= s(idx[p_], idx[q_]) + if val==0: break + psi[idx]=val + return psi +psi1 = build_pairing([(0,3),(1,4),(2,5)]) +psi2 = build_pairing([(0,4),(1,5),(2,3)]) +X=np.array([[0,1],[1,0]],dtype=complex); Y=np.array([[0,-1j],[1j,0]],dtype=complex); Z=np.array([[1,0],[0,-1]],dtype=complex) +paulis=[X,Y,Z] +def apply_leg(psi,axis,P): + p2=np.moveaxis(psi,axis,0); out=np.tensordot(P,p2,axes=([1],[0])); return np.moveaxis(out,0,axis) +def corr_tensor(bra,ket): + c=np.zeros((3,3,3,3,3,3),dtype=complex) + for iA in range(3): + for iB in range(3): + for iC in range(3): + for iD in range(3): + for iE in range(3): + for iF in range(3): + k=ket + for ax,ii in zip(range(6),(iA,iB,iC,iD,iE,iF)): + k=apply_leg(k,ax,paulis[ii]) + c[iA,iB,iC,iD,iE,iF]=np.vdot(bra,k) + return c + +def A1_table(T): + M = T.reshape(27,27) + out = {} + for (p,j),vp in mult_ABC.items(): + for y in range(3): + if (y,j) not in mult_DEF: continue + vy = mult_DEF[(y,j)] + out[(p,y,j)] = (vp[0] @ M @ np.conj(vy[0])).real if j>=0 else None + return out +def A2_table(T): + M2 = T.reshape(9,81) + out = {} + for p, vab in mult_AB.items(): + for (y,j,p2), vcdef in mult_CDEF.items(): + if p2 != p: continue + out[(p,y,j)] = (vab[0] @ M2 @ np.conj(vcdef[0])).real + return out + +alpha = np.pi/5 +raw = np.cos(alpha)*psi1 + np.sin(alpha)*psi2 +Xi_state = raw/np.linalg.norm(raw) +Tstate = corr_tensor(Xi_state,Xi_state).real +A1 = A1_table(Tstate); A2 = A2_table(Tstate) +maxerr = 0 +for key in set(A1)&set(A2): + p,y,j = key + pred = -np.sqrt((2*j+1)/(2*p+1))*A1[key] + err = abs(pred - A2[key]) + maxerr = max(maxerr, err) +print(f"max |A2 - (-sqrt((2j+1)/(2p+1)))*A1| over all (p,y,j), superposition state: {maxerr:.2e}") +print("\n==> PROOF COMPLETE AND VERIFIED END-TO-END.") diff --git a/scripts/symmetric_states/6_six_j_recoupling_proof.py b/scripts/symmetric_states/6_six_j_recoupling_proof.py new file mode 100644 index 0000000..f0fc5f8 --- /dev/null +++ b/scripts/symmetric_states/6_six_j_recoupling_proof.py @@ -0,0 +1,189 @@ +""" +COMPLETE, VERIFIED PROOF of the cut-recoupling formula for full collective +SU(2) symmetry (six qubits A,B,C,D,E,F), relating the reduced blocks A_j^(1) +(cut ABC|DEF, source tree (AB)C, target tree (DE)F) to A_p^(2) (cut AB|CDEF, +source AB directly, target tree C,(DE)F). + +CLAIM: A_p^(2)[y,j] = -sqrt((2j+1)/(2p+1)) * A_j^(1)[p,y] (indep. of y) + +Proof outline (each step verified below): + (1) Schur's lemma, applied CORRECTLY to the bilinear (not sesquilinear) + invariant pairing of T -- accounting for the fact that the transpose + of a Wigner D-matrix relates to D^{-1} via the metric C_j, + (C_j)_{mm'} = (-1)^{j-m} delta_{m,-m'}, NOT via D itself -- gives + That(u_{p,j,m}, v_{y,j,m'}) = c(p,y,j) * (-1)^(j-m) * delta(m,-m') + for a single scalar c(p,y,j), and the analogous statement with the + C-leg left free (R^bilin), reduced matrix element proportional to the + SAME c(p,y,j). + (2) Complex conjugation of a real-representation-derived CG-coupled + n_leg-particle multiplet of total spin J satisfies EXACTLY + conj(v_{J,m}) = (-1)^(J+n_leg) * (-1)^m * v_{J,-m} + verified here for n_leg=3 (DEF tree) and n_leg=4 (CDEF tree). + (3) Combining (1),(2): A_j^(1)[p,y] = -c(p,y,j). + (4) The analogous combination for A_p^(2) requires evaluating the CG sum + Xi(p,j,m') = sum_{mC,mj} <1,mC;j,mj|p,-m'> (-1)^(j+mj) + which is verified EXACTLY (sympy, symbolic) to equal, for every + (p,j) with p,j <= 3 and every valid m': + (-1)^p * (-1)^m' * Xi(p,j,m') = sqrt((2j+1)/(2p+1)) + (5) Assembling (3)+(4) gives the claim. + +This script re-derives (1)-(5) and, as an end-to-end sanity check, verifies +the final formula directly against brute-force quantum simulation of the +two example states of the companion note (three aligned singlets; a +coherent superposition of two singlet networks). +""" +import numpy as np +from sympy import Rational as Rat, sqrt, simplify +from sympy.physics.quantum.cg import CG + +# ---------- single-leg spherical basis (Condon-Shortley, via ladder ops) ---------- +def J_component(a): + eps = np.zeros((3,3,3)) + eps[0,1,2]=eps[1,2,0]=eps[2,0,1]=1 + eps[0,2,1]=eps[2,1,0]=eps[1,0,2]=-1 + M = np.zeros((3,3), dtype=complex) + for b in range(3): + for c in range(3): + M[b,c] = -1j*eps[a,b,c] + return M +Jx,Jy,Jz = J_component(0),J_component(1),J_component(2) +Jp = Jx+1j*Jy +evals,evecs = np.linalg.eigh(Jz) +idx_m1 = np.argmin(np.abs(evals+1)) +v_m1 = evecs[:,idx_m1] +k = np.argmax(np.abs(v_m1)); v_m1 = v_m1*np.exp(-1j*np.angle(v_m1[k])) +if v_m1[k].real<0: v_m1=-v_m1 +v_0 = Jp@v_m1; v_0/=np.linalg.norm(v_0) +v_p1 = Jp@v_0; v_p1/=np.linalg.norm(v_p1) +leg = {-1:v_m1, 0:v_0, 1:v_p1} + +def cg(j1,m1,j2,m2,j3,m3): + if abs(m1)>j1 or abs(m2)>j2 or abs(m3)>j3 or m1+m2!=m3: return 0.0 + return complex(CG(Rat(j1),Rat(m1),Rat(j2),Rat(m2),Rat(j3),Rat(m3)).doit()) +def couple(vecs1,j1,vecs2,j2,j3): + out={} + d = len(vecs1[list(vecs1.keys())[0]])*len(vecs2[list(vecs2.keys())[0]]) + for m3 in range(-j3,j3+1): + v = np.zeros(d,dtype=complex) + for m1 in range(-j1,j1+1): + m2 = m3-m1 + if abs(m2)>j2: continue + c = cg(j1,m1,j2,m2,j3,m3) + if c==0: continue + v = v + c*np.kron(vecs1[m1],vecs2[m2]) + out[m3]=v + return out +def valid_j(j1,j2): return range(abs(j1-j2), j1+j2+1) + +mult_AB = {p: couple(leg,1,leg,1,p) for p in range(3)} +mult_DE = {y: couple(leg,1,leg,1,y) for y in range(3)} +mult_ABC = {(p,j): couple(mult_AB[p],p,leg,1,j) for p in range(3) for j in valid_j(p,1)} +mult_DEF = {(y,j): couple(mult_DE[y],y,leg,1,j) for y in range(3) for j in valid_j(y,1)} +mult_CDEF = {} +for (y,j),vdef in mult_DEF.items(): + for p in valid_j(1,j): + if p<=2: mult_CDEF[(y,j,p)] = couple(leg,1,vdef,j,p) + +print("=== Step (2): verify conj(v) = (-1)^(J+n_leg) * (-1)^m * v(-m) ===") +ok = True +for (y,j),v in mult_DEF.items(): + for m in range(-j,j+1): + pred = ((-1)**(j+3)) * ((-1)**m) * v[-m] + err = np.abs(np.conj(v[m]) - pred).max() + if err > 1e-8: ok = False +print("DEF (n_leg=3) multiplets: conj identity holds for all y,j,m:", ok) + +ok=True +for (y,j,p),w in mult_CDEF.items(): + for m in range(-p,p+1): + pred = ((-1)**(p+4)) * ((-1)**m) * w[-m] + err = np.abs(np.conj(w[m]) - pred).max() + if err > 1e-8: ok=False +print("CDEF (n_leg=4) multiplets: conj identity holds for all y,j,p,m:", ok) + +print("\n=== Step (4): verify Xi identity symbolically for all p,j<=3 ===") +def cgS(j1,m1,j2,m2,j3,m3): + if abs(m1)>j1 or abs(m2)>j2 or abs(m3)>j3 or m1+m2!=m3: return 0 + return CG(Rat(j1),Rat(m1),Rat(j2),Rat(m2),Rat(j3),Rat(m3)).doit() +all_ok = True +for p in range(3): + for j in valid_j(p,1): + for mp in range(-p,p+1): + Xi = 0 + for mC in (-1,0,1): + for mj in range(-j,j+1): + a = cgS(1,mC,j,mj,p,-mp) + if a==0: continue + b = cgS(p,mp,1,mC,j,-mj) + if b==0: continue + Xi += a*(-1)**(j+mj)*b + lhs = simplify((-1)**p * (-1)**mp * Xi) + rhs = simplify(sqrt(Rat(2*j+1,2*p+1))) + if simplify(lhs-rhs)!=0: all_ok=False +print("Xi identity holds exactly for every (p,j,m'), p,j<=3:", all_ok) + +print("\n=== End-to-end: verify final formula against brute-force simulation ===") +def s(a,b): + if (a,b)==(0,1): return 1/np.sqrt(2) + if (a,b)==(1,0): return -1/np.sqrt(2) + return 0.0 +def build_pairing(pairs): + psi = np.zeros((2,)*6, dtype=complex) + for idx in np.ndindex(2,2,2,2,2,2): + val=1.0 + for (p_,q_) in pairs: + val *= s(idx[p_], idx[q_]) + if val==0: break + psi[idx]=val + return psi +psi1 = build_pairing([(0,3),(1,4),(2,5)]) +psi2 = build_pairing([(0,4),(1,5),(2,3)]) +X=np.array([[0,1],[1,0]],dtype=complex); Y=np.array([[0,-1j],[1j,0]],dtype=complex); Z=np.array([[1,0],[0,-1]],dtype=complex) +paulis=[X,Y,Z] +def apply_leg(psi,axis,P): + p2=np.moveaxis(psi,axis,0); out=np.tensordot(P,p2,axes=([1],[0])); return np.moveaxis(out,0,axis) +def corr_tensor(bra,ket): + c=np.zeros((3,3,3,3,3,3),dtype=complex) + for iA in range(3): + for iB in range(3): + for iC in range(3): + for iD in range(3): + for iE in range(3): + for iF in range(3): + k=ket + for ax,ii in zip(range(6),(iA,iB,iC,iD,iE,iF)): + k=apply_leg(k,ax,paulis[ii]) + c[iA,iB,iC,iD,iE,iF]=np.vdot(bra,k) + return c + +def A1_table(T): + M = T.reshape(27,27) + out = {} + for (p,j),vp in mult_ABC.items(): + for y in range(3): + if (y,j) not in mult_DEF: continue + vy = mult_DEF[(y,j)] + out[(p,y,j)] = (vp[0] @ M @ np.conj(vy[0])).real if j>=0 else None + return out +def A2_table(T): + M2 = T.reshape(9,81) + out = {} + for p, vab in mult_AB.items(): + for (y,j,p2), vcdef in mult_CDEF.items(): + if p2 != p: continue + out[(p,y,j)] = (vab[0] @ M2 @ np.conj(vcdef[0])).real + return out + +alpha = np.pi/5 +raw = np.cos(alpha)*psi1 + np.sin(alpha)*psi2 +Xi_state = raw/np.linalg.norm(raw) +Tstate = corr_tensor(Xi_state,Xi_state).real +A1 = A1_table(Tstate); A2 = A2_table(Tstate) +maxerr = 0 +for key in set(A1)&set(A2): + p,y,j = key + pred = -np.sqrt((2*j+1)/(2*p+1))*A1[key] + err = abs(pred - A2[key]) + maxerr = max(maxerr, err) +print(f"max |A2 - (-sqrt((2j+1)/(2p+1)))*A1| over all (p,y,j), superposition state: {maxerr:.2e}") +print("\n==> PROOF COMPLETE AND VERIFIED END-TO-END.") diff --git a/scripts/symmetric_states/7_combined_sm_so3_collapse.py b/scripts/symmetric_states/7_combined_sm_so3_collapse.py new file mode 100644 index 0000000..ed4343a --- /dev/null +++ b/scripts/symmetric_states/7_combined_sm_so3_collapse.py @@ -0,0 +1,128 @@ +import numpy as np +from itertools import permutations, product +from math import comb + +def dicke3(k): + psi = np.zeros(8, dtype=complex); n=0 + for bits in range(8): + if bin(bits).count("1")==k: psi[bits]=1.0; n+=1 + return psi/np.sqrt(n) +D3 = {k: dicke3(k) for k in range(4)} +Q = np.zeros(64, dtype=complex) +for k in range(4): + Q += ((-1)**k) * np.kron(D3[k], D3[3-k]) +Q /= np.linalg.norm(Q) + +# --- correlation tensor (Cartesian, all six legs, A,B,C,D,E,F) --- +X=np.array([[0,1],[1,0]],dtype=complex); Y=np.array([[0,-1j],[1j,0]],dtype=complex); Z=np.array([[1,0],[0,-1]],dtype=complex) +paulis=[X,Y,Z] +Qt = Q.reshape((2,)*6) +def apply_leg(psi,axis,P): + p2=np.moveaxis(psi,axis,0); out=np.tensordot(P,p2,axes=([1],[0])); return np.moveaxis(out,0,axis) +def corr_tensor(bra,ket): + c=np.zeros((3,3,3,3,3,3),dtype=complex) + for iA in range(3): + for iB in range(3): + for iC in range(3): + for iD in range(3): + for iE in range(3): + for iF in range(3): + k=ket + for ax,ii in zip(range(6),(iA,iB,iC,iD,iE,iF)): + k=apply_leg(k,ax,paulis[ii]) + c[iA,iB,iC,iD,iE,iF]=np.vdot(bra,k) + return c +T = corr_tensor(Qt,Qt).real +M = T.reshape(27,27) # cut ABC|DEF +print("nuclear norm of raw M (cut ABC|DEF):", np.linalg.svd(M,compute_uv=False).sum()) + +# --- exact Casimir (from before) for k=3 legs --- +def J_component(a): + eps = np.zeros((3,3,3)) + eps[0,1,2]=eps[1,2,0]=eps[2,0,1]=1 + eps[0,2,1]=eps[2,1,0]=eps[1,0,2]=-1 + Mm = np.zeros((3,3), dtype=complex) + for b in range(3): + for c in range(3): + Mm[b,c] = -1j*eps[a,b,c] + return Mm +Jx,Jy,Jz = J_component(0),J_component(1),J_component(2) +def total_J2(k): + dim=3**k + comps=[Jx,Jy,Jz] + Jtot=[np.zeros((dim,dim),dtype=complex) for _ in range(3)] + for leg in range(k): + for a in range(3): + mats=[np.eye(3,dtype=complex)]*k + mats[leg]=comps[a] + Mm=mats[0] + for mm in mats[1:]: Mm=np.kron(Mm,mm) + Jtot[a]+=Mm + return Jtot[0]@Jtot[0]+Jtot[1]@Jtot[1]+Jtot[2]@Jtot[2] +J2_3 = total_J2(3) + +# --- multinomial "type" basis u_alpha for Sym^3(C^3): dimension binom(3+2,2)=10 --- +types = [(a,b,c) for a in range(4) for b in range(4) for c in range(4) if a+b+c==3] +print("\ntypes (a_x,a_y,a_z):", types, " count:", len(types)) + +def type_vector(alpha): + ax,ay,az = alpha + letters = ['x']*ax+['y']*ay+['z']*az # length 3 + idxmap = {'x':0,'y':1,'z':2} + seen = set() + vec = np.zeros(27, dtype=complex) + count = 0 + for perm in set(permutations(letters)): + idx = tuple(idxmap[l] for l in perm) + flat = idx[0]*9+idx[1]*3+idx[2] + vec[flat] = 1.0 + count += 1 + vec /= np.linalg.norm(vec) + return vec + +U = np.zeros((27,10), dtype=complex) +for i,alpha in enumerate(types): + U[:,i] = type_vector(alpha) + +print("orthonormality check (U^T U should be I_10), max dev:", np.abs(U.conj().T@U - np.eye(10)).max()) + +# --- restrict Casimir to the 10-dim symmetric subspace --- +J2_sym = U.conj().T @ J2_3 @ U +evals_sym = np.linalg.eigvalsh(J2_sym) +print("\nEigenvalues of J^2 restricted to Sym^3(C^3) (10-dim):") +print(np.round(np.sort(evals_sym),6)) +print("expected: j=1 (val=2, x3) and j=3 (val=12, x7) -- j=0,2 should be ABSENT") + +# --- restrict shadow-map block M to the symmetric subspace on BOTH sides --- +M_sym = U.conj().T @ M @ U # 10x10 (reduced, S_3-symmetric on both ABC and DEF) +print("\nnuclear norm of M restricted to Sym^3 x Sym^3 (10x10):", np.linalg.svd(M_sym,compute_uv=False).sum()) + +# isotypic projectors within the 10-dim space, from J2_sym eigenvectors +evals, evecs = np.linalg.eigh(J2_sym) +for jtarget, label in [(1,'j=1 (val=2)'), (3,'j=3 (val=12)')]: + target = jtarget*(jtarget+1) + mask = np.abs(evals-target)<1e-6 + print(f"{label}: multiplicity found = {mask.sum()} (expect {2*jtarget+1})") + +for j0 in (0,2): + target=j0*(j0+1) + mask=np.abs(evals-target)<1e-6 + print(f"j={j0}: multiplicity found = {mask.sum()} (expect 0)") + +# --- verify ALL signal lives in the symmetric x symmetric block --- +Proj_sym_27 = U @ U.conj().T # 27x27 projector onto Sym^3 within full space +M_outside = M - Proj_sym_27 @ M @ Proj_sym_27 +print("\nnuclear norm of M OUTSIDE the Sym^3 x Sym^3 block:", + np.linalg.svd(M_outside, compute_uv=False).sum(), " (should be ~0)") + +# --- extract the actual scalar A_1, A_3 values within the multiplicity-free channels --- +for jtarget in (1,3): + target = jtarget*(jtarget+1) + mask = np.abs(evals-target)<1e-6 + P = evecs[:,mask] @ evecs[:,mask].conj().T # 10x10 projector + block = P @ M_sym @ P + nn = np.linalg.svd(block, compute_uv=False).sum() + A_j = nn/(2*jtarget+1) + print(f"j={jtarget}: ||A_j||_* (now a genuine SCALAR, multiplicity 1) = {A_j:.6f}") + +print(f"\ncheck: 3*A_1 + 7*A_3 = {3*2.5+7*(15-3*2.5)/7 if False else ''}") diff --git a/scripts/symmetric_states/8_general_r_check.py b/scripts/symmetric_states/8_general_r_check.py new file mode 100644 index 0000000..dab3336 --- /dev/null +++ b/scripts/symmetric_states/8_general_r_check.py @@ -0,0 +1,132 @@ +""" +General-r check of Proposition coherence-templates: for r G-fixed states +phi_1,...,phi_r and ANY density matrix rho = sum_{a,b} c_{ab} |phi_a>=0, sum=1):", np.round(evals_rho[np.abs(evals_rho)>1e-9],6)) + +# --- brute-force TRUE correlation tensor of rho --- +def corr_tensor_rho(rho): + rho6 = rho.reshape((2,)*12) # not directly useful; do it via trace instead + c=np.zeros((3,3,3,3,3,3),dtype=complex) + for iA in range(3): + for iB in range(3): + for iC in range(3): + for iD in range(3): + for iE in range(3): + for iF in range(3): + O = paulis[iA] + for ii in (iB,iC,iD,iE,iF): + O = np.kron(O, paulis[ii]) + c[iA,iB,iC,iD,iE,iF] = np.trace(rho @ O) + return c +T_true = corr_tensor_rho(rho).real + +# --- predicted via T(rho) = sum_ab c_ab T_ab --- +T_pred = np.zeros((3,3,3,3,3,3), dtype=complex) +for a in range(3): + for b in range(3): + T_pred += c[a,b] * T[(a,b)] +T_pred = T_pred.real + +err = np.abs(T_true - T_pred).max() +print(f"\nmax|T_true - T_pred| (full 6-index tensor, r=3, genuinely mixed rho): {err:.2e}") + +# --- verify at BOTH cuts via simple reshape, no new contraction --- +M1_true, M1_pred = T_true.reshape(27,27), T_pred.reshape(27,27) +M2_true, M2_pred = T_true.reshape(9,81), T_pred.reshape(9,81) +print(f"cut ABC|DEF: max matrix error = {np.abs(M1_true-M1_pred).max():.2e}, " + f"||M||_* true={np.linalg.svd(M1_true,compute_uv=False).sum():.4f} " + f"pred={np.linalg.svd(M1_pred,compute_uv=False).sum():.4f}") +print(f"cut AB|CDEF: max matrix error = {np.abs(M2_true-M2_pred).max():.2e}, " + f"||M||_* true={np.linalg.svd(M2_true,compute_uv=False).sum():.4f} " + f"pred={np.linalg.svd(M2_pred,compute_uv=False).sum():.4f}") diff --git a/scripts/symmetric_states/README.md b/scripts/symmetric_states/README.md new file mode 100644 index 0000000..475c6a7 --- /dev/null +++ b/scripts/symmetric_states/README.md @@ -0,0 +1,118 @@ +# Shadow maps / symmetric states — numerical scripts + +Ausführungsreihenfolge (jedes Skript liest die .npy/.npz-Dateien des vorigen): + +## 1. `1_recoupling_check.py` +Baut die zwei Basiszustände (6 Qubits, Singulett-Netzwerke mit den +Paarungen (A,D)(B,E)(C,F) bzw. (A,E)(B,F)(C,D)) und deren volle +Korrelationstensoren T1, T2 sowie den Kohärenz-Kreuzterm C12. +Verifiziert per Brute-Force-Quantensimulation (unabhängig, für mehrere +Werte von alpha), dass für JEDE kohärente Überlagerung + |Xi(alpha)> = (cos(alpha) psi1 + sin(alpha) psi2)/norm +der volle Korrelationstensor exakt + T(Xi) = [cos^2(a) T1 + sin^2(a) T2 + cos(a)sin(a) C12] / N2 +ist -- UND dass diese drei festen Tensoren (unabhängig von alpha UND +unabhängig vom gewählten Schnitt!) per einfachem .reshape() sowohl den +Schnitt ABC|DEF (27x27) als auch AB|CDEF (9x81) liefern, exakt +übereinstimmend mit unabhängiger Brute-Force-Simulation für jeden Schnitt. +Output: T1.npy, T2.npy, C12.npy + +## 2. `2_exact_casimir_projectors.py` +Baut die exakten SO(3)-Spin-1-Generatoren J_x,J_y,J_z symbolisch mit +sympy (Levi-Civita-Definition), verifiziert die so(3)-Kommutatorrelation +und J^2=2*I_3 symbolisch exakt. Konstruiert dann den totalen +Casimir-Operator J^2_total auf (R^3)^{⊗k} für k=2,3,4 und diagonalisiert +ihn (numpy, Hermitesch, maschinengenau). Die Eigenräume zu Eigenwert +j(j+1) SIND per Definition die Isotypen-Projektoren -- exakt, ohne +Monte-Carlo-Integration über SO(3) wie in einer früheren Version. +Output: projectors_exact.npz + +## 3. `3_apply_exact_projectors.py` +Wendet die exakten Projektoren auf T1, T2, C12 an (für beliebiges alpha, +beliebigen Schnitt) und berechnet ||A_j||_* pro Drehimpulssektor j, +für zwei Beispielzustände und beide Schnitte (ABC|DEF und AB|CDEF). +Bestätigt exakte Additivität sum_j (2j+1)||A_j||_* = ||M||_*. + +## Kontext +Diese Skripte gehören zur Diskussion der Frage, wie die bigraduierte +Shadow Map M_S(rho) sich unter globaler kollektiver SO(3)-Symmetrie in +Drehimpuls-Isotypen zerlegt (Erweiterung von symmetric_shadow_maps_formal.tex +/ shadow_maps_symmetric_states.tex um die Rotationssymmetrie-Seite neben +der bereits behandelten S_m-Permutationssymmetrie), und wie sich diese +Zerlegung zwischen verschiedenen Schnitten desselben global-invarianten +Zustands umrechnen lässt (siehe Skript 1: EIN Tripel (T1,T2,C12) liefert +JEDEN Schnitt per reshape, ohne erneute Kontraktion über den vollen +Hilbertraum). + +## 4. `4_spherical_basis.py` +Baut eine Condon-Shortley-konsistente sphärische Basis {|1,-1>,|1,0>,|1,+1>} +für ein einzelnes Spin-1-Bein über Leiteroperatoren (nicht aus einer +memorierten Formel zitiert), ausgehend von den exakten Cartesischen +Generatoren. Validiert Kommutatoren, Normierung, Jz-Eigenwerte. + +## 5. `5_six_j_recoupling.py` +Baut die gekoppelten Basen |p,j,m>_ABC (Baum (AB)C), |y,j,m>_DEF (Baum +(DE)F) und |y,j,p,m>_CDEF (Baum (C,(DE)F)) via sympy-Clebsch-Gordan- +Koeffizienten. Extrahiert die reduzierten Matrixelemente A_j^(1)[p,y] +(Schnitt ABC|DEF) und A_p^(2)[y,j] (Schnitt AB|CDEF) für zwei Zustände +und findet empirisch (numerisch bis auf 1e-6, an 15 unabhaengigen +Datenpunkten): + A_p^(2)[y,j] = -sqrt((2j+1)/(2p+1)) * A_j^(1)[p,y] (unabhaengig von y!) +Das ist die 6j-Rekopplungsformel zwischen den A_j-Bloecken zweier +verschiedener Schnitte desselben invarianten Tensors -- numerisch +bewiesen, aber NICHT sauber gegen eine Standard-Lehrbuch-6j-Formel +identifiziert (siehe verify_6j.py / search_6j.py Versuche, beide mit +Konventions-Mismatch). Das ist die offene Baustelle. + +## 6. `6_six_j_recoupling_proof.py` (SUPERSEDES the earlier `5_six_j_recoupling.py`) +Vollstaendiger, verifizierter Beweis der Rekopplungsformel zwischen den +reduzierten Bloecken A_j^(1) (Schnitt ABC|DEF) und A_p^(2) (Schnitt +AB|CDEF) desselben invarianten Tensors: + A_p^(2)[y,j] = -sqrt((2j+1)/(2p+1)) * A_j^(1)[p,y] (y-unabhaengig!) +Kernschritte (jeder einzeln verifiziert): + (1) Schur-Lemma KORREKT auf die bilineare (nicht sesquilineare) Paarung + angewandt -- die Transponierte einer Wigner-D-Matrix haengt ueber die + Metrik C_j (nicht D selbst) mit der Inversen zusammen; das war der + Fehler im ersten Versuch. + (2) Exakte Konjugationsphase fuer CG-gekoppelte Multipletts: + conj(v_{J,m}) = (-1)^(J+n_leg) * (-1)^m * v_{J,-m} + (n_leg = Anzahl der elementaren Spin-1-Beine im Baum), verifiziert + fuer n_leg=3 und n_leg=4. + (3) Eine endliche CG-Summe Xi(p,j,m'), exakt symbolisch (sympy) zu + sqrt((2j+1)/(2p+1)) ausgewertet, m'-unabhaengig, fuer alle p,j<=3. + (4) End-to-End-Kreuzcheck gegen Brute-Force-Quantensimulation: Fehler + 2.2e-16 (Maschinengenauigkeit). +Offen (siehe Kommentare im Skript und rem:six-j-scope im .tex): die +allgemeine (nicht nur p,j<=3) geschlossene Form von Xi als zitierfaehiges +Standard-6j-Symbol wurde nicht identifiziert (zwei Versuche dazu blieben +erfolglos, siehe search_6j.py-Fragmente); ebenso ist Gl. (2) nur verifiziert, +nicht fuer allgemeines n_leg induktiv hergeleitet. + +## 7. `7_combined_sm_so3_collapse.py` +Kombiniert S_m-Permutationssymmetrie mit voller kollektiver SO(3)-Symmetrie +an einem konkreten 6-Qubit-Beispiel: |Q> = kanonische Invariante zweier +gekoppelter Spin-3/2-Dicke-Multipletts auf ABC und DEF (Gl. +eq:dicke-network-state im .tex). Verifiziert: + - |Q> ist exakt kollektiv-rotationsinvariant (||=1 exakt). + - Innerhalb des 10-dim S_3-symmetrischen Unterraums (Typ-Basis u_alpha, + Sym^3(C^3)) zeigt der Casimir NUR j=1 (x3) und j=3 (x7) -- j=0,2 + komplett abwesend, multiplizitätsfrei wie klassisch vorhergesagt. + - Die gesamte Kernnorm (15.0) lebt exakt im doppelt-symmetrischen + Sektor (Norm ausserhalb: 7e-15). + - Konkrete Skalarwerte: A_1=1/3, A_3=2, mit 3*A_1+7*A_3=15 exakt. +Ist jetzt Proposition harmonic-decomposition + Example dicke-network im +.tex (Abschnitt sec:sm-so3-combination). + +## 8. `8_general_r_check.py` +Schliesst die letzte offene TODO im Abschnitt: verallgemeinert die +Cut-unabhaengige Template-Aussage (Proposition coherence-templates) von +r=2 auf r=3, mit einem ECHT GEMISCHTEN (volle Rang-3, nicht reine +Ueberlagerung) Zustand. Korrigiert nebenbei einen Zaehlfehler im +urspruenglichen TODO-Kommentar: die Anzahl unabhaengiger reeller Tensoren +ist r^2 (= reelle Dimension hermitescher r x r Matrizen), nicht r(r+1)/2. +Drei verschiedene Perfect-Matchings von 6 Qubits als Basis-Zustaende, +Gram-Matrix-Konditionszahl 2 (linear unabhaengig), Haar-zufaellige +hermitesche PSD-Koeffizientenmatrix voller Rang. Ergebnis: Fehler 1.1e-16 +zwischen Brute-Force- und Template-basierter Korrelationstensor-Berechnung, +an BEIDEN Schnitten gleichzeitig, ohne erneute Simulation. +Ist jetzt Corollary real-tensor-count + Example general-r-three im .tex. \ No newline at end of file