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
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")
|
||||
Loading…
Add table
Add a link
Reference in a new issue