feat: add numeric and symbolic scripts

This commit is contained in:
Hans Aschauer 2026-07-26 14:09:49 +02:00
parent 6ea7900b55
commit e80b7c3582
38 changed files with 3314 additions and 0 deletions

View file

@ -5,8 +5,11 @@ description = "Add your description here"
readme = "README.md" readme = "README.md"
requires-python = ">=3.13" requires-python = ">=3.13"
dependencies = [ dependencies = [
"cvxpy>=1.9.2",
"matplotlib>=3.10.9", "matplotlib>=3.10.9",
"numpy>=2.5.1",
"qtensor", "qtensor",
"scipy>=1.18.0",
"sympy>=1.14.0", "sympy>=1.14.0",
] ]

BIN
scripts/O_seed.npz Normal file

Binary file not shown.

View file

@ -79,3 +79,87 @@ When adding another script, add a short entry above with:
- required dependencies beyond NumPy, if any; - required dependencies beyond NumPy, if any;
- the paper values, table, or figure it reproduces; - the paper values, table, or figure it reproduces;
- notes about normalization conventions if the output includes both raw and normalized quantities. - notes about normalization conventions if the output includes both raw and normalized quantities.
---
# Shadow-Map GME-Analyse: Skriptpaket
Alle Skripte aus der Diskussion zu "Symmetric Shadow Maps and Multipartite Correlation
Criteria", konsolidiert und bereinigt (keine überholten/fehlerhaften Zwischenversionen).
Qubit-Reihenfolge durchgehend (A,B,C,D) = (0,1,2,3).
## Grundmodule (werden von allem anderen importiert)
- **core.py** -- Parametrisierung reiner biseparabler 4-Qubit-Zustände
(`state_1_3`, `state_2_2`, `random_pure_state`).
- **core2.py** -- schnelle, vektorisierte Berechnung des vollen Pauli-Korrelationstensors
und der Einzelpartei-Shadow-Map (`full_tensor`, `phi_sym`).
- **cluster.py** -- die volle 15×15 bigraduierte Cluster-Shadow-Map `M_S` für
Zwei-Qubit-Quellcluster (`cluster_map`), plus Tensor-Extraktion für gemischte Zustände.
- **subblock.py** -- der "fully-active" 9×9-Unterblock von `M_S`.
- **party_blocks.py** -- rohe Einzelpartei-zu-Einzelpartei 3×3-Korrelationsblöcke.
## Kapitel 1: Universelle Sättigungs-Decke (bewiesen)
- **universal_ceiling.py** -- geschlossene Formel für das globale Maximum von
`||M_S||_*` über *alle* Zustände (Cauchy-Schwarz + Spur-Identität), reproduziert
`sqrt(6)`, `6/sqrt(7)`, `2.19089...` (n=3,4,5) und `5.0` (2-Qubit-Cluster, n=4) exakt.
## Kapitel 2: Warum Φ_sym und einzelne M_S keine GME-Zeugen sind
- **optimize_phi_sym.py** -- zeigt, dass die biseparable Suprema von Φ_sym (2|2- und
1|3-Schnitt) und das globale Supremum über *alle* Zustände identisch sind.
- **optimize_cluster_witness.py** -- zeigt dasselbe für einzelne `||M_AB||_*`, plus
erste (noch zu enge) Suche nach `min(M_AB,M_AC,M_AD)` über 1|3-biseparable Zustände.
- **pairwise_correlation_demo.py** -- Exkurs: warum paarweise Korrelationsblöcke
ebenfalls kein sauberes Kriterium liefern (Graphzustände haben selbst verschwindende
Paar-Blöcke).
## Kapitel 3: Der funktionierende GME-Zeuge `min(M_AB,M_AC,M_AD)`
- **mixture_search.py** -- Kernstück: exakte geschlossene Form für die
Bell-Paar-Mischungsfamilie (`p=1/2` optimal, Wert exakt 3), plus allgemeine
Mischungssuchen (2/3/4/6 Komponenten), die alle bei 3.0 landen.
- **sum_bound_proof.py** -- der rigorose Beweis der Schranke `<= 11/3` (Summe statt
Minimum ist konvex; LP bestätigt, dass Gleichgewichtung optimal für diese Technik ist).
## Kapitel 4: SDP-Verschärfung über PPT-Mixtures
Benötigt zusätzlich `pip install cvxpy` (Solver SCS ist enthalten).
- **sdp_ppt_mixture.py** -- PPT-Mixture-SDP + alternierendes (Frank-Wolfe-artiges)
Suchschema über duale Zeugen. **Wichtig:** `order='C'` in beiden `cp.reshape`-Aufrufen
ist notwendig (Standard-Fortran-Order von cvxpy passt sonst nicht zur row-major
Konstruktion von `PT_MATRIX`/`COEFF`).
- **compute_seed_witnesses.py** -- erzeugt `O_seed.npz` (optimale duale Zeugen für den
bekannten 3.0-Punkt). Vor den beiden folgenden Skripten einmal ausführen.
- **diagnostic_seeded_run.py** -- Sanity-Check: ein einzelner SDP-Schritt mit den
exakten Zeugen des bekannten Punktes muss `t >= 3.0` liefern (sonst Implementierungsfehler).
- **seeded_exploration.py** -- Stabilitäts-/Störungstest um den bekannten Punkt herum,
über die *volle* PPT-Mixture-Menge (echte Obermenge der biseparablen Zustände).
## Empfohlene Ausführungsreihenfolge
```bash
python3 universal_ceiling.py
python3 optimize_phi_sym.py # dauert ~1-2 Minuten
python3 optimize_cluster_witness.py # dauert ~1-2 Minuten
python3 pairwise_correlation_demo.py
python3 mixture_search.py # die Mischungs-Optimierungen sind langsam (Powell
# skaliert schlecht mit der Parameterzahl) --
# einzelne Restarts können 1-2 Minuten dauern
python3 sum_bound_proof.py
# für Kapitel 4 (SDP):
pip install cvxpy
python3 compute_seed_witnesses.py
python3 diagnostic_seeded_run.py
python3 seeded_exploration.py
```
## Status der zentralen Vermutung
- **Bewiesen:** `min(M_AB,M_AC,M_AD) <= 11/3` für jeden biseparablen Zustand.
- **Numerisch sehr robust (mehrere unabhängige Methoden, inkl. SDP über die größere
PPT-Mixture-Menge), aber nicht formal bewiesen:** die wahre Schranke ist exakt `3.0`.

50
scripts/cluster.py Normal file
View file

@ -0,0 +1,50 @@
"""cluster.py -- the full 15x15 bigraduated cluster shadow map M_S for a 2-qubit source
cluster S (within 4 qubits), and helpers for building the Pauli tensor of a general
(possibly mixed) density matrix."""
import numpy as np
I2 = np.eye(2, dtype=complex)
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)
_PAULI = [I2, X, Y, Z]
def cluster_map(C, s0, s1):
"""C: (4,4,4,4) Pauli correlation tensor. S = {s0,s1} (source cluster), complement =
the other two qubits. Returns the normalized 15x15 matrix M_S(rho), normalization
1/sqrt((d_S-1)(d_Sc-1)) = 1/sqrt(3*3) = 1/3 for two-qubit clusters in 4 qubits."""
others = [k for k in range(4) if k not in (s0, s1)]
c0, c1 = others
rows = [(i, j) for i in range(4) for j in range(4) if (i, j) != (0, 0)]
cols = [(i, j) for i in range(4) for j in range(4) if (i, j) != (0, 0)]
M = np.zeros((15, 15))
for ri, (ia, ib) in enumerate(rows):
for ci, (ic, idd) in enumerate(cols):
idx = [0, 0, 0, 0]
idx[s0] = ia
idx[s1] = ib
idx[c0] = ic
idx[c1] = idd
M[ri, ci] = C[tuple(idx)]
return M / 3.0
def nuc(M):
return np.linalg.svd(M, compute_uv=False).sum()
def full_tensor_mixed(rho):
"""Pauli-tensor extraction for a general (possibly mixed) 16x16 density matrix rho,
via direct trace. Slower than core2.full_tensor (which needs a pure-state vector)
but works for explicit mixtures (e.g. the Smolin state)."""
def kron4(a, b, c, d):
return np.kron(np.kron(a, b), np.kron(c, d))
C = np.zeros((4, 4, 4, 4))
for i0 in range(4):
for i1 in range(4):
for i2 in range(4):
for i3 in range(4):
op = kron4(_PAULI[i0], _PAULI[i1], _PAULI[i2], _PAULI[i3])
C[i0, i1, i2, i3] = np.real(np.trace(rho @ op))
return C

View file

@ -0,0 +1,43 @@
"""compute_seed_witnesses.py -- computes and saves the exact optimal dual witnesses
O_AB, O_AC, O_AD (each with operator norm 1) for the known-good state
rho_mix = 0.5 * (Bell_AB x Bell_CD) + 0.5 * (Bell_AC x Bell_BD)
which is a manifestly valid PPT-mixture state (explicit convex combination of two
product states) scoring EXACTLY (3.0, 3.0, 3.0) for
(||M_AB||_*, ||M_AC||_*, ||M_AD||_*) -- see mixture_search.py for the derivation.
Run this once to produce O_seed.npz, which diagnostic_seeded_run.py and
seeded_exploration.py both load.
"""
import numpy as np
from cluster import cluster_map, nuc
from mixture_search import bellpair_state, full_tensor_mixed_fast
CLUSTERS = [('AB', 0, 1), ('AC', 0, 2), ('AD', 0, 3)]
def true_norms_and_witnesses(M_vals):
norms, O_opt = {}, {}
for name, M in M_vals.items():
U, s, Vt = np.linalg.svd(M)
norms[name] = s.sum()
O_opt[name] = U @ Vt
return norms, O_opt
if __name__ == "__main__":
psi1 = bellpair_state(((0, 1), (2, 3)))
psi2 = bellpair_state(((0, 2), (1, 3)))
rho_mix = 0.5 * np.outer(psi1, np.conj(psi1)) + 0.5 * np.outer(psi2, np.conj(psi2))
C = full_tensor_mixed_fast(rho_mix)
M_vals = {name: cluster_map(C, s0, s1) for name, s0, s1 in CLUSTERS}
norms, O_seed = true_norms_and_witnesses(M_vals)
print('norms at rho_mix:', norms, ' (expect all == 3.0)')
for k, v in O_seed.items():
print(f' operator norm of O_seed[{k}] =', np.linalg.norm(v, ord=2), '(should be 1.0)')
np.savez('O_seed.npz', **O_seed)
print('Saved O_seed.npz')

61
scripts/core.py Normal file
View file

@ -0,0 +1,61 @@
"""core.py -- pure-state parametrizations for biseparable 4-qubit states.
state_1_3(params, source): pure state, product across `source` | (other 3 qubits).
state_2_2(params, pair1, pair2): pure state, product across pair1 | pair2 (each a
2-qubit tuple of indices).
Qubit order throughout this project is (A,B,C,D) = (0,1,2,3), and a 16-dim state
vector is indexed linearly as idx = i0*8 + i1*4 + i2*2 + i3.
"""
import numpy as np
def random_pure_state(dim, rng):
v = rng.normal(size=dim) + 1j * rng.normal(size=dim)
return v / np.linalg.norm(v)
def state_1_3(params, source):
"""Pure state product across `source` (single qubit) | (other 3 qubits).
params: 18 reals = 2 (single-qubit Bloch angles theta,phi) + 16 (3-qubit target
complex amplitudes, given as 8 real + 8 imaginary parts)."""
theta, phi = params[0], params[1]
q_src = np.array([np.cos(theta / 2), np.exp(1j * phi) * np.sin(theta / 2)], dtype=complex)
tgt_re = params[2:2 + 8]
tgt_im = params[2 + 8:2 + 16]
q_tgt = tgt_re + 1j * tgt_im
q_tgt = q_tgt / np.linalg.norm(q_tgt)
others = [k for k in range(4) if k != source]
full = np.zeros(16, dtype=complex)
for s in range(2):
for t_idx in range(8):
bits = [(t_idx >> 2) & 1, (t_idx >> 1) & 1, t_idx & 1]
idx = [0, 0, 0, 0]
idx[source] = s
for oi, o in enumerate(others):
idx[o] = bits[oi]
lin = idx[0] * 8 + idx[1] * 4 + idx[2] * 2 + idx[3]
full[lin] = q_src[s] * q_tgt[t_idx]
return full
def state_2_2(params, pair1, pair2):
"""Pure state product across pair1 | pair2 (each a 2-qubit index tuple).
params: 16 reals = 8 (pair1 complex amplitudes) + 8 (pair2 complex amplitudes)."""
p1 = params[0:4] + 1j * params[4:8]
p1 = p1 / np.linalg.norm(p1)
p2 = params[8:12] + 1j * params[12:16]
p2 = p2 / np.linalg.norm(p2)
full = np.zeros(16, dtype=complex)
for a in range(2):
for b in range(2):
for c in range(2):
for d in range(2):
idx = [0, 0, 0, 0]
idx[pair1[0]] = a
idx[pair1[1]] = b
idx[pair2[0]] = c
idx[pair2[1]] = d
lin = idx[0] * 8 + idx[1] * 4 + idx[2] * 2 + idx[3]
full[lin] = p1[a * 2 + b] * p2[c * 2 + d]
return full

61
scripts/core2.py Normal file
View file

@ -0,0 +1,61 @@
"""core2.py -- fast, vectorized computation of the 4-qubit Pauli/Bloch correlation
tensor and the single-party combined shadow map M_a, plus the source-aggregated
functional Phi_sym.
C[i0,i1,i2,i3] = tr(rho * sigma_i0 x sigma_i1 x sigma_i2 x sigma_i3), i_k in {0,1,2,3}
(0 = identity, 1,2,3 = X,Y,Z), for a PURE state psi (rho = |psi><psi|).
"""
import numpy as np
I2 = np.eye(2, dtype=complex)
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)
PAULI = np.stack([I2, X, Y, Z]) # shape (4,2,2), index (pauli_label, row, col)
def full_tensor(psi):
"""psi: length-16 complex state vector, qubit order (A,B,C,D).
Returns the (4,4,4,4) real Pauli correlation tensor."""
t = psi.reshape(2, 2, 2, 2)
tc = np.conj(t)
C = np.einsum('abcd,iae,jbf,kcg,ldh,efgh->ijkl',
tc, PAULI, PAULI, PAULI, PAULI, t, optimize=True)
return C.real
# all (j0,j1,j2) index combinations excluding the all-identity (0,0,0) target sector
_COLS = np.array([(j0, j1, j2) for j0 in range(4) for j1 in range(4) for j2 in range(4)
if (j0, j1, j2) != (0, 0, 0)])
def shadow_map_singleparty(C, a):
"""Normalized combined single-party shadow map M_a(rho): 3 x 63 real matrix,
normalization 1/sqrt(2^(n-1)-1) = 1/sqrt(7) for n=4 qubits."""
others = [k for k in range(4) if k != a]
rows = np.array([1, 2, 3])
n_cols = len(_COLS)
Ridx = np.repeat(rows, n_cols)
J0 = np.tile(_COLS[:, 0], 3)
J1 = np.tile(_COLS[:, 1], 3)
J2 = np.tile(_COLS[:, 2], 3)
idx_full = [None] * 4
idx_full[a] = Ridx
idx_full[others[0]] = J0
idx_full[others[1]] = J1
idx_full[others[2]] = J2
vals = C[idx_full[0], idx_full[1], idx_full[2], idx_full[3]]
M = vals.reshape(3, n_cols)
return M / np.sqrt(7.0)
def nuclear_norm(M):
return np.linalg.svd(M, compute_uv=False).sum()
def phi_sym(psi):
"""Phi_sym(rho) = (1/4) sum_a ||M_a(rho)||_*, for a pure 4-qubit state psi.
Returns (mean_value, [values per party A,B,C,D])."""
C = full_tensor(psi)
vals = [nuclear_norm(shadow_map_singleparty(C, a)) for a in range(4)]
return float(np.mean(vals)), vals

View file

@ -0,0 +1,54 @@
"""
DIAGNOSTIC: run a single SDP step seeded with the EXACT optimal dual witnesses of the
known-good point rho_mix = 0.5*(Bell_AB x Bell_CD) + 0.5*(Bell_AC x Bell_BD), which is a
manifestly valid PPT-mixture state (explicit convex combination of two product states)
scoring EXACTLY (3.0, 3.0, 3.0) for (||M_AB||_*, ||M_AC||_*, ||M_AD||_*).
Since rho_mix is feasible and, for these SPECIFIC witnesses O_S = U_S V_S^T (from its own
SVD), achieves t = sum_S tr(O_S^T M_S(rho_mix)) = 3.0 exactly, any correct implementation
of
max_{rho in PPT-mixtures} min_S tr(O_S^T M_S(rho))
MUST return an optimal value >= 3.0 (the SDP maximizes over a set containing rho_mix).
If the reported optimal t comes back < 3.0 here, that is conclusive evidence of an
implementation bug in the SDP construction itself (not just a weakness of the alternating
heuristic / random restarts).
Run this BEFORE re-running the full alternating_search -- it isolates the problem.
"""
import numpy as np
from sdp_ppt_mixture import solve_fixed_witness_step, true_norms_and_witnesses, CLUSTERS
data = np.load('O_seed.npz')
O = {name: data[name] for name, _, _ in CLUSTERS}
print("Loaded seed witnesses (each should have operator norm 1):")
for name, Om in O.items():
print(f" ||O_{name}||_op =", np.linalg.norm(Om, ord=2))
print()
print("Solving ONE SDP step with these witnesses...")
rho_val, t_val, M_vals = solve_fixed_witness_step(O, verbose=True)
print()
print("SDP optimal t =", t_val)
print("Expected: t >= 3.0 (since rho_mix itself is feasible and scores exactly 3.0 here)")
print()
norms, _ = true_norms_and_witnesses(M_vals)
print("True nuclear norms of the returned optimal rho:", norms)
if t_val < 2.99:
print()
print("!!! t < 3.0 -- there IS an implementation bug in the SDP construction. !!!")
print("Next diagnostic step: check prob.status, and manually verify PSD/PPT of")
print("rho_val's 7 constituent blocks (may need to re-solve while keeping rho_gammas")
print("accessible, i.e. return them from solve_fixed_witness_step for inspection).")
else:
print()
print("t >= 3.0 as expected: the SDP construction is correct.")
print("The earlier runs' convergence to 7/3 was the alternating heuristic getting")
print("stuck in a (large-basin) symmetric fixed point from random starts -- not a bug.")
print("Fix: warm-start alternating_search from these O_seed witnesses (or from a small")
print("random perturbation of them) instead of purely random O's.")

View file

@ -0,0 +1,55 @@
"""
DIAGNOSTIC: run a single SDP step seeded with the EXACT optimal dual witnesses of the
known-good point rho_mix = 0.5*(Bell_AB x Bell_CD) + 0.5*(Bell_AC x Bell_BD), which is a
manifestly valid PPT-mixture state (explicit convex combination of two product states)
scoring EXACTLY (3.0, 3.0, 3.0) for (||M_AB||_*, ||M_AC||_*, ||M_AD||_*).
Since rho_mix is feasible and, for these SPECIFIC witnesses O_S = U_S V_S^T (from its own
SVD), achieves t = sum_S tr(O_S^T M_S(rho_mix)) = 3.0 exactly, any correct implementation
of
max_{rho in PPT-mixtures} min_S tr(O_S^T M_S(rho))
MUST return an optimal value >= 3.0 (the SDP maximizes over a set containing rho_mix).
If the reported optimal t comes back < 3.0 here, that is conclusive evidence of an
implementation bug in the SDP construction itself (not just a weakness of the alternating
heuristic / random restarts).
Run this BEFORE re-running the full alternating_search -- it isolates the problem.
"""
import numpy as np
import cvxpy as cp
from sdp_ppt_mixture import solve_fixed_witness_step, CLUSTERS
data = np.load('O_seed.npz')
O = {name: data[name] for name, _, _ in CLUSTERS}
print("Loaded seed witnesses (each should have operator norm 1):")
for name, Om in O.items():
print(f" ||O_{name}||_op =", np.linalg.norm(Om, ord=2))
print()
print("Solving ONE SDP step with these witnesses...")
rho_val, t_val, M_vals = solve_fixed_witness_step(O, verbose=True)
print()
print("SDP optimal t =", t_val)
print("Expected: t >= 3.0 (since rho_mix itself is feasible and scores exactly 3.0 here)")
print()
norms, _ = __import__('sdp_ppt_mixture').true_norms_and_witnesses(M_vals)
print("True nuclear norms of the returned optimal rho:", norms)
if t_val < 2.99:
print()
print("!!! t < 3.0 -- there IS an implementation bug in the SDP construction. !!!")
print("Next diagnostic step: check prob.status, and manually verify PSD/PPT of")
print("rho_val's 7 constituent blocks (may need to re-solve while keeping rho_gammas")
print("accessible, i.e. return them from solve_fixed_witness_step for inspection).")
else:
print()
print("t >= 3.0 as expected: the SDP construction is correct.")
print("The earlier runs' convergence to 7/3 was the alternating heuristic getting")
print("stuck in a (large-basin) symmetric fixed point from random starts -- not a bug.")
print("Fix: warm-start alternating_search from these O_seed witnesses (or from a small")
print("random perturbation of them) instead of purely random O's.")

View file

@ -0,0 +1,96 @@
"""
01_werner_qubit_symbolic.py
Exact symbolic (sympy) check: the two-qubit Werner state
rho(p) = p |Psi-><Psi-| + (1-p) I/4, |Psi-> = (|01>-|10>)/sqrt(2)
is invariant under U (x) U for every U in SU(2). Since the adjoint
representation of SU(2) on the traceless qubit Bloch space R^3 is
irreducible (single isotype), the correlation matrix is forced to be
proportional to the identity. We check this exactly and compare the
resulting nuclear-norm threshold to the exact PPT/separability threshold.
Result: BOTH give exactly p = 1/3 -- the order-1 correlation-matrix
criterion is exactly tight here (a low-dimensional special case, since
PPT=separable for 2x2 systems by the Horodecki theorem).
Requires: sympy. Runtime: a few seconds.
"""
import sympy as sp
from sympy import sqrt, I, simplify, Matrix, eye, zeros, re, symbols
X = Matrix([[0, 1], [1, 0]])
Y = Matrix([[0, -I], [I, 0]])
Z = Matrix([[1, 0], [0, -1]])
I2 = eye(2)
def kron(A, B):
mA, nA = A.shape
mB, nB = B.shape
out = zeros(mA * mB, nA * nB)
for i in range(mA):
for j in range(nA):
out[i * mB:(i + 1) * mB, j * nB:(j + 1) * nB] = A[i, j] * B
return out
def op_A(P):
return kron(P, I2)
def op_B(P):
return kron(I2, P)
p = symbols('p', real=True)
psi = zeros(4, 1)
psi[1, 0] = 1 / sqrt(2)
psi[2, 0] = -1 / sqrt(2)
rho_singlet = simplify(psi * psi.H)
rho_p = simplify(p * rho_singlet + (1 - p) * eye(4) / 4)
print("rho(p) =")
sp.pprint(rho_p)
def entry(rho, ops):
M = None
for op in ops:
M = op if M is None else M * op
return simplify(re(simplify((rho * M).trace())))
plist = [X, Y, Z]
T = Matrix(3, 3, lambda i, j: entry(rho_p, [op_A(plist[i]), op_B(plist[j])]))
print("\nCorrelation matrix T(p) =")
sp.pprint(T)
G = simplify(T.T * T)
eigs = G.eigenvals()
singular_values = []
for ev, mult in eigs.items():
singular_values += [simplify(sqrt(ev))] * mult
nuclear_norm = simplify(sum(singular_values))
print("\nNuclear norm ||M_A(rho(p))||_* =", nuclear_norm)
print("Shadow-map threshold (||.||_* = 1):", sp.solve(sp.Eq(nuclear_norm, 1), p))
def partial_transpose_B(M):
Mpt = zeros(4, 4)
for a in range(2):
for b in range(2):
for c in range(2):
for dd in range(2):
i, j = a * 2 + b, c * 2 + dd
i2, j2 = a * 2 + dd, c * 2 + b
Mpt[i2, j2] = M[i, j]
return Mpt
rho_pt = partial_transpose_B(rho_p)
print("\nEigenvalues of the partial transpose rho(p)^{T_B}:")
for e_ in rho_pt.eigenvals().keys():
print(" ", simplify(e_), " = 0 at p =", sp.solve(sp.Eq(e_, 0), p))

View file

@ -0,0 +1,103 @@
"""
02_werner_qutrit_symbolic.py
Same check as 01_werner_qubit_symbolic.py, generalized to d=3 (qutrits),
using the sqrt(3/2)-scaled Gell-Mann convention fixed in the paper's own
Tiles example (tr(sigma_i sigma_j) = d delta_ij = 3 delta_ij).
State family: rho(p) = p * P_anti/dim(P_anti) + (1-p) * I/9
(the natural qutrit "Werner state" built from the antisymmetric subspace
of C^3 x C^3, dimension 3), invariant under U(x)U for all U in U(3).
Result (the interesting part): the order-1 shadow-map/correlation-matrix
criterion gives p_c = 1/2, but the TRUE separability threshold (Werner
1989, p_sep = 1/(d+1)) is p_c = 1/4. Unlike the qubit case, the criterion
is here only a valid but NOT tight sufficient condition -- symmetry forces
"concentration" of the signal (single isotype => correlation matrix
proportional to identity) but not "sharpening" of the threshold itself.
Requires: sympy. Runtime: under a minute.
"""
import sympy as sp
from sympy import sqrt, I, simplify, Matrix, eye, zeros, re, symbols, Rational
d = 3
lam = [None] * 8
lam[0] = Matrix([[0, 1, 0], [1, 0, 0], [0, 0, 0]])
lam[1] = Matrix([[0, -I, 0], [I, 0, 0], [0, 0, 0]])
lam[2] = Matrix([[1, 0, 0], [0, -1, 0], [0, 0, 0]])
lam[3] = Matrix([[0, 0, 1], [0, 0, 0], [1, 0, 0]])
lam[4] = Matrix([[0, 0, -I], [0, 0, 0], [I, 0, 0]])
lam[5] = Matrix([[0, 0, 0], [0, 0, 1], [0, 1, 0]])
lam[6] = Matrix([[0, 0, 0], [0, 0, -I], [0, I, 0]])
lam[7] = (1 / sqrt(3)) * Matrix([[1, 0, 0], [0, 1, 0], [0, 0, -2]])
c = sqrt(Rational(3, 2))
sigma = [simplify(c * L) for L in lam]
for i in range(8):
for j in range(8):
val = simplify((sigma[i] * sigma[j]).trace())
assert val == (3 if i == j else 0), (i, j, val)
I3 = eye(3)
def op_A(P):
return sp.Matrix(sp.kronecker_product(P, I3))
def op_B(P):
return sp.Matrix(sp.kronecker_product(I3, P))
V = zeros(9, 9)
for a in range(3):
for b in range(3):
V[b * 3 + a, a * 3 + b] = 1
I9 = eye(9)
P_anti = simplify((I9 - V) / 2)
dim_anti = simplify(P_anti.trace()) # = 3
p = symbols('p', real=True)
rho_p = simplify(p * P_anti / dim_anti + (1 - p) * I9 / 9)
def entry(rho, ops):
M = None
for op in ops:
M = op if M is None else M * op
return simplify(re(simplify((rho * M).trace())))
T = Matrix(8, 8, lambda i, j: entry(rho_p, [op_A(sigma[i]), op_B(sigma[j])]))
print("Correlation matrix T(p) (should be proportional to I_8):")
sp.pprint(T)
norm_const = 1 / sqrt(4) # (d_a-1)(d_bar_a-1) = 2*2 = 4
Mn = simplify(norm_const * T)
nuclear_norm = simplify(8 * sp.Abs(Mn[0, 0]))
print("\nShadow-map nuclear norm:", nuclear_norm)
print("Shadow-map threshold:", sp.solve(sp.Eq(nuclear_norm, 1), p))
def partial_transpose_B_d(M, dim):
Mpt = zeros(dim * dim, dim * dim)
for a in range(dim):
for b in range(dim):
for cc in range(dim):
for dd in range(dim):
i, j = a * dim + b, cc * dim + dd
i2, j2 = a * dim + dd, cc * dim + b
Mpt[i2, j2] = M[i, j]
return Mpt
rho_pt = partial_transpose_B_d(rho_p, 3)
print("\nPartial-transpose eigenvalues (PPT / true-separability threshold):")
for ev in rho_pt.eigenvals().keys():
print(" ", simplify(ev), " = 0 at p =", sp.solve(sp.Eq(ev, 0), p))
print("\nExpected: shadow-map threshold p=1/2 (NOT tight);"
" true threshold (Werner 1989, p_sep=1/(d+1)) p=1/4.")

