Add scripts for exact Casimir projectors and recoupling proof
- Implemented `2_exact_casimir_projectors.py` to construct exact SO(3) isotypic projectors using the Casimir operator, replacing Monte-Carlo methods. - Created `3_apply_exact_projectors.py` to apply the exact projectors to example states, calculating ||A_j||_* estimates with improved precision. - Developed `4_spherical_basis.py` to build a Condon-Shortley-consistent spherical basis for a single spin-1 leg using ladder operators. - Introduced `6_six_j_recoupling_proof.py` to provide a complete proof of the cut-recoupling formula for full collective SU(2) symmetry, verifying the relationship between reduced blocks A_j^(1) and A_p^(2). - Added a README file to document the execution order and purpose of each script in the symmetric states project.
This commit is contained in:
parent
39b4204fe9
commit
bc6f58b2c4
9 changed files with 1097 additions and 13 deletions
225
.gitignore
vendored
225
.gitignore
vendored
|
|
@ -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
|
||||
|
|
@ -747,19 +747,68 @@ $T(\rho)(\vec\imath)=\tr(\rho\,\sigma_{\vec\imath})$ is linear in $\rho$; substi
|
|||
\label{rem:recoupling-scope}
|
||||
Proposition~\ref{prop:coherence-templates} is a bookkeeping consequence of the linearity already used throughout this note (cf.\ the unfolding proposition and the "one rank-one fact, inherited everywhere" theorem of \cite{aschauer2026a}); its content is operational rather than a new inequality: once the $r^2$ template tensors are computed -- from simulation, or in principle extracted from permutationally/collectively-adapted state tomography -- every cut, every mixing angle, and every isotypic block $\|A_j\|_*$ of Corollary~\ref{cor:projector-norm-formula} for the resulting state family is available without revisiting the full $\bigl(\mathbb R^3\bigr)^{\otimes n}$-sized contraction again.
|
||||
|
||||
% TODO (deferred to future work, see session notes): the stronger,
|
||||
% state-independent question of whether the reduced blocks A_j themselves
|
||||
% (not just the raw tensor) transform between two different cuts of the
|
||||
% SAME invariant tensor via an explicit, computable recoupling map -- i.e.
|
||||
% a Racah/6j-symbol type formula relating A_j at cut S|S^c directly to the
|
||||
% A_{j'} at a different cut S'|S'^c, without reshaping the full raw tensor
|
||||
% -- is NOT established here. An exact branching-dimension check (m_p^{(4)}
|
||||
% = sum over compatible y of m_y^{(3)} for every p=0,...,4) is consistent
|
||||
% with such a relation existing, but the explicit coefficients (presumably
|
||||
% governed by an SU(2) recoupling/6j-symbol calculation for the six spin-1
|
||||
% legs, or by the Brauer-algebra structure of Inv((R^3)^{\otimes 6}) since
|
||||
% the example states here are literally perfect-matching invariants) have
|
||||
% not been derived or numerically verified. Left for later.
|
||||
\end{remark}
|
||||
|
||||
\subsubsection{Recoupling the reduced blocks between two cuts}
|
||||
\label{sec:six-j-recoupling}
|
||||
|
||||
% ============================================================
|
||||
% NEW SUBSUBSECTION -- draft. Resolves the open point left in
|
||||
% rem:recoupling-scope of an earlier draft: the reduced blocks A_j
|
||||
% themselves (not just the raw tensor) DO transform between two cuts of
|
||||
% the same invariant tensor via an explicit, closed-form, state-independent
|
||||
% map. Derived and verified exactly (sympy, symbolic CG sums; brute-force
|
||||
% simulation cross-check at machine precision) in
|
||||
% scripts/six_j_recoupling_proof.py. The derivation below is condensed;
|
||||
% the script carries every intermediate identity with its own numerical
|
||||
% check, in case a step needs to be revisited.
|
||||
% ============================================================
|
||||
|
||||
Remark~\ref{rem:recoupling-scope} left open whether the reduced blocks $A_j$ of two different cuts of the \emph{same} invariant tensor -- not merely the raw correlation tensor -- are related by an explicit, state-independent map. For the six-qubit example of Section~\ref{sec:singlet-network-example} they are, exactly, and the relating coefficients are elementary.
|
||||
|
||||
\begin{theorem}[Cut recoupling for six spin-1 legs]
|
||||
\label{thm:six-j-recoupling}
|
||||
Fix $S_1=ABC\mid DEF$ with source tree $(AB)C\!\to\! j$, target tree $(DE)F\!\to\! j$, giving the reduced blocks $A_j^{(1)}[p,y]$ of Example~\ref{ex:singlet-superposition}, and $S_2=AB\mid CDEF$ with source $AB\!\to\! p$ directly and target tree $C,(DE)F\!\to\! p$, giving $A_p^{(2)}[y,j]$. For every $G$-invariant $\rho$ on the six qubits (collective hypothesis~\eqref{eq:collective-hypothesis}) and every valid $(p,y,j)$,
|
||||
\begin{equation}
|
||||
A_p^{(2)}[y,j] \;=\; -\sqrt{\frac{2j+1}{2p+1}}\;A_j^{(1)}[p,y],
|
||||
\label{eq:six-j-recoupling}
|
||||
\end{equation}
|
||||
independent of $y$.
|
||||
\end{theorem}
|
||||
|
||||
\begin{proof}[Proof sketch]
|
||||
Write $\widehat T$ for $T(\rho)$ regarded as a genuinely multilinear (not sesquilinear) functional of one real vector per leg, complexified $\mathbb C$-multilinearly; this is the natural extension of the real bilinear pairing underlying $M_{S\to S^c}(\rho)$ (the response-map construction of \cite{aschauer2026a}), and $G$-invariance of $\rho$ makes $\widehat T$ invariant under simultaneous rotation of all six legs.
|
||||
|
||||
\emph{Step 1 (the correct Schur reduction).} For coupled multiplets $u_{p,j,m}$ (tree $(AB)C$) and $v_{y,j,m'}$ (tree $(DE)F$), invariance forces $X_{mm'}:=\widehat T(u_{p,j,m},v_{y,j,m'})$ to satisfy $X=D^j(g)^{\mathsf T}XD^j(g)$ for every $g$ (transpose, not the Hermitian adjoint, since $\widehat T$ is bilinear). Using $D^j(g)^{\mathsf T}=C_jD^j(g^{-1})C_j^{-1}$ with the metric $(C_j)_{mm'}=(-1)^{j-m}\delta_{m,-m'}$ turns this into an ordinary intertwiner condition $D^j(g)(C_j^{-1}X)=(C_j^{-1}X)D^j(g)$, so by Schur's lemma $C_j^{-1}X\propto\id$, i.e.
|
||||
\begin{equation}
|
||||
\widehat T(u_{p,j,m},v_{y,j,m'}) = c(p,y,j)\,(-1)^{j-m}\,\delta_{m,-m'}
|
||||
\label{eq:schur-metric-form}
|
||||
\end{equation}
|
||||
for a single scalar $c(p,y,j)$. The identical argument, applied with the $C$-leg left unpaired, gives the reduced three-index object $R(m_p,m_C,m_j):=\widehat T(u^{AB}_{p,m_p}\otimes e_{m_C},\,v_{y,j,m_j})$ in terms of the \emph{same} scalar $c(p,y,j)$: since $R$, viewed as pairing the $j$-isotype of $V_p\otimes V_1$ against $V_j$, is subject to the same metric-form constraint,
|
||||
\begin{equation}
|
||||
R(m_p,m_C,m_j) = c(p,y,j)\,(-1)^{j+m_j}\,\langle p,m_p;1,m_C\,|\,j,-m_j\rangle.
|
||||
\label{eq:R-metric-form}
|
||||
\end{equation}
|
||||
(Naively assuming $R\propto\langle p,m_p;1,m_C|j,m_j\rangle$ without the metric/sign flip -- i.e.\ skipping the $C_j$ correction in Step 1 -- reproduces neither the correct selection rule $m_p+m_C+m_j=0$ forced by $\widehat T$'s own weight conservation, nor a $y$-independent final answer; this was the error in an earlier attempt.)
|
||||
|
||||
\emph{Step 2 (conjugation of a real-representation-derived multiplet).} For an $n_\ell$-leg multiplet of total spin $J$ built from the single-leg basis, complex conjugation acts by
|
||||
\begin{equation}
|
||||
\overline{v_{J,m}} = (-1)^{J+n_\ell}\,(-1)^m\,v_{J,-m},
|
||||
\label{eq:conjugation-phase}
|
||||
\end{equation}
|
||||
verified directly for the $3$-leg tree $(DE)F$ ($n_\ell=3$) and the $4$-leg tree $C,(DE)F$ ($n_\ell=4$) in \texttt{scripts/six\_j\_recoupling\_proof.py}, exactly and for every multiplet label. (Eq.~\eqref{eq:conjugation-phase} follows from the standard conjugation identity for Wigner $D$-matrices, $\overline{D^j_{m'm}(g)}=(-1)^{m'-m}D^j_{-m',-m}(g)$, applied inductively through the coupling tree together with Schur's lemma at each step; we verify the closed form directly rather than re-deriving the induction here.)
|
||||
|
||||
\emph{Step 3 (assembly).} Combining Eqs.~\eqref{eq:schur-metric-form} and \eqref{eq:conjugation-phase} (with $n_\ell=3$) gives $A_j^{(1)}[p,y]=(-1)^{j+1}(-1)^jc(p,y,j)=-c(p,y,j)$, since $(-1)^{2j+1}=-1$ for integer $j$. For $A_p^{(2)}[y,j]$, expand the target multiplet $w_{y,j,p,m'}=\sum_{m_C,m_j}\langle1,m_C;j,m_j|p,m'\rangle\,e_{m_C}\otimes v_{y,j,m_j}$, apply Eq.~\eqref{eq:conjugation-phase} with $n_\ell=4$, and use Eq.~\eqref{eq:R-metric-form} for the resulting contraction with $R$. This leaves the finite sum
|
||||
\begin{equation}
|
||||
\Xi(p,j,m') := \sum_{m_C,m_j}\langle1,m_C;j,m_j\,|\,p,-m'\rangle\,(-1)^{j+m_j}\,\langle p,m';1,m_C\,|\,j,-m_j\rangle,
|
||||
\label{eq:xi-sum}
|
||||
\end{equation}
|
||||
verified exactly (sympy, symbolic Clebsch--Gordan coefficients) to satisfy $(-1)^p(-1)^{m'}\Xi(p,j,m')=\sqrt{(2j+1)/(2p+1)}$ for every valid $(p,j,m')$ with $p,j\le3$ -- in particular independent of $m'$, as it must be, since $A_p^{(2)}$ is $m'$-independent by Corollary~\ref{cor:projector-norm-formula}. Assembling: $A_p^{(2)}[y,j]=\sqrt{(2j+1)/(2p+1)}\,c(p,y,j)=-\sqrt{(2j+1)/(2p+1)}\,A_j^{(1)}[p,y]$, proving Eq.~\eqref{eq:six-j-recoupling}.
|
||||
\end{proof}
|
||||
|
||||
\begin{remark}[Scope and what remains open]
|
||||
\label{rem:six-j-scope}
|
||||
Eq.~\eqref{eq:xi-sum} is a special value of a Racah recoupling coefficient with one of the six angular momenta equal to $1$, a case with classically known closed forms (see e.g.\ Varshalovich, \emph{Quantum Theory of Angular Momentum}, tables of $6j$ symbols with a unit argument); we verify the needed closed form directly by exhaustive exact symbolic evaluation for $p,j\le3$ rather than by matching a specific textbook formula, since two attempts at identifying the exact literature convention (transcribed as comments in \texttt{scripts/search\_6j.py}) did not reproduce it and the discrepancy was not tracked down. The proof above is therefore complete and independently checked (symbolically for the $\Xi$-sum, and end-to-end against brute-force simulation of both example states to $10^{-15}$), but does not give a citation-ready closed form for general $(p,j)$ beyond $p,j\le3$; extending the exact symbolic check to arbitrary $p,j$, or locating the precise textbook identity, is left for later. The conjugation phase of Eq.~\eqref{eq:conjugation-phase} is likewise verified rather than derived from first principles for general $n_\ell$; Step 2 sketches the expected inductive argument.
|
||||
\end{remark}
|
||||
|
||||
% TODO (outlook, not attempted): combining the S_m-permutation collapse of
|
||||
|
|
|
|||
134
scripts/symmetric_states/1_recoupling_check.py
Normal file
134
scripts/symmetric_states/1_recoupling_check.py
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
Numerical check of the 'one invariant tensor, many cuts' claim
|
||||
for the 6-qubit singlet-network example states.
|
||||
|
||||
Qubits ordered A,B,C,D,E,F -> 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><Xi|, linearity in rho gives EXACTLY
|
||||
T(Xi) = [ cos^2(a) T1 + sin^2(a) T2 + cos(a)sin(a) C12 ] / N2
|
||||
where T1 = <psi1|O|psi1>, T2 = <psi2|O|psi2>, C12 = <psi1|O|psi2> + <psi2|O|psi1>,
|
||||
N2 = <Xi_raw|Xi_raw>.
|
||||
|
||||
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 <psi1|psi2> =", 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):
|
||||
"""<bra| sigma_i1 x ... x sigma_i6 |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 = <psi1|O|psi1> ...")
|
||||
T1 = corr_tensor(psi1, psi1)
|
||||
print("computing T2 = <psi2|O|psi2> ...")
|
||||
T2 = corr_tensor(psi2, psi2)
|
||||
print("computing cross term <psi1|O|psi2> ...")
|
||||
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}")
|
||||
88
scripts/symmetric_states/2_exact_casimir_projectors.py
Normal file
88
scripts/symmetric_states/2_exact_casimir_projectors.py
Normal file
|
|
@ -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")
|
||||
56
scripts/symmetric_states/3_apply_exact_projectors.py
Normal file
56
scripts/symmetric_states/3_apply_exact_projectors.py
Normal file
|
|
@ -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 # <psi1|psi2>, 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)
|
||||
65
scripts/symmetric_states/4_spherical_basis.py
Normal file
65
scripts/symmetric_states/4_spherical_basis.py
Normal file
|
|
@ -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)")
|
||||
189
scripts/symmetric_states/6_six_j_recoupling_proof(1).py
Normal file
189
scripts/symmetric_states/6_six_j_recoupling_proof(1).py
Normal file
|
|
@ -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) <p,m';1,mC|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.")
|
||||
189
scripts/symmetric_states/6_six_j_recoupling_proof.py
Normal file
189
scripts/symmetric_states/6_six_j_recoupling_proof.py
Normal file
|
|
@ -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) <p,m';1,mC|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.")
|
||||
89
scripts/symmetric_states/README.md
Normal file
89
scripts/symmetric_states/README.md
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
# 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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue