From e80b7c358239e785ce440fd46b5623d91f76dabc Mon Sep 17 00:00:00 2001 From: Hans Aschauer Date: Sun, 26 Jul 2026 14:09:49 +0200 Subject: [PATCH] feat: add numeric and symbolic scripts --- pyproject.toml | 3 + scripts/O_seed.npz | Bin 0 -> 6130 bytes scripts/README.md | 84 ++++ scripts/cluster.py | 50 +++ scripts/compute_seed_witnesses.py | 43 ++ scripts/core.py | 61 +++ scripts/core2.py | 61 +++ scripts/diagnostic_seeded_run.py | 54 +++ scripts/diagnostic_seeded_run_1.py | 55 +++ .../dps_hierarchy/01_werner_qubit_symbolic.py | 96 +++++ .../02_werner_qutrit_symbolic.py | 103 +++++ scripts/dps_hierarchy/03_dps_level2_demo.py | 54 +++ scripts/dps_hierarchy/04_tiles_noise_scan.py | 50 +++ scripts/dps_hierarchy/05_local_filtering.py | 42 ++ .../dps_hierarchy/06_dps_level2_filtered.py | 42 ++ scripts/dps_hierarchy/07_dps_level3_single.py | 30 ++ .../dps_hierarchy/08_dps_level_k_bisection.py | 84 ++++ .../09_dps_level3_filtered_bisection.py | 86 ++++ scripts/dps_hierarchy/README.md | 96 +++++ scripts/dps_hierarchy/common.py | 167 +++++++ scripts/dps_hierarchy/dps_hierarchy.py | 110 +++++ .../dps_level3_bisection_state.json | 70 +++ .../dps_level3_filtered_bisection_state.json | 70 +++ scripts/dps_hierarchy/requirements.txt | 8 + scripts/ghz3_perturbation_symbolic.py | 209 +++++++++ scripts/ghz3_shadow_map_symbolic.py | 158 +++++++ scripts/mixture_search.py | 171 ++++++++ scripts/optimize_cluster_witness.py | 84 ++++ scripts/optimize_phi_sym.py | 66 +++ scripts/pairwise_correlation_demo.py | 65 +++ scripts/party_blocks.py | 18 + scripts/sdp_ppt_mixture.py | 225 ++++++++++ scripts/seeded_exploration.py | 69 +++ scripts/subblock.py | 25 ++ scripts/sum_bound_proof.py | 71 +++ scripts/symmetry_oracle.py | 183 ++++++++ scripts/universal_ceiling.py | 45 ++ uv.lock | 406 ++++++++++++++++++ 38 files changed, 3314 insertions(+) create mode 100644 scripts/O_seed.npz create mode 100644 scripts/cluster.py create mode 100644 scripts/compute_seed_witnesses.py create mode 100644 scripts/core.py create mode 100644 scripts/core2.py create mode 100644 scripts/diagnostic_seeded_run.py create mode 100644 scripts/diagnostic_seeded_run_1.py create mode 100644 scripts/dps_hierarchy/01_werner_qubit_symbolic.py create mode 100644 scripts/dps_hierarchy/02_werner_qutrit_symbolic.py create mode 100644 scripts/dps_hierarchy/03_dps_level2_demo.py create mode 100644 scripts/dps_hierarchy/04_tiles_noise_scan.py create mode 100644 scripts/dps_hierarchy/05_local_filtering.py create mode 100644 scripts/dps_hierarchy/06_dps_level2_filtered.py create mode 100644 scripts/dps_hierarchy/07_dps_level3_single.py create mode 100644 scripts/dps_hierarchy/08_dps_level_k_bisection.py create mode 100644 scripts/dps_hierarchy/09_dps_level3_filtered_bisection.py create mode 100644 scripts/dps_hierarchy/README.md create mode 100644 scripts/dps_hierarchy/common.py create mode 100644 scripts/dps_hierarchy/dps_hierarchy.py create mode 100644 scripts/dps_hierarchy/dps_level3_bisection_state.json create mode 100644 scripts/dps_hierarchy/dps_level3_filtered_bisection_state.json create mode 100644 scripts/dps_hierarchy/requirements.txt create mode 100644 scripts/ghz3_perturbation_symbolic.py create mode 100644 scripts/ghz3_shadow_map_symbolic.py create mode 100644 scripts/mixture_search.py create mode 100644 scripts/optimize_cluster_witness.py create mode 100644 scripts/optimize_phi_sym.py create mode 100644 scripts/pairwise_correlation_demo.py create mode 100644 scripts/party_blocks.py create mode 100644 scripts/sdp_ppt_mixture.py create mode 100644 scripts/seeded_exploration.py create mode 100644 scripts/subblock.py create mode 100644 scripts/sum_bound_proof.py create mode 100644 scripts/symmetry_oracle.py create mode 100644 scripts/universal_ceiling.py diff --git a/pyproject.toml b/pyproject.toml index 06d6a2e..b93e9b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -5,8 +5,11 @@ description = "Add your description here" readme = "README.md" requires-python = ">=3.13" dependencies = [ + "cvxpy>=1.9.2", "matplotlib>=3.10.9", + "numpy>=2.5.1", "qtensor", + "scipy>=1.18.0", "sympy>=1.14.0", ] diff --git a/scripts/O_seed.npz b/scripts/O_seed.npz new file mode 100644 index 0000000000000000000000000000000000000000..24230011ff1ac4101dd50e31f8322ed0f3a9870a GIT binary patch literal 6130 zcmWIWW@gc4fB;2?*LOBf_zwka3?dAUPI`F-m5dAm3?1wcH86UzU#M?DBqKu^L$!KJ zYH@Orx|M>uO`3(ej)Hnxeo;wLVqScHQA#RE+$}MuI29;foRL_N3gl}Tn(8PRnriAO z)GCk(xFD|iU=MZr08t zwj)l!cPE6lwOms&@xo!N=K2hR<~c-ZZ=U;Hi=g{p_VUSc%xph9c@NZn`&)1KA61>d zX3zcbw2RxHw(YsIK=R3l$cY5ao237_mp!-V0T(su_Bpz)v#Edzw8J6rhcLoIJ?Aa;T`Z{n&pA9Vhak~*BvjeK0kUH|@ zsqcT%+%a;?A4oV6AO3$9bWPH1ZLv9F$FL^mP=(FWB(;N9ZA~`dWCcuD+o9=XNr5fb z^~DqR5El<0>;v%E-_E1;H@=W0KK}5TLcZL{%|D~{Hz?J77_Gm_&pcRlj@I8;g9V4+ z$Sr>$0YZHFPoMhxa_fTWDE)7j!CHSqT=^cO|2JV({HIMt(}--p;7FE42>*ig|KR=a zCv)wM{R1}Rw0GVK{#{?iR^XI>N>Q@MVe-V%(Db(lT|G4YVT3=r zI@= 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`. diff --git a/scripts/cluster.py b/scripts/cluster.py new file mode 100644 index 0000000..ec13c08 --- /dev/null +++ b/scripts/cluster.py @@ -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 diff --git a/scripts/compute_seed_witnesses.py b/scripts/compute_seed_witnesses.py new file mode 100644 index 0000000..363ff40 --- /dev/null +++ b/scripts/compute_seed_witnesses.py @@ -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') diff --git a/scripts/core.py b/scripts/core.py new file mode 100644 index 0000000..f6f7769 --- /dev/null +++ b/scripts/core.py @@ -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 diff --git a/scripts/core2.py b/scripts/core2.py new file mode 100644 index 0000000..b9f18f8 --- /dev/null +++ b/scripts/core2.py @@ -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>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 diff --git a/scripts/diagnostic_seeded_run.py b/scripts/diagnostic_seeded_run.py new file mode 100644 index 0000000..b11947c --- /dev/null +++ b/scripts/diagnostic_seeded_run.py @@ -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.") diff --git a/scripts/diagnostic_seeded_run_1.py b/scripts/diagnostic_seeded_run_1.py new file mode 100644 index 0000000..2c1a525 --- /dev/null +++ b/scripts/diagnostic_seeded_run_1.py @@ -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.") \ No newline at end of file diff --git a/scripts/dps_hierarchy/01_werner_qubit_symbolic.py b/scripts/dps_hierarchy/01_werner_qubit_symbolic.py new file mode 100644 index 0000000..711ff43 --- /dev/null +++ b/scripts/dps_hierarchy/01_werner_qubit_symbolic.py @@ -0,0 +1,96 @@ +""" +01_werner_qubit_symbolic.py + +Exact symbolic (sympy) check: the two-qubit Werner state + + rho(p) = p |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)) diff --git a/scripts/dps_hierarchy/02_werner_qutrit_symbolic.py b/scripts/dps_hierarchy/02_werner_qutrit_symbolic.py new file mode 100644 index 0000000..db8d8f4 --- /dev/null +++ b/scripts/dps_hierarchy/02_werner_qutrit_symbolic.py @@ -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.") diff --git a/scripts/dps_hierarchy/03_dps_level2_demo.py b/scripts/dps_hierarchy/03_dps_level2_demo.py new file mode 100644 index 0000000..29da447 --- /dev/null +++ b/scripts/dps_hierarchy/03_dps_level2_demo.py @@ -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}") diff --git a/scripts/dps_hierarchy/04_tiles_noise_scan.py b/scripts/dps_hierarchy/04_tiles_noise_scan.py new file mode 100644 index 0000000..36000f2 --- /dev/null +++ b/scripts/dps_hierarchy/04_tiles_noise_scan.py @@ -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}%)") diff --git a/scripts/dps_hierarchy/05_local_filtering.py b/scripts/dps_hierarchy/05_local_filtering.py new file mode 100644 index 0000000..22f603c --- /dev/null +++ b/scripts/dps_hierarchy/05_local_filtering.py @@ -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%)") diff --git a/scripts/dps_hierarchy/06_dps_level2_filtered.py b/scripts/dps_hierarchy/06_dps_level2_filtered.py new file mode 100644 index 0000000..d326e21 --- /dev/null +++ b/scripts/dps_hierarchy/06_dps_level2_filtered.py @@ -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}%)") diff --git a/scripts/dps_hierarchy/07_dps_level3_single.py b/scripts/dps_hierarchy/07_dps_level3_single.py new file mode 100644 index 0000000..7aeb7d0 --- /dev/null +++ b/scripts/dps_hierarchy/07_dps_level3_single.py @@ -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]") diff --git a/scripts/dps_hierarchy/08_dps_level_k_bisection.py b/scripts/dps_hierarchy/08_dps_level_k_bisection.py new file mode 100644 index 0000000..fb4587a --- /dev/null +++ b/scripts/dps_hierarchy/08_dps_level_k_bisection.py @@ -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).") diff --git a/scripts/dps_hierarchy/09_dps_level3_filtered_bisection.py b/scripts/dps_hierarchy/09_dps_level3_filtered_bisection.py new file mode 100644 index 0000000..36276dd --- /dev/null +++ b/scripts/dps_hierarchy/09_dps_level3_filtered_bisection.py @@ -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).") diff --git a/scripts/dps_hierarchy/README.md b/scripts/dps_hierarchy/README.md new file mode 100644 index 0000000..4a23817 --- /dev/null +++ b/scripts/dps_hierarchy/README.md @@ -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. diff --git a/scripts/dps_hierarchy/common.py b/scripts/dps_hierarchy/common.py new file mode 100644 index 0000000..e7bb150 --- /dev/null +++ b/scripts/dps_hierarchy/common.py @@ -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 diff --git a/scripts/dps_hierarchy/dps_hierarchy.py b/scripts/dps_hierarchy/dps_hierarchy.py new file mode 100644 index 0000000..2178bad --- /dev/null +++ b/scripts/dps_hierarchy/dps_hierarchy.py @@ -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") diff --git a/scripts/dps_hierarchy/dps_level3_bisection_state.json b/scripts/dps_hierarchy/dps_level3_bisection_state.json new file mode 100644 index 0000000..ed9f75f --- /dev/null +++ b/scripts/dps_hierarchy/dps_level3_bisection_state.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/scripts/dps_hierarchy/dps_level3_filtered_bisection_state.json b/scripts/dps_hierarchy/dps_level3_filtered_bisection_state.json new file mode 100644 index 0000000..4c99a55 --- /dev/null +++ b/scripts/dps_hierarchy/dps_level3_filtered_bisection_state.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/scripts/dps_hierarchy/requirements.txt b/scripts/dps_hierarchy/requirements.txt new file mode 100644 index 0000000..90196af --- /dev/null +++ b/scripts/dps_hierarchy/requirements.txt @@ -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. diff --git a/scripts/ghz3_perturbation_symbolic.py b/scripts/ghz3_perturbation_symbolic.py new file mode 100644 index 0000000..eebde79 --- /dev/null +++ b/scripts/ghz3_perturbation_symbolic.py @@ -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() diff --git a/scripts/ghz3_shadow_map_symbolic.py b/scripts/ghz3_shadow_map_symbolic.py new file mode 100644 index 0000000..631894a --- /dev/null +++ b/scripts/ghz3_shadow_map_symbolic.py @@ -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() diff --git a/scripts/mixture_search.py b/scripts/mixture_search.py new file mode 100644 index 0000000..78e96b5 --- /dev/null +++ b/scripts/mixture_search.py @@ -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)") diff --git a/scripts/optimize_cluster_witness.py b/scripts/optimize_cluster_witness.py new file mode 100644 index 0000000..e1ecf55 --- /dev/null +++ b/scripts/optimize_cluster_witness.py @@ -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) diff --git a/scripts/optimize_phi_sym.py b/scripts/optimize_phi_sym.py new file mode 100644 index 0000000..045dc10 --- /dev/null +++ b/scripts/optimize_phi_sym.py @@ -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) diff --git a/scripts/pairwise_correlation_demo.py b/scripts/pairwise_correlation_demo.py new file mode 100644 index 0000000..0e8779b --- /dev/null +++ b/scripts/pairwise_correlation_demo.py @@ -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:') diff --git a/scripts/party_blocks.py b/scripts/party_blocks.py new file mode 100644 index 0000000..5cc4e91 --- /dev/null +++ b/scripts/party_blocks.py @@ -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() diff --git a/scripts/sdp_ppt_mixture.py b/scripts/sdp_ppt_mixture.py new file mode 100644 index 0000000..44e5eac --- /dev/null +++ b/scripts/sdp_ppt_mixture.py @@ -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") diff --git a/scripts/seeded_exploration.py b/scripts/seeded_exploration.py new file mode 100644 index 0000000..5a5b312 --- /dev/null +++ b/scripts/seeded_exploration.py @@ -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)") diff --git a/scripts/subblock.py b/scripts/subblock.py new file mode 100644 index 0000000..0a0273d --- /dev/null +++ b/scripts/subblock.py @@ -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() diff --git a/scripts/sum_bound_proof.py b/scripts/sum_bound_proof.py new file mode 100644 index 0000000..df6e80d --- /dev/null +++ b/scripts/sum_bound_proof.py @@ -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)') diff --git a/scripts/symmetry_oracle.py b/scripts/symmetry_oracle.py new file mode 100644 index 0000000..4b651db --- /dev/null +++ b/scripts/symmetry_oracle.py @@ -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") diff --git a/scripts/universal_ceiling.py b/scripts/universal_ceiling.py new file mode 100644 index 0000000..1f55322 --- /dev/null +++ b/scripts/universal_ceiling.py @@ -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)") diff --git a/uv.lock b/uv.lock index b451f41..ec2a1b7 100644 --- a/uv.lock +++ b/uv.lock @@ -7,18 +7,115 @@ name = "2026-05-07-quantum-info" version = "0.1.0" source = { virtual = "." } dependencies = [ + { name = "cvxpy" }, { name = "matplotlib" }, + { name = "numpy" }, { name = "qtensor" }, + { name = "scipy" }, { name = "sympy" }, ] [package.metadata] requires-dist = [ + { name = "cvxpy", specifier = ">=1.9.2" }, { name = "matplotlib", specifier = ">=3.10.9" }, + { name = "numpy", specifier = ">=2.5.1" }, { name = "qtensor", directory = "../qtensor" }, + { name = "scipy", specifier = ">=1.18.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]] name = "contourpy" 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" }, ] +[[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]] name = "cycler" 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" }, ] +[[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]] name = "kiwisolver" 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" }, ] +[[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]] name = "matplotlib" 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" }, ] +[[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]] name = "packaging" 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" }, ] +[[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]] name = "pyparsing" 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" }, ] +[[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]] name = "qtensor" version = "0.2.0" @@ -393,6 +701,83 @@ docs = [ { 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]] name = "six" 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" }, ] +[[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]] name = "sympy" version = "1.14.0"