View file

@ -0,0 +1,54 @@
"""
03_dps_level2_demo.py
Demonstrates the level-2 DPS SDP (via dps_hierarchy.build_dps_problem) on
two test states:
A) the qutrit Werner state -- sanity check. PPT is already exactly
tight for this family (p_c=1/4, see 02_werner_qutrit_symbolic.py), so
DPS-2 cannot improve on it; well away from the boundary both should
agree.
B) the Tiles UPB bound-entangled state -- the interesting case. Plain
PPT is blind (min eigenvalue ~0, "PPT to machine precision" as noted
in the paper's own Tiles example), but DPS level 2 correctly detects
the entanglement.
Expected runtime: well under a minute with SCS.
"""
import cvxpy as cp
from common import RHO_TILES, werner_qutrit, plain_ppt_feasible, plain_ppt_min_eig
from dps_hierarchy import build_dps_problem, dps_feasible
SOLVER = cp.SCS # swap to cp.MOSEK if you have a license -- likely much faster
prob, rho_param, sigma = build_dps_problem(d=3, k=2)
print(f"DPS level-2 SDP built: sigma shape {sigma.shape}\n")
print("=" * 70)
print("Sanity check: qutrit Werner state (known exact threshold p=1/4)")
print("=" * 70)
for p in [0.20, 0.40]:
rho = werner_qutrit(p)
ppt_ok = plain_ppt_feasible(rho)
eig = plain_ppt_min_eig(rho)
ok = dps_feasible(prob, rho_param, rho, solver=SOLVER, eps=1e-6)
print(f"p={p:.2f}: PPT feasible={ppt_ok} (min eig {eig:+.5f}) "
f"DPS-2 feasible={ok}")
print("\n" + "=" * 70)
print("Tiles UPB bound-entangled state (the interesting case)")
print("=" * 70)
ppt_ok = plain_ppt_feasible(RHO_TILES)
eig = plain_ppt_min_eig(RHO_TILES)
print(f"Plain PPT feasible: {ppt_ok} (min eigenvalue: {eig:.10f})")
ok = dps_feasible(prob, rho_param, RHO_TILES, solver=SOLVER, eps=1e-6)
print(f"DPS level-2 feasible: {ok} "
f"({'NOT detected' if ok else 'ENTANGLEMENT DETECTED'})")
print("\nRobustness across solver tolerances (guards against SDP numerical "
"artifacts near a threshold -- see the chat for a case where this "
"mattered):")
for eps in [1e-5, 1e-6, 1e-7, 1e-8]:
dps_feasible(prob, rho_param, RHO_TILES, solver=SOLVER, eps=eps, max_iters=50000)
print(f" eps={eps}: status={prob.status}")

View file

@ -0,0 +1,50 @@
"""
04_tiles_noise_scan.py
Noise-robustness comparison: find the critical white-noise fraction p_c at
which each criterion stops detecting entanglement of
rho(p) = p * rho_Tiles + (1-p) * I/9
Reproduces (see the chat for the full discussion/derivation):
- plain shadow-map / de Vicente Bloch-representation criterion:
p_c ~ 0.9493 (tolerance ~5.07%)
- DPS level 2:
p_c ~ 0.951 (tolerance ~4.9%)
i.e. DPS level 2 barely improves on the much cheaper order-1 correlation
criterion for THIS state -- see 05_local_filtering.py for the much bigger
lever (local filtering).
Expected runtime: seconds for the shadow-map part; a few minutes for the
DPS-2 bisection (12-16 SDP solves).
"""
import cvxpy as cp
from common import noisy_tiles, shadow_map_nuclear_norm
from dps_hierarchy import build_dps_problem, dps_feasible
SOLVER = cp.SCS
print("Shadow-map criterion threshold:")
lo, hi = 0.0, 1.0
for _ in range(40):
mid = (lo + hi) / 2
if shadow_map_nuclear_norm(noisy_tiles(mid)) > 1:
hi = mid
else:
lo = mid
print(f" p_c = {hi:.5f} (tolerance {100 * (1 - hi):.2f}%)")
print("\nDPS level-2 threshold:")
prob, rho_param, sigma = build_dps_problem(d=3, k=2)
lo, hi = 0.0, 1.0
for i in range(16):
mid = (lo + hi) / 2
ok = dps_feasible(prob, rho_param, noisy_tiles(mid), solver=SOLVER,
eps=1e-7, warm_start=True)
if ok:
lo = mid
else:
hi = mid
print(f" [{i + 1}] p={mid:.5f} feasible={ok}")
print(f" p_c = {hi:.5f} (tolerance {100 * (1 - hi):.2f}%)")

View file

@ -0,0 +1,42 @@
"""
05_local_filtering.py
Applies the operator-Sinkhorn local-filtering (SLOCC normal-form)
algorithm to the Tiles state family, then re-evaluates the plain
shadow-map criterion on the FILTERED state.
Reproduces the literature's "Filter Covariance Matrix Criterion"
(Gittsovich, Guehne, Hyllus, Eisert, "Unifying several separability
conditions using the covariance matrix criterion", arXiv:0803.0757,
Proposition IV.13) threshold almost exactly:
filtered shadow-map (this script): p_c ~ 0.8722 (tolerance 12.78%)
literature Filter-CMC (Prop IV.13): p_c = 0.8723 (tolerance 12.77%)
i.e. local filtering + the paper's OWN, already-existing order-1
criterion reproduces a specialized literature result almost to 4 decimal
places, with no new criterion needed -- just the right pre-processing.
Expected runtime: a few seconds (filtering is cheap linear algebra,
no SDP involved here; ~20-30 Sinkhorn iterations per state).
"""
from common import noisy_tiles, RHO_TILES, operator_sinkhorn, shadow_map_nuclear_norm
print("Filtering the pure Tiles state (p=1):")
rho_f = operator_sinkhorn(RHO_TILES, verbose=True)
print(" shadow-map nuclear norm BEFORE filtering:", shadow_map_nuclear_norm(RHO_TILES))
print(" shadow-map nuclear norm AFTER filtering:", shadow_map_nuclear_norm(rho_f))
print("\nBisection for the filtered-shadow-map threshold:")
lo, hi = 0.80, 0.95
for _ in range(20):
mid = (lo + hi) / 2
rho_pf = operator_sinkhorn(noisy_tiles(mid))
nn = shadow_map_nuclear_norm(rho_pf)
if nn > 1:
hi = mid
else:
lo = mid
print(f" p_c = {hi:.5f} (tolerance {100 * (1 - hi):.2f}%)")
print(" Literature (Filter-CMC, Prop. IV.13, arXiv:0803.0757): "
"p_c = 0.87230 (tolerance 12.77%)")

View file

@ -0,0 +1,42 @@
"""
06_dps_level2_filtered.py
Applies DPS level 2 to the FILTERED Tiles state family (filtering + higher
extension order, combined). The interesting (somewhat counter-intuitive)
result: filtering helps DPS-2 only marginally --
p_c ~ 0.9426 (tolerance 5.74%)
-- much less than it helps the plain shadow-map criterion alone
(p_c ~ 0.8722, tolerance 12.78%, see 05_local_filtering.py). I.e. for this
state, "which local basis you filter into" matters far more than "how
many extension copies you add" -- filtering and DPS-extension-order are
not equally powerful levers here, and they don't simply stack.
Expected runtime: a few minutes (DPS-2 bisection with re-filtering the
state at each bisection point).
"""
import cvxpy as cp
from common import noisy_tiles, operator_sinkhorn
from dps_hierarchy import build_dps_problem, dps_feasible
SOLVER = cp.SCS
prob, rho_param, sigma = build_dps_problem(d=3, k=2)
print("DPS level 2 on the filtered pure Tiles state (p=1):")
rho_f = operator_sinkhorn(noisy_tiles(1.0))
ok = dps_feasible(prob, rho_param, rho_f, solver=SOLVER, eps=1e-7)
print(f" feasible={ok}")
print("\nBisection for the filtered-DPS-2 threshold:")
lo, hi = 0.5, 0.95
for i in range(14):
mid = (lo + hi) / 2
rho_pf = operator_sinkhorn(noisy_tiles(mid))
ok = dps_feasible(prob, rho_param, rho_pf, solver=SOLVER, eps=1e-7, warm_start=True)
if ok:
lo = mid
else:
hi = mid
print(f" [{i + 1}] p={mid:.5f} feasible={ok}")
print(f" p_c = {hi:.5f} (tolerance {100 * (1 - hi):.2f}%)")

View file

@ -0,0 +1,30 @@
"""
07_dps_level3_single.py
A single DPS level-3 feasibility check on the pure Tiles state, to confirm
the construction works and see its cost before committing to a full
noise-threshold bisection (see 08_dps_level_k_bisection.py).
In the original sandbox this took ~137s with SCS (single solve, cold
start). Expect similar or better on a modern laptop; likely far faster
with an interior-point solver (MOSEK, if you have a license) since the
PSD cone here (81x81 complex Hermitian) is small by modern SDP standards
-- SCS is a first-order method tuned for large sparse problems and is not
especially fast on small/dense feasibility problems like this one.
Expected result: status "infeasible" (i.e. entanglement IS detected).
"""
import time
import cvxpy as cp
from common import RHO_TILES
from dps_hierarchy import build_dps_problem, dps_feasible
SOLVER = cp.SCS # try cp.MOSEK if available -- likely much faster at this size
prob, rho_param, sigma = build_dps_problem(d=3, k=3)
print(f"sigma shape: {sigma.shape} "
f"PPT-constraint (PSD cone) size: {3 * 3 ** 3} x {3 * 3 ** 3}")
t0 = time.time()
ok = dps_feasible(prob, rho_param, RHO_TILES, solver=SOLVER, eps=1e-6, max_iters=20000)
print(f"DPS level-3 feasible: {ok} status={prob.status} [{time.time() - t0:.1f}s]")

View file

@ -0,0 +1,84 @@
"""
08_dps_level_k_bisection.py
General, RESUMABLE bisection for the DPS level-k noise-robustness
threshold of the Tiles state family. Each run performs STEPS_PER_RUN
bisection steps and saves progress to a JSON state file, so you can call
it repeatedly (e.g. in a shell loop, or across separate sessions) without
losing progress -- useful since each SDP solve can take anywhere from
under a second (k=2) to a few minutes (k=3, with SCS) depending on k,
your hardware, and the solver.
Usage:
python3 08_dps_level_k_bisection.py
Configure LEVEL, SOLVER, STEPS_PER_RUN, and EPS below.
The state file is named dps_level{LEVEL}_bisection_state.json.
--------------------------------------------------------------------
Progress already made in the original chat session for LEVEL=3 (8 SCS
solves, ~135-227s each) is included alongside this script as
dps_level3_bisection_state.json:
bracket so far: [0.90982, 0.91080] (i.e. p_c ~ 0.910-0.911)
Just run this script (with LEVEL=3, the default) to continue narrowing
it -- it will pick up automatically from that saved state. Delete the
state file to start over, or change LEVEL to try a different extension
order (4, 5, ... but see README.md for how fast the PPT-constraint size,
and hence the cost, grows: 3*3^k).
--------------------------------------------------------------------
"""
import json
import os
import time
import cvxpy as cp
from common import noisy_tiles
from dps_hierarchy import build_dps_problem, dps_feasible
LEVEL = 3
SOLVER = cp.SCS # swap to cp.MOSEK if available -- likely much faster
STEPS_PER_RUN = 1 # raise this if your machine/solver is fast enough
EPS = 1e-5 # solver tolerance; tighten once you have a rough bracket
DEFAULT_BRACKET = (0.70, 0.951) # 0.951 is a proven-safe upper bound (= DPS level-2 threshold,
# since DPS level 3 can only detect at <= that noise level)
STATE_FILE = f"dps_level{LEVEL}_bisection_state.json"
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
state = json.load(f)
print(f"Resuming from saved state: bracket [{state['lo']:.5f}, {state['hi']:.5f}], "
f"{state['iter']} iterations so far.")
else:
state = {"lo": DEFAULT_BRACKET[0], "hi": DEFAULT_BRACKET[1], "iter": 0, "log": []}
print(f"Starting fresh: bracket {DEFAULT_BRACKET}")
prob, rho_param, sigma = build_dps_problem(d=3, k=LEVEL)
print(f"sigma shape: {sigma.shape}, PPT-constraint (PSD cone) size: "
f"{3 * 3 ** LEVEL} x {3 * 3 ** LEVEL}\n")
for _ in range(STEPS_PER_RUN):
lo, hi = state["lo"], state["hi"]
mid = (lo + hi) / 2
t0 = time.time()
feasible = dps_feasible(prob, rho_param, noisy_tiles(mid), solver=SOLVER,
eps=EPS, max_iters=20000, warm_start=True)
dt = time.time() - t0
if feasible:
state["lo"] = mid
else:
state["hi"] = mid
state["iter"] += 1
state["log"].append({"iter": state["iter"], "p": mid, "feasible": feasible,
"time_s": round(dt, 1), "status": prob.status})
print(f"[iter {state['iter']}] p={mid:.5f} feasible={feasible} "
f"status={prob.status} ({dt:.1f}s) "
f"bracket now [{state['lo']:.5f}, {state['hi']:.5f}]")
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2)
print(f"\nCurrent bracket: [{state['lo']:.5f}, {state['hi']:.5f}] "
f"(width {state['hi'] - state['lo']:.5f})")
print("Run again to continue narrowing it further (progress is saved).")

View file

@ -0,0 +1,86 @@
"""
09_dps_level3_filtered_bisection.py
Combines local filtering (operator-Sinkhorn, as in 05/06) with DPS level 3
(as in 07/08): at each candidate noise level p, first bring rho(p) to its
local-filtering normal form, then run the DPS level-3 feasibility SDP on
the FILTERED state.
Precedent from level 2 (06_dps_level2_filtered.py): filtering helped DPS-2
only modestly (4.90% -> 5.74% tolerance), far less than it helped the
plain order-1 shadow-map criterion alone (-> 12.78%, see 05). Expect a
similarly modest improvement here, NOT a jump to ~13% territory.
Cost note: this is the most expensive script in the collection. Each
solve costs about as much as plain DPS-3 (07/08) -- filtering itself is
cheap, the SDP solve dominates -- so a full bisection needs roughly the
same total time as 08's bisection, i.e. another dozen-ish solves at
~85-140s each on hardware like yours.
On the starting bracket: for the level-2 case, filtering turned out to
help (0.9426 < 0.9510), but this is NOT something proven in general here
-- local filtering does not obviously commute with the k-extension
structure the way it does with plain separability (which is SLOCC-
invariant by definition). So, unlike 08's upper bound (0.951, rigorously
justified by DPS monotonicity in k alone), the bracket below is only an
empirically-motivated starting guess, not a proven bound. If a bisection
step ever reports "feasible" surprisingly close to hi, that's a sign the
true threshold may be above the assumed bracket -- widen it and restart
if so.
Usage:
python3 09_dps_level3_filtered_bisection.py
"""
import json
import os
import time
import cvxpy as cp
from common import noisy_tiles, operator_sinkhorn
from dps_hierarchy import build_dps_problem, dps_feasible
LEVEL = 3
SOLVER = cp.SCS # swap to cp.MOSEK if available -- likely much faster
STEPS_PER_RUN = 1
EPS = 1e-6
DEFAULT_BRACKET = (0.8, 0.95) # empirically-motivated, NOT rigorously proven (see above)
STATE_FILE = f"dps_level{LEVEL}_filtered_bisection_state.json"
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
state = json.load(f)
print(f"Resuming from saved state: bracket [{state['lo']:.5f}, {state['hi']:.5f}], "
f"{state['iter']} iterations so far.")
else:
state = {"lo": DEFAULT_BRACKET[0], "hi": DEFAULT_BRACKET[1], "iter": 0, "log": []}
print(f"Starting fresh: bracket {DEFAULT_BRACKET}")
prob, rho_param, sigma = build_dps_problem(d=3, k=LEVEL)
print(f"sigma shape: {sigma.shape}, PPT-constraint (PSD cone) size: "
f"{3 * 3 ** LEVEL} x {3 * 3 ** LEVEL}\n")
for _ in range(STEPS_PER_RUN):
lo, hi = state["lo"], state["hi"]
mid = (lo + hi) / 2
t0 = time.time()
rho_filtered = operator_sinkhorn(noisy_tiles(mid))
feasible = dps_feasible(prob, rho_param, rho_filtered, solver=SOLVER,
eps=EPS, max_iters=20000, warm_start=True)
dt = time.time() - t0
if feasible:
state["lo"] = mid
else:
state["hi"] = mid
state["iter"] += 1
state["log"].append({"iter": state["iter"], "p": mid, "feasible": feasible,
"time_s": round(dt, 1), "status": prob.status})
print(f"[iter {state['iter']}] p={mid:.5f} feasible={feasible} "
f"status={prob.status} ({dt:.1f}s) "
f"bracket now [{state['lo']:.5f}, {state['hi']:.5f}]")
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2)
print(f"\nCurrent bracket: [{state['lo']:.5f}, {state['hi']:.5f}] "
f"(width {state['hi'] - state['lo']:.5f})")
print("Run again to continue narrowing it further (progress is saved).")

View file

