feat: add numeric and symbolic scripts
This commit is contained in:
parent
6ea7900b55
commit
e80b7c3582
38 changed files with 3314 additions and 0 deletions
167
scripts/dps_hierarchy/common.py
Normal file
167
scripts/dps_hierarchy/common.py
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
"""
|
||||
common.py
|
||||
|
||||
Shared numpy utilities for the Tiles-state / DPS-hierarchy scripts (03-08).
|
||||
Convention: qutrits (d=3), Gell-Mann generators scaled so that
|
||||
tr(sigma_i sigma_j) = d * delta_ij = 3 * delta_ij, matching the paper's own
|
||||
convention (see the Tiles benchmark in symmetric_shadow_maps_formal.tex).
|
||||
"""
|
||||
import numpy as np
|
||||
|
||||
d = 3
|
||||
|
||||
# --- Gell-Mann matrices, paper convention ---
|
||||
_lam = [None] * 8
|
||||
_lam[0] = np.array([[0, 1, 0], [1, 0, 0], [0, 0, 0]], dtype=complex)
|
||||
_lam[1] = np.array([[0, -1j, 0], [1j, 0, 0], [0, 0, 0]], dtype=complex)
|
||||
_lam[2] = np.array([[1, 0, 0], [0, -1, 0], [0, 0, 0]], dtype=complex)
|
||||
_lam[3] = np.array([[0, 0, 1], [0, 0, 0], [1, 0, 0]], dtype=complex)
|
||||
_lam[4] = np.array([[0, 0, -1j], [0, 0, 0], [1j, 0, 0]], dtype=complex)
|
||||
_lam[5] = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]], dtype=complex)
|
||||
_lam[6] = np.array([[0, 0, 0], [0, 0, -1j], [0, 1j, 0]], dtype=complex)
|
||||
_lam[7] = (1 / np.sqrt(3)) * np.array([[1, 0, 0], [0, 1, 0], [0, 0, -2]], dtype=complex)
|
||||
GELLMANN = [np.sqrt(3 / 2) * L for L in _lam]
|
||||
|
||||
I3 = np.eye(3, dtype=complex)
|
||||
I9 = np.eye(9, dtype=complex)
|
||||
|
||||
|
||||
def opA(P):
|
||||
return np.kron(P, I3)
|
||||
|
||||
|
||||
def opB(P):
|
||||
return np.kron(I3, P)
|
||||
|
||||
|
||||
def e(i):
|
||||
v = np.zeros(3)
|
||||
v[i] = 1
|
||||
return v
|
||||
|
||||
|
||||
# --- The Tiles UPB bound-entangled state (Bennett, DiVincenzo, Mor, Shor,
|
||||
# Smolin, Terhal 1999), as used in the paper's own qutrit benchmark ---
|
||||
def _build_tiles():
|
||||
sqrt2, sqrt3 = np.sqrt(2), np.sqrt(3)
|
||||
upb = [
|
||||
np.kron(e(0), (e(0) - e(1)) / sqrt2),
|
||||
np.kron(e(2), (e(1) - e(2)) / sqrt2),
|
||||
np.kron((e(0) - e(1)) / sqrt2, e(2)),
|
||||
np.kron((e(1) - e(2)) / sqrt2, e(0)),
|
||||
np.kron((e(0) + e(1) + e(2)) / sqrt3, (e(0) + e(1) + e(2)) / sqrt3),
|
||||
]
|
||||
P_UPB = sum(np.outer(v, v) for v in upb)
|
||||
return ((np.eye(9) - P_UPB) / 4).astype(complex)
|
||||
|
||||
|
||||
RHO_TILES = _build_tiles()
|
||||
|
||||
|
||||
def noisy_tiles(p):
|
||||
"""rho(p) = p * rho_Tiles + (1-p) * I/9"""
|
||||
return p * RHO_TILES + (1 - p) * I9 / 9
|
||||
|
||||
|
||||
# --- Qutrit Werner state (antisymmetric-subspace family), Werner 1989 ---
|
||||
def _swap_matrix(dim=3):
|
||||
V = np.zeros((dim * dim, dim * dim))
|
||||
for a in range(dim):
|
||||
for b in range(dim):
|
||||
V[b * dim + a, a * dim + b] = 1
|
||||
return V
|
||||
|
||||
|
||||
SWAP_3 = _swap_matrix(3)
|
||||
P_ANTI = (np.eye(9) - SWAP_3) / 2
|
||||
DIM_ANTI = np.trace(P_ANTI).real # = 3
|
||||
|
||||
|
||||
def werner_qutrit(p):
|
||||
"""rho(p) = p * P_anti/3 + (1-p) * I/9. Known exact separability
|
||||
threshold: p = 1/(d+1) = 1/4 (Werner 1989)."""
|
||||
return p * P_ANTI / DIM_ANTI + (1 - p) * I9 / 9
|
||||
|
||||
|
||||
# --- Correlation matrix / shadow-map criterion (paper Section "tensor
|
||||
# viewpoint" / Tiles benchmark) ---
|
||||
def correlation_matrix(rho):
|
||||
T = np.zeros((8, 8))
|
||||
for i in range(8):
|
||||
for j in range(8):
|
||||
T[i, j] = np.trace(rho @ opA(GELLMANN[i]) @ opB(GELLMANN[j])).real
|
||||
return T
|
||||
|
||||
|
||||
def shadow_map_nuclear_norm(rho):
|
||||
"""||M_A(rho)||_*, normalization sqrt((d_A-1)(d_B-1)) = 2 for qutrits.
|
||||
Separable states satisfy this <= 1 (Theorem "cut-bound" in the note)."""
|
||||
T = correlation_matrix(rho)
|
||||
return np.linalg.svd(T / 2.0, compute_uv=False).sum()
|
||||
|
||||
|
||||
# --- Plain PPT (Peres-Horodecki) check ---
|
||||
def plain_ppt_min_eig(rho, dim=3):
|
||||
rho_pt = np.zeros((dim * dim, dim * dim), dtype=complex)
|
||||
for a in range(dim):
|
||||
for b in range(dim):
|
||||
for ap in range(dim):
|
||||
for bp in range(dim):
|
||||
i, j = a * dim + b, ap * dim + bp
|
||||
i2, j2 = a * dim + bp, ap * dim + b
|
||||
rho_pt[i2, j2] = rho[i, j]
|
||||
return np.linalg.eigvalsh(rho_pt).min()
|
||||
|
||||
|
||||
def plain_ppt_feasible(rho, dim=3, tol=1e-9):
|
||||
return plain_ppt_min_eig(rho, dim) >= -tol
|
||||
|
||||
|
||||
# --- numpy partial traces, used only by the operator-Sinkhorn filter ---
|
||||
def partial_trace_B_np(X, dim=3):
|
||||
T = X.reshape(dim, dim, dim, dim)
|
||||
return np.einsum('ikjk->ij', T)
|
||||
|
||||
|
||||
def partial_trace_A_np(X, dim=3):
|
||||
T = X.reshape(dim, dim, dim, dim)
|
||||
return np.einsum('kikj->ij', T)
|
||||
|
||||
|
||||
def _inv_sqrt_psd(M, eps=1e-12):
|
||||
w, v = np.linalg.eigh(M)
|
||||
w = np.clip(w, eps, None)
|
||||
return (v * (w ** -0.5)) @ v.conj().T
|
||||
|
||||
|
||||
def operator_sinkhorn(rho, dim=3, max_iter=3000, tol=1e-11, verbose=False):
|
||||
"""Local-filtering (SLOCC) normal-form algorithm: alternately rescale
|
||||
each side by (reduced state)^{-1/2} until both marginals are maximally
|
||||
mixed. Standard algorithm (Verstraete-Dehaene-DeMoor 2001/2003); the
|
||||
resulting fixed point is the Leinaas-Myrheim-Ovrum (2006) normal form."""
|
||||
X = rho.copy() / np.trace(rho).real
|
||||
devA = devB = None
|
||||
for it in range(max_iter):
|
||||
rhoA = partial_trace_B_np(X, dim)
|
||||
rhoA /= np.trace(rhoA).real
|
||||
devA = np.linalg.norm(rhoA - np.eye(dim) / dim)
|
||||
FA = np.kron(_inv_sqrt_psd(rhoA), np.eye(dim))
|
||||
X = FA @ X @ FA.conj().T
|
||||
X /= np.trace(X).real
|
||||
|
||||
rhoB = partial_trace_A_np(X, dim)
|
||||
rhoB /= np.trace(rhoB).real
|
||||
devB = np.linalg.norm(rhoB - np.eye(dim) / dim)
|
||||
FB = np.kron(np.eye(dim), _inv_sqrt_psd(rhoB))
|
||||
X = FB @ X @ FB.conj().T
|
||||
X /= np.trace(X).real
|
||||
|
||||
if devA < tol and devB < tol:
|
||||
if verbose:
|
||||
print(f" Sinkhorn converged after {it + 1} iterations")
|
||||
break
|
||||
else:
|
||||
if verbose:
|
||||
print(f" Sinkhorn did NOT fully converge in {max_iter} iters "
|
||||
f"(devA={devA:.2e}, devB={devB:.2e})")
|
||||
return X
|
||||
Loading…
Add table
Add a link
Reference in a new issue