@ -0,0 +1,96 @@
# Entanglement-detection scripts from this chat
This is the code from a conversation that started with the "symmetric shadow
maps" paper (`symmetric_shadow_maps_formal.tex`) and worked outward through a
chain of entanglement-detection techniques on the two-qutrit **Tiles**
bound-entangled state (Bennett-DiVincenzo-Mor-Shor-Smolin-Terhal UPB state,
already used as a benchmark in the paper): symmetric-state sanity checks,
DPS symmetric-extension SDPs, noise-robustness thresholds, local filtering,
and (in progress) a third DPS extension level.
**Note: none of this has been re-run/verified after being assembled into
this package** (per your request) -- it's a straight extraction of the
code from the chat. The numbers quoted in each docstring/comment are what
the sandbox actually produced during the conversation; treat them as
"expected results to check against" rather than guaranteed.
## Setup
```
pip install -r requirements.txt
```
`scs` is the default (open-source, first-order) SDP solver used throughout.
If you have a MOSEK license (free for academics), it is very likely much
faster for these problem sizes -- just change `SOLVER = cp.SCS` to
`SOLVER = cp.MOSEK` near the top of scripts 03, 04, 06, 07, 08.
## Files, in the order they came up in the conversation
| File | What it does | Expected result | Rough runtime |
|---|---|---|---|
| `common.py` | Shared utilities: Gell-Mann generators (paper convention), Tiles state, qutrit Werner state, correlation-matrix/shadow-map criterion, plain PPT check, operator-Sinkhorn filter. Imported by scripts 03-06. | -- | -- |
| `dps_hierarchy.py` | General, level-`k`-parametrized DPS symmetric-extension SDP builder (used for levels 2 and 3, and usable for higher `k`). | -- | -- |
| `01_werner_qubit_symbolic.py` | Exact (sympy) check: 2-qubit Werner state, `SU(2)` symmetry forces the correlation matrix `∝ I`. | Shadow-map threshold *exactly* matches PPT: **p_c = 1/3** both ways. | seconds |
| `02_werner_qutrit_symbolic.py` | Same, generalized to qutrits (antisymmetric-subspace Werner state). | Shadow-map threshold **p_c = 1/2**, but true threshold (Werner 1989) is **p_c = 1/4** -- the order-1 criterion is valid but NOT tight in d=3 (unlike d=2). | under a minute |
| `03_dps_level2_demo.py` | DPS level 2 via `dps_hierarchy`. Sanity check on Werner qutrit; then the interesting case: Tiles state, where plain PPT is exactly blind (min eigenvalue ≈ 0) but DPS-2 detects it. | Werner: consistent with p=1/4 away from the boundary. Tiles: PPT feasible=True, DPS-2 feasible=False (detected). Robust across solver tolerances 1e-5..1e-8. | under a minute |
| `04_tiles_noise_scan.py` | Noise-threshold bisection for (a) the plain shadow-map criterion and (b) DPS level 2, on the noisy Tiles family. | Shadow-map **p_c ≈ 0.9493** (5.07% tolerance); DPS-2 **p_c ≈ 0.951** (4.9%) -- i.e. DPS-2 barely improves on the much cheaper order-1 criterion for this state. | a few minutes (DPS-2 bisection) |
| `05_local_filtering.py` | Operator-Sinkhorn local filtering (SLOCC normal form) + the plain shadow-map criterion on the filtered state. | **p_c ≈ 0.8722** (12.78% tolerance) -- matches the literature's "Filter Covariance Matrix Criterion" (Gittsovich, Gühne, Hyllus, Eisert, arXiv:0803.0757, Prop. IV.13: p_c = 0.8723, 12.77%) to ~4 decimal places. | seconds |
| `06_dps_level2_filtered.py` | DPS level 2 applied to the *filtered* state (combining both levers). | **p_c ≈ 0.9426** (5.74%) -- filtering helps DPS-2 only marginally, much less than it helps the plain shadow-map (05). Filtering and DPS-extension-order are not equally powerful levers here, and don't simply stack. | a few minutes |
| `07_dps_level3_single.py` | A single DPS level-3 feasibility check on the pure Tiles state, to confirm level 3 is tractable at all. | status = infeasible (detected). Took **~137s** with SCS in the original sandbox. | ~1-3 minutes |
| `08_dps_level_k_bisection.py` | General, **resumable** bisection for the DPS level-`k` noise threshold, one step per invocation, progress saved to JSON. Defaults to `LEVEL=3`. | See below -- **in progress**. | ~2-4 min per step with SCS (k=3) |
| `dps_level3_bisection_state.json` | Saved progress for the level-3 bisection from the original session (8 SCS solves already spent). | Current bracket: **[0.90982, 0.91080]**, i.e. `p_c ≈ 0.910-0.911`. | -- |
| `09_dps_level3_filtered_bisection.py` | Combines local filtering (05) with DPS level 3 (07/08): filter the state, then run the level-3 SDP on it. Resumable, same pattern as 08. | Untested/in progress -- based on the level-2 precedent (06), expect only a modest improvement over plain level 3, not a jump to ~13%. Starting bracket is an educated guess, not a proven bound (see the script's docstring). | most expensive script here: ~85-140s per solve, ~12-16 solves for a full bisection |
## Where the level-3 bisection currently stands
```json
{"lo": 0.90982, "hi": 0.91080, "iter": 8}
```
So DPS level 3 detects entanglement for `p ≳ 0.910`, i.e. roughly
**9.0% noise tolerance** -- already better than level 2's 4.9-5.7%, but
still well short of the 12.77-12.78% that local filtering alone achieves.
Just re-run `08_dps_level_k_bisection.py` (it picks up the saved state
automatically) to narrow this further.
## The overall picture that emerged (for reference)
| Method | p_c | Noise tolerance |
|---|---|---|
| plain PPT | ~1.0 | ~0% (knife-edge) |
| shadow-map / de Vicente Bloch criterion (order 1) | 0.9493 | 5.07% |
| DPS level 2 | 0.9510 | 4.90% |
| DPS level 2 + filtering | 0.9426 | 5.74% |
| DPS level 3 (partial result so far) | ~0.910 | ~9.0% (narrowing) |
| **local filtering + shadow-map (order 1)** | **0.8722** | **12.78%** |
| literature: Filter-CMC (Prop. IV.13) | 0.8723 | 12.77% |
| literature: best known positive map | 0.8744 | 12.56% |
Headline takeaway: for this particular state, **local filtering (a SLOCC
pre-processing step) is a far bigger lever than increasing the DPS
extension order**, and the two don't stack additively -- filtering the
state and then applying the cheapest possible (order-1) criterion already
matches a specialized literature result almost exactly, while adding DPS
levels on top gives comparatively little.
## A performance note on why level 3+ gets slow
The DPS SDP *variable* is parametrized on `A ⊗ Sym^k(B)`, dimension
`d · C(d+k-1, k)` -- polynomial in `k` (this is the "exploit the built-in
Bose symmetry of the extension copies" trick). But the **PPT constraint**
itself has to be checked on the full, unsymmetrized embedding
`A ⊗ B_1 ⊗ ... ⊗ B_k`, dimension `d^(k+1)` -- exponential in `k`. Since
SDP solver cost is governed by the size of the PSD cone (the PPT
constraint), not by the number of free variables, this is why level 3
(81×81 cone) is already much slower than level 2 (27×27 cone), and level 4
(243×243) would be slower still. A proper fix would exploit
representation-theoretic structure of the PPT constraint itself, not just
of the extension -- that's a bigger undertaking than what's implemented
here.
If you have MOSEK (or another interior-point solver): try it first for
levels 3-4. Interior-point methods are usually much faster than SCS on
small/medium, dense SDPs like these -- SCS is tuned for large sparse
problems and is likely the main reason level 3 took ~137s-227s per solve
here rather than a fraction of a second.

View 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

View file

@ -0,0 +1,110 @@
"""
dps_hierarchy.py
General DPS (Doherty-Parrilo-Spedalieri) level-k symmetric-extension SDP,
for a bipartite qudit state rho_AB with local dimension d, extending party
B to k Bose-symmetric copies.
Key design point (discussed at length in the chat this was extracted
from): the SDP *variable* sigma is parametrized directly on
A x Sym^k(C^d), dimension d * C(d+k-1,k) -- POLYNOMIAL in k. But the PPT
constraint (sigma^{T_A} >= 0) must be checked on the full, unsymmetrized
embedding A x B_1 x ... x B_k, dimension d^{k+1} -- EXPONENTIAL in k. So
this construction saves on free parameters but NOT on the size of the
PSD cone that actually drives SDP solve time. See the README for measured
timings (k=2: ~27x27 cone, sub-second; k=3: ~81x81 cone, ~2-4 minutes
with SCS in the original sandbox).
Requires: numpy, cvxpy.
"""
import math
from itertools import permutations
import numpy as np
import cvxpy as cp
def sym_isometry(d, k):
"""Isometry W, shape (d**k, dim Sym^k(C^d)), spanning the totally
symmetric subspace of (C^d)^{tensor k}. Built by brute-force averaging
over all k! permutations of the k tensor factors -- fine for k up to
~6-7; for larger k this construction itself becomes the bottleneck,
independently of the SDP."""
n = d ** k
P = np.zeros((n, n))
for perm in permutations(range(k)):
M = np.zeros((n, n))
for idx in np.ndindex(*([d] * k)):
new_idx = tuple(idx[perm[i]] for i in range(k))
row = 0
col = 0
for i in range(k):
row = row * d + new_idx[i]
col = col * d + idx[i]
M[row, col] = 1
P += M
P /= math.factorial(k)
eigvals, eigvecs = np.linalg.eigh(P)
cols = [eigvecs[:, i] for i in range(n) if abs(eigvals[i] - 1) < 1e-9]
return np.column_stack(cols)
def partial_trace_keep_first_copy(full_expr, d, k):
"""full_expr indexed by (a, b_1, ..., b_k) with combined index
a*d**k + b_1*d**(k-1) + ... + b_k. Traces out b_2..b_k, keeping (a,b_1)
-- i.e. returns the marginal on A x (first copy of B)."""
rest_dim = d ** (k - 1)
rows = []
for a in range(d):
for b1 in range(d):
row = []
for ap in range(d):
for b1p in range(d):
terms = [full_expr[(a * d + b1) * rest_dim + r,
(ap * d + b1p) * rest_dim + r]
for r in range(rest_dim)]
row.append(sum(terms))
rows.append(row)
return cp.bmat(rows)
def partial_transpose_first_system(full_expr, d1, d2):
"""Partial transpose on the first (d1-dim) system of a
(d1*d2) x (d1*d2) matrix. Has the same eigenvalues as transposing the
second system instead (standard fact: M^{T_A} and M^{T_B} always share
a spectrum, since M^{T_B} = (M^{T_A})^T)."""
rows = []
for i in range(d1):
for kk in range(d2):
row = []
for j in range(d1):
for l in range(d2):
row.append(full_expr[j * d2 + kk, i * d2 + l])
rows.append(row)
return cp.bmat(rows)
def build_dps_problem(d, k):
"""Returns (prob, rho_param, sigma) for the level-k DPS feasibility
SDP. Set rho_param.value = <(d*d)x(d*d) target state>, then call
dps_feasible(...) or prob.solve(...) directly."""
W = sym_isometry(d, k)
dim_sym = W.shape[1]
Iso = np.kron(np.eye(d), W) # d**(k+1) x (d * dim_sym)
sigma = cp.Variable((d * dim_sym, d * dim_sym), hermitian=True)
full = Iso @ sigma @ Iso.conj().T
ptrace = partial_trace_keep_first_copy(full, d, k)
pt = partial_transpose_first_system(full, d1=d, d2=d ** k)
rho_param = cp.Parameter((d * d, d * d), hermitian=True)
constraints = [sigma >> 0, cp.trace(sigma) == 1,
ptrace == rho_param, pt >> 0]
prob = cp.Problem(cp.Minimize(0), constraints)
return prob, rho_param, sigma
def dps_feasible(prob, rho_param, rho_target, solver=cp.SCS, **solve_kwargs):
"""Solve the (already-built) DPS problem for a given target state and
return True iff a valid extension was found (i.e. rho_target is NOT
certified entangled at this level)."""
rho_param.value = rho_target
prob.solve(solver=solver, **solve_kwargs)
return prob.status in ("optimal", "optimal_inaccurate")

View file

@ -0,0 +1,70 @@
{
"lo": 0.9098203125,
"hi": 0.9103105468749999,
"iter": 9,
"log": [
{
"iter": 1,
"p": 0.8254999999999999,
"feasible": true,
"time_s": 144.7,
"status": "optimal"
},
{
"iter": 2,
"p": 0.88825,
"feasible": true,
"time_s": 223.2,
"status": "optimal_inaccurate"
},
{
"iter": 3,
"p": 0.9196249999999999,
"feasible": false,
"time_s": 135.9,
"status": "infeasible"
},
{
"iter": 4,
"p": 0.9039375,
"feasible": true,
"time_s": 226.1,
"status": "optimal_inaccurate"
},
{
"iter": 5,
"p": 0.91178125,
"feasible": false,
"time_s": 139.1,
"status": "infeasible"
},
{
"iter": 6,
"p": 0.9078593749999999,
"feasible": true,
"time_s": 226.9,
"status": "optimal_inaccurate"
},
{
"iter": 7,
"p": 0.9098203125,
"feasible": true,
"time_s": 227.1,
"status": "optimal_inaccurate"
},
{
"iter": 8,
"p": 0.91080078125,
"feasible": false,
"time_s": 139.0,
"status": "infeasible"
},
{
"iter": 9,
"p": 0.9103105468749999,
"feasible": false,
"time_s": 83.2,
"status": "infeasible"
}
]
}

View file

@ -0,0 +1,70 @@
{
"lo": 0.90107421875,
"hi": 0.9013671875,
"iter": 9,
"log": [
{
"iter": 1,
"p": 0.875,
"feasible": true,
"time_s": 140.1,
"status": "optimal_inaccurate"
},
{
"iter": 2,
"p": 0.9125,
"feasible": false,
"time_s": 83.4,
"status": "infeasible"
},
{
"iter": 3,
"p": 0.89375,
"feasible": true,
"time_s": 146.0,
"status": "optimal_inaccurate"
},
{
"iter": 4,
"p": 0.903125,
"feasible": false,
"time_s": 84.0,
"status": "infeasible"
},
{
"iter": 5,
"p": 0.8984375,
"feasible": true,
"time_s": 149.6,
"status": "optimal_inaccurate"
},
{
"iter": 6,
"p": 0.90078125,
"feasible": true,
"time_s": 148.3,
"status": "optimal_inaccurate"
},
{
"iter": 7,
"p": 0.9019531249999999,
"feasible": false,
"time_s": 84.1,
"status": "infeasible"
},
{
"iter": 8,
"p": 0.9013671875,
"feasible": false,
"time_s": 84.6,
"status": "infeasible"
},
{
"iter": 9,
"p": 0.90107421875,
"feasible": true,
"time_s": 148.0,
"status": "optimal_inaccurate"
}
]
}

View file

@ -0,0 +1,8 @@
numpy
sympy
cvxpy
scs
# Optional, much faster for the DPS SDPs (03, 04, 06, 07, 08) if you have
# a license (free for academics): mosek, and set SOLVER = cp.MOSEK in
# those scripts.

View file

@ -0,0 +1,209 @@
"""
ghz3_perturbation_symbolic.py
Exact symbolic (sympy) first-order degenerate perturbation theory for the
GHZ3 shadow map under single-qubit dephasing noise on party B.
Background / formalism
-----------------------
At rho0 = GHZ3, the normalized combined shadow map M_A(rho0) has an exactly
threefold-degenerate singular value sigma = sqrt(2/3), with orthonormal
bases U0 (15x3, target side) and V0 = I_3 (3x3, source side) -- see
ghz3_shadow_map_symbolic.py.
For a perturbation rho(eps) = rho0 + eps * delta_rho, the map itself is
exactly linear: M_A(rho(eps)) = M_A(rho0) + eps * M_A(delta_rho).
To first order in eps, the perturbed singular values within the degenerate
block are
sigma_i(eps) = sigma + eps * lambda_i(K) + O(eps^2),
where K is the symmetrized projection of the perturbing map onto the
degenerate subspace:
K = (1/2) * ( U0^T M_A(delta_rho) V0
+ V0^T M_A(delta_rho)^T U0 ).
This is the direct analogue, for singular values, of ordinary degenerate
perturbation theory for Hermitian eigenvalues. K is automatically real
symmetric here because U0, V0 are real orthonormal bases.
Physical perturbation studied here: single-qubit dephasing on party B,
i.e. the (unnormalized, direction-only) Lindbladian jump direction
delta_rho^(P) = P_B rho0 P_B - rho0, P in {X, Y, Z}.
P = Z models T2-type dephasing in the computational (stabilizer) basis --
the dominant error channel on most physical qubit platforms. P = X, Y model
dephasing along an axis that does not commute with the GHZ3 stabilizer
group.
We show, exactly:
- Z-dephasing on B: eigenvalues of K are {-2*sqrt(6)/3 (x2), 0 (x1)}
-> the "z" target-response channel is exactly protected to first order,
while the "x","y" channels decay at twice the generic rate.
- X- or Y-dephasing on B: eigenvalues of K are {-sqrt(6)/3 (x3)}
-> fully isotropic decay, no protected direction.
The physical reason: Z_A Z_B is a stabilizer generator of GHZ3 and commutes
with Z_B, so the z-channel survives Z_B-dephasing unchanged to first order;
X_AX_B, Y_AY_B do not commute with Z_B and decay.
A numeric finite-difference cross-check (against the singular values of the
exactly perturbed matrix, not just the leading-order K prediction) is
included at the end.
Run: python3 ghz3_perturbation_symbolic.py
"""
import sympy as sp
from sympy import sqrt, I, simplify, Matrix, eye, zeros, re, Rational, N
# ---------------------------------------------------------------------
# 1. Rebuild the same primitives as in ghz3_shadow_map_symbolic.py
# (kept self-contained so this script can be run standalone)
# ---------------------------------------------------------------------
X = Matrix([[0, 1], [1, 0]])
Y = Matrix([[0, -I], [I, 0]])
Z = Matrix([[1, 0], [0, -1]])
I2 = eye(2)
PAULIS = {'x': X, 'y': Y, 'z': Z}
def kron(A, B):
mA, nA = A.shape
mB, nB = B.shape
out = zeros(mA * mB, nA * nB)
for i in range(mA):
for j in range(nA):
out[i * mB:(i + 1) * mB, j * nB:(j + 1) * nB] = A[i, j] * B
return out
def kron3(a, b, c): return kron(kron(a, b), c)
def op_A(P): return kron3(P, I2, I2)
def op_B(P): return kron3(I2, P, I2)
def op_C(P): return kron3(I2, I2, P)
def ghz3_state():
psi = zeros(8, 1)
psi[0, 0] = 1 / sqrt(2)
psi[7, 0] = 1 / sqrt(2)
return simplify(psi * psi.H)
def entry(rho, ops):
M = None
for op in ops:
M = op if M is None else M * op
return simplify(re(simplify((rho * M).trace())))
def build_M(rho):
"""Same 15x3 unnormalized shadow-map matrix as in the companion script."""
rows = []
for pb in ['x', 'y', 'z']:
rows.append([entry(rho, [op_A(PAULIS[pa]), op_B(PAULIS[pb])])
for pa in ['x', 'y', 'z']])
for pc in ['x', 'y', 'z']:
rows.append([entry(rho, [op_A(PAULIS[pa]), op_C(PAULIS[pc])])
for pa in ['x', 'y', 'z']])
for pb in ['x', 'y', 'z']:
for pc in ['x', 'y', 'z']:
rows.append([entry(rho, [op_A(PAULIS[pa]), op_B(PAULIS[pb]), op_C(PAULIS[pc])])
for pa in ['x', 'y', 'z']])
return Matrix(rows)
NORM_CONST = 1 / sqrt(3) # combined-map normalization for n=3 qubits
# ---------------------------------------------------------------------
# 2. Degenerate-perturbation-theory machinery
# ---------------------------------------------------------------------
def Kmatrix(delta_rho, U0, V0):
"""Symmetrized first-order splitting matrix for the degenerate block
spanned by (U0, V0), given a perturbation direction delta_rho."""
Md = simplify(NORM_CONST * build_M(delta_rho))
A = simplify(U0.T * Md * V0)
return simplify(Rational(1, 2) * (A + A.T))
def main():
rho0 = ghz3_state()
M0 = build_M(rho0)
Mn0 = simplify(NORM_CONST * M0)
# Degenerate subspace bases (see companion script for derivation):
# Gram matrix Mn0^T Mn0 = (2/3) I_3 exactly, so V0 = I_3 and
# U0 = Mn0 rescaled to unit-norm columns.
sigma = sqrt(Rational(2, 3))
U0 = simplify(Mn0 / sigma)
V0 = eye(3)
print(f"Unperturbed degenerate singular value: sigma = {sigma} "
f"= {float(sigma):.6f} (should be sqrt(6)/3, threefold)\n")
ops_B = {
'Z (T2-type, computational-basis dephasing)': op_B(Z),
'X': op_B(X),
'Y': op_B(Y),
}
K_store = {}
for label, OB in ops_B.items():
delta_rho = simplify(OB * rho0 * OB - rho0)
K = Kmatrix(delta_rho, U0, V0)
K_store[label] = (K, delta_rho)
print("=" * 70)
print(f"Dephasing on qubit B along {label}")
print("K =")
sp.pprint(K)
eigs = K.eigenvals()
print("Exact eigenvalues of K (first-order singular-value shifts):")
for ev, mult in eigs.items():
print(f" {sp.nsimplify(ev)} (multiplicity {mult}) "
f"= {float(ev):.6f}")
print(f"trace(K) = {simplify(sp.trace(K))} "
f"= {float(sp.trace(K)):.6f} "
f"(this is d/d(eps) ||M_A(rho(eps))||_* at eps=0)\n")
# -------------------------------------------------------------
# 3. Numeric finite-difference cross-check (independent of the
# symbolic K-matrix machinery): compute the *exact* singular
# values of M_A(rho0 + eps*delta_rho) for small eps and compare
# to sigma + eps*lambda_i(K).
# -------------------------------------------------------------
print("=" * 70)
print("Finite-difference cross-check for Z-dephasing on B")
print("(exact singular values of the perturbed matrix vs. first-order "
"prediction from K)\n")
K_Z, delta_rho_Z = K_store['Z (T2-type, computational-basis dephasing)']
eig_list = sorted(K_Z.eigenvals().keys(), reverse=True) # e.g. [0, -2sqrt6/3, -2sqrt6/3]
# build the multiset of 3 eigenvalues (respecting multiplicity)
eig_multiset = []
for ev, mult in K_Z.eigenvals().items():
eig_multiset += [ev] * mult
eig_multiset = sorted(eig_multiset, reverse=True)
for eps_val in [sp.Rational(1, 100), sp.Rational(1, 1000)]:
rho_eps = rho0 + eps_val * delta_rho_Z
M_eps = simplify(NORM_CONST * build_M(rho_eps))
G_eps = simplify(M_eps.T * M_eps)
sv_exact = sorted([sp.sqrt(ev) for ev in G_eps.eigenvals().keys()
for _ in range(G_eps.eigenvals()[ev])],
key=lambda v: float(v), reverse=True)
sv_predicted = sorted([sigma + eps_val * ev for ev in eig_multiset],
key=lambda v: float(v), reverse=True)
print(f"eps = {eps_val} :")
print(" exact singular values:", [f"{float(v):.6f}" for v in sv_exact])
print(" 1st-order prediction :", [f"{float(v):.6f}" for v in sv_predicted])
print()
if __name__ == "__main__":
main()

View file

@ -0,0 +1,158 @@
"""
ghz3_shadow_map_symbolic.py
Exact symbolic (sympy) construction of the combined shadow map M_A(rho) for the
three-qubit GHZ state, with source party A and target complement {B,C}.
This reproduces, with exact algebraic numbers (no floating point), the claim
from Section "Qubit examples" of the paper:
For |GHZ_3> = (|000> + |111>)/sqrt(2), the three singular values of the
normalized combined shadow map M_A(rho) are all equal to sqrt(2/3),
i.e. ||M_A(rho)||_* = sqrt(6).
Convention (matches the .tex draft):
- Pauli generators sigma_1=X, sigma_2=Y, sigma_3=Z, normalized by
tr(sigma_i sigma_j) = 2 delta_ij (qubit case, d_a = 2).
- Target sectors for source A are T in { {B}, {C}, {B,C} }, stacked as
rows of one 15 x 3 matrix (3 from B, 3 from C, 9 from BC).
- Combined shadow map normalization: 1/sqrt((d_a-1)(d_bar_a-1))
= 1/sqrt(1*3) = 1/sqrt(3) for n=3 qubits (Eq. "combined-map" in the note).
Run: python3 ghz3_shadow_map_symbolic.py
"""
import sympy as sp
from sympy import sqrt, I, simplify, Matrix, eye, zeros, re, Rational
# ---------------------------------------------------------------------
# 1. Pauli matrices (exact, symbolic entries)
# ---------------------------------------------------------------------
X = Matrix([[0, 1], [1, 0]])
Y = Matrix([[0, -I], [I, 0]])
Z = Matrix([[1, 0], [0, -1]])
I2 = eye(2)
PAULIS = {'x': X, 'y': Y, 'z': Z}
def kron(A, B):
"""Kronecker (tensor) product of two sympy matrices, built manually
so everything stays exact/symbolic (no numeric backend needed)."""
mA, nA = A.shape
mB, nB = B.shape
out = zeros(mA * mB, nA * nB)
for i in range(mA):
for j in range(nA):
out[i * mB:(i + 1) * mB, j * nB:(j + 1) * nB] = A[i, j] * B
return out
def kron3(a, b, c):
"""Tensor product of three single-qubit operators -> 8x8 matrix."""
return kron(kron(a, b), c)
# Embeddings of a single-qubit operator P onto party A, B, or C
# within the 3-qubit Hilbert space (order A ⊗ B ⊗ C).
def op_A(P): return kron3(P, I2, I2)
def op_B(P): return kron3(I2, P, I2)
def op_C(P): return kron3(I2, I2, P)
# ---------------------------------------------------------------------
# 2. The GHZ_3 state
# ---------------------------------------------------------------------
def ghz3_state():
"""Density matrix of (|000> + |111>)/sqrt(2), as an 8x8 sympy Matrix."""
psi = zeros(8, 1)
psi[0, 0] = 1 / sqrt(2) # |000>
psi[7, 0] = 1 / sqrt(2) # |111>
rho = psi * psi.H # outer product, .H = conjugate transpose
return simplify(rho)
# ---------------------------------------------------------------------
# 3. Correlation-tensor entries and the shadow-map matrix
# ---------------------------------------------------------------------
def entry(rho, ops):
"""tr(rho * op1 * op2 * ...), simplified and forced real
(expectation values of Hermitian operators in a Hermitian state
are always real; re(...) just discards a numerically/symbolically
residual zero imaginary part)."""
M = None
for op in ops:
M = op if M is None else M * op
return simplify(re(simplify((rho * M).trace())))
def build_M(rho):
"""Unnormalized shadow-map matrix M_A(rho): 15 (target) x 3 (source A).
Row blocks, in order:
rows 0-2 : target sector T = {B} (source index x,y,z; target x,y,z)
rows 3-5 : target sector T = {C}
rows 6-14 : target sector T = {B,C} (9 = 3x3 combinations)
Column index: source generator on A, in order x,y,z.
"""
rows = []
for pb in ['x', 'y', 'z']:
rows.append([entry(rho, [op_A(PAULIS[pa]), op_B(PAULIS[pb])])
for pa in ['x', 'y', 'z']])
for pc in ['x', 'y', 'z']:
rows.append([entry(rho, [op_A(PAULIS[pa]), op_C(PAULIS[pc])])
for pa in ['x', 'y', 'z']])
for pb in ['x', 'y', 'z']:
for pc in ['x', 'y', 'z']:
rows.append([entry(rho, [op_A(PAULIS[pa]), op_B(PAULIS[pb]), op_C(PAULIS[pc])])
for pa in ['x', 'y', 'z']])
return Matrix(rows)
# ---------------------------------------------------------------------
# 4. Main: build, normalize, and diagonalize
# ---------------------------------------------------------------------
def main():
rho0 = ghz3_state()
print("tr(rho0) =", simplify(rho0.trace()), " (sanity check, should be 1)\n")
M0 = build_M(rho0)
print("Unnormalized shadow matrix M0 (15x3):")
sp.pprint(M0)
# Combined-map normalization for n=3 qubits: 1/sqrt((d_a-1)(d_bar_a-1)) = 1/sqrt(3)
norm_const = 1 / sqrt(3)
Mn0 = simplify(norm_const * M0)
# Singular values of Mn0 are sqrt(eigenvalues of the Gram matrix Mn0^T Mn0).
# This avoids sympy's (slower/less robust) generic SVD and is exact here
# because Mn0^T Mn0 is a small 3x3 symmetric matrix.
G = simplify(Mn0.T * Mn0)
print("\nGram matrix Mn0^T Mn0 =")
sp.pprint(G)
eigs = G.eigenvals() # dict: eigenvalue -> multiplicity
print("\nEigenvalues of the Gram matrix (= squared singular values):")
for ev, mult in eigs.items():
sigma = simplify(sqrt(ev))
print(f" lambda = {ev} (multiplicity {mult}) -> sigma = {sigma}"
f" = {float(sigma):.6f}")
print("\nExpected from the paper: sigma = sqrt(2/3) = sqrt(6)/3 ≈ 0.816497,"
" threefold degenerate.")
# Save U0, V0 (orthonormal bases of the degenerate singular subspace) for
# reuse in the perturbation-theory script. Since the Gram matrix is
# exactly (2/3) * I_3 here, the source space is untouched (V0 = I_3) and
# U0 is simply Mn0 rescaled to unit-norm columns.
sigma_val = sqrt(Rational(2, 3))
U0 = simplify(Mn0 / sigma_val)
V0 = eye(3)
print("\nU0 (15x3, orthonormal columns spanning the degenerate target subspace):")
sp.pprint(U0)
print("\nCheck U0^T U0 = I_3:", simplify(U0.T * U0))
return rho0, U0, V0, sigma_val
if __name__ == "__main__":
main()

171
scripts/mixture_search.py Normal file
View file

@ -0,0 +1,171 @@
"""mixture_search.py -- searching (and partly proving) the biseparable supremum of
min(||M_AB||_*, ||M_AC||_*, ||M_AD||_*) via convex mixtures of pure product states
across different bipartitions.
min() of convex functions is NOT itself convex, so (unlike Phi_sym or a single
||M_S||_*) the supremum over biseparable states can genuinely lie ABOVE what any single
pure product state achieves, and can only be found by explicitly searching mixtures.
Contains:
- exact closed-form derivation/verification for the 2-component Bell-pair mixture
family rho(p) = p*(Bell_AB x Bell_CD) + (1-p)*(Bell_AC x Bell_BD):
M_AB(p) = 5 - 4p, M_AC(p) = 1 + 4p, M_AD(p) = 3 + 2|2p-1|
so min(...)(p) is maximized EXACTLY at p=1/2, value = 3 (proven by hand from the
2x2-block eigenvalue structure of the mixed correlation tensor).
- general K-component mixture optimizations (free internal state parameters + free
softmax weights) that repeatedly rediscover this same value 3.0 as the best found,
across 2-, 3-, 4- and 6-component mixture families.
"""
import numpy as np
from scipy.optimize import minimize
from core import state_2_2, state_1_3
from cluster import cluster_map, nuc
I2 = np.eye(2, dtype=complex)
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)
_PAULI = [I2, X, Y, Z]
def _kron4(a, b, c, d):
return np.kron(np.kron(a, b), np.kron(c, d))
_OPS = np.zeros((4, 4, 4, 4, 16, 16), dtype=complex)
for _i0 in range(4):
for _i1 in range(4):
for _i2 in range(4):
for _i3 in range(4):
_OPS[_i0, _i1, _i2, _i3] = _kron4(_PAULI[_i0], _PAULI[_i1], _PAULI[_i2], _PAULI[_i3])
_OPS_FLAT = _OPS.reshape(256, 16, 16)
def full_tensor_mixed_fast(rho):
"""Fast Pauli-tensor extraction for a general (possibly mixed) 16x16 density
matrix, via a single vectorized einsum over all 256 precomputed Pauli operators."""
vals = np.einsum('kij,ji->k', _OPS_FLAT, rho)
return vals.real.reshape(4, 4, 4, 4)
def triple_mixed(rho):
C = full_tensor_mixed_fast(rho)
return nuc(cluster_map(C, 0, 1)), nuc(cluster_map(C, 0, 2)), nuc(cluster_map(C, 0, 3))
def bellpair_state(pairing):
bell = np.array([1, 0, 0, 1]) / np.sqrt(2)
(p1a, p1b), (p2a, p2b) = pairing
psi = np.zeros(16, dtype=complex)
for x in range(2):
for y in range(2):
for u in range(2):
for v in range(2):
idx = [0, 0, 0, 0]
idx[p1a] = x
idx[p1b] = y
idx[p2a] = u
idx[p2b] = v
lin = idx[0] * 8 + idx[1] * 4 + idx[2] * 2 + idx[3]
psi[lin] = bell[x * 2 + y] * bell[u * 2 + v]
return psi
# ---------------------------------------------------------------------
# Exact closed form for the 2-component Bell-pair mixture family
# ---------------------------------------------------------------------
def bell_mixture_exact_formula(p):
M_AB = 5 - 4 * p
M_AC = 1 + 4 * p
M_AD = 3 + 2 * abs(2 * p - 1)
return M_AB, M_AC, M_AD
def bell_mixture_numeric(p):
rho1 = np.outer(bellpair_state(((0, 1), (2, 3))), np.conj(bellpair_state(((0, 1), (2, 3)))))
rho2 = np.outer(bellpair_state(((0, 2), (1, 3))), np.conj(bellpair_state(((0, 2), (1, 3)))))
rho = p * rho1 + (1 - p) * rho2
return triple_mixed(rho)
# ---------------------------------------------------------------------
# General K-component mixture optimizations
# ---------------------------------------------------------------------
def neg_min_mixture_2comp(params):
"""2 components: pure states biseparable across AB|CD and AC|BD, full internal
freedom, weight via sigmoid."""
x1, x2 = params[0:16], params[16:32]
w = 1 / (1 + np.exp(-params[32]))
rho1 = np.outer(state_2_2(x1, (0, 1), (2, 3)), np.conj(state_2_2(x1, (0, 1), (2, 3))))
rho2 = np.outer(state_2_2(x2, (0, 2), (1, 3)), np.conj(state_2_2(x2, (0, 2), (1, 3))))
rho = w * rho1 + (1 - w) * rho2
return -min(triple_mixed(rho))
def neg_min_mixture_3comp(params):
"""3 components: pure states biseparable across each of the three 2|2 cuts, full
internal freedom, softmax weights."""
x1, x2, x3 = params[0:16], params[16:32], params[32:48]
w = np.exp(params[48:51] - np.max(params[48:51]))
w = w / np.sum(w)
psis = [state_2_2(x1, (0, 1), (2, 3)), state_2_2(x2, (0, 2), (1, 3)), state_2_2(x3, (0, 3), (1, 2))]
rho = sum(wi * np.outer(p, np.conj(p)) for wi, p in zip(w, psis))
return -min(triple_mixed(rho))
def neg_min_mixture_4slot(params):
"""4 slots: all three 2|2 cut types plus one 1|3 cut type, full internal freedom,
softmax weights (optimizer is free to zero out unused slots)."""
x0, x1, x2, x3 = params[0:16], params[16:32], params[32:48], params[48:66]
w = np.exp(params[66:70] - np.max(params[66:70]))
w = w / np.sum(w)
psis = [state_2_2(x0, (0, 1), (2, 3)), state_2_2(x1, (0, 2), (1, 3)),
state_2_2(x2, (0, 3), (1, 2)), state_1_3(x3, 0)]
rho = sum(wi * np.outer(p, np.conj(p)) for wi, p in zip(w, psis))
return -min(triple_mixed(rho))
def neg_min_mixture_6slot(params):
"""6 slots: AB|CD, AC|BD, AD|BC, AB|CD (2nd copy), AC|BD (2nd copy), A|BCD -- allows
two independently-parametrized states of the SAME cut type to mix together too."""
xs = [params[16 * k:16 * (k + 1)] for k in range(5)]
x5 = params[80:98]
w = np.exp(params[98:104] - np.max(params[98:104]))
w = w / np.sum(w)
psis = [state_2_2(xs[0], (0, 1), (2, 3)), state_2_2(xs[1], (0, 2), (1, 3)),
state_2_2(xs[2], (0, 3), (1, 2)), state_2_2(xs[3], (0, 1), (2, 3)),
state_2_2(xs[4], (0, 2), (1, 3)), state_1_3(x5, 0)]
rho = sum(wi * np.outer(p, np.conj(p)) for wi, p in zip(w, psis))
return -min(triple_mixed(rho))
def multistart(objective, nparams, n_restarts, seed0, label, maxiter=2000):
best = -np.inf
bx = None
for i in range(n_restarts):
rng = np.random.default_rng(seed0 + i)
x0 = rng.normal(size=nparams) * 0.7
res = minimize(objective, x0, method='Powell',
options={'maxiter': maxiter, 'xtol': 1e-8, 'ftol': 1e-10})
v = -res.fun
if v > best:
best = v
bx = res.x
print(f'{label}: best min(M_AB,M_AC,M_AD) = {best:.6f} ({n_restarts} restarts)')
return best, bx
if __name__ == "__main__":
print("=== Exact closed form vs numeric verification, Bell-pair mixture family ===")
for p in [0.0, 0.25, 0.5, 0.75, 1.0]:
formula = bell_mixture_exact_formula(p)
numeric = bell_mixture_numeric(p)
print(f" p={p:.2f} formula={tuple(round(x, 4) for x in formula)} "
f"numeric={tuple(round(x, 4) for x in numeric)}")
print()
print("=== General mixture optimizations (stress-testing the p=1/2 optimum, 3.0) ===")
multistart(neg_min_mixture_2comp, 33, 6, 3000, "2-component (AB|CD + AC|BD, free params)")
multistart(neg_min_mixture_3comp, 51, 3, 5000, "3-component (all three 2|2 cuts, free weights)")
multistart(neg_min_mixture_4slot, 70, 2, 7000, "4-slot (three 2|2 + one 1|3, free weights)")
multistart(neg_min_mixture_6slot, 104, 1, 8000, "6-slot (duplicated cut types)")

View file

@ -0,0 +1,84 @@
"""optimize_cluster_witness.py --
(1) optimize the individual cluster map ||M_AB||_* over different biseparable cut types,
showing that its universal ceiling (5.0) is reached not just by the "home" cut AB|CD
but also by a state biseparable across the UNRELATED cut AC|BD (Bell_AC x Bell_BD);
(2) optimize min(||M_AB||_*, ||M_AC||_*, ||M_AD||_*) over 1|3-biseparable pure states,
finding a naive ceiling of 7/3 -- later shown (via mixture_search.py) to be beatable
by proper MIXTURES, since min() is not convex.
"""
import numpy as np
from scipy.optimize import minimize
from core import state_1_3, state_2_2
from core2 import full_tensor
from cluster import cluster_map, nuc
def triple(psi):
C = full_tensor(psi)
return nuc(cluster_map(C, 0, 1)), nuc(cluster_map(C, 0, 2)), nuc(cluster_map(C, 0, 3))
# --- witness ||M_AB||_* over various biseparable cut types ---
def obj_A_BCD(params):
psi = state_1_3(params, 0)
C = full_tensor(psi)
return -nuc(cluster_map(C, 0, 1))
def obj_C_ABD(params):
psi = state_1_3(params, 2)
C = full_tensor(psi)
return -nuc(cluster_map(C, 0, 1))
def obj_AC_BD(params):
psi = state_2_2(params, (0, 2), (1, 3))
C = full_tensor(psi)
return -nuc(cluster_map(C, 0, 1))
def obj_AB_CD(params):
psi = state_2_2(params, (0, 1), (2, 3))
C = full_tensor(psi)
return -nuc(cluster_map(C, 0, 1))
# --- min(M_AB,M_AC,M_AD) over 1|3-biseparable pure states ---
def neg_min_A_BCD(params):
psi = state_1_3(params, 0)
return -min(triple(psi))
def neg_min_B_ACD(params):
psi = state_1_3(params, 1)
return -min(triple(psi))
def run(obj, nparams, args, n_restarts, seed0, label, maxiter=2500):
best = -np.inf
bx = None
for i in range(n_restarts):
rng = np.random.default_rng(seed0 + i)
x0 = rng.normal(size=nparams)
res = minimize(obj, x0, args=args, method='Powell',
options={'maxiter': maxiter, 'xtol': 1e-9, 'ftol': 1e-11})
v = -res.fun
if v > best:
best = v
bx = res.x
print(f'{label}: best value = {best:.8f} ({n_restarts} restarts)')
return best, bx
if __name__ == "__main__":
print("=== ||M_AB||_* over various biseparable cut types (universal ceiling = 5) ===")
run(obj_A_BCD, 18, (), 8, 10, "biseparable A|BCD")
run(obj_C_ABD, 18, (), 8, 20, "biseparable C|ABD")
run(obj_AC_BD, 16, (), 8, 30, "biseparable AC|BD <-- reaches 5 (Bell_AC x Bell_BD)")
run(obj_AB_CD, 16, (), 6, 40, "biseparable AB|CD (home cut, sanity <= 1)")
print()
print("=== min(M_AB,M_AC,M_AD) over 1|3-biseparable states (naive ceiling = 7/3) ===")
run(neg_min_A_BCD, 18, (), 10, 300, "max over biseparable A|BCD")
run(neg_min_B_ACD, 18, (), 10, 400, "max over biseparable B|ACD")
print("For comparison: GHZ4 / connected graph states also give min = 7/3 =", 7 / 3)

View file

@ -0,0 +1,66 @@
"""optimize_phi_sym.py -- multi-start optimization of Phi_sym over biseparable pure
states (1|3 and 2|2 cuts) and over ALL pure 4-qubit states (unconstrained).
Key finding: all three searches converge to the SAME value, 6/sqrt(7) -- i.e. Phi_sym's
biseparable supremum equals its global supremum over the entire state space, achieved
both by connected 4-qubit graph states AND by a trivial biseparable state (two Bell
pairs). This shows Phi_sym cannot certify genuine multipartite entanglement at that
threshold.
"""
import numpy as np
from scipy.optimize import minimize
from core import state_1_3, state_2_2
from core2 import phi_sym
def neg_phi_1_3(params, source):
psi = state_1_3(params, source)
val, _ = phi_sym(psi)
return -val
def neg_phi_2_2(params, pair1, pair2):
psi = state_2_2(params, pair1, pair2)
val, _ = phi_sym(psi)
return -val
def neg_phi_general(params):
v = params[:16] + 1j * params[16:]
v = v / np.linalg.norm(v)
val, _ = phi_sym(v)
return -val
def run_multistart(objective, nparams, args, n_restarts, seed0, label):
best = -np.inf
bx = None
for i in range(n_restarts):
rng = np.random.default_rng(seed0 + i)
x0 = rng.normal(size=nparams)
res = minimize(objective, x0, args=args, method='Powell',
options={'maxiter': 2000, 'xtol': 1e-9, 'ftol': 1e-11})
v = -res.fun
if v > best:
best = v
bx = res.x
print(f'{label}: best Phi_sym = {best:.10f} ({n_restarts} restarts)')
return best, bx
if __name__ == "__main__":
# biseparable across 2|2 cut AB|CD -> exact optimum 6/sqrt(7), achieved by
# Bell_AB (x) Bell_CD (verified in closed form: two maximal Bell pairs)
v22, x22 = run_multistart(neg_phi_2_2, 16, ((0, 1), (2, 3)), 15, 100,
"biseparable 2|2 (AB|CD)")
# biseparable across 1|3 cut A|BCD -> exact optimum (1+18/sqrt(7))/4,
# achieved by (any single qubit) (x) GHZ_3(B,C,D)
v13, x13 = run_multistart(neg_phi_1_3, 18, (0,), 15, 200,
"biseparable 1|3 (A|BCD)")
# fully unconstrained over ALL pure 4-qubit states -> same ceiling 6/sqrt(7)
vgen, xgen = run_multistart(neg_phi_general, 32, (), 20, 500,
"unconstrained (all 4-qubit states)")
print()
print("6/sqrt(7) =", 6 / np.sqrt(7))
print("(1+18/sqrt(7))/4 =", (1 + 18 / np.sqrt(7)) / 4)

View file

@ -0,0 +1,65 @@
"""pairwise_correlation_demo.py -- compares the raw single-party-to-single-party 3x3
correlation blocks for a trivially biseparable state (two Bell pairs), GHZ4, and the
ring graph state. Shows that "some pairwise block vanishes" is NOT a valid biseparability
signature: the ring graph state (genuinely entangled) also has several exactly-vanishing
pairwise blocks -- a well-known feature of graph states, confirmed here directly.
"""
import numpy as np
from core2 import full_tensor
from party_blocks import party_block, nuc
def show(psi, label):
C = full_tensor(psi)
print(label)
for a, b, name in [(0, 1, 'A-B'), (0, 2, 'A-C'), (0, 3, 'A-D'),
(1, 2, 'B-C'), (1, 3, 'B-D'), (2, 3, 'C-D')]:
print(f' {name}: ||M_party||_* = {nuc(party_block(C, a, b)):.4f}')
def bellpair_state(pairing):
bell = np.array([1, 0, 0, 1]) / np.sqrt(2)
(p1a, p1b), (p2a, p2b) = pairing
psi = np.zeros(16, dtype=complex)
for x in range(2):
for y in range(2):
for u in range(2):
for v in range(2):
idx = [0, 0, 0, 0]
idx[p1a] = x
idx[p1b] = y
idx[p2a] = u
idx[p2b] = v
lin = idx[0] * 8 + idx[1] * 4 + idx[2] * 2 + idx[3]
psi[lin] = bell[x * 2 + y] * bell[u * 2 + v]
return psi
def ring_graph_state():
plus = np.array([1, 1]) / np.sqrt(2)
psi = np.kron(np.kron(plus, plus), np.kron(plus, plus))
def apply_CZ(psi, a, b):
psi = psi.reshape([2] * 4)
idx = [slice(None)] * 4
idx[a] = 1
idx[b] = 1
psi[tuple(idx)] *= -1
return psi.reshape(16)
psi = apply_CZ(psi, 0, 1)
psi = apply_CZ(psi, 1, 2)
psi = apply_CZ(psi, 2, 3)
psi = apply_CZ(psi, 3, 0)
return psi
if __name__ == "__main__":
show(bellpair_state(((0, 1), (2, 3))), 'Bell_AB x Bell_CD:')
print()
ghz4 = np.zeros(16, dtype=complex)
ghz4[0] = 1 / np.sqrt(2)
ghz4[15] = 1 / np.sqrt(2)
show(ghz4, 'GHZ4:')
print()
show(ring_graph_state(), 'ring graph state:')

18
scripts/party_blocks.py Normal file
View file

@ -0,0 +1,18 @@
"""party_blocks.py -- extract the raw (unnormalized) single-party-to-single-party 3x3
correlation blocks M_{a->b} from the Pauli correlation tensor."""
import numpy as np
def party_block(C, a, b):
M = np.zeros((3, 3))
for i, ia in enumerate([1, 2, 3]):
for j, ib in enumerate([1, 2, 3]):
idx = [0, 0, 0, 0]
idx[a] = ia
idx[b] = ib
M[i, j] = C[tuple(idx)]
return M
def nuc(M):
return np.linalg.svd(M, compute_uv=False).sum()

225
scripts/sdp_ppt_mixture.py Normal file
View file

@ -0,0 +1,225 @@
"""
SDP-based sharpening of the biseparable threshold for min(||M_AB||_*, ||M_AC||_*, ||M_AD||_*)
on 4 qubits, via the PPT-mixture relaxation (Jungnitsch-Moroder-Guehne 2011 style SDP).
WHY NOT A ONE-SHOT SDP
-----------------------
||M_S(rho)||_* is CONVEX in rho. Maximizing a convex function over a convex set is itself
a non-convex problem -- no SDP solver can do this directly.
THE FIX -- alternating (Frank-Wolfe) scheme using the dual (support-function) form of the
nuclear norm:
||X||_* = max_{||O||_op <= 1} tr(O^T X)
For FIXED O_AB, O_AC, O_AD (each with operator norm <= 1), min_S tr(O_S^T M_S(rho)) is a
min of THREE LINEAR functions of rho, hence CONCAVE, hence
max_{rho in PPT-mixtures} min_S tr(O_S^T M_S(rho))
IS a legitimate concave-maximization problem -> a genuine SDP.
Loop:
1) fix O's -> solve the SDP -> get rho*
2) at rho*, compute the TRUE nuclear norms and their exact dual witnesses
O_S = U_S V_S^T (from the SVD of M_S(rho*)) -> update O's
3) repeat
This is a heuristic (finds a local stationary point of a genuinely non-convex problem),
but it searches the FULL convex PPT-mixture body (a strict superset of the biseparable
states), not just a hand-picked family of pure-state mixtures -- a much stronger stress
test of the conjectured biseparable supremum (~3.0) than black-box optimization over a
parametrized ansatz.
Requires: pip install cvxpy numpy scipy
Every piece of linear algebra here (Pauli-tensor extraction, partial-transpose
permutation, the fast coefficient-matrix construction) was verified against a slow
reference implementation in pure numpy before being translated to cvxpy; only the
cvxpy Problem-building/solving itself is unverified in the sandbox this was written in
(no cvxpy, no network there).
"""
import numpy as np
import cvxpy as cp
# ----------------------------------------------------------------------
# 1. Pauli tensor operators, index k = i0*64 + i1*16 + i2*4 + i3
# ----------------------------------------------------------------------
I2 = np.eye(2, dtype=complex)
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)
PAULI = [I2, X, Y, Z]
def kron4(a, b, c, d):
return np.kron(np.kron(a, b), np.kron(c, d))
PAULI_OPS = np.zeros((256, 16, 16), dtype=complex)
for i0 in range(4):
for i1 in range(4):
for i2 in range(4):
for i3 in range(4):
k = i0 * 64 + i1 * 16 + i2 * 4 + i3
PAULI_OPS[k] = kron4(PAULI[i0], PAULI[i1], PAULI[i2], PAULI[i3])
CLUSTERS = [('AB', 0, 1), ('AC', 0, 2), ('AD', 0, 3)]
def build_coeff_matrix(s0, s1):
"""(225,256) complex matrix Coeff s.t., for rho flattened row-major (vec[a*16+b]=rho[a,b]),
(Coeff @ vec).reshape(15,15).real / 3.0 == the normalized bigraduated cluster map M_S,
S = {s0,s1}, complement the other two qubits. Verified against a slow trace-based
reference (max abs diff ~2e-17)."""
others = [k for k in range(4) if k not in (s0, s1)]
c0, c1 = others
rows = [(i, j) for i in range(4) for j in range(4) if (i, j) != (0, 0)]
cols = [(i, j) for i in range(4) for j in range(4) if (i, j) != (0, 0)]
Coeff = np.zeros((225, 256), dtype=complex)
entry = 0
for (ia, ib) in rows:
for (ic, idd) in cols:
idx = [0, 0, 0, 0]
idx[s0] = ia
idx[s1] = ib
idx[c0] = ic
idx[c1] = idd
k = idx[0] * 64 + idx[1] * 16 + idx[2] * 4 + idx[3]
# trace(rho @ P_k) = sum_{a,b} rho[a,b] P_k[b,a]; row-major vec_rho[a*16+b]=rho[a,b]
Coeff[entry, :] = PAULI_OPS[k].T.flatten()
entry += 1
return Coeff
COEFF = {name: build_coeff_matrix(s0, s1) for name, s0, s1 in CLUSTERS}
# ----------------------------------------------------------------------
# 2. Partial transpose as an explicit (256,256) permutation matrix
# ----------------------------------------------------------------------
def partial_transpose_perm(T, n=4):
perm = np.zeros(4 ** n, dtype=int)
for row in range(2 ** n):
rbits = [(row >> (n - 1 - i)) & 1 for i in range(n)]
for col in range(2 ** n):
cbits = [(col >> (n - 1 - i)) & 1 for i in range(n)]
new_row = [cbits[i] if i in T else rbits[i] for i in range(n)]
new_col = [rbits[i] if i in T else cbits[i] for i in range(n)]
new_row_idx = sum(b << (n - 1 - i) for i, b in enumerate(new_row))
new_col_idx = sum(b << (n - 1 - i) for i, b in enumerate(new_col))
perm[new_row_idx * (2 ** n) + new_col_idx] = row * (2 ** n) + col
return perm
def perm_matrix(T, n=4):
perm = partial_transpose_perm(T, n)
P = np.zeros((4 ** n, 4 ** n))
for new_idx, old_idx in enumerate(perm):
P[new_idx, old_idx] = 1.0
return P
# The 7 bipartitions of {A,B,C,D}={0,1,2,3}; PT taken w.r.t. the listed (smaller) side.
# PPT is equivalent for either side of a bipartition, so this choice is arbitrary but fixed.
BIPARTITIONS = [
('AB|CD', {0, 1}), ('AC|BD', {0, 2}), ('AD|BC', {0, 3}),
('A|BCD', {0}), ('B|ACD', {1}), ('C|ABD', {2}), ('D|ABC', {3}),
]
PT_MATRIX = {name: perm_matrix(side) for name, side in BIPARTITIONS}
def cvxpy_flatten_rowmajor(rho_expr):
"""16x16 cvxpy expression -> length-256 cvxpy expression, row-major."""
return cp.hstack([rho_expr[i, :] for i in range(16)])
def cvxpy_partial_transpose(rho_expr, name):
vec = cvxpy_flatten_rowmajor(rho_expr)
pt_vec = PT_MATRIX[name] @ vec
return cp.reshape(pt_vec, (16, 16), order='C') # MUST match row-major PT_MATRIX construction
def cvxpy_cluster_maps(rho_expr):
vec = cvxpy_flatten_rowmajor(rho_expr)
out = {}
for name, s0, s1 in CLUSTERS:
flat = COEFF[name] @ vec / 3.0
out[name] = cp.real(cp.reshape(flat, (15, 15), order='C')) # MUST match row-major COEFF construction
return out
# ----------------------------------------------------------------------
# 3. PPT-mixture SDP + one alternating step
# ----------------------------------------------------------------------
def solve_fixed_witness_step(O, solver=cp.SCS, verbose=False):
"""O: dict name(in {'AB','AC','AD'}) -> 15x15 real array with operator norm <= 1.
Returns (rho_value, sdp_optimal_t, dict of numeric M_S values)."""
rho_gammas = {}
constraints = []
for name, side in BIPARTITIONS:
r = cp.Variable((16, 16), hermitian=True)
constraints.append(r >> 0) # PSD
constraints.append(cvxpy_partial_transpose(r, name) >> 0) # PPT across this cut
rho_gammas[name] = r
rho = sum(rho_gammas.values())
constraints.append(cp.real(cp.trace(rho)) == 1)
M = cvxpy_cluster_maps(rho)
t = cp.Variable()
for name, _, _ in CLUSTERS:
constraints.append(t <= cp.sum(cp.multiply(O[name], M[name])))
prob = cp.Problem(cp.Maximize(t), constraints)
prob.solve(solver=solver, verbose=verbose)
if rho.value is None:
raise RuntimeError(f"SDP did not solve to a usable solution (status={prob.status}).")
M_vals = {name: M[name].value for name, _, _ in CLUSTERS}
return rho.value, prob.value, M_vals
def true_norms_and_witnesses(M_vals):
"""Exact nuclear norms of the numeric M_S matrices, plus their optimal dual witnesses
O_S = U_S V_S^T (operator norm exactly 1, achieves tr(O_S^T M_S) = ||M_S||_*)."""
norms, O_opt = {}, {}
for name, M in M_vals.items():
U, s, Vt = np.linalg.svd(M)
norms[name] = s.sum()
O_opt[name] = U @ Vt
return norms, O_opt
# ----------------------------------------------------------------------
# 4. Alternating search with multiple random restarts
# ----------------------------------------------------------------------
def alternating_search(n_restarts=5, n_iters=15, seed0=0, verbose=True):
best_min, best_rho = -np.inf, None
for r in range(n_restarts):
rng = np.random.default_rng(seed0 + r)
O = {}
for name, _, _ in CLUSTERS:
A = rng.normal(size=(15, 15))
O[name] = A / np.linalg.norm(A, ord=2) # operator norm 1
if verbose:
print(f"--- restart {r} ---")
for it in range(n_iters):
rho_val, t_val, M_vals = solve_fixed_witness_step(O)
norms, O = true_norms_and_witnesses(M_vals)
cur_min = min(norms.values())
if verbose:
nice = {k: round(v, 4) for k, v in norms.items()}
print(f" iter {it:2d}: SDP t={t_val:.4f} true norms={nice} min={cur_min:.4f}")
if cur_min > best_min:
best_min, best_rho = cur_min, rho_val
if verbose:
print()
return best_min, best_rho
if __name__ == "__main__":
print("Proven upper bound (pure-state extreme points + convexity of the sum): 11/3 =", 11 / 3)
print("Conjectured true biseparable / PPT-mixture supremum: ~3.0")
print()
best_min, best_rho = alternating_search(n_restarts=5, n_iters=15)
print("=" * 60)
print("Best min(||M_AB||_*, ||M_AC||_*, ||M_AD||_*) found over PPT-mixtures:", best_min)
print("- if this stays at/near 3.0 across restarts -> strong evidence 3.0 is exact")
print("- if it clearly exceeds 3.0 -> best_rho is a concrete witness state to inspect")

View file

@ -0,0 +1,69 @@
"""
Seeded exploration around the known-good point (3.0, 3.0, 3.0).
Two questions this answers:
1) Is 3.0 a STABLE fixed point of the alternating scheme (perturb the witnesses a
little, does it converge back to 3.0)?
2) Does searching the FULL PPT-mixture body (strictly larger than biseparable states)
starting from near this point ever find something BETTER than 3.0?
If nothing beats 3.0 even when explicitly seeded nearby and given many iterations, that
is now fairly strong evidence -- across both a from-scratch parametrized search (earlier)
and this SDP-based search over the larger PPT-mixture relaxation -- that 3.0 is the true
supremum (at least for PPT-mixtures, hence an upper bound on the biseparable one too,
since biseparable subset PPT-mixtures).
"""
import numpy as np
from sdp_ppt_mixture import solve_fixed_witness_step, true_norms_and_witnesses, CLUSTERS
data = np.load('O_seed.npz')
O_seed = {name: data[name] for name, _, _ in CLUSTERS}
def project_to_unit_opnorm(A):
"""Rescale A to have operator norm exactly 1 (SVD-based projection)."""
U, s, Vt = np.linalg.svd(A)
return U @ Vt if s.max() == 0 else A / s.max()
def run_seeded(perturbation_strength, n_iters=25, seed=0, verbose=True):
rng = np.random.default_rng(seed)
O = {}
for name, _, _ in CLUSTERS:
noise = rng.normal(size=(15, 15)) * perturbation_strength
O[name] = project_to_unit_opnorm(O_seed[name] + noise)
best_min = -np.inf
history = []
for it in range(n_iters):
rho_val, t_val, M_vals = solve_fixed_witness_step(O)
norms, O = true_norms_and_witnesses(M_vals)
cur_min = min(norms.values())
history.append(cur_min)
best_min = max(best_min, cur_min)
if verbose:
nice = {k: round(v, 5) for k, v in norms.items()}
print(f" iter {it:2d}: SDP t={t_val:.5f} norms={nice} min={cur_min:.5f}")
return best_min, history
if __name__ == "__main__":
print("=== Stability check: seed EXACTLY at the known optimum (no perturbation) ===")
best0, _ = run_seeded(perturbation_strength=0.0, n_iters=10, seed=0)
print(f" best min found: {best0:.6f} (should stay essentially at 3.0)\n")
print("=== Perturbation sweep: does it converge back to 3.0, drift, or improve? ===")
results = {}
for strength in [0.05, 0.1, 0.2, 0.4, 0.7, 1.0]:
print(f"--- perturbation strength {strength} ---")
best, hist = run_seeded(perturbation_strength=strength, n_iters=25, seed=1, verbose=True)
results[strength] = best
print(f" final best: {best:.6f}\n")
print("=" * 60)
for s, v in results.items():
print(f" perturbation {s:.2f} -> best min found = {v:.6f}")
overall_best = max(results.values())
print()
print("Overall best across all perturbed seeded runs:", overall_best)
print("Compare: 3.0 (conjectured exact), 11/3 =", 11/3, "(proven upper bound)")

25
scripts/subblock.py Normal file
View file

@ -0,0 +1,25 @@
"""subblock.py -- extract the "fully active" 9x9 sub-block M_{S->S^c} (both source and
target sectors fully active, i.e. every party involved) from the full bigraduated
cluster map. This is Corollary "sub-block witnesses" specialized to V=S, T=S^c."""
import numpy as np
def fully_active_block(C, s0, s1):
others = [k for k in range(4) if k not in (s0, s1)]
c0, c1 = others
M = np.zeros((9, 9))
rows = [(i, j) for i in [1, 2, 3] for j in [1, 2, 3]]
cols = [(i, j) for i in [1, 2, 3] for j in [1, 2, 3]]
for ri, (ia, ib) in enumerate(rows):
for ci, (ic, idd) in enumerate(cols):
idx = [0, 0, 0, 0]
idx[s0] = ia
idx[s1] = ib
idx[c0] = ic
idx[c1] = idd
M[ri, ci] = C[tuple(idx)]
return M / 3.0 # same normalization convention as cluster.cluster_map
def nuc(M):
return np.linalg.svd(M, compute_uv=False).sum()

View file

@ -0,0 +1,71 @@
"""sum_bound_proof.py -- proof ingredients for the RIGOROUS bound
rho biseparable => min(||M_AB||_*, ||M_AC||_*, ||M_AD||_*) <= 11/3
Step 1: verify (numerically, over each cut type) that
||M_AB||_* + ||M_AC||_* + ||M_AD||_* <= 11
for every PURE state product across a single bipartition (the extreme points of the
biseparable set). Since the SUM of nuclear norms IS convex (unlike the min!), this bound
then extends by convexity to ALL biseparable (mixed) states -- this is the key trick that
lets a convexity/extreme-point argument work here even though it fails for min() itself.
Step 2: verify via linear programming that uniform weights (1/3,1/3,1/3) are optimal for
turning the sum bound into a bound on min(...) via min(a,b,c) <= w.(a,b,c) for any
w in the simplex -- i.e. that 11/3 is the best bound achievable by this proof technique
(cannot be tightened just by re-weighting).
"""
import numpy as np
from scipy.optimize import minimize, linprog
from core import state_1_3, state_2_2
from core2 import full_tensor
from cluster import cluster_map, nuc
def sum3(psi):
C = full_tensor(psi)
return nuc(cluster_map(C, 0, 1)) + nuc(cluster_map(C, 0, 2)) + nuc(cluster_map(C, 0, 3))
def neg_sum_2_2(params, pair1, pair2):
return -sum3(state_2_2(params, pair1, pair2))
def neg_sum_1_3(params, source):
return -sum3(state_1_3(params, source))
def run(obj, nparams, args, n_restarts, seed0, label):
best = -np.inf
for i in range(n_restarts):
rng = np.random.default_rng(seed0 + i)
x0 = rng.normal(size=nparams)
res = minimize(obj, x0, args=args, method='Powell',
options={'maxiter': 1500, 'xtol': 1e-9, 'ftol': 1e-11})
v = -res.fun
if v > best:
best = v
print(f'{label}: max sum = {best:.6f} ({n_restarts} restarts)')
return best
if __name__ == "__main__":
print("=== Step 1: max(||M_AB||+||M_AC||+||M_AD||) over each pure single-cut family ===")
r1 = run(neg_sum_2_2, 16, ((0, 1), (2, 3)), 8, 6000, 'cut AB|CD')
r2 = run(neg_sum_2_2, 16, ((0, 2), (1, 3)), 8, 6100, 'cut AC|BD')
r3 = run(neg_sum_1_3, 18, (0,), 8, 6200, 'cut A|BCD')
r4 = run(neg_sum_1_3, 18, (1,), 8, 6300, 'cut B|ACD')
print()
print('Overall max sum over ALL single-cut pure product states:', max(r1, r2, r3, r4))
print('(convexity of the sum then extends this bound to ALL biseparable mixtures)')
print()
print("=== Step 2: is uniform weighting (1/3,1/3,1/3) optimal for the resulting bound? ===")
# extreme points of the "sum" bound: (1,5,5), (5,1,5), (5,5,1)
c = [0, 0, 0, 1]
A_ub = [[1, 5, 5, -1], [5, 1, 5, -1], [5, 5, 1, -1]]
b_ub = [0, 0, 0]
A_eq = [[1, 1, 1, 0]]
b_eq = [1]
bounds = [(0, 1), (0, 1), (0, 1), (None, None)]
res = linprog(c, A_ub=A_ub, b_ub=b_ub, A_eq=A_eq, b_eq=b_eq, bounds=bounds, method='highs')
print('LP-optimal weights:', res.x[:3], ' bound t=', res.x[3])
print('11/3 =', 11 / 3, ' (confirms uniform weights are optimal for this proof technique)')

183
scripts/symmetry_oracle.py Normal file
View file

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

View file

@ -0,0 +1,45 @@
"""universal_ceiling.py -- closed-form universal ceiling for shadow-map nuclear norms,
derived via Cauchy-Schwarz (rank bound) + a trace identity for the Pauli correlation
tensor of a pure n-qubit state.
Single-party source (m=1) in n qubits:
max_rho ||M_a(rho)||_* = 3 * sqrt(2^(n-2) / (2^(n-1)-1))
reproduces sqrt(6) (n=3), 6/sqrt(7) (n=4), 2.19089... (n=5) -- exactly the "common
values" the paper reports numerically for GHZ_n / line_n / ring_n / connected graph
states.
General m-qubit cluster source S (m <= n/2):
max_rho ||M_S(rho)||_* = sqrt( (2^(2m)-1)(2^n - 2^(n-2m)) / ((2^m-1)(2^(n-m)-1)) )
which reduces to the m=1 formula above, and gives exactly 5 for (n=4, m=2) -- matching
the numerically found ceiling for the 2-qubit cluster maps M_AB, M_AC, M_AD.
Equality holds iff (i) the m-qubit source marginal rho_S is maximally mixed
(tr(rho_S^2) = 1/2^m), and (ii) the resulting shadow map has all singular values equal
("isotropic"). This ceiling is saturated not only by highly symmetric stabilizer states
(GHZ_n, connected graph states) but also by simple biseparable states across an
UNRELATED cut (e.g. two Bell pairs) -- which is why Phi_sym / a single ||M_S||_* cannot
serve as a genuine multipartite entanglement witness on their own.
"""
import numpy as np
def universal_ceiling(n, m):
num = (2 ** (2 * m) - 1) * (2 ** n - 2 ** (n - 2 * m))
den = (2 ** m - 1) * (2 ** (n - m) - 1)
return np.sqrt(num / den)
def universal_ceiling_singleparty(n):
return 3 * np.sqrt(2 ** (n - 2) / (2 ** (n - 1) - 1))
if __name__ == "__main__":
print("Single-party (m=1) ceiling for n=3,4,5:")
for n in [3, 4, 5]:
print(f" n={n}: {universal_ceiling_singleparty(n):.6f} "
f"(general formula gives: {universal_ceiling(n, 1):.6f})")
print(" compare: sqrt(6) =", np.sqrt(6), " 6/sqrt(7) =", 6 / np.sqrt(7))
print()
print("2-qubit cluster (m=2) ceiling for n=4 qubits:")
print(f" {universal_ceiling(4, 2):.6f} (matches the numerically found value 5.0)")

406
uv.lock generated
View file

@ -7,18 +7,115 @@ name = "2026-05-07-quantum-info"
version = "0.1.0" version = "0.1.0"
source = { virtual = "." } source = { virtual = "." }
dependencies = [ dependencies = [
{ name = "cvxpy" },
{ name = "matplotlib" }, { name = "matplotlib" },
{ name = "numpy" },
{ name = "qtensor" }, { name = "qtensor" },
{ name = "scipy" },
{ name = "sympy" }, { name = "sympy" },
] ]
[package.metadata] [package.metadata]
requires-dist = [ requires-dist = [
{ name = "cvxpy", specifier = ">=1.9.2" },
{ name = "matplotlib", specifier = ">=3.10.9" }, { name = "matplotlib", specifier = ">=3.10.9" },
{ name = "numpy", specifier = ">=2.5.1" },
{ name = "qtensor", directory = "../qtensor" }, { name = "qtensor", directory = "../qtensor" },
{ name = "scipy", specifier = ">=1.18.0" },
{ name = "sympy", specifier = ">=1.14.0" }, { name = "sympy", specifier = ">=1.14.0" },
] ]
[[package]]
name = "cffi"
version = "2.1.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "pycparser", marker = "implementation_name != 'PyPy'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/57/5f/ff100cae70ebe9d8df1c01a00e510e45d9adb5c1fdda84791b199141de97/cffi-2.1.0.tar.gz", hash = "sha256:efc1cdd798b1aaf39b4610bba7aad28c9bea9b910f25c784ccf9ec1fa719d1f9", size = 531036, upload-time = "2026-07-06T21:34:30.382Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/88/a996879e2eeccb815f6e3a5967b12a308257412acec882039d386bd2aa7b/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:10537b1df4967ca26d21e5072d7d54188354483b91dc75058968d3f0cf13fbda", size = 194331, upload-time = "2026-07-06T21:33:03.697Z" },
{ url = "https://files.pythonhosted.org/packages/58/85/7ae00d5c8dd6266f4e944c3db630f3c5c9a98b61d469c714d848b1d8138a/cffi-2.1.0-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:a95b05f9baf29b91171b3a8bd2020b028835243e7b0ff6bb23e2a3c228518b1b", size = 196966, upload-time = "2026-07-06T21:33:05.353Z" },
{ url = "https://files.pythonhosted.org/packages/8c/e9/45c3a76ad8d43ad9261f4c95436da61128d3ca545d72b9612c0ab5be0b1c/cffi-2.1.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:15faec4adfff450819f3aee0e2e02c812de6edb88203aa58807955db2003472a", size = 184795, upload-time = "2026-07-06T21:33:06.699Z" },
{ url = "https://files.pythonhosted.org/packages/84/4c/82f132cb4418ee6d953d982b19191e87e2a6372c8a4ce36e50b69d6ade4a/cffi-2.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:716ff8ec22f20b4d988b12884086bcef0fc99737043e503f7a3935a6be99b1ea", size = 184746, upload-time = "2026-07-06T21:33:08.071Z" },
{ url = "https://files.pythonhosted.org/packages/a0/1c/4ed5a0e5bdca6cbc275556de3328dd1b76fd0c11cc13c88fe66d1d8715f2/cffi-2.1.0-cp313-cp313-manylinux1_i686.manylinux2014_i686.manylinux_2_17_i686.manylinux_2_5_i686.whl", hash = "sha256:63960549e4f8dc41e31accb97b975abaecfc44c03e396c093a6436763c2ea7db", size = 214747, upload-time = "2026-07-06T21:33:09.671Z" },
{ url = "https://files.pythonhosted.org/packages/3a/a6/e879bb68cc23a2bc9ba8f4b7d8019f0c2694bad2ab6c4a3701d429439f58/cffi-2.1.0-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:ff067a8d8d880e7809e4ac88eb009bb848870115317b306666502ccad30b147f", size = 222392, upload-time = "2026-07-06T21:33:10.896Z" },
{ url = "https://files.pythonhosted.org/packages/88/f6/01890cfd63c08f8eb96a8319b0443690197d240a8bd6346048cf7bde9190/cffi-2.1.0-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3b926723c13eba9f81d2ef3820d63aeceec3b2d4639906047bf675cb8a7a500d", size = 210285, upload-time = "2026-07-06T21:33:12.251Z" },
{ url = "https://files.pythonhosted.org/packages/a6/cf/2b684132056f438567b61e19d690dd31cd0921ace051e0a458be6074369e/cffi-2.1.0-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:47ff3a8bfd8cb9da1af7524b965127095055654c177fcfc7578debcb015eecd0", size = 208801, upload-time = "2026-07-06T21:33:13.617Z" },
{ url = "https://files.pythonhosted.org/packages/6f/08/f2e7d62c460faae0926f2d6e423694aa409ced3bc1fe2927a0a6e5f05416/cffi-2.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:799416bae98336e400981ff6e532d67d5c709cfb30afb79865a1315f94b0e224", size = 221808, upload-time = "2026-07-06T21:33:15.466Z" },
{ url = "https://files.pythonhosted.org/packages/38/37/04f54b8e63a02f3d908332c9effbf8c366167c6f733ed8a3d4f79b7e2a1e/cffi-2.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:961be50688f7fba2fa65f63712d3b9b341a22311f5253460ce933f52f0de1c8c", size = 225241, upload-time = "2026-07-06T21:33:16.869Z" },
{ url = "https://files.pythonhosted.org/packages/a9/d6/c72eecca433cd3e681c65ed313ab4835d9d4a379704d0f628a6a05f51c2e/cffi-2.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:bf5c6cf48238b0eb4c086978c492ad1cbc22373fc5b2d7353b3a598ce6db887a", size = 223588, upload-time = "2026-07-06T21:33:18.239Z" },
{ url = "https://files.pythonhosted.org/packages/c6/4b/e706f67279140f92939da3475ad610df18bfd52d50f14953a8e5fede71d5/cffi-2.1.0-cp313-cp313-win32.whl", hash = "sha256:db3eb7d46527159a878ec3460e9d40615bc25ba337d477db681aea6e4f05c5d2", size = 175248, upload-time = "2026-07-06T21:33:19.799Z" },
{ url = "https://files.pythonhosted.org/packages/5a/47/59eb7975cb0e4ef0afa764ea945b29a5bb4537a9f771cb7d6c8a5dd74c95/cffi-2.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:8e74a6135550c4748af665b1b1118b6aab33b1fc6a16f9aff630af107c3b4512", size = 185717, upload-time = "2026-07-06T21:33:21.47Z" },
{ url = "https://files.pythonhosted.org/packages/5a/af/34fee85c48f8d94efc8597bc09470c9dd274c145f1c12e0fbc6ab6d38d74/cffi-2.1.0-cp313-cp313-win_arm64.whl", hash = "sha256:2282cd5e38aa8accd03e99d1256af8411c84cdbee6a89d841b563fdbd1f3e50f", size = 180114, upload-time = "2026-07-06T21:33:22.515Z" },
{ url = "https://files.pythonhosted.org/packages/d8/f0/81478e482afa03f6d18dc8f2afb5edc45b3080853b634b5ed91961be0998/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d2117334c3af3bdcb9a88522b844a2bdb5efdc4f71c6c822df55486ae1c3347a", size = 194142, upload-time = "2026-07-06T21:33:23.657Z" },
{ url = "https://files.pythonhosted.org/packages/7d/95/8de304305cd9204974b0ca051b86d307cafca13aa575a0ef1b44d92c0d8c/cffi-2.1.0-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:702c436735fbe99d59ada02a1f65cfc0d31c0ee8b7290912f8fbc5cd1e4b16c3", size = 196819, upload-time = "2026-07-06T21:33:25.007Z" },
{ url = "https://files.pythonhosted.org/packages/20/71/7c8372d30e42415602ed9f268f7cfd66f1b855fed881ecd168bcb45dbc0b/cffi-2.1.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:1ff3456eab0d889592d1936d6125bbfbc7ae4d3354a700f8bd80450a66445d4d", size = 184965, upload-time = "2026-07-06T21:33:26.605Z" },
{ url = "https://files.pythonhosted.org/packages/d6/5c/584e626835f0375c928176c04137c96927165cb8733cdb3150ec04e5ee5e/cffi-2.1.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c4165821e131d6d4ca444347c2b694e2311bcfa3fe5a861cc72968f28867beac", size = 184952, upload-time = "2026-07-06T21:33:27.823Z" },
{ url = "https://files.pythonhosted.org/packages/2e/d2/065fcae1c73979fac8e054462478d0ff8a29c40cdc2ed7ea5676a061df53/cffi-2.1.0-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:276f20fffd7b396e12516ba8edf9509210ac248cbbc5acbc39cd512f9f59ebe6", size = 222353, upload-time = "2026-07-06T21:33:29.178Z" },
{ url = "https://files.pythonhosted.org/packages/ed/a5/e8bbb1ce5b3ac2f53ad6a10bde44318a5a8d99d4f4a000d44a6e39aeb3e4/cffi-2.1.0-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:7d5980a3433d4b71a5e120f9dd551403d7824e31e2e67124fe2769c404c06913", size = 210051, upload-time = "2026-07-06T21:33:30.534Z" },
{ url = "https://files.pythonhosted.org/packages/28/ed/c127d3ac36e899c965e3361357c3befacd6578c03f40125183e41c3b219e/cffi-2.1.0-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:6ca4919c6e4f89aa99c42510b42cf54596892c00b3f9077f6bdd1505e24b9c8d", size = 208630, upload-time = "2026-07-06T21:33:31.753Z" },
{ url = "https://files.pythonhosted.org/packages/cc/d7/97d3136f81db489ec8d1d67748c110d6c994268fd7528014aa9f2b085e4e/cffi-2.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d53d10f7da99ae46f7373b9150393e9c5eab9b224909982b43832668de4779f5", size = 221593, upload-time = "2026-07-06T21:33:33.044Z" },
{ url = "https://files.pythonhosted.org/packages/d3/27/93195977168ee63aed233a1a0993a2178798654d1f4bddcdd321d6fd3b21/cffi-2.1.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:c351efb95e832a853a29361675f33a7ce53de1a109cd73fd47af0712213aa4ce", size = 225146, upload-time = "2026-07-06T21:33:34.224Z" },
{ url = "https://files.pythonhosted.org/packages/b3/c1/6dbd291ee2ae5a50a034aa057207081f545923bbf15dad4511e985aafff5/cffi-2.1.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:dbf7c7a88e2bac086f06d14577332760bdeecc42bdec8ac4077f6260557d9326", size = 223240, upload-time = "2026-07-06T21:33:35.57Z" },
{ url = "https://files.pythonhosted.org/packages/0f/6f/ade5ce9863a57992a6ea3d0d10d7e29b8749fc127204b3d493d667b2815f/cffi-2.1.0-cp314-cp314-win32.whl", hash = "sha256:1854b724d00f6654c742097d5387569021be12d3a0f770eae1df8f8acfcc6acd", size = 177723, upload-time = "2026-07-06T21:33:51.626Z" },
{ url = "https://files.pythonhosted.org/packages/41/de/92b9eeed4ae4a21d6fd9b2a2c8505cbed573299902ea73981cc13f7ff62c/cffi-2.1.0-cp314-cp314-win_amd64.whl", hash = "sha256:1b96bfe2c4bd825681b7d311ad6d9b7280a091f43e8f63da5729638083cd3bfb", size = 187937, upload-time = "2026-07-06T21:33:53.403Z" },
{ url = "https://files.pythonhosted.org/packages/2e/1a/cc6ae6c2913a03aab8898eee57963cf1035b8df5872ed8b9115fcc7e2be8/cffi-2.1.0-cp314-cp314-win_arm64.whl", hash = "sha256:7d28dff1db6764108bc30788d85d61c876beff416d9a49cb9dd7c5a9f34f5804", size = 183001, upload-time = "2026-07-06T21:33:54.74Z" },
{ url = "https://files.pythonhosted.org/packages/14/f0/134c00ce0779ec86dea2aa1aac69339c2741a8045072676763512363a2ea/cffi-2.1.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:7ea6b3e2c4250ff1de21c630fe72d0f63eb95c2c32ffbf64a358cf4a8836d714", size = 188538, upload-time = "2026-07-06T21:33:36.792Z" },
{ url = "https://files.pythonhosted.org/packages/50/d8/3b86aba791cb610d24e8a3e1b2cd529e71fa15096b04e4d4e360049d4a4c/cffi-2.1.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:6af371f3767faeffc6ac1ef57cdfd25844403e9d3f476c5537caee499de96376", size = 188230, upload-time = "2026-07-06T21:33:38.011Z" },
{ url = "https://files.pythonhosted.org/packages/14/d0/117dcd9209255ad8571fbc8c92ef32593a1d294dcec91ddc4e4db50606f2/cffi-2.1.0-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:eb4e8997a49aa2c08a3e43c9045d224448b8941d88e7ac163c7d383e560cbf98", size = 223899, upload-time = "2026-07-06T21:33:39.514Z" },
{ url = "https://files.pythonhosted.org/packages/b6/3d/f20f8b886b254e3ad10e15cd4186d3aed49f3e6a35ab37aab9f8f25f7c03/cffi-2.1.0-cp314-cp314t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:bf01d8c84cbea96b944c73b22182e6c7c432b3475632b8111dbfdc95ddad6e13", size = 211652, upload-time = "2026-07-06T21:33:40.851Z" },
{ url = "https://files.pythonhosted.org/packages/28/3b/fad54de07260b93ddeef4b96d0131d57ea900675df1d410ae1deee52d7a6/cffi-2.1.0-cp314-cp314t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:33eb1ad83ebe8f313e0df035c406227d55a79456704a863fad9842136af5ad7d", size = 210755, upload-time = "2026-07-06T21:33:42.183Z" },
{ url = "https://files.pythonhosted.org/packages/cc/82/3d5c705acb7abbba9bbd7d79b8e62e0f25b6120eb7ae6ac49f1b721722fe/cffi-2.1.0-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ac0f1a2d0cfa7eea3f2aaf006ab6e70e8feeb16b75d65b7e5939982ca2f11056", size = 223933, upload-time = "2026-07-06T21:33:43.603Z" },
{ url = "https://files.pythonhosted.org/packages/6c/d0/47e338384ab6b1004241002fa616301020cea4fc95f283506565d252f276/cffi-2.1.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:c16914df9fb7f500e440e6875fa23ff5e0b31db01fa9c06af98d59a91f0dc2e4", size = 226749, upload-time = "2026-07-06T21:33:45.046Z" },
{ url = "https://files.pythonhosted.org/packages/70/25/65bd5b58ea4bfdfc15cde02cb5365f89ef8ab8b2adfb8fe5c4bd4233382f/cffi-2.1.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5ecbd0499275d57506d397eebe1981cee87b47fcd9ef5c22cab7ed7644a39a94", size = 225703, upload-time = "2026-07-06T21:33:46.374Z" },
{ url = "https://files.pythonhosted.org/packages/dc/78/aa01ac599a8a4322533d45a1f9bc93b338276d2d59dabbe7c6d92a775c81/cffi-2.1.0-cp314-cp314t-win32.whl", hash = "sha256:7d034dcffa09e9a46c93fa3a3be402096cb5354ac6e41ab8e5cc9cd8b642ad76", size = 182857, upload-time = "2026-07-06T21:33:47.696Z" },
{ url = "https://files.pythonhosted.org/packages/b9/26/d00496b22de4d4228f32dde94ad996f350c8aad676d63bcca0743c8dea4d/cffi-2.1.0-cp314-cp314t-win_amd64.whl", hash = "sha256:0582a58f3051372229ca8e7f5f589f9e5632678208d8636fea3676711fdf7fe5", size = 194065, upload-time = "2026-07-06T21:33:48.953Z" },
{ url = "https://files.pythonhosted.org/packages/d5/dd/0c7dbf815a579ff005008a2d815a55d6bb047c349eef536d9dc53d3f0a8d/cffi-2.1.0-cp314-cp314t-win_arm64.whl", hash = "sha256:510aeeeac94811b138077451da1fb18b308a5feab47dd2b603af55804155e1c8", size = 186404, upload-time = "2026-07-06T21:33:50.309Z" },
{ url = "https://files.pythonhosted.org/packages/55/c7/8c8c50cb11c6750051daf12164098a9a6f027ac4356967fd4d800a07f242/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphoneos.whl", hash = "sha256:2e9dabb9abcb7ad15938c7196ad5c1718a4e6d33cc79b4c0209bdb64c4a54a5c", size = 194121, upload-time = "2026-07-06T21:33:56.109Z" },
{ url = "https://files.pythonhosted.org/packages/99/e2/67680bf19a6b60d2bb7ff83baefa2a4c3d2d7dc0f3277034b802e1fc504c/cffi-2.1.0-cp315-cp315-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:37f525a7e7e50c017fdebe58b787be310ad59357ae43a053943a6e1a6c526001", size = 196820, upload-time = "2026-07-06T21:33:57.288Z" },
{ url = "https://files.pythonhosted.org/packages/ed/da/4bbe583a3b3a5c8c60892124fe17f3fa3656523faf0d3484eae90f091853/cffi-2.1.0-cp315-cp315-macosx_10_15_x86_64.whl", hash = "sha256:95f2954c2c9473d892eca6e0409f3568b37ab62a8eedb122461f73cc273476e3", size = 184936, upload-time = "2026-07-06T21:33:58.765Z" },
{ url = "https://files.pythonhosted.org/packages/e5/4b/1f4c36ab273980d7aa75bb126ea4f8971f24a96108acad3a0a084028c57b/cffi-2.1.0-cp315-cp315-macosx_11_0_arm64.whl", hash = "sha256:cdf2448aab5f661c9315308ec8b93f4e8a1a67a3c733f8631067a2b67d5913dc", size = 185045, upload-time = "2026-07-06T21:34:00.085Z" },
{ url = "https://files.pythonhosted.org/packages/ef/c3/ad299dc38f3583f8d916b299f028af418a9ec98bc695fcbebeae7420691c/cffi-2.1.0-cp315-cp315-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:90bec57cf82089383bd06a605b3eb8daebf7e5a668520beaf6e327a83a947699", size = 222342, upload-time = "2026-07-06T21:34:01.814Z" },
{ url = "https://files.pythonhosted.org/packages/eb/d8/df4543cc087245044ed02ef3ad8e0a26619d0075ac7a77a12dc81177851b/cffi-2.1.0-cp315-cp315-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:6274dcb2d15cef48daa73ed1be5a40d501d74dccd0cd6db364776d12cb6ba022", size = 210073, upload-time = "2026-07-06T21:34:03.255Z" },
{ url = "https://files.pythonhosted.org/packages/2c/0e/fac738d73728c6cea2a88a2883dca54892496cbba88a1dc1f2909cb8a6f5/cffi-2.1.0-cp315-cp315-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:2b71d409cccee78310ab5dec549aed052aaea483346e282c7b02362596e01bb0", size = 208551, upload-time = "2026-07-06T21:34:04.433Z" },
{ url = "https://files.pythonhosted.org/packages/e6/3f/0b04a700dd64f465c93020253a793a82c9b4dff9961f48facd0df945d9b8/cffi-2.1.0-cp315-cp315-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:7d3538f9c0e50670f4deb93dbb696576e60590369cae2faf7de681e597a8a1f1", size = 221649, upload-time = "2026-07-06T21:34:06.157Z" },
{ url = "https://files.pythonhosted.org/packages/5d/7c/b7379a5704c79eda57ce075869ba70a0368d1c850f803b3c0d078d39dcaf/cffi-2.1.0-cp315-cp315-musllinux_1_2_aarch64.whl", hash = "sha256:8f9ec95b8a043d3dfbc74d9abc6f7baf524dd27a8dc160b0a32ff9cdab650c28", size = 225203, upload-time = "2026-07-06T21:34:07.489Z" },
{ url = "https://files.pythonhosted.org/packages/5a/02/d5e6c43ea85c41bda2a184a3418f195fe7cf602967a8d2b94e085b83deef/cffi-2.1.0-cp315-cp315-musllinux_1_2_x86_64.whl", hash = "sha256:af5e2915d41fe6c961694d7bfdc8562942638200f3ce2765dfb8b745cf997629", size = 223263, upload-time = "2026-07-06T21:34:08.712Z" },
{ url = "https://files.pythonhosted.org/packages/2c/d8/772b8259bf75749adffb1c546828978381fb516f60cf701f6c83daf60c85/cffi-2.1.0-cp315-cp315-win32.whl", hash = "sha256:0a42c688d19fca6e095a53c6a6e2295a5b050a8b289f109adab02a9e61a25de6", size = 177696, upload-time = "2026-07-06T21:34:26.355Z" },
{ url = "https://files.pythonhosted.org/packages/2f/dd/afa2191fc6d57fedd26e5844a2fe2fcc0bbfa00961bbaa5a41e4921e7cca/cffi-2.1.0-cp315-cp315-win_amd64.whl", hash = "sha256:bccbbb5ee76a61f9d99b5bf3846a51d7fca4b6a732fe46f89295610edaf41853", size = 187914, upload-time = "2026-07-06T21:34:27.58Z" },
{ url = "https://files.pythonhosted.org/packages/05/ef/6cd4f8c671517162379dc79cfae5aea9106bc38abb89628d5c16adf6a838/cffi-2.1.0-cp315-cp315-win_arm64.whl", hash = "sha256:8d35c139744adb3e727cd51b1a18324bbe44b8bd41bf8322bca4d41289f48eda", size = 183004, upload-time = "2026-07-06T21:34:28.905Z" },
{ url = "https://files.pythonhosted.org/packages/11/b6/12fc55092817a5faa26fb8c40c7f9d662e11a46ee248c137aafc42517d92/cffi-2.1.0-cp315-cp315t-macosx_10_15_x86_64.whl", hash = "sha256:f9912624a0c0b834b7520d7769b3644453aabc0a7e1c839da7359f050750e9bc", size = 188378, upload-time = "2026-07-06T21:34:09.926Z" },
{ url = "https://files.pythonhosted.org/packages/8d/2e/cdac88979f295fde5daa69622c7d2111e56e7ceb94f211357fbe452339e4/cffi-2.1.0-cp315-cp315t-macosx_11_0_arm64.whl", hash = "sha256:df92f2aba50eb4d96718b68ef76f2e57a57b54f2fa62333496d16c6d585a85ca", size = 188319, upload-time = "2026-07-06T21:34:11.101Z" },
{ url = "https://files.pythonhosted.org/packages/e0/27/1d0b408497e41a74795af122d7b603c418c5fed0171450f899afd04e594f/cffi-2.1.0-cp315-cp315t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:0520e1f4c35f44e209cbbb421b67eec42e6a157f59444dfb6058874ff3610e5d", size = 223904, upload-time = "2026-07-06T21:34:12.606Z" },
{ url = "https://files.pythonhosted.org/packages/8b/31/e115c985105dd7ffb32444505f18ceb874bb42d992af05d5dced7ecf1980/cffi-2.1.0-cp315-cp315t-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:3681e031db29958a7502f5c0c9d6bbc4c36cb20f7b104086fa642d1799631ff8", size = 211554, upload-time = "2026-07-06T21:34:13.987Z" },
{ url = "https://files.pythonhosted.org/packages/5a/67/9e6e09409336d9e515c58367e7cfcf4f89df06ad25252675595a58eb59d5/cffi-2.1.0-cp315-cp315t-manylinux2014_s390x.manylinux_2_17_s390x.whl", hash = "sha256:762f99479dcb369f60ab9017ad4ab97a36a1dd7c1ee5a3b15db0f4b8659120cd", size = 210795, upload-time = "2026-07-06T21:34:15.972Z" },
{ url = "https://files.pythonhosted.org/packages/19/e5/d3cc82a4a0be7902af279c04181ad038449c096734464a5ae1de3e1401bd/cffi-2.1.0-cp315-cp315t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:0611e7ebf90573a535ebdc33ae9da222d037853983e13359f580fab781ca017f", size = 223843, upload-time = "2026-07-06T21:34:17.509Z" },
{ url = "https://files.pythonhosted.org/packages/b9/65/b434abc97ce7cecc2c640fde160507c0ecc7e21544b483ba3325d2e2ea17/cffi-2.1.0-cp315-cp315t-musllinux_1_2_aarch64.whl", hash = "sha256:86cf8755a791f72c85dc287128cc62d4f24d392e3f1e15837245623f4a33cccc", size = 226773, upload-time = "2026-07-06T21:34:19.05Z" },
{ url = "https://files.pythonhosted.org/packages/b5/9f/d4dc66ca651eb1145a133314cda721abf13cfac3d28c4a0402263ae6ad75/cffi-2.1.0-cp315-cp315t-musllinux_1_2_x86_64.whl", hash = "sha256:ba00f661f8ba35d075c937174e27c2c421cec3942fd2e0ea3e66996757c0fdd9", size = 225719, upload-time = "2026-07-06T21:34:20.576Z" },
{ url = "https://files.pythonhosted.org/packages/68/5a/e536c528bc8057496c360c0978559a2dc45653f89dd6151078aa7d8fca1a/cffi-2.1.0-cp315-cp315t-win32.whl", hash = "sha256:cb96698e3c7413d906ce83f8ffd245ec1bd94707541f299d0ce4d6b0193e982b", size = 182760, upload-time = "2026-07-06T21:34:22.059Z" },
{ url = "https://files.pythonhosted.org/packages/d3/0b/0ffe8b82d3875bced5fa1e7986a7a46b748262a40ab7f60b475eb9fb1bb3/cffi-2.1.0-cp315-cp315t-win_amd64.whl", hash = "sha256:f146d154428a2523f9cc7936c02353c2459b8f6cf07d3cd1ee1c0a611109c5d5", size = 193769, upload-time = "2026-07-06T21:34:23.589Z" },
{ url = "https://files.pythonhosted.org/packages/a0/17/1073b53b68c9b5ca6914adf5f8bf55aacc2d3be102418c90700160ea8605/cffi-2.1.0-cp315-cp315t-win_arm64.whl", hash = "sha256:cbb7640ce37159548d2147b5b8c241f962143d4c71231431820783f4dc78f210", size = 186405, upload-time = "2026-07-06T21:34:24.857Z" },
]
[[package]]
name = "clarabel"
version = "0.11.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "cffi" },
{ name = "numpy" },
{ name = "scipy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/81/e2/47f692161779dbd98876015de934943effb667a014e6f79a6d746b3e4c2a/clarabel-0.11.1.tar.gz", hash = "sha256:e7c41c47f0e59aeab99aefff9e58af4a8753ee5269bbeecbd5526fc6f41b9598", size = 253949, upload-time = "2025-06-11T16:49:05.864Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/34/f7/f82698b6d00a40a80c67e9a32b2628886aadfaf7f7b32daa12a463e44571/clarabel-0.11.1-cp39-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c39160e4222040f051f2a0598691c4f9126b4d17f5b9e7678f76c71d611e12d8", size = 1039511, upload-time = "2025-06-11T16:48:58.525Z" },
{ url = "https://files.pythonhosted.org/packages/b0/8f/13650cfe25762b51175c677330e6471d5d2c5851a6fbd6df77f0681bb34e/clarabel-0.11.1-cp39-abi3-macosx_11_0_arm64.whl", hash = "sha256:8963687ee250d27310d139eea5a6816f9c3ae31f33691b56579ca4f0f0b64b63", size = 935135, upload-time = "2025-06-11T16:48:59.901Z" },
{ url = "https://files.pythonhosted.org/packages/2b/9e/7af10d2b540b39f1a05d1ebba604fce933cc9bc0e65e88ec3b7a84976425/clarabel-0.11.1-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4837b9d0db01e98239f04b1e3526a6cf568529d3c19a8b3f591befdc467f9bb", size = 1079226, upload-time = "2025-06-11T16:49:00.987Z" },
{ url = "https://files.pythonhosted.org/packages/6b/a9/c76edf781ca3283186ff4b54a9a4fb51367fd04313a68e2b09f062407439/clarabel-0.11.1-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c8c41aaa6f3f8c0f3bd9d86c3e568dcaee079562c075bd2ec9fb3a80287380ef", size = 1164345, upload-time = "2025-06-11T16:49:02.675Z" },
{ url = "https://files.pythonhosted.org/packages/41/e6/4eee3062088c221e5a18b054e51c69f616e0bb0dc1b0a1a5e0fe90dfa18e/clarabel-0.11.1-cp39-abi3-win_amd64.whl", hash = "sha256:557d5148a4377ae1980b65d00605ae870a8f34f95f0f6a41e04aa6d3edf67148", size = 887310, upload-time = "2025-06-11T16:49:04.277Z" },
]
[[package]] [[package]]
name = "contourpy" name = "contourpy"
version = "1.3.3" version = "1.3.3"
@ -74,6 +171,38 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" }, { url = "https://files.pythonhosted.org/packages/ae/8c/469afb6465b853afff216f9528ffda78a915ff880ed58813ba4faf4ba0b6/contourpy-1.3.3-cp314-cp314t-win_arm64.whl", hash = "sha256:b7448cb5a725bb1e35ce88771b86fba35ef418952474492cf7c764059933ff8b", size = 203831, upload-time = "2025-07-26T12:02:51.449Z" },
] ]
[[package]]
name = "cvxpy"
version = "1.9.2"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "clarabel" },
{ name = "highspy" },
{ name = "numpy" },
{ name = "osqp" },
{ name = "qdldl" },
{ name = "scipy" },
{ name = "scs" },
{ name = "sparsediffpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/ea/b7/209c6df38f3621fc2f32298c93e7d0310b8a1ee4ef15e5fa59d90492fa6f/cvxpy-1.9.2.tar.gz", hash = "sha256:b2e939f197a7081a300d5a95812fec8643fabaf23a149abf7e67ca7f89671d92", size = 1916772, upload-time = "2026-06-22T04:37:31.975Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/9d/0a/40f3ef2bd1a89002a4716fbe3f6aa9f55f92ee7a095a73d23242b528463a/cvxpy-1.9.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5dbbd7e149f191e21e21ec5bda41ab562d05c5d553d0103cabe3c7b8f7f5f90b", size = 1635226, upload-time = "2026-06-22T04:35:21.744Z" },
{ url = "https://files.pythonhosted.org/packages/20/26/0c0b02d6a116997412c311481b12fa628a8c43f128a405a1afea41b25040/cvxpy-1.9.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f649df44a798420c5a2fec75e073c067ffda92fb00a6d64d2221bef275d2d065", size = 1425026, upload-time = "2026-06-22T04:35:23.099Z" },
{ url = "https://files.pythonhosted.org/packages/80/3b/86e06eb6cc5d42166f02b67956403a0f2dc44e35fe0f94912659bc343d8b/cvxpy-1.9.2-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae51f459010fa8d8ecb4b19bb8a8c63781592fc070ba2e3d119c6b9c10995e26", size = 4379643, upload-time = "2026-06-22T04:35:59.328Z" },
{ url = "https://files.pythonhosted.org/packages/80/81/19e6a2b66124dc42eee0e542c3fa47d2b4dd6c2c9f0030543698c7a50be7/cvxpy-1.9.2-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a48e498cbe2ced52a20dd3a1b14bcd2aa939fa0a51a766c443aa8a75081285ce", size = 4427686, upload-time = "2026-06-22T04:36:00.695Z" },
{ url = "https://files.pythonhosted.org/packages/54/bd/6cbe32217d3221578211518efce0a1edc45761d7b60fb360d027d576005d/cvxpy-1.9.2-cp313-cp313-win_amd64.whl", hash = "sha256:956b292452a5eed7cacffed25d7d1848618e97ca6ea4a83302591a46149f8f2f", size = 1386056, upload-time = "2026-06-22T04:35:41.799Z" },
{ url = "https://files.pythonhosted.org/packages/79/5f/db68cbe59c20a21e477ecba53c1fe93d34785f7d44e114d447b02f3ad5d2/cvxpy-1.9.2-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:54a34e4ff10b80659023ba5f1517f4171ea9b905e90c824b467a7a4234379faf", size = 1636070, upload-time = "2026-06-22T04:34:15.021Z" },
{ url = "https://files.pythonhosted.org/packages/8a/e4/f01d7f8921a7e3ccce2848459b2265a4712bd132d199c03e6c60119cfc0a/cvxpy-1.9.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:908b0423e1fd29768b076547aa2328876e6c09b4d3550d1a37409423f907dd6e", size = 1425490, upload-time = "2026-06-22T04:34:16.175Z" },
{ url = "https://files.pythonhosted.org/packages/e3/b6/a88a61cea293d90a1751b39c77a5d5775b8a3dd4907f4461d0b34e7aa558/cvxpy-1.9.2-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:417a2881d6490f3a13b3b88ee98b3ece5bbc1f1a76aa7a374e0690735f14ba40", size = 4376093, upload-time = "2026-06-22T04:36:45.174Z" },
{ url = "https://files.pythonhosted.org/packages/a3/e0/e7b8caba3b0fc86e9402452b7c3d037af2e995110385d9760e3bc984b491/cvxpy-1.9.2-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:44ca674f9427d7d8019bf80e71fd1035f1aa9eabc6a09b5e6bad52f352f830e5", size = 4423171, upload-time = "2026-06-22T04:36:46.518Z" },
{ url = "https://files.pythonhosted.org/packages/44/08/03871e44cfef2b604fcbe0649d6c02893c60737748580d641a6084ebc532/cvxpy-1.9.2-cp314-cp314-win_amd64.whl", hash = "sha256:d25f16ab7da70ac3a44b49df985f00afbe89e64c44625afd38379ca5d77f4787", size = 1392074, upload-time = "2026-06-22T04:33:59.083Z" },
{ url = "https://files.pythonhosted.org/packages/63/b8/2be562808af2ca22edc3c7b74caeace9ebcae3e7e3467ac67e19d5a8f836/cvxpy-1.9.2-cp314-cp314t-macosx_10_15_universal2.whl", hash = "sha256:fb6aa08b89aedfee2a3c62902551df67d5c6b8684038e019fbe8e5a472142845", size = 1643586, upload-time = "2026-06-22T04:35:11.342Z" },
{ url = "https://files.pythonhosted.org/packages/66/a1/ab623b3e94533e7bfd34abd881d2215956864d706313f244b79d4964aba5/cvxpy-1.9.2-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:8cc42bfa468cc474609900de211b1f6db69ca1215c850cbf00f2646d06d5cbf9", size = 1429833, upload-time = "2026-06-22T04:35:12.604Z" },
{ url = "https://files.pythonhosted.org/packages/90/8a/5fa9717f974d30b573c34b49d0704afab1379d12a7b1b0ebc928dd777c4f/cvxpy-1.9.2-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:27d0d08dfc1484eed29cbb98616409a677be41a81e58c91c2b954b8c19968dfa", size = 4362226, upload-time = "2026-06-22T04:37:39.135Z" },
{ url = "https://files.pythonhosted.org/packages/07/9e/5711925094fba396062ef700f95427a8be6614631a28fa255a3e526333ce/cvxpy-1.9.2-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4a07593837d772edd5efafee3dbd069a5299f78633c944004469ce2728e1c0ac", size = 4414598, upload-time = "2026-06-22T04:37:40.586Z" },
]
[[package]] [[package]]
name = "cycler" name = "cycler"
version = "0.12.1" version = "0.12.1"
@ -116,6 +245,58 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" }, { url = "https://files.pythonhosted.org/packages/2c/47/c99d5268f354002ce80f8d029cd9d7d872969da1de8b93d32de4dc56d6f4/fonttools-4.63.0-py3-none-any.whl", hash = "sha256:445af2eab030a16b9171ea8bdda7ebf7d96bda2df88ee182a464252f6e05e20d", size = 1164562, upload-time = "2026-05-14T12:04:29.092Z" },
] ]
[[package]]
name = "highspy"
version = "1.15.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/87/02/c6b658f79911fee921721da728b9ab8f5e19ff06121fff36f90f77127f4d/highspy-1.15.1.tar.gz", hash = "sha256:20ed2fbf1cb64bf3044ee6632364b7e2653d93e6901e2b19fd3d5df10702e8c5", size = 1703256, upload-time = "2026-07-02T12:03:25.009Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/3f/1e/283ea32eac82dd24fe86c439013d7c7666f4889de89f0957362ea5fa425e/highspy-1.15.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:4db297486a7a42a18656d1cc0ea9e1596fe45b8f7f75669a0c55b9081531ee0a", size = 4878819, upload-time = "2026-07-02T12:02:24.668Z" },
{ url = "https://files.pythonhosted.org/packages/7f/1c/c6518fc7c2bd5c90d86bd7a8f3cf16c1ea0ace4335a80d45b8d3f96c0cba/highspy-1.15.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:818256db731339605a7b2c31cabfcbf820fe50402ff5e9b7aa8410ead06e8735", size = 4474016, upload-time = "2026-07-02T12:02:26.572Z" },
{ url = "https://files.pythonhosted.org/packages/2f/97/4b5e345affc107f1f315c55dd0b6f35f13be07092feccbdfe1d9bfe38e63/highspy-1.15.1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:383cd3f28cce0753dec8e949719b10864e068c53a485624fcab4c6b585496dd7", size = 4636833, upload-time = "2026-07-02T12:02:28.467Z" },
{ url = "https://files.pythonhosted.org/packages/ca/6e/f00e914f2bd88e2b73a8b3ea1b47171a85cfa23d1a06dc373ca797f43208/highspy-1.15.1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:238b2ee88b974b21c7e9ef198139502a7d87451939cae143dce789bbda121182", size = 5034140, upload-time = "2026-07-02T12:02:30.294Z" },
{ url = "https://files.pythonhosted.org/packages/61/03/8f821d39dc8ee06a35e0fa54c754ab592139640c1e839b587e60068ad822/highspy-1.15.1-cp313-cp313-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:b6dcc545235c0765b48fc736122b105e174d907622d20986ac653c5b2a04911f", size = 5862229, upload-time = "2026-07-02T12:02:32.065Z" },
{ url = "https://files.pythonhosted.org/packages/ea/55/708b7523ad80106b91fb66471ab8b1c178a8c8adc222c839cc14147542cd/highspy-1.15.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6e1f8a21a0f48aedb129a5a60d4cad9ee0767de271cd7450de16192440671b38", size = 6191747, upload-time = "2026-07-02T12:02:34.034Z" },
{ url = "https://files.pythonhosted.org/packages/8d/cd/737f43e9c56163ebae501ab21fdbc37dd2dde3e02fd18e37d0062b9b9c7c/highspy-1.15.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:9ea683af80e4fb7c9d712b5df4bae34c63fa9e6afc78d750ba2d9f5e6f3203e0", size = 7233066, upload-time = "2026-07-02T12:02:36.109Z" },
{ url = "https://files.pythonhosted.org/packages/33/60/b9ae92e8454f42cb5c5ccca63862a75f5d43afead1f725f3b8af19f507f5/highspy-1.15.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:565cf6a6e7c84e36c101b118a3c5fd09bc14aeece599bba12625e79b5ab0cecb", size = 6636826, upload-time = "2026-07-02T12:02:37.863Z" },
{ url = "https://files.pythonhosted.org/packages/54/0b/35e5e63be2e70951c3224ed33c4300f1cd37fcfe4eb6d25e259e13571e0f/highspy-1.15.1-cp313-cp313-win32.whl", hash = "sha256:6cc7008b82094b2a2377338398b38f5b6c306397bd23282e55dec46a101a2dac", size = 2306720, upload-time = "2026-07-02T12:02:39.839Z" },
{ url = "https://files.pythonhosted.org/packages/ca/63/2e104bab0117415c68950f249e42f0974f74665d0313dfeddceb1f74c47d/highspy-1.15.1-cp313-cp313-win_amd64.whl", hash = "sha256:46fe314b918257361c54170852bc561c78d0f84d94e2ad263859d818127e6e76", size = 2711119, upload-time = "2026-07-02T12:02:41.861Z" },
{ url = "https://files.pythonhosted.org/packages/0c/73/8cd42c3ca7baf4857494a0294ef068f2216f1216173f3f298046820a7a57/highspy-1.15.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:a7b11dc80781052a6e7c163b5c2696fe9e06c72927cfdb48f67f7e8c77096f4f", size = 4879694, upload-time = "2026-07-02T12:02:43.623Z" },
{ url = "https://files.pythonhosted.org/packages/fb/5b/308821aeefa0e85f90645e15a86bc63c156bf08b00e33a0a906a0c430b41/highspy-1.15.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:9a00e1278ea46a426b1eaa0aea69df9d72ed1d75b18227cad992384ebbdc0c74", size = 4475059, upload-time = "2026-07-02T12:02:45.455Z" },
{ url = "https://files.pythonhosted.org/packages/a3/20/9c75531c03c7121d576ef0ff8415bfb255fdd060c435f9f26e5b103b0559/highspy-1.15.1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:193b9751d3705bc948552b138800af0ad8af17a5b801d5940d7db7ff1ffc4f10", size = 4637880, upload-time = "2026-07-02T12:02:47.239Z" },
{ url = "https://files.pythonhosted.org/packages/89/ea/6d6136f01ce82c049740b00380a39999a15c689a9a4d43fdb1ea25090c2b/highspy-1.15.1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6298b6ef691e83544d395d45fa4e856874c44b32936d85c36564f7697d27bb0b", size = 5034792, upload-time = "2026-07-02T12:02:49.044Z" },
{ url = "https://files.pythonhosted.org/packages/38/9d/ccf4a0d4e7a4fa4141dbabe9f78e94fa9d37b6b5becafe8a61e8369031eb/highspy-1.15.1-cp314-cp314-manylinux_2_26_i686.manylinux_2_28_i686.whl", hash = "sha256:9d436b5f8d50b01497d494606695746147e15b8e22eec6ae475a60cb8b22c1d7", size = 5861891, upload-time = "2026-07-02T12:02:51.432Z" },
{ url = "https://files.pythonhosted.org/packages/19/b4/655f6ce06e17159c001456c97c4be84dcb1448477e3ea52bd5401f5c27c3/highspy-1.15.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:bbb22b7ceed298c0b75237186eb4671915b1c41c07f966e527643af10493671e", size = 6195707, upload-time = "2026-07-02T12:02:53.446Z" },
{ url = "https://files.pythonhosted.org/packages/ea/93/a35495b3326cdc0c2ff59de69d26f2600f41399d9381b59f4826742c054e/highspy-1.15.1-cp314-cp314-musllinux_1_2_i686.whl", hash = "sha256:74c1eb71d3c0fa0c190492d9c0c67266d1dd6b4244c93b53e95a687504db309d", size = 7235019, upload-time = "2026-07-02T12:02:55.989Z" },
{ url = "https://files.pythonhosted.org/packages/20/5e/8b21c908ee94db28f2de58326c8a25e361b3d504f145f7972f096028a908/highspy-1.15.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:cb8b8298a74786e1cbc1a9e102b7749e2bbd9c41826ffd4a1d7ba738232646ff", size = 6637185, upload-time = "2026-07-02T12:02:58.165Z" },
{ url = "https://files.pythonhosted.org/packages/25/81/8f984e500536ca40a8fb1d74ecb7a213e170683adcfd01edee8e21e5735b/highspy-1.15.1-cp314-cp314-win32.whl", hash = "sha256:780c021441f548711818833d3a986fcb253849734aa00c3bf83d342c38b03629", size = 2362473, upload-time = "2026-07-02T12:03:00.147Z" },
{ url = "https://files.pythonhosted.org/packages/bf/97/e85d751aaba8231e86915077532fd584711d30aa9eb85c26331e2bd87596/highspy-1.15.1-cp314-cp314-win_amd64.whl", hash = "sha256:864258c59aeaea9d3bd7ccdd10c03258e2be764e2cf1e21f829fd1f8d8c15d57", size = 2813851, upload-time = "2026-07-02T12:03:01.836Z" },
]
[[package]]
name = "jinja2"
version = "3.1.6"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "markupsafe" },
]
sdist = { url = "https://files.pythonhosted.org/packages/df/bf/f7da0350254c0ed7c72f3e33cef02e048281fec7ecec5f032d4aac52226b/jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d", size = 245115, upload-time = "2025-03-05T20:05:02.478Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/62/a1/3d680cbfd5f4b8f15abc1d571870c5fc3e594bb582bc3b64ea099db13e56/jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67", size = 134899, upload-time = "2025-03-05T20:05:00.369Z" },
]
[[package]]
name = "joblib"
version = "1.5.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/41/f2/d34e8b3a08a9cc79a50b2208a93dce981fe615b64d5a4d4abee421d898df/joblib-1.5.3.tar.gz", hash = "sha256:8561a3269e6801106863fd0d6d84bb737be9e7631e33aaed3fb9ce5953688da3", size = 331603, upload-time = "2025-12-15T08:41:46.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7b/91/984aca2ec129e2757d1e4e3c81c3fcda9d0f85b74670a094cc443d9ee949/joblib-1.5.3-py3-none-any.whl", hash = "sha256:5fc3c5039fc5ca8c0276333a188bbd59d6b7ab37fe6632daa76bc7f9ec18e713", size = 309071, upload-time = "2025-12-15T08:41:44.973Z" },
]
[[package]] [[package]]
name = "kiwisolver" name = "kiwisolver"
version = "1.5.0" version = "1.5.0"
@ -183,6 +364,58 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" }, { url = "https://files.pythonhosted.org/packages/99/a2/ca7dc962848040befed12732dff6acae7fb3c4f6fc4272b3f6c9a30b8713/kiwisolver-1.5.0-cp314-cp314t-win_arm64.whl", hash = "sha256:58f812017cd2985c21fbffb4864d59174d4903dd66fa23815e74bbc7a0e2dd57", size = 70032, upload-time = "2026-03-09T13:15:34.411Z" },
] ]
[[package]]
name = "markupsafe"
version = "3.0.3"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/7e/99/7690b6d4034fffd95959cbe0c02de8deb3098cc577c67bb6a24fe5d7caa7/markupsafe-3.0.3.tar.gz", hash = "sha256:722695808f4b6457b320fdc131280796bdceb04ab50fe1795cd540799ebe1698", size = 80313, upload-time = "2025-09-27T18:37:40.426Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/38/2f/907b9c7bbba283e68f20259574b13d005c121a0fa4c175f9bed27c4597ff/markupsafe-3.0.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:e1cf1972137e83c5d4c136c43ced9ac51d0e124706ee1c8aa8532c1287fa8795", size = 11622, upload-time = "2025-09-27T18:36:41.777Z" },
{ url = "https://files.pythonhosted.org/packages/9c/d9/5f7756922cdd676869eca1c4e3c0cd0df60ed30199ffd775e319089cb3ed/markupsafe-3.0.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:116bb52f642a37c115f517494ea5feb03889e04df47eeff5b130b1808ce7c219", size = 12029, upload-time = "2025-09-27T18:36:43.257Z" },
{ url = "https://files.pythonhosted.org/packages/00/07/575a68c754943058c78f30db02ee03a64b3c638586fba6a6dd56830b30a3/markupsafe-3.0.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:133a43e73a802c5562be9bbcd03d090aa5a1fe899db609c29e8c8d815c5f6de6", size = 24374, upload-time = "2025-09-27T18:36:44.508Z" },
{ url = "https://files.pythonhosted.org/packages/a9/21/9b05698b46f218fc0e118e1f8168395c65c8a2c750ae2bab54fc4bd4e0e8/markupsafe-3.0.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ccfcd093f13f0f0b7fdd0f198b90053bf7b2f02a3927a30e63f3ccc9df56b676", size = 22980, upload-time = "2025-09-27T18:36:45.385Z" },
{ url = "https://files.pythonhosted.org/packages/7f/71/544260864f893f18b6827315b988c146b559391e6e7e8f7252839b1b846a/markupsafe-3.0.3-cp313-cp313-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:509fa21c6deb7a7a273d629cf5ec029bc209d1a51178615ddf718f5918992ab9", size = 21990, upload-time = "2025-09-27T18:36:46.916Z" },
{ url = "https://files.pythonhosted.org/packages/c2/28/b50fc2f74d1ad761af2f5dcce7492648b983d00a65b8c0e0cb457c82ebbe/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a4afe79fb3de0b7097d81da19090f4df4f8d3a2b3adaa8764138aac2e44f3af1", size = 23784, upload-time = "2025-09-27T18:36:47.884Z" },
{ url = "https://files.pythonhosted.org/packages/ed/76/104b2aa106a208da8b17a2fb72e033a5a9d7073c68f7e508b94916ed47a9/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:795e7751525cae078558e679d646ae45574b47ed6e7771863fcc079a6171a0fc", size = 21588, upload-time = "2025-09-27T18:36:48.82Z" },
{ url = "https://files.pythonhosted.org/packages/b5/99/16a5eb2d140087ebd97180d95249b00a03aa87e29cc224056274f2e45fd6/markupsafe-3.0.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:8485f406a96febb5140bfeca44a73e3ce5116b2501ac54fe953e488fb1d03b12", size = 23041, upload-time = "2025-09-27T18:36:49.797Z" },
{ url = "https://files.pythonhosted.org/packages/19/bc/e7140ed90c5d61d77cea142eed9f9c303f4c4806f60a1044c13e3f1471d0/markupsafe-3.0.3-cp313-cp313-win32.whl", hash = "sha256:bdd37121970bfd8be76c5fb069c7751683bdf373db1ed6c010162b2a130248ed", size = 14543, upload-time = "2025-09-27T18:36:51.584Z" },
{ url = "https://files.pythonhosted.org/packages/05/73/c4abe620b841b6b791f2edc248f556900667a5a1cf023a6646967ae98335/markupsafe-3.0.3-cp313-cp313-win_amd64.whl", hash = "sha256:9a1abfdc021a164803f4d485104931fb8f8c1efd55bc6b748d2f5774e78b62c5", size = 15113, upload-time = "2025-09-27T18:36:52.537Z" },
{ url = "https://files.pythonhosted.org/packages/f0/3a/fa34a0f7cfef23cf9500d68cb7c32dd64ffd58a12b09225fb03dd37d5b80/markupsafe-3.0.3-cp313-cp313-win_arm64.whl", hash = "sha256:7e68f88e5b8799aa49c85cd116c932a1ac15caaa3f5db09087854d218359e485", size = 13911, upload-time = "2025-09-27T18:36:53.513Z" },
{ url = "https://files.pythonhosted.org/packages/e4/d7/e05cd7efe43a88a17a37b3ae96e79a19e846f3f456fe79c57ca61356ef01/markupsafe-3.0.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:218551f6df4868a8d527e3062d0fb968682fe92054e89978594c28e642c43a73", size = 11658, upload-time = "2025-09-27T18:36:54.819Z" },
{ url = "https://files.pythonhosted.org/packages/99/9e/e412117548182ce2148bdeacdda3bb494260c0b0184360fe0d56389b523b/markupsafe-3.0.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:3524b778fe5cfb3452a09d31e7b5adefeea8c5be1d43c4f810ba09f2ceb29d37", size = 12066, upload-time = "2025-09-27T18:36:55.714Z" },
{ url = "https://files.pythonhosted.org/packages/bc/e6/fa0ffcda717ef64a5108eaa7b4f5ed28d56122c9a6d70ab8b72f9f715c80/markupsafe-3.0.3-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:4e885a3d1efa2eadc93c894a21770e4bc67899e3543680313b09f139e149ab19", size = 25639, upload-time = "2025-09-27T18:36:56.908Z" },
{ url = "https://files.pythonhosted.org/packages/96/ec/2102e881fe9d25fc16cb4b25d5f5cde50970967ffa5dddafdb771237062d/markupsafe-3.0.3-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8709b08f4a89aa7586de0aadc8da56180242ee0ada3999749b183aa23df95025", size = 23569, upload-time = "2025-09-27T18:36:57.913Z" },
{ url = "https://files.pythonhosted.org/packages/4b/30/6f2fce1f1f205fc9323255b216ca8a235b15860c34b6798f810f05828e32/markupsafe-3.0.3-cp313-cp313t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:b8512a91625c9b3da6f127803b166b629725e68af71f8184ae7e7d54686a56d6", size = 23284, upload-time = "2025-09-27T18:36:58.833Z" },
{ url = "https://files.pythonhosted.org/packages/58/47/4a0ccea4ab9f5dcb6f79c0236d954acb382202721e704223a8aafa38b5c8/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:9b79b7a16f7fedff2495d684f2b59b0457c3b493778c9eed31111be64d58279f", size = 24801, upload-time = "2025-09-27T18:36:59.739Z" },
{ url = "https://files.pythonhosted.org/packages/6a/70/3780e9b72180b6fecb83a4814d84c3bf4b4ae4bf0b19c27196104149734c/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_riscv64.whl", hash = "sha256:12c63dfb4a98206f045aa9563db46507995f7ef6d83b2f68eda65c307c6829eb", size = 22769, upload-time = "2025-09-27T18:37:00.719Z" },
{ url = "https://files.pythonhosted.org/packages/98/c5/c03c7f4125180fc215220c035beac6b9cb684bc7a067c84fc69414d315f5/markupsafe-3.0.3-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:8f71bc33915be5186016f675cd83a1e08523649b0e33efdb898db577ef5bb009", size = 23642, upload-time = "2025-09-27T18:37:01.673Z" },
{ url = "https://files.pythonhosted.org/packages/80/d6/2d1b89f6ca4bff1036499b1e29a1d02d282259f3681540e16563f27ebc23/markupsafe-3.0.3-cp313-cp313t-win32.whl", hash = "sha256:69c0b73548bc525c8cb9a251cddf1931d1db4d2258e9599c28c07ef3580ef354", size = 14612, upload-time = "2025-09-27T18:37:02.639Z" },
{ url = "https://files.pythonhosted.org/packages/2b/98/e48a4bfba0a0ffcf9925fe2d69240bfaa19c6f7507b8cd09c70684a53c1e/markupsafe-3.0.3-cp313-cp313t-win_amd64.whl", hash = "sha256:1b4b79e8ebf6b55351f0d91fe80f893b4743f104bff22e90697db1590e47a218", size = 15200, upload-time = "2025-09-27T18:37:03.582Z" },
{ url = "https://files.pythonhosted.org/packages/0e/72/e3cc540f351f316e9ed0f092757459afbc595824ca724cbc5a5d4263713f/markupsafe-3.0.3-cp313-cp313t-win_arm64.whl", hash = "sha256:ad2cf8aa28b8c020ab2fc8287b0f823d0a7d8630784c31e9ee5edea20f406287", size = 13973, upload-time = "2025-09-27T18:37:04.929Z" },
{ url = "https://files.pythonhosted.org/packages/33/8a/8e42d4838cd89b7dde187011e97fe6c3af66d8c044997d2183fbd6d31352/markupsafe-3.0.3-cp314-cp314-macosx_10_13_x86_64.whl", hash = "sha256:eaa9599de571d72e2daf60164784109f19978b327a3910d3e9de8c97b5b70cfe", size = 11619, upload-time = "2025-09-27T18:37:06.342Z" },
{ url = "https://files.pythonhosted.org/packages/b5/64/7660f8a4a8e53c924d0fa05dc3a55c9cee10bbd82b11c5afb27d44b096ce/markupsafe-3.0.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:c47a551199eb8eb2121d4f0f15ae0f923d31350ab9280078d1e5f12b249e0026", size = 12029, upload-time = "2025-09-27T18:37:07.213Z" },
{ url = "https://files.pythonhosted.org/packages/da/ef/e648bfd021127bef5fa12e1720ffed0c6cbb8310c8d9bea7266337ff06de/markupsafe-3.0.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f34c41761022dd093b4b6896d4810782ffbabe30f2d443ff5f083e0cbbb8c737", size = 24408, upload-time = "2025-09-27T18:37:09.572Z" },
{ url = "https://files.pythonhosted.org/packages/41/3c/a36c2450754618e62008bf7435ccb0f88053e07592e6028a34776213d877/markupsafe-3.0.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:457a69a9577064c05a97c41f4e65148652db078a3a509039e64d3467b9e7ef97", size = 23005, upload-time = "2025-09-27T18:37:10.58Z" },
{ url = "https://files.pythonhosted.org/packages/bc/20/b7fdf89a8456b099837cd1dc21974632a02a999ec9bf7ca3e490aacd98e7/markupsafe-3.0.3-cp314-cp314-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:e8afc3f2ccfa24215f8cb28dcf43f0113ac3c37c2f0f0806d8c70e4228c5cf4d", size = 22048, upload-time = "2025-09-27T18:37:11.547Z" },
{ url = "https://files.pythonhosted.org/packages/9a/a7/591f592afdc734f47db08a75793a55d7fbcc6902a723ae4cfbab61010cc5/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ec15a59cf5af7be74194f7ab02d0f59a62bdcf1a537677ce67a2537c9b87fcda", size = 23821, upload-time = "2025-09-27T18:37:12.48Z" },
{ url = "https://files.pythonhosted.org/packages/7d/33/45b24e4f44195b26521bc6f1a82197118f74df348556594bd2262bda1038/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_riscv64.whl", hash = "sha256:0eb9ff8191e8498cca014656ae6b8d61f39da5f95b488805da4bb029cccbfbaf", size = 21606, upload-time = "2025-09-27T18:37:13.485Z" },
{ url = "https://files.pythonhosted.org/packages/ff/0e/53dfaca23a69fbfbbf17a4b64072090e70717344c52eaaaa9c5ddff1e5f0/markupsafe-3.0.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:2713baf880df847f2bece4230d4d094280f4e67b1e813eec43b4c0e144a34ffe", size = 23043, upload-time = "2025-09-27T18:37:14.408Z" },
{ url = "https://files.pythonhosted.org/packages/46/11/f333a06fc16236d5238bfe74daccbca41459dcd8d1fa952e8fbd5dccfb70/markupsafe-3.0.3-cp314-cp314-win32.whl", hash = "sha256:729586769a26dbceff69f7a7dbbf59ab6572b99d94576a5592625d5b411576b9", size = 14747, upload-time = "2025-09-27T18:37:15.36Z" },
{ url = "https://files.pythonhosted.org/packages/28/52/182836104b33b444e400b14f797212f720cbc9ed6ba34c800639d154e821/markupsafe-3.0.3-cp314-cp314-win_amd64.whl", hash = "sha256:bdc919ead48f234740ad807933cdf545180bfbe9342c2bb451556db2ed958581", size = 15341, upload-time = "2025-09-27T18:37:16.496Z" },
{ url = "https://files.pythonhosted.org/packages/6f/18/acf23e91bd94fd7b3031558b1f013adfa21a8e407a3fdb32745538730382/markupsafe-3.0.3-cp314-cp314-win_arm64.whl", hash = "sha256:5a7d5dc5140555cf21a6fefbdbf8723f06fcd2f63ef108f2854de715e4422cb4", size = 14073, upload-time = "2025-09-27T18:37:17.476Z" },
{ url = "https://files.pythonhosted.org/packages/3c/f0/57689aa4076e1b43b15fdfa646b04653969d50cf30c32a102762be2485da/markupsafe-3.0.3-cp314-cp314t-macosx_10_13_x86_64.whl", hash = "sha256:1353ef0c1b138e1907ae78e2f6c63ff67501122006b0f9abad68fda5f4ffc6ab", size = 11661, upload-time = "2025-09-27T18:37:18.453Z" },
{ url = "https://files.pythonhosted.org/packages/89/c3/2e67a7ca217c6912985ec766c6393b636fb0c2344443ff9d91404dc4c79f/markupsafe-3.0.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1085e7fbddd3be5f89cc898938f42c0b3c711fdcb37d75221de2666af647c175", size = 12069, upload-time = "2025-09-27T18:37:19.332Z" },
{ url = "https://files.pythonhosted.org/packages/f0/00/be561dce4e6ca66b15276e184ce4b8aec61fe83662cce2f7d72bd3249d28/markupsafe-3.0.3-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1b52b4fb9df4eb9ae465f8d0c228a00624de2334f216f178a995ccdcf82c4634", size = 25670, upload-time = "2025-09-27T18:37:20.245Z" },
{ url = "https://files.pythonhosted.org/packages/50/09/c419f6f5a92e5fadde27efd190eca90f05e1261b10dbd8cbcb39cd8ea1dc/markupsafe-3.0.3-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fed51ac40f757d41b7c48425901843666a6677e3e8eb0abcff09e4ba6e664f50", size = 23598, upload-time = "2025-09-27T18:37:21.177Z" },
{ url = "https://files.pythonhosted.org/packages/22/44/a0681611106e0b2921b3033fc19bc53323e0b50bc70cffdd19f7d679bb66/markupsafe-3.0.3-cp314-cp314t-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:f190daf01f13c72eac4efd5c430a8de82489d9cff23c364c3ea822545032993e", size = 23261, upload-time = "2025-09-27T18:37:22.167Z" },
{ url = "https://files.pythonhosted.org/packages/5f/57/1b0b3f100259dc9fffe780cfb60d4be71375510e435efec3d116b6436d43/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:e56b7d45a839a697b5eb268c82a71bd8c7f6c94d6fd50c3d577fa39a9f1409f5", size = 24835, upload-time = "2025-09-27T18:37:23.296Z" },
{ url = "https://files.pythonhosted.org/packages/26/6a/4bf6d0c97c4920f1597cc14dd720705eca0bf7c787aebc6bb4d1bead5388/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_riscv64.whl", hash = "sha256:f3e98bb3798ead92273dc0e5fd0f31ade220f59a266ffd8a4f6065e0a3ce0523", size = 22733, upload-time = "2025-09-27T18:37:24.237Z" },
{ url = "https://files.pythonhosted.org/packages/14/c7/ca723101509b518797fedc2fdf79ba57f886b4aca8a7d31857ba3ee8281f/markupsafe-3.0.3-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:5678211cb9333a6468fb8d8be0305520aa073f50d17f089b5b4b477ea6e67fdc", size = 23672, upload-time = "2025-09-27T18:37:25.271Z" },
{ url = "https://files.pythonhosted.org/packages/fb/df/5bd7a48c256faecd1d36edc13133e51397e41b73bb77e1a69deab746ebac/markupsafe-3.0.3-cp314-cp314t-win32.whl", hash = "sha256:915c04ba3851909ce68ccc2b8e2cd691618c4dc4c4232fb7982bca3f41fd8c3d", size = 14819, upload-time = "2025-09-27T18:37:26.285Z" },
{ url = "https://files.pythonhosted.org/packages/1a/8a/0402ba61a2f16038b48b39bccca271134be00c5c9f0f623208399333c448/markupsafe-3.0.3-cp314-cp314t-win_amd64.whl", hash = "sha256:4faffd047e07c38848ce017e8725090413cd80cbc23d86e55c587bf979e579c9", size = 15426, upload-time = "2025-09-27T18:37:27.316Z" },
{ url = "https://files.pythonhosted.org/packages/70/bc/6f1c2f612465f5fa89b95bead1f44dcb607670fd42891d8fdcd5d039f4f4/markupsafe-3.0.3-cp314-cp314t-win_arm64.whl", hash = "sha256:32001d6a8fc98c8cb5c947787c5d08b0a50663d139f1305bac5885d98d9b40fa", size = 14146, upload-time = "2025-09-27T18:37:28.327Z" },
]
[[package]] [[package]]
name = "matplotlib" name = "matplotlib"
version = "3.11.0" version = "3.11.0"
@ -279,6 +512,36 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" }, { url = "https://files.pythonhosted.org/packages/a1/5a/4d2b1601df3602dba7a14f3348ba9bfe94a18adb428e693df6154c293831/numpy-2.5.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a6db61f9aaa57e369905c67d852045d3c4f7126405b29d09b19dec118e9c9cb", size = 10697674, upload-time = "2026-07-04T17:07:58.506Z" },
] ]
[[package]]
name = "osqp"
version = "1.1.3"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "jinja2" },
{ name = "joblib" },
{ name = "numpy" },
{ name = "scipy" },
{ name = "setuptools" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b0/7f/b441062e4766851fdf1d066857134c4c3bcf7e0089e4a1d007b6114cecd5/osqp-1.1.3.tar.gz", hash = "sha256:48f53ef5ec89e6ce99ffa955bc6ea0cf2eec09ea3d40905f0c9fadc939609907", size = 57816, upload-time = "2026-06-12T16:59:27.208Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/14/f8/9f74aa53b35cb9c552ff1348abe5a17d22b4e65b0dfbe987980874c8bd36/osqp-1.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:ff6cf15a5404b28a4d9aaada72ae73c6b179120892321ec9b70f52ecb7f34261", size = 328538, upload-time = "2026-06-12T16:58:55.702Z" },
{ url = "https://files.pythonhosted.org/packages/6d/4e/4a7197f508cfa62abf40f7eacc7efa2f6147e6d9f7bbb22b18e8e10d6736/osqp-1.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a9c46b4adf2b1f76a40ef8f72426be5aa014c94efca20ac5acf8afa52e352afa", size = 308914, upload-time = "2026-06-12T16:58:56.906Z" },
{ url = "https://files.pythonhosted.org/packages/e7/1d/e967f4f1e3709baf6500914abd212d863a54fc90e90cd61dbbb0af14d6a4/osqp-1.1.3-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:bcca27693d0506c6816f989cf38e6352ed74ac8eaada9babc5487456d0cb0bf8", size = 328191, upload-time = "2026-06-12T16:58:58.211Z" },
{ url = "https://files.pythonhosted.org/packages/5f/dc/7c06a4ce4d1de3e5fbf294cf435ee351a39e44d534154647b2a1a18bc6a2/osqp-1.1.3-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2d3ee63e8c65ef89fce979c068d05bfc3ed92b1bbc4246fbacc663f86cbe02b2", size = 354138, upload-time = "2026-06-12T16:58:59.398Z" },
{ url = "https://files.pythonhosted.org/packages/05/22/2b4198a40847bff9b19230f3e55ac7537fe275a55a27eccebfdfee77fecb/osqp-1.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:9b9fe3daa15313d281233babeb102007062933241a855f6b399becd03ec5f1e3", size = 316744, upload-time = "2026-06-12T16:59:00.702Z" },
{ url = "https://files.pythonhosted.org/packages/e3/54/bdb0611de308ddf9868d7bd75e61d62ca8a3bacca71260e1876a354ebd71/osqp-1.1.3-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f5eade025877e5d2fb61efdd91337cc6f259335b2783ca59bce10e7be61c1d12", size = 328883, upload-time = "2026-06-12T16:59:02.162Z" },
{ url = "https://files.pythonhosted.org/packages/84/7f/4bca14a7a0402e5dd865225f193074217f019273f7a91e2d0018853b1ad5/osqp-1.1.3-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:e730047c4cba86ad97ca73c03ea4c3cca765b22fa79c3fbefff11b3cce317742", size = 309534, upload-time = "2026-06-12T16:59:03.427Z" },
{ url = "https://files.pythonhosted.org/packages/b6/0d/8e5ca2e9f4630f249602e91cb78b052b61744fac042f91b22a9e4d86088b/osqp-1.1.3-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1547e515c16feb1ab2889b64bcd4eecbbc59c40af4faf67258047a199780b98a", size = 328836, upload-time = "2026-06-12T16:59:04.833Z" },
{ url = "https://files.pythonhosted.org/packages/83/17/cb502af2a69da5f915d487db565b61e67684c65d3a4e5ae1ea42cece31f7/osqp-1.1.3-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4bdcbe47c2c37bf7296971a40811a883e793923699394a73a9d677ab7a9093e8", size = 354268, upload-time = "2026-06-12T16:59:06.014Z" },
{ url = "https://files.pythonhosted.org/packages/56/09/56bb7302f545fafbdfd9f0ad1042424c51c7a89407569bf005cc012b8a0f/osqp-1.1.3-cp314-cp314-win_amd64.whl", hash = "sha256:e80447b95d7b7dec3d13cc9e5a67ef4c5eaac3affee60fd83d1ce461e9270816", size = 322177, upload-time = "2026-06-12T16:59:07.175Z" },
{ url = "https://files.pythonhosted.org/packages/93/11/b4edd9cf7f3ceac4785357203f98b7661ae489b2721f30d994a2870aaa09/osqp-1.1.3-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:97df452d5e3b000b075fcce1c9f628289411501a2c0ea9a94acea281ed57bbd3", size = 336601, upload-time = "2026-06-12T16:59:08.521Z" },
{ url = "https://files.pythonhosted.org/packages/51/0f/ebc13231f58eaf4a7f73d5c2116c05214a871f49e777d63ff2cc80ee2439/osqp-1.1.3-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:3efaf5fa72b45bec9b67dcaf5ae58a4e2d1bd5d8053b249c0ecd5b8b60c1bc0e", size = 318836, upload-time = "2026-06-12T16:59:09.684Z" },
{ url = "https://files.pythonhosted.org/packages/22/0b/b74016cdd06cf37e4efb9d7540ce56e466d77a803ccd1ffc80a8b3e3d489/osqp-1.1.3-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e1d6cf30cdccf07baecb7232dc5fe22a1421ea3278a88a40580df3563a419d64", size = 330294, upload-time = "2026-06-12T16:59:11.094Z" },
{ url = "https://files.pythonhosted.org/packages/e0/22/d9a756198d1e48931ddf6363df7162a5c13aecdca5af1f1489bebfa92a7d/osqp-1.1.3-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a8ae5b09be77b987421216dae535b4c007a14f6c034a844ad6eb4244289a881d", size = 355773, upload-time = "2026-06-12T16:59:12.274Z" },
{ url = "https://files.pythonhosted.org/packages/07/fc/07ad9b479417934c7533e137be82ed0c0ef76663d6d67a1af5e068a44466/osqp-1.1.3-cp314-cp314t-win_amd64.whl", hash = "sha256:f5d66a344ff483eeb5a9213521b91427d2d898885d7e2db0712f648664482264", size = 337988, upload-time = "2026-06-12T16:59:13.366Z" },
]
[[package]] [[package]]
name = "packaging" name = "packaging"
version = "26.2" version = "26.2"
@ -350,6 +613,15 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" }, { url = "https://files.pythonhosted.org/packages/3d/68/1f3066acedf37673694a7141381d8f811ae97f30d34413d236abe7d489f1/pillow-12.3.0-cp315-cp315t-win_arm64.whl", hash = "sha256:06ff022112bc9cbf83b60f8e028d94ad87b60621706487e65f673de61610ab59", size = 2567491, upload-time = "2026-07-01T11:56:23.506Z" },
] ]
[[package]]
name = "pycparser"
version = "3.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/1b/7d/92392ff7815c21062bea51aa7b87d45576f649f16458d78b7cf94b9ab2e6/pycparser-3.0.tar.gz", hash = "sha256:600f49d217304a5902ac3c37e1281c9fe94e4d0489de643a9504c5cdfdfc6b29", size = 103492, upload-time = "2026-01-21T14:26:51.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/0c/c3/44f3fbbfa403ea2a7c779186dc20772604442dde72947e7d01069cbe98e3/pycparser-3.0-py3-none-any.whl", hash = "sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992", size = 48172, upload-time = "2026-01-21T14:26:50.693Z" },
]
[[package]] [[package]]
name = "pyparsing" name = "pyparsing"
version = "3.3.2" version = "3.3.2"
@ -371,6 +643,42 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" },
] ]
[[package]]
name = "qdldl"
version = "0.1.9.post1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "scipy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/51/4e/452984a63df9421cf8e7d25e8e6a44832cf0247a5e7b65e437cd516a0f8f/qdldl-0.1.9.post1.tar.gz", hash = "sha256:da2016d541c26cefc79bca4d8b5bebfa00f35db19704abb20efbd1c08df3b4c7", size = 76295, upload-time = "2026-02-19T16:48:36.651Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/42/f6/5ab7f37e7607396eb7a61736471b54be881fc5697bb18e57992fb14a0867/qdldl-0.1.9.post1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:00331a5e45cc60bf6f75f027ae2a2c137295de0b5e1a0af358a37aaa07427476", size = 122503, upload-time = "2026-02-19T16:47:57.435Z" },
{ url = "https://files.pythonhosted.org/packages/e1/49/144ec3c6b7cfe650191dac35a21d93eebfcc043a3ecd8b499adb3b7cec1d/qdldl-0.1.9.post1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:dbfb46f7290917146b076449fb3fee1eefdf018e710c7033c11739ae1723da07", size = 117760, upload-time = "2026-02-19T16:47:58.407Z" },
{ url = "https://files.pythonhosted.org/packages/82/a1/f8d58f100140d416f40912a7bf8bc28ab8276e7e8e6a24a8889a4e5c895a/qdldl-0.1.9.post1-cp313-cp313-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e881f51dc779441c0910dc5a9d89bfd40a459daaf8088934aebf6f4c84adf1e0", size = 1448988, upload-time = "2026-02-19T16:47:59.377Z" },
{ url = "https://files.pythonhosted.org/packages/f3/7d/6d621819d3340f2cb4555e5263481be1e741578634d09569c67acc186ff0/qdldl-0.1.9.post1-cp313-cp313-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:bc0f30e521da345d25e4fa5308fda4964e25beacd10e8de9f08d084a600a42e2", size = 1476075, upload-time = "2026-02-19T16:48:00.583Z" },
{ url = "https://files.pythonhosted.org/packages/76/97/423a7cbe11add0b7b6afe97c9d6e934776c632a8c6e1d2a457c1f9dffbf6/qdldl-0.1.9.post1-cp313-cp313-win_amd64.whl", hash = "sha256:27b02a730c39b2dba205bb5c08dca5deb994f655f6eeacee687ac006c50b3366", size = 104586, upload-time = "2026-02-19T16:48:01.586Z" },
{ url = "https://files.pythonhosted.org/packages/ca/dc/425dfec0bd5a18ad43790764f091567fba18d63d9f9d6b202a796a8fd9ca/qdldl-0.1.9.post1-cp313-cp313-win_arm64.whl", hash = "sha256:98a13e234ea335484cf76441b95638d0f9ecdd43242dd5a073f77c758977d980", size = 98954, upload-time = "2026-02-19T16:48:02.486Z" },
{ url = "https://files.pythonhosted.org/packages/7f/a4/bc38a8f2edd88adc3bd87a53493e3ddfcab1c173a0310dcfe7f56063d254/qdldl-0.1.9.post1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:576edaf8b847d087806e3d2a860a06d52a435aa3192b21bbc39e69a55187805d", size = 129346, upload-time = "2026-02-19T16:48:03.325Z" },
{ url = "https://files.pythonhosted.org/packages/21/3f/0b157bd706ee91838d22d57dd0bd3a7ea0837142fb97b5df3d8acfad9f6c/qdldl-0.1.9.post1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:547f73046ef615827317fbedb21b5e1b11f5082d304e8d775e9e17d6c447a598", size = 124236, upload-time = "2026-02-19T16:48:04.313Z" },
{ url = "https://files.pythonhosted.org/packages/30/ab/185e0620e424fab6eeafd3e37f37723e72c80a795419556575d47b27d4c5/qdldl-0.1.9.post1-cp313-cp313t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cb2e82b4f40cb18638da47adf491a016d76d0d82f25377c4c330453a8785f94", size = 1494339, upload-time = "2026-02-19T16:48:05.364Z" },
{ url = "https://files.pythonhosted.org/packages/1a/55/0982ba5565863e3fa4306891158cb509088a875e76a5eeb683744d8a1610/qdldl-0.1.9.post1-cp313-cp313t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1ddd5b43b760ffb4cc85ca1acfb0eb139bff78bcc7bd30cad4c55564074685d4", size = 1517878, upload-time = "2026-02-19T16:48:06.517Z" },
{ url = "https://files.pythonhosted.org/packages/79/bf/b75b0fee2ea07e0dd2c8824356cd78200ac429172fa24f12a2cf84f95bd8/qdldl-0.1.9.post1-cp313-cp313t-win_amd64.whl", hash = "sha256:0fba64949c3197c6691c8fe4c3fbcb4f95d6e85b314824ba75981efcdf06d98c", size = 113642, upload-time = "2026-02-19T16:48:07.858Z" },
{ url = "https://files.pythonhosted.org/packages/f8/ed/cc4bfa56f8d29a5ed7956c97ede9dfc4ec408f61e4b75ecd9e19232a64a8/qdldl-0.1.9.post1-cp313-cp313t-win_arm64.whl", hash = "sha256:31296314679c6ccfad8760704dbc9e25b5e49fd442f803638a2aaa1886724f7d", size = 104321, upload-time = "2026-02-19T16:48:08.79Z" },
{ url = "https://files.pythonhosted.org/packages/08/28/9b991fdcc16569b1bc7119ae1f62495c948033d791d469c031058d7b2c4c/qdldl-0.1.9.post1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:0d69a8d011f47f287c5e7668b92b9e3574798b9687bc751af6f89c15037aa26e", size = 122733, upload-time = "2026-02-19T16:48:09.694Z" },
{ url = "https://files.pythonhosted.org/packages/18/92/56437eb5a31edb9275450dac94a94f7df252147527974ef50ba56cbb8d66/qdldl-0.1.9.post1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:496bebf154c002fb7a36443b9a26cc0f7251e8e9251d3742590300cd2edec7a8", size = 117977, upload-time = "2026-02-19T16:48:10.606Z" },
{ url = "https://files.pythonhosted.org/packages/b2/7e/9594e0cea5aaf33c2579f722ad02e5117ede62e99aca62245f2c2d3df0d5/qdldl-0.1.9.post1-cp314-cp314-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1922c11ddb59419ab969cd1a069e85da9e37db9b14d92b0900e73be94d25c318", size = 1448404, upload-time = "2026-02-19T16:48:11.497Z" },
{ url = "https://files.pythonhosted.org/packages/bf/85/c5e380aeaa3f5d61cbf21b815b3caac5909a165c747aadd5675500fa92dc/qdldl-0.1.9.post1-cp314-cp314-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8c0148de01346722ef07ce9434485b97b8f3aa7042cac5c341b58a728fe7588", size = 1474644, upload-time = "2026-02-19T16:48:12.831Z" },
{ url = "https://files.pythonhosted.org/packages/b6/a7/a3b62d9ec100604778d0596910f4bd20eaf23445af20bdd1d3cac77b6632/qdldl-0.1.9.post1-cp314-cp314-win_amd64.whl", hash = "sha256:9adb8625012e96ceb6c24a8278b8aa9477727ae8fde35dee730988747ab95144", size = 107693, upload-time = "2026-02-19T16:48:14.231Z" },
{ url = "https://files.pythonhosted.org/packages/30/ec/55b21669cad250f55fee6a8ae0fe65dfb547baeb3463f576e8642795e392/qdldl-0.1.9.post1-cp314-cp314-win_arm64.whl", hash = "sha256:0db9f197fb51c6fa96ff1964707e2ba9a89b7b0d91c305e6e0b668e1bcb66fbf", size = 102546, upload-time = "2026-02-19T16:48:15.226Z" },
{ url = "https://files.pythonhosted.org/packages/43/e8/09c59c9f4a1df9b2b415cbc13eb0daadab418e252c698f622d94e4dcb026/qdldl-0.1.9.post1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:25da2bcd44dd272435db3b4208b47943837503f43db4d24c02e5e15b7d5ddf33", size = 129500, upload-time = "2026-02-19T16:48:16.221Z" },
{ url = "https://files.pythonhosted.org/packages/86/03/dcf73d806a4c3f0250a56cc56dafadc2dfacccf5df9caf8f7f181a5e3a89/qdldl-0.1.9.post1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e3438e9cb3f8a26531a24f25bb05152a7446112f4c8ff62d537debfb8147435c", size = 124224, upload-time = "2026-02-19T16:48:17.17Z" },
{ url = "https://files.pythonhosted.org/packages/5b/2e/7770f0ad70c6cc834041c2c19262c3315d312d0d3e72ac33ecb7c94f2f9a/qdldl-0.1.9.post1-cp314-cp314t-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9a2aed01dbdc705bcab71270ce20753a016bd7b112ea7e06d1166d897e7483cb", size = 1485196, upload-time = "2026-02-19T16:48:18.415Z" },
{ url = "https://files.pythonhosted.org/packages/0b/6d/f1919ae97e1482484239edcda8b4f29aaba11817c808c775403e8a35aa54/qdldl-0.1.9.post1-cp314-cp314t-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:dd08bbb431f138c2946a0d99cb70fbfe67435c8b3dc1dbf23e4e80edfd2b1456", size = 1509644, upload-time = "2026-02-19T16:48:20.273Z" },
{ url = "https://files.pythonhosted.org/packages/7a/33/89ca4741cb651385a12600c0e93d24ad20eef5a954bb2b7ddc596442fc8c/qdldl-0.1.9.post1-cp314-cp314t-win_amd64.whl", hash = "sha256:213f6125564f61d9597d5d36c5556d4c898225bc31a99f8c87fd549b2e65b60a", size = 117569, upload-time = "2026-02-19T16:48:21.459Z" },
{ url = "https://files.pythonhosted.org/packages/63/48/34fa827457aa0e373fb50dab4490757350076f9afcf7eb749a71926023af/qdldl-0.1.9.post1-cp314-cp314t-win_arm64.whl", hash = "sha256:fcc2184cac1e502ed624efe7f00c1c215b95cfd324a8cacf7f0053c00fa524f5", size = 107844, upload-time = "2026-02-19T16:48:22.899Z" },
]
[[package]] [[package]]
name = "qtensor" name = "qtensor"
version = "0.2.0" version = "0.2.0"
@ -393,6 +701,83 @@ docs = [
{ name = "mkdocs-material", specifier = ">=9.7.6" }, { name = "mkdocs-material", specifier = ">=9.7.6" },
] ]
[[package]]
name = "scipy"
version = "1.18.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/a7/25/c2700dfaf6442b4effaa91af24ebce5dc9d31bb4a69706313aae70d72cd0/scipy-1.18.0.tar.gz", hash = "sha256:67b2ad2ad54c72ca6d04975a9b2df8c3638c34ddd5b28738e94fc2b57929d378", size = 30774447, upload-time = "2026-06-19T15:01:43.456Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/05/52/9c0136c2de7ae0779b7b366447766cec6d9f0702c56bb8ffeb04c8fd3af4/scipy-1.18.0-cp313-cp313-macosx_10_15_x86_64.whl", hash = "sha256:09143f676d157d9f546d663504ef9c1becb819824f1afc018814176411942446", size = 31036107, upload-time = "2026-06-19T15:00:14.03Z" },
{ url = "https://files.pythonhosted.org/packages/02/73/0291a64843270f4efb86cdcf2ee0f2048631b65ec6b405398b2b4dbf11bf/scipy-1.18.0-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:5efe260f69417b97ddae455bfb5a95e8359f7f66ad7fa9522a60feb66f169520", size = 28663303, upload-time = "2026-06-19T15:00:16.819Z" },
{ url = "https://files.pythonhosted.org/packages/d3/0f/10ffa0b697a572f4e0d48b92a88895d366422f019f723e7e14a84c050dac/scipy-1.18.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:68363b7eaacd8b5dd426df56d782cc156468ac79a127a1b87ca597d6e2e82197", size = 20404960, upload-time = "2026-06-19T15:00:19.635Z" },
{ url = "https://files.pythonhosted.org/packages/7e/d2/e896cea21ba8edd6c81d4c55b1ffcc717e79698dcbebf9641b4cfb4c6622/scipy-1.18.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:c5557d8be5da8e41353fcd4d21491fdbab83b062fc579e94dc09a7c8ab4f669b", size = 23034074, upload-time = "2026-06-19T15:00:22.107Z" },
{ url = "https://files.pythonhosted.org/packages/ea/b2/e83ea34279a52c03374477c74006256ec78df65fc877baa4617d6de1d202/scipy-1.18.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0d13bca67c096d89fb95ced0d8921807300fce0275643aef9533cc63a0773468", size = 33942038, upload-time = "2026-06-19T15:00:24.964Z" },
{ url = "https://files.pythonhosted.org/packages/f6/af/e8fe5fb136f51e2b01678b92cb4106d10d8cd68ec147ead2e7cb0ac75398/scipy-1.18.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a46f9273dbd0eb1cefba61c9b8648b4dfe3cbc14a080176f9a73e44b8336dc7f", size = 35266390, upload-time = "2026-06-19T15:00:28.059Z" },
{ url = "https://files.pythonhosted.org/packages/3a/49/2c5cbb907b56695fc67517811d1db234dfd83381a84814ec220aded2794d/scipy-1.18.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:5aba46108853ddfc77906b6557aac839d2b52e900c1d72a1180adaaab58d265f", size = 35551324, upload-time = "2026-06-19T15:00:31.014Z" },
{ url = "https://files.pythonhosted.org/packages/bb/73/eda39f7a2d306ff0ffc574afd13c0bbb6d10a603d9a413998ee269487a80/scipy-1.18.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b6f758e35f12757b5d95c00bc6de2438e229c2664b7a92e96f205959d9f2dfa4", size = 37404785, upload-time = "2026-06-19T15:00:34.072Z" },
{ url = "https://files.pythonhosted.org/packages/b7/d2/ae881ee28d014f38e0ccbfd974a06a919ba9af34f1f74bf42b5301891d63/scipy-1.18.0-cp313-cp313-win_amd64.whl", hash = "sha256:1afac4a847207c7ff8efd321734a50b06d0280b3b2a2c0fc2f413101747ad7c7", size = 36554943, upload-time = "2026-06-19T15:00:36.903Z" },
{ url = "https://files.pythonhosted.org/packages/70/3a/21154e2d54eb3639c6bf4dbae2e531c68356bfe95990daa30df33b30d556/scipy-1.18.0-cp313-cp313-win_arm64.whl", hash = "sha256:c5dbddf60e58c2312316d097271a8e73d40eaf2eabfa4d95ed7d3695bbf2ce7b", size = 24350911, upload-time = "2026-06-19T15:00:40.062Z" },
{ url = "https://files.pythonhosted.org/packages/78/b5/915a19b3de2f7430062b509653563db1633ddbb6f021b06731521115d4e2/scipy-1.18.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:4c256ee70c0d1a8a2ace807e199ccd4e3f57037433842abb3fb36bc17eaa9578", size = 31036253, upload-time = "2026-06-19T15:00:43.216Z" },
{ url = "https://files.pythonhosted.org/packages/d7/88/b72def7262e150d16be13fca37a96481138d624e700340bc3362a7588929/scipy-1.18.0-cp314-cp314-macosx_12_0_arm64.whl", hash = "sha256:2ef3abc54a4ffc53765374b0d5728532dfdd2585ed23f6b11c206a1f0b1b9af8", size = 28673758, upload-time = "2026-06-19T15:00:46.663Z" },
{ url = "https://files.pythonhosted.org/packages/91/02/2e636a61a525632c373cf6a9c24442a3ffb79e364d38e98b32042964ac32/scipy-1.18.0-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:f2a6af57bd9e4a75d70e4117e78a1bbee84f79ae3fbb6d0111005d6ebcc4cb8d", size = 20415514, upload-time = "2026-06-19T15:00:49.399Z" },
{ url = "https://files.pythonhosted.org/packages/c9/b6/2135974442f6aba159d9d39d774a1c8cb19947016725d69fecc685df45bf/scipy-1.18.0-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:3f1ac564d3bf6c03d861d2cd87a1bea0da2887136f7fb1bf519c05a8971452d6", size = 23034398, upload-time = "2026-06-19T15:00:51.941Z" },
{ url = "https://files.pythonhosted.org/packages/f6/e6/ba89ec5abf6ee9257c0d1ec985573f3ae32742c24bc03e016388a40b1b15/scipy-1.18.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:40395a5fcd1abee49a5c7aaa98c29db393eedc835138560a588c47ec16156690", size = 33998032, upload-time = "2026-06-19T15:00:54.838Z" },
{ url = "https://files.pythonhosted.org/packages/7f/c4/bc41eb19b0fd0db868f4132920879019318d80cc522ad8f2bca4611af808/scipy-1.18.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8ca01e8ae69f1b18e9a58d91afead31be3cef0dd905a10249dac559ee15460a0", size = 35283333, upload-time = "2026-06-19T15:00:58.152Z" },
{ url = "https://files.pythonhosted.org/packages/53/a4/cbdeef6eb3830a8462a9d4ada814de5fc984345cc9ecf17cbec51a036f1e/scipy-1.18.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:7a7f3b01647384dbc3a711e8c6778e0aabbe93959249fef5c7393396bcac0867", size = 35610216, upload-time = "2026-06-19T15:01:01.155Z" },
{ url = "https://files.pythonhosted.org/packages/80/4d/b2b82502b65f661d1b789c1665dcdf315d5f12194e06fc0b37946294ebae/scipy-1.18.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:6aa94e78ec192a30063a5e72e561c28af769dc311190b24fe91774eff1969709", size = 37418960, upload-time = "2026-06-19T15:01:04.155Z" },
{ url = "https://files.pythonhosted.org/packages/93/3e/902d836831474b0ab5a37d16404f7bc5fafd9efba632890e271ba952635f/scipy-1.18.0-cp314-cp314-win_amd64.whl", hash = "sha256:2d8bbdc6c817f5b4006a54d799d4f5bab6f910193cbb9a1ff310833d4d270f61", size = 37288845, upload-time = "2026-06-19T15:01:07.822Z" },
{ url = "https://files.pythonhosted.org/packages/b6/43/8d73b337a3bdb14daa0314f0434210747c02d79d729ce1777574a817dcf6/scipy-1.18.0-cp314-cp314-win_arm64.whl", hash = "sha256:18e9575f1569b2c54174e6159d32942e03731177f63dce7975f0a0c88d102f5b", size = 24988971, upload-time = "2026-06-19T15:01:11.076Z" },
{ url = "https://files.pythonhosted.org/packages/b4/b4/f11918b0508a2787031a0499a03fbe3546f3bb5ca05d01038c45b278c09a/scipy-1.18.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:f351e0dd702687d12a402b867a1b4146a256923e1c38317cbc472f6372b94707", size = 31399325, upload-time = "2026-06-19T15:01:13.723Z" },
{ url = "https://files.pythonhosted.org/packages/7b/d1/1f287b57c0ff0ee5185dff3946d92c8017d39b0e431f0ae79a3ff1859512/scipy-1.18.0-cp314-cp314t-macosx_12_0_arm64.whl", hash = "sha256:7c7a51b33ce387193c97f228320cf8e87361daa1bba750638677729598b3e677", size = 29092110, upload-time = "2026-06-19T15:01:16.908Z" },
{ url = "https://files.pythonhosted.org/packages/ff/1a/7b74eb6c392fdcb27d414c0e7558a6d0231eb3b6d73571f479bb81ea8794/scipy-1.18.0-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:84031d7b052a54fae2f8632e0ec802073d385476eb9a63079bce6e23ef9283d4", size = 20833811, upload-time = "2026-06-19T15:01:20.488Z" },
{ url = "https://files.pythonhosted.org/packages/7c/ad/f3941716320a7b9cb4d68734a903b45fe16eff5fb7da7e16f2e619304979/scipy-1.18.0-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:56abf29a7c067dde59be8b9a22d606a4ea1b2f2a4b756d9d903c62818f5dacce", size = 23396644, upload-time = "2026-06-19T15:01:23.364Z" },
{ url = "https://files.pythonhosted.org/packages/22/22/1446b62ffe07f9719b7d9b1b6a4e05a772833ae8f441fe4c22c34c9b250f/scipy-1.18.0-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1ad44305cfa24b1ba5803cbbebf033590ccbac1aa5d612d727b785325ab408b0", size = 34079318, upload-time = "2026-06-19T15:01:26.002Z" },
{ url = "https://files.pythonhosted.org/packages/56/3b/b87da667098bb470fa30c7011b0ba351ee976dd395c78798c66e941665a3/scipy-1.18.0-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:945c1761b93f38d7f99ae81ae80c63e621471608c7eeead563f6df025585cd58", size = 35324320, upload-time = "2026-06-19T15:01:28.881Z" },
{ url = "https://files.pythonhosted.org/packages/f8/a1/c7932f91909759b0267f75fdea34e91309f96b895757534b76a90b6b4344/scipy-1.18.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:1a4441f15d620578772a49e5ab48c0ee1f7a0220e387110283062729136b2553", size = 35699541, upload-time = "2026-06-19T15:01:31.968Z" },
{ url = "https://files.pythonhosted.org/packages/f7/86/5185061a1fcc41d18c5dc2463969b3a3964b31d9ac67b2fb05d4c7ff7670/scipy-1.18.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:9aac6192fac56bf2ca534389d24623f07b39ff83317d58287285e7fbd622ff76", size = 37472480, upload-time = "2026-06-19T15:01:35.136Z" },
{ url = "https://files.pythonhosted.org/packages/31/8e/f04c68e39919a010d34f2ee1367fd705b0a25a02f609d755f0bfbc0a15fc/scipy-1.18.0-cp314-cp314t-win_amd64.whl", hash = "sha256:e40baea28ae7f5475c779741e2d90b1247c78531207b49c7030e698ff81cee3f", size = 37365390, upload-time = "2026-06-19T15:01:38.091Z" },
{ url = "https://files.pythonhosted.org/packages/d5/19/969dc072906c84dd0a3b05dcf57ea750936087d7873549e408b35cfc3f97/scipy-1.18.0-cp314-cp314t-win_arm64.whl", hash = "sha256:368e0a705903c466aa5f08eefb39e6b1b6b2d659e7352a31fd9e2438365be0f8", size = 25279661, upload-time = "2026-06-19T15:01:40.817Z" },
]
[[package]]
name = "scs"
version = "3.2.11"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "scipy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/9e/59/5cb7f9612a5a3ff6efd4ab2d899902a536cc5974a7edb589084c5577291c/scs-3.2.11.tar.gz", hash = "sha256:2a5455cf2093d07f84f2f848c199faed52e79cdb3a11fe250b5622b6bbac4913", size = 1691825, upload-time = "2026-01-09T17:53:54.074Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/90/1b/6611b98b114621444078da50be9e83c43fadc4079ef0df867091b5c9ee38/scs-3.2.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:a42696b0a26c3e749b8da8d2ffc57a93af4f0f500fc3a83acb50daad92386de4", size = 96309, upload-time = "2026-01-09T17:53:13.589Z" },
{ url = "https://files.pythonhosted.org/packages/52/16/e49eff778292000bc7dca204952e430bc138c21e7eb1c4348341f3bd2ef5/scs-3.2.11-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:50d204ae417c014a1756be36e8c0b857ed39c7b64e2c63b6afb1ff64c0a465d0", size = 5071425, upload-time = "2026-01-09T17:53:14.966Z" },
{ url = "https://files.pythonhosted.org/packages/7f/f4/b7b2df5bece1b7e5f11d2bf21744c9fa346f3cea0a849cb54dabb9b83055/scs-3.2.11-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:594c09207395de922e0ff40ced562453e46f4f197dd0128022cb82c096f06615", size = 12079963, upload-time = "2026-01-09T17:53:16.976Z" },
{ url = "https://files.pythonhosted.org/packages/d6/78/11db8c58c071ece82aaefe53c7f6d5932fe8dafe381b2f6fd35fcc22cf33/scs-3.2.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fd672701ed81744e8c300df71d823700b05e7fe3d6a26f8b19b74b0a31fe3c8a", size = 11973938, upload-time = "2026-01-09T17:53:19.239Z" },
{ url = "https://files.pythonhosted.org/packages/8a/ca/cd9cc63fc22f452188b9cda41a8d65d4124e733bc54f34f706b9c5939f92/scs-3.2.11-cp313-cp313-win_amd64.whl", hash = "sha256:2f4ebc0be14783ce3fcda61c616a7e922ac528af033e44a0da952dda0fe98091", size = 7478466, upload-time = "2026-01-09T17:53:21.494Z" },
{ url = "https://files.pythonhosted.org/packages/94/8a/52facc80a6515edd6560d918a68a0dd9186299a709e64c180ad24551aeb8/scs-3.2.11-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:fe43181c3822bed600363c25c7566a643b319e0edb0c2af385c5f086a9c826d2", size = 96344, upload-time = "2026-01-09T17:53:22.949Z" },
{ url = "https://files.pythonhosted.org/packages/91/2b/c125e6b01aa6936f604e1b46a4b8c37e126af703cc228af7e9d0fe012bcb/scs-3.2.11-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b3c59ce43585d3ea0c6771c5ce3df272b6c8239231acbb9567876be5d0a0474d", size = 5071403, upload-time = "2026-01-09T17:53:24.327Z" },
{ url = "https://files.pythonhosted.org/packages/58/ae/94055cafac0d9b81ffa2a12f7050c394c39182ec901faff42a471cca50cc/scs-3.2.11-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3c46f597892c9f8c5551bb9a3a680dfb86e86a1a6c3bc67b09a5af2e89ba5357", size = 12079963, upload-time = "2026-01-09T17:53:26.2Z" },
{ url = "https://files.pythonhosted.org/packages/d8/72/43ff8bc4a281e84d4ae8f13dcf7436ff030bc5f67fba02368c829537386d/scs-3.2.11-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:513131af6991fb4983f84c4ba276c756c0a3574003c2790dda891c68d5b6da30", size = 11973979, upload-time = "2026-01-09T17:53:29.979Z" },
{ url = "https://files.pythonhosted.org/packages/af/55/695c509c0852bc32695b1995ff12227dfc78e9d91867ccf637d7cf85a948/scs-3.2.11-cp314-cp314-win_amd64.whl", hash = "sha256:7b2c37e87baca0389f005fe19a0ca8209d43c0f1e9136a1a6fde23cae1735db9", size = 7569717, upload-time = "2026-01-09T17:53:32.938Z" },
{ url = "https://files.pythonhosted.org/packages/4e/a1/b30e470a7440c57ed53a1d92a9e58f17ecf548888b4eea658be047500ae5/scs-3.2.11-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:29c0a5c233fb5a964ea5f7523ec2b2209f000217c0a24423ab5dcd8b8922f37d", size = 97042, upload-time = "2026-01-09T17:53:35.676Z" },
{ url = "https://files.pythonhosted.org/packages/14/31/86b6aa0fca4be4701b59bcfcf29007b16dea118051a5b46a43396f2a4543/scs-3.2.11-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7519c2f436e793b004d1eae4aaf98c18857e519f8169219d1167fe88b3b0a568", size = 5072473, upload-time = "2026-01-09T17:53:37.021Z" },
{ url = "https://files.pythonhosted.org/packages/98/eb/2c07015938c50f46e9323e379e9799c3e28e0d07c9bae8b6735a6ecf1b6c/scs-3.2.11-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:2c166768dc87c389b2d000b5dcd472bb0ba40f96b4cf0e63c0fb603a4a5c80db", size = 12080259, upload-time = "2026-01-09T17:53:38.897Z" },
{ url = "https://files.pythonhosted.org/packages/f2/1b/d52e3b17554791726ba788abff053f4b27df157a49438f01134fec3c859d/scs-3.2.11-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:f51a14a5315974fae4ca4e1b4dc8926f872eca7e66b42e070dbdcfa6904b7860", size = 11974311, upload-time = "2026-01-09T17:53:41.003Z" },
{ url = "https://files.pythonhosted.org/packages/cb/d7/023ba290cfaf97b21c710b675b8a860b97d8226f62e35d7a08e37ddbb6d3/scs-3.2.11-cp314-cp314t-win_amd64.whl", hash = "sha256:7fe26e8a0efc96232f4c5b7649817e48dae04a61be911417e925071091b8cbf6", size = 7570221, upload-time = "2026-01-09T17:53:42.845Z" },
]
[[package]]
name = "setuptools"
version = "83.0.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/34/26/f5d29e25ffdb535afef2d35cdb55b325298f96debd670da4c325e08d70f4/setuptools-83.0.0.tar.gz", hash = "sha256:025bccbbf0fa05b6192bc64ae1e7b16e001fd6d6d4d5de03c97b1c1ade523bef", size = 1154254, upload-time = "2026-07-04T15:31:22.699Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/5d/40/e1e72872c6354b306daef1703549e8e83b4d43cfea356311bf722a043752/setuptools-83.0.0-py3-none-any.whl", hash = "sha256:29b23c360f22f414dc7336bb39178cc7bcbf6021ed2733cde173f09dba19abb3", size = 1008090, upload-time = "2026-07-04T15:31:20.885Z" },
]
[[package]] [[package]]
name = "six" name = "six"
version = "1.17.0" version = "1.17.0"
@ -402,6 +787,27 @@ wheels = [
{ url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" }, { url = "https://files.pythonhosted.org/packages/b7/ce/149a00dd41f10bc29e5921b496af8b574d8413afcd5e30dfa0ed46c2cc5e/six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274", size = 11050, upload-time = "2024-12-04T17:35:26.475Z" },
] ]
[[package]]
name = "sparsediffpy"
version = "0.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
]
sdist = { url = "https://files.pythonhosted.org/packages/0e/e7/6a3227a25a79a440e5ec5eff1e90f5911515a7c219ee95fc6dfe5d74ec30/sparsediffpy-0.3.0.tar.gz", hash = "sha256:fdd9115db63ee228d09e1917365b263a16811645c6d32ee7dce50ada09b3d5a5", size = 180927, upload-time = "2026-05-14T06:57:48.196Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/be/e0/c9bc5b18b025567b02df87198d434c286777f491bc3c0b38949861e2e039/sparsediffpy-0.3.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aae700842a43bfdf09c7709c23354999d8e34da7f82dbc78d933a008fd63c285", size = 207404, upload-time = "2026-05-14T06:57:28.183Z" },
{ url = "https://files.pythonhosted.org/packages/11/ca/021de6a523e739a038f6e527bf71acab6ccb5181ddd5beff304f349e968e/sparsediffpy-0.3.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:afcc042d56af4e978e9a798c8c41a7432d9ff715d993cc2ac733565d72080a5e", size = 138483, upload-time = "2026-05-14T06:57:29.899Z" },
{ url = "https://files.pythonhosted.org/packages/76/64/e5ffb808435040177345cbaeed5623ebb49bc5567665e2001373197d6239/sparsediffpy-0.3.0-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:6bc0720d58f4858ed28b1a760a96bc328d4a9591da5abf2c2c46955bcd2d6f59", size = 5091193, upload-time = "2026-05-14T06:57:32.066Z" },
{ url = "https://files.pythonhosted.org/packages/8f/17/670c5fd1f0b4da16e4b691d48963b195092d7bcf3d9afb92722a6a878f42/sparsediffpy-0.3.0-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:73df847a5ae8fbb14b4af9b8eb8e0c2e96f2c8479da2eff8afd89adc55752c5d", size = 12099975, upload-time = "2026-05-14T06:57:34.696Z" },
{ url = "https://files.pythonhosted.org/packages/6e/1c/8c2a4f03e10b418dc957db87287a777cda74b58cf39779cecefccd5f4b2d/sparsediffpy-0.3.0-cp313-cp313-win_amd64.whl", hash = "sha256:b018a3e9783ae547a83a82049f26d72a6f0a5dfb61f42eb88ed921412f8aa087", size = 130147, upload-time = "2026-05-14T06:57:36.83Z" },
{ url = "https://files.pythonhosted.org/packages/80/f0/0b8b505563699767fa125a453cd40004476084c3e7a1975e7dfca957ca3a/sparsediffpy-0.3.0-cp314-cp314-macosx_10_15_universal2.whl", hash = "sha256:b62d0531c6470d6cea800ff60952857d16b7a3e177369c729a8e5f31dba9a4ea", size = 207430, upload-time = "2026-05-14T06:57:38.273Z" },
{ url = "https://files.pythonhosted.org/packages/56/8c/9be63f71098b8c3ffe82ab4094195ea95a33822787fa67b97e8eb5e13b77/sparsediffpy-0.3.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:5140ee4bb70c877d01ad1e07cee76a375c684af8e3672bf37c1de424e6f8b3b7", size = 138542, upload-time = "2026-05-14T06:57:40.124Z" },
{ url = "https://files.pythonhosted.org/packages/77/01/e2f5cc15d0fd7244c26632a4ee746e94d2721e5e44f49c2a3988b3e23d87/sparsediffpy-0.3.0-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7a746cc822c2bce50dd696ef7a7c59f4e795f2c4c0a24750146b7c106809e12a", size = 5091213, upload-time = "2026-05-14T06:57:41.842Z" },
{ url = "https://files.pythonhosted.org/packages/48/03/6656660e7f0564c99405173848c65d92408af0372bbd30b8a6728d9bcee2/sparsediffpy-0.3.0-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6a28052dc02a9424b84406a0ac8c573f19471ccbf3f0ee4331588549e4de5dbe", size = 12099943, upload-time = "2026-05-14T06:57:44.524Z" },
{ url = "https://files.pythonhosted.org/packages/20/ce/57472c85900e2937b921ccc1aa492e9449a1babf8b4c6a228f892f5d3b7f/sparsediffpy-0.3.0-cp314-cp314-win_amd64.whl", hash = "sha256:2c13dd751963611419273d7cf99c9f7e0c7793c7edba2941a57847cc62f3aa18", size = 132153, upload-time = "2026-05-14T06:57:46.598Z" },
]
[[package]] [[package]]
name = "sympy" name = "sympy"
version = "1.14.0" version = "1.14.0"