Research-Stack/4-Infrastructure/shim/rrc_bosonic_tensor_network.py
allaun 69988453bc chore(infra): stage working tree modifications
Build: 8604 jobs, 0 errors (lake build)
2026-06-22 01:23:17 -05:00

818 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
rrc_bosonic_tensor_network.py — Beyond-Perceval photonic centrality via
bosonic tensor network contraction (quimb + opt_einsum).
Bypasses the Perceval FockState cap (256 modes) by representing the
K-photon state as a K-rank tensor with each index of dimension N (modes),
not as a BasicState with 256 entries.
Core identity:
A K-photon evolution under unitary U is U^⊗K acting on the initial
K-photon Fock state. For K indistinguishable photons the output
amplitudes are permanents of K×K submatrices of U.
We compute the full output tensor T_out[i_1..i_K] = Per(U_sub) / √(K!)
via tensor contraction + symmetrization, then marginalise to get
mode occupation probabilities (photonic eigenvector centrality).
Theory of operation:
K=1 — T[i] = U[i,0] O(N)
K=2 — T[i,j] = (U[i,0]U[j,1] + U[i,1]U[j,0]) / √2 O(N²)
K=3 — T[i,j,k] = sym(∏U) / √6 O(N³)
K≥4 — distinguishable approximation or permanent sampling
This shim answers: does the 3-photon entropy collapse at N=256 we observed
in Perceval hold at larger N? Is it physical or an emulator artifact?
"""
from __future__ import annotations
import argparse
import hashlib
import itertools
import json
import math
import sys
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional
import numpy as np
# ── quimb / opt_einsum — bosonic tensor network ──────────────
try:
import quimb.tensor as qtn
import opt_einsum
_HAS_QUIMB = True
except ImportError:
_HAS_QUIMB = False
SHIM = Path(__file__).resolve().parent
RECEIPT_PATH = SHIM / "rrc_bosonic_tensor_receipt.json"
# =========================================================================
# I. Unitary building (same as Perceval: U = exp(-iAt))
# =========================================================================
def adjacency_to_unitary(
adj: np.ndarray,
coupling_phase: float = math.pi / 4,
) -> np.ndarray:
"""Return N×N unitary U = exp(-i*A*θ) from adjacency matrix A."""
H = adj.astype(np.complex128)
eigenvalues, eigenvectors = np.linalg.eigh(H)
U = eigenvectors @ np.diag(np.exp(-1j * eigenvalues * coupling_phase)) @ eigenvectors.conj().T
return U
# =========================================================================
# II. Random graph builder
# =========================================================================
def make_random_graph(N: int, p: float = 0.4, seed: int = 42) -> np.ndarray:
"""Synthetic ErdősRényi adjacency."""
rng = np.random.RandomState(seed)
adj = (rng.random((N, N)) < p).astype(np.float64)
adj = np.triu(adj, 1) + np.triu(adj, 1).T
return adj
def make_representation_graph() -> np.ndarray:
"""22-representation graph from burgers_chaos_game.py."""
SHIM_PATH = Path(__file__).resolve().parent
sys.path.insert(0, str(SHIM_PATH))
from burgers_chaos_game import build_representation_graph
labels, adj = build_representation_graph()
return adj, labels
# =========================================================================
# III. Bosonic tensor network centrality
# =========================================================================
def perf_pmf(K: int, U: np.ndarray, row: int) -> float:
"""Compute probability contribution for row index in K-photon permanent.
For marginal computation, not used directly — we use symmetrized tensor.
"""
# placeholder for K=3+ permanent formula
pass
def _symmetrize_2(T: np.ndarray) -> np.ndarray:
"""Symmetrize a 2-index tensor for indistinguishable bosons: T_sym[i,j]."""
return (T + T.swapaxes(0, 1)) / math.sqrt(2)
def _symmetrize_3(T: np.ndarray) -> np.ndarray:
"""Symmetrize a 3-index tensor for indistinguishable bosons."""
# All 6 permutations
s = (T +
T.swapaxes(1, 2) +
T.swapaxes(0, 1) +
T.swapaxes(0, 1).swapaxes(1, 2) +
T.swapaxes(0, 2) +
T.swapaxes(0, 2).swapaxes(1, 2))
return s / math.sqrt(6)
def bosonic_centrality(
U: np.ndarray,
n_photons: int = 1,
shots: int = 10000,
timeout_s: float = 120.0,
method: str = "auto",
) -> dict:
"""Compute photonic eigenvector centrality via bosonic tensor network.
Args:
U: N×N unitary matrix (from adjacency_to_unitary).
n_photons: Number of indistinguishable photons.
shots: Number of samples (used for K≥3 Monte Carlo if exact too large).
timeout_s: Max wall-clock seconds.
method: "auto" (choose based on K,N), "tensor" (exact TN),
"distinguishable" (product approx, fast).
Returns:
Dict with centralities, entropy, diagnostics.
"""
N = U.shape[0]
start = time.time()
if n_photons == 1:
return _centrality_1(U, start, timeout_s)
elif n_photons == 2:
return _centrality_2(U, start, timeout_s)
elif n_photons == 3:
return _centrality_3(U, shots, start, timeout_s)
else:
if method == "auto" and n_photons >= 5:
return _centrality_k(U, n_photons, "distinguishable", shots, start, timeout_s)
return _centrality_k(U, n_photons, method, shots, start, timeout_s)
def _check_timeout(start: float, timeout_s: float, phase: str) -> None:
if time.time() - start > timeout_s:
raise TimeoutError(f"{phase}")
def _mode_entropy(probs: np.ndarray, total: float) -> float:
"""Shannon entropy of a probability distribution."""
eps = 1e-15
p = np.asarray(probs, dtype=np.float64)
p = p / max(total, eps)
p = np.clip(p, eps, 1.0)
return float(-np.sum(p * np.log2(p)))
# ── K = 1 ──────────────────────────────────────────────────────
def _centrality_1(U: np.ndarray, start: float, timeout_s: float) -> dict:
"""1-photon: P(m) = |U[m, 0]|²."""
N = U.shape[0]
t0 = time.time() - start
# First column of U squared magnitude
col0 = U[:, 0]
mode_probs = np.abs(col0) ** 2
total_prob = float(np.sum(mode_probs))
centrality = mode_probs / max(total_prob, 1e-15)
entropy = _mode_entropy(mode_probs, total_prob)
nonzero = int(np.sum(mode_probs > 1e-15))
has_nan = bool(np.any(np.isnan(centrality)) or np.any(np.isinf(centrality)))
_check_timeout(start, timeout_s, "1-photon")
elapsed = time.time() - start
return dict(
n=N, n_photons=1,
centrality=np.round(centrality, 6).tolist(),
mode_occupations=np.round(mode_probs, 6).tolist(),
output_entropy=round(entropy, 6),
nonzero_output_states=nonzero,
total_samples=1,
has_nan=has_nan,
method="tensor",
contract_ms=round(t0 * 1000, 1),
total_ms=round(elapsed * 1000, 1),
edges_successful=True,
)
# ── K = 2 ──────────────────────────────────────────────────────
def _centrality_2(U: np.ndarray, start: float, timeout_s: float) -> dict:
"""2-photon indistinguishable: contract + symmetrize.
T[i,j] = (U[i,0]U[j,1] + U[i,1]U[j,0]) / √2
P(m) = |T[m,m]|² + Σ_{j≠m} |T[m,j]|²
"""
N = U.shape[0]
t0 = time.time() - start
col0 = U[:, 0]
col1 = U[:, 1]
# Build distinguishable product
T_dist = np.outer(col0, col1) # shape (N, N)
# Symmetrize for indistinguishable bosons
T = _symmetrize_2(T_dist)
_check_timeout(start, timeout_s, "2-photon build")
# Mode occupation probabilities
mode_probs = np.zeros(N, dtype=np.float64)
for m in range(N):
# P(m) = sum over j of |T[m,j]|² with symmetry factor
# For m≠j: |T[m,j]|² directly
# For m=j: |T[m,m]|² (already correct for both-in-same-mode)
mode_probs[m] = float(np.sum(np.abs(T[m, :]) ** 2))
total_prob = float(np.sum(mode_probs))
centrality = mode_probs / max(total_prob, 1e-15)
# Distribution entropy — compute from full output distribution
output_dist = np.abs(T) ** 2
# Preserve 2-fold symmetry: each off-diagonal pair (m,j) and (j,m) halves
# The Fock-space probability for |1_m,1_j⟩ is output_dist[m,j] + output_dist[j,m]
fock_probs = []
for i in range(N):
for j in range(i, N):
if i == j:
p = float(output_dist[i, i])
else:
p = float(output_dist[i, j] + output_dist[j, i])
if p > 1e-15:
fock_probs.append(p)
fock_probs = np.array(fock_probs)
fock_total = float(np.sum(fock_probs))
entropy = _mode_entropy(fock_probs, fock_total)
nonzero = int(np.sum(fock_probs > 1e-15))
has_nan = bool(np.any(np.isnan(centrality)) or np.any(np.isinf(centrality)))
_check_timeout(start, timeout_s, "2-photon post")
elapsed = time.time() - start
return dict(
n=N, n_photons=2,
centrality=np.round(centrality, 6).tolist(),
mode_occupations=np.round(mode_probs, 6).tolist(),
output_entropy=round(entropy, 6),
nonzero_output_states=nonzero,
hilbert_dim=N * (N + 1) // 2,
total_samples=1,
has_nan=has_nan,
method="tensor_sym2",
contract_ms=round(t0 * 1000, 1),
total_ms=round(elapsed * 1000, 1),
edges_successful=True,
)
# ── K = 3 ──────────────────────────────────────────────────────
def _centrality_3(
U: np.ndarray,
shots: int,
start: float,
timeout_s: float,
) -> dict:
"""3-photon indistinguishable: permanent-based tensor.
T[i,j,k] = sym(U[i,0]U[j,1]U[k,2]) / √6
Then marginalise to get mode probabilities.
For large N (N > 1000) falls back to Monte Carlo sampling.
"""
N = U.shape[0]
N_total = N ** 3
hilbert_dim = math.comb(N + 2, 3)
t0 = time.time() - start
# Choose strategy based on size
if N_total > 50_000_000: # too big for full tensor
return _centrality_3_mc(U, shots, start, timeout_s)
# Full tensor contraction — O(N³) memory + compute
col0 = U[:, 0]
col1 = U[:, 1]
col2 = U[:, 2]
# Build as outer product, then symmetrize
T_dist = np.einsum('i,j,k->ijk', col0, col1, col2)
_check_timeout(start, timeout_s, "3-photon build")
T = _symmetrize_3(T_dist)
_check_timeout(start, timeout_s, "3-photon sym")
# Marginal: P(m) = Σ_{j,k} |T[m,j,k]|²
output_density = np.abs(T) ** 2
mode_probs = np.sum(output_density, axis=(1, 2)) # sum over j,k
total_prob = float(np.sum(mode_probs))
centrality = mode_probs / max(total_prob, 1e-15)
# Fock-space entropy
fock_probs = []
for i in range(N):
for j in range(i, N):
for k in range(j, N):
# Symmetrize the Fock probability from tensor elements
if i == j == k:
p = float(output_density[i, i, i])
elif i == j:
p = float(output_density[i, i, k] + output_density[i, k, i] + output_density[k, i, i])
elif j == k:
p = float(output_density[i, j, j] + output_density[j, i, j] + output_density[j, j, i])
else:
p = float(output_density[i, j, k] + output_density[i, k, j] +
output_density[j, i, k] + output_density[j, k, i] +
output_density[k, i, j] + output_density[k, j, i])
if p > 1e-15:
fock_probs.append(p)
fock_probs = np.array(fock_probs)
fock_total = float(np.sum(fock_probs))
entropy = _mode_entropy(fock_probs, fock_total)
nonzero = int(np.sum(fock_probs > 1e-15))
has_nan = bool(np.any(np.isnan(centrality)) or np.any(np.isinf(centrality)))
_check_timeout(start, timeout_s, "3-photon post")
elapsed = time.time() - start
return dict(
n=N, n_photons=3,
centrality=np.round(centrality, 6).tolist(),
mode_occupations=np.round(mode_probs, 6).tolist(),
output_entropy=round(entropy, 6),
nonzero_output_states=nonzero,
hilbert_dim=hilbert_dim,
total_samples=1,
has_nan=has_nan,
method="tensor_sym3",
contract_ms=round(t0 * 1000, 1),
total_ms=round(elapsed * 1000, 1),
edges_successful=True,
)
def _centrality_3_mc(
U: np.ndarray,
shots: int,
start: float,
timeout_s: float,
) -> dict:
"""3-photon Monte Carlo via permanent sampling for large N."""
N = U.shape[0]
col0 = U[:, 0]
col1 = U[:, 1]
col2 = U[:, 2]
rng = np.random.RandomState(42)
mode_counts = np.zeros(N, dtype=np.float64)
used_shots = 0
for s in range(shots):
if time.time() - start > timeout_s:
break
# Importance sampling: propose from distinguishable distribution
p_dist = np.abs(col0) ** 2
# Accept/reject based on permanent ratio
i = rng.choice(N, p=p_dist)
j = rng.choice(N, p=np.abs(col1) ** 2)
k = rng.choice(N, p=np.abs(col2) ** 2)
# Full 3×3 permanent of rows [i,j,k], cols [0,1,2]
# For 3×3 matrix: Per = a₁₁(a₂₂a₃₃ + a₂₃a₃₂) + a₁₂(a₂₁a₃₃ + a₂₃a₃₁) + a₁₃(a₂₁a₃₂ + a₂₂a₃₁)
M = np.array([
[U[i, 0], U[i, 1], U[i, 2]],
[U[j, 0], U[j, 1], U[j, 2]],
[U[k, 0], U[k, 1], U[k, 2]],
])
perm = (M[0, 0] * (M[1, 1] * M[2, 2] + M[1, 2] * M[2, 1]) +
M[0, 1] * (M[1, 0] * M[2, 2] + M[1, 2] * M[2, 0]) +
M[0, 2] * (M[1, 0] * M[2, 1] + M[1, 1] * M[2, 0]))
# Symmetry factor for indistinguishability
weight = np.abs(perm) ** 2 / 6
if weight > 0:
mode_counts[i] += weight
mode_counts[j] += weight
mode_counts[k] += weight
used_shots += 1
total_prob = float(np.sum(mode_counts))
centrality = mode_counts / max(total_prob, 1e-15)
entropy = _mode_entropy(mode_counts, total_prob)
nonzero = int(np.sum(mode_counts > 1e-15))
elapsed = time.time() - start
return dict(
n=N, n_photons=3,
centrality=np.round(centrality, 6).tolist(),
mode_occupations=np.round(mode_counts / max(total_prob, 1e-15), 6).tolist(),
output_entropy=round(entropy, 6),
nonzero_output_states=nonzero,
hilbert_dim=math.comb(N + 2, 3),
total_samples=used_shots,
has_nan=False,
method="mc_permanent3",
total_ms=round(elapsed * 1000, 1),
edges_successful=True,
)
def _permanent_ryser(M: np.ndarray) -> complex:
"""Compute permanent of a small square complex matrix via Ryser's formula."""
n = M.shape[0]
if n == 0:
return 1.0 + 0.0j
total = 0.0 + 0.0j
for k in range(n + 1):
for cols in itertools.combinations(range(n), k):
row_sums = np.zeros(n, dtype=np.complex128)
for j in cols:
row_sums += M[:, j]
total += (-1) ** k * np.prod(row_sums)
return (-1) ** n * total
def _centrality_k_mc(
U: np.ndarray,
n_photons: int,
shots: int,
start: float,
timeout_s: float,
) -> dict:
"""K-photon bosonic Monte Carlo via Ryser permanent sampling.
Samples output mode tuples, evaluates the KxK permanent of the induced
scattering submatrix, and accumulates mode occupations weighted by
|Per(M)|^2 / K!. Preserves true bosonic statistics for any K.
"""
N = U.shape[0]
K = n_photons
rng = np.random.RandomState(42)
# Precompute column probability distributions for importance sampling
col_probs = [np.abs(U[:, k]) ** 2 for k in range(K)]
mode_counts = np.zeros(N, dtype=np.float64)
used_shots = 0
factor = math.factorial(K)
for _ in range(shots):
if time.time() - start > timeout_s:
break
# Sample one output mode per input photon
rows = np.array([rng.choice(N, p=col_probs[k]) for k in range(K)], dtype=np.int64)
# Build KxK submatrix: selected output rows vs input columns 0..K-1
M = U[rows, :K]
perm = _permanent_ryser(M)
weight = np.abs(perm) ** 2 / factor
if weight > 0:
for r in rows:
mode_counts[r] += weight
used_shots += 1
total_prob = float(np.sum(mode_counts))
centrality = mode_counts / max(total_prob, 1e-15)
entropy = _mode_entropy(mode_counts, total_prob)
nonzero = int(np.sum(mode_counts > 1e-15))
elapsed = time.time() - start
return dict(
n=N, n_photons=K,
centrality=np.round(centrality, 6).tolist(),
mode_occupations=np.round(centrality, 6).tolist(),
output_entropy=round(entropy, 6),
nonzero_output_states=nonzero,
hilbert_dim=math.comb(N + K - 1, K),
total_samples=used_shots,
has_nan=False,
method=f"mc_permanent{K}",
total_ms=round(elapsed * 1000, 1),
edges_successful=True,
)
# ── K ≥ 4 (distinguishable approximation or bosonic MC) ─────────
def _centrality_k(
U: np.ndarray,
n_photons: int,
method: str,
shots: int,
start: float,
timeout_s: float,
) -> dict:
"""K-photon centrality: bosonic MC if requested, else distinguishable approximation."""
if method == "bosonic-mc":
return _centrality_k_mc(U, n_photons, shots, start, timeout_s)
# Distinguishable-photon fallback (fast, loses bosonic interference)
N = U.shape[0]
t0 = time.time() - start
rng = np.random.RandomState(42)
mode_probs = np.ones(N, dtype=np.float64)
for k in range(min(n_photons, N)):
col = U[:, k]
mode_probs *= (1.0 - np.abs(col) ** 2)
mode_probs = 1.0 - mode_probs
total_prob = float(np.sum(mode_probs))
centrality = mode_probs / max(total_prob, 1e-15)
entropy = _mode_entropy(mode_probs, total_prob)
nonzero = int(np.sum(mode_probs > 1e-15))
has_nan = bool(np.any(np.isnan(centrality)) or np.any(np.isinf(centrality)))
_check_timeout(start, timeout_s, f"{n_photons}-photon")
elapsed = time.time() - start
return dict(
n=N, n_photons=n_photons,
centrality=np.round(centrality, 6).tolist(),
mode_occupations=np.round(mode_probs, 6).tolist(),
output_entropy=round(entropy, 6),
nonzero_output_states=nonzero,
hilbert_dim=math.comb(N + n_photons - 1, n_photons),
total_samples=1,
has_nan=has_nan,
method=f"distinguishable_k{n_photons}",
contract_ms=round(t0 * 1000, 1),
total_ms=round(elapsed * 1000, 1),
edges_successful=True,
)
# =========================================================================
# IV. Stress-test driver
# =========================================================================
def stress_test(
sizes: list[int],
n_photons_list: list[int],
shots: int = 10000,
timeout_s: float = 120.0,
use_real_graph: bool = True,
coupling_phase: float = math.pi / 4,
bosonic_mc: bool = False,
) -> list[dict]:
"""Iterate over sizes, record when the bosonic TN breaks."""
results: list[dict] = []
real_adj, real_labels = None, None
if use_real_graph:
real_adj, real_labels = make_representation_graph()
for N in sizes:
for n_photons in n_photons_list:
if n_photons > N:
continue
# Build adjacency
if use_real_graph and real_adj is not None and N <= real_adj.shape[0]:
adj = real_adj[:N, :N]
else:
adj = make_random_graph(N)
edge_count = int(np.sum(adj > 0) // 2)
print(f" N={N:>5d} p={n_photons:>2d} edges={edge_count:>6d} dim≈{math.comb(N+n_photons-1, n_photons):>12,}", end="")
t0 = time.time()
try:
U = adjacency_to_unitary(adj, coupling_phase)
result = bosonic_centrality(
U, n_photons=n_photons, shots=shots,
timeout_s=timeout_s,
)
elapsed = time.time() - t0
result["size_label"] = f"N={N}_p={n_photons}"
result["n_modes"] = N
result["coupling_phase"] = coupling_phase
result["shots"] = shots
result["elapsed_s"] = round(elapsed, 2)
result["edge_count"] = edge_count
error = result.get("error")
if error:
status = "FAIL"
ent_str = error[:40]
else:
entropy = result.get("output_entropy", 0)
ent_str = f"H={entropy:.3f}"
status = "OK"
print(f" {status:>4s} {ent_str:>12s} {elapsed:>6.1f}s")
results.append(result)
if elapsed > timeout_s:
print(f" → TIMEOUT at N={N}, photons={n_photons}")
break
except TimeoutError:
print(f" FAIL timeout {time.time()-t0:>6.1f}s")
results.append({
"error": f"timeout after {time.time()-t0:.1f}s",
"n": N, "n_photons": n_photons, "size_label": f"N={N}_p={n_photons}",
"n_modes": N, "coupling_phase": coupling_phase, "shots": shots,
"elapsed_s": round(time.time() - t0, 2), "edge_count": edge_count,
})
if time.time() - t0 > timeout_s:
break
except MemoryError:
print(f" FAIL OOM {time.time()-t0:>6.1f}s")
results.append({
"error": "MemoryError (OOM)",
"n": N, "n_photons": n_photons, "size_label": f"N={N}_p={n_photons}",
"n_modes": N, "coupling_phase": coupling_phase, "shots": shots,
"elapsed_s": round(time.time() - t0, 2), "edge_count": edge_count,
})
break
except Exception as exc:
msg = str(exc)
print(f" FAIL {msg[:40]} {time.time()-t0:>6.1f}s")
results.append({
"error": msg[:200],
"n": N, "n_photons": n_photons, "size_label": f"N={N}_p={n_photons}",
"n_modes": N, "coupling_phase": coupling_phase, "shots": shots,
"elapsed_s": round(time.time() - t0, 2), "edge_count": edge_count,
})
break
else:
continue
break
return results
# =========================================================================
# V. Main
# =========================================================================
def main() -> int:
if not _HAS_QUIMB:
print("quimb not installed. Install with: pip install quimb")
return 1
parser = argparse.ArgumentParser(
description="RRC Bosonic Tensor Network — Beyond Perceval SLOS"
)
parser.add_argument("--sizes", type=int, nargs="*", default=[
2, 4, 6, 8, 10, 12, 14, 16, 18, 20,
30, 50, 100, 200, 300, 500, 1000, 2000,
], help="Number of modes to test")
parser.add_argument("--photons", type=int, nargs="*", default=[1],
help="Photon counts to test")
parser.add_argument("--shots", type=int, default=10000,
help="Samples (for MC fallback)")
parser.add_argument("--timeout", type=float, default=120.0,
help="Timeout per run (seconds)")
parser.add_argument("--synthetic", action="store_true",
help="Use synthetic random graphs")
parser.add_argument("--phase", type=float, default=math.pi / 4,
help="Coupling phase")
parser.add_argument("--sweep-photons", action="store_true",
help="Sweep 2,3,4 photons")
parser.add_argument("--perceval-compare", action="store_true",
help="Compare K=3 entropy with Perceval at small N")
parser.add_argument("--bosonic-mc", action="store_true",
help="Use bosonic permanent MC for p>=4 (default is distinguishable approx)")
args = parser.parse_args()
n_photons_list = list(args.photons)
if args.sweep_photons:
n_photons_list = sorted(set(n_photons_list + [1, 2, 3, 4]))
if args.perceval_compare:
n_photons_list = sorted(set(n_photons_list + [3]))
print("=" * 70)
print("RRC Bosonic Tensor Network — Beyond Perceval Cap")
print("=" * 70)
print(f" Sizes: {args.sizes[0]}{args.sizes[-1]} modes")
print(f" Photons: {n_photons_list}")
print(f" Shots: {args.shots}")
print(f" Timeout: {args.timeout}s")
print(f" Phase: {args.phase:.4f}")
print(f" Graph: {'synthetic' if args.synthetic else 'representation'}")
print()
print("Hilbert dimensions:")
for np_ in n_photons_list:
for N in [args.sizes[0], 10, 20, 50, 100, 200, 500, 1000]:
if N <= args.sizes[-1]:
print(f" N={N:>5d}, {np_} photon(s): dim≈{math.comb(N+np_-1, np_):>15,}")
print()
results = stress_test(
sizes=args.sizes,
n_photons_list=n_photons_list,
shots=args.shots,
timeout_s=args.timeout,
use_real_graph=not args.synthetic,
coupling_phase=args.phase,
bosonic_mc=args.bosonic_mc,
)
# Summary
failures = [r for r in results if "error" in r]
successes = [r for r in results if "error" not in r]
max_ok = max([r["n"] for r in successes], default=0)
max_ok_photons = max([r["n_photons"] for r in successes], default=0)
first_fail = failures[0] if failures else None
print()
print("=" * 70)
print("RESULT SUMMARY")
print("=" * 70)
print(f" Total runs: {len(results)}")
print(f" Successful: {len(successes)}")
print(f" Failed: {len(failures)}")
if successes:
best = max(successes, key=lambda r: (r["n"], r["n_photons"]))
print(f"\n Largest successful run:")
print(f" N={best['n']} modes, {best['n_photons']} photon(s)")
print(f" H={best.get('output_entropy', 'N/A')}")
print(f" Method: {best.get('method', 'N/A')}")
print(f" Total: {best.get('total_ms', 0):.0f} ms")
if first_fail:
print(f"\n First failure:")
print(f" N={first_fail['n']} modes, {first_fail['n_photons']} photon(s)")
print(f" Error: {first_fail['error'][:120]}")
fail_dim = math.comb(first_fail["n"] + first_fail["n_photons"] - 1, first_fail["n_photons"]) if first_fail["n"] >= first_fail["n_photons"] else 0
print(f" Hilbert dim ≈ {fail_dim:,}")
max_ok_dim = math.comb(max_ok + max_ok_photons - 1, max_ok_photons) if successes and max_ok >= max_ok_photons else 0
print(f"\n Max SUCCESS: N={max_ok}, p={max_ok_photons}, dim≈{max_ok_dim:,}")
# Receipt
receipt = dict(
schema="rrc_bosonic_tensor_network_v1",
claim_boundary="beyond-perceval-bosonic-tensor-network-entropy-mapping;no-decision-logic",
parameters=dict(
sizes=args.sizes,
n_photons_list=sorted(n_photons_list),
shots=args.shots,
timeout_s=args.timeout,
phase=args.phase,
synthetic_graph=args.synthetic,
),
hilbert_dims={
f"N={N}_p={np}": math.comb(N+np-1, np)
for np in n_photons_list
for N in [args.sizes[0], 10, 20, 50, 100, 200, 500, 1000]
if N <= args.sizes[-1] and N >= np
},
results=results,
summary=dict(
total_runs=len(results),
successful=len(successes),
failed=len(failures),
max_successful_n=max_ok,
max_successful_photons=max_ok_photons,
first_failure=dict(
n=first_fail["n"],
n_photons=first_fail["n_photons"],
error=first_fail["error"],
) if first_fail else None,
),
)
canonical = json.dumps(receipt, sort_keys=True, separators=(",", ":"), default=str)
receipt["receipt_sha256"] = hashlib.sha256(canonical.encode()).hexdigest()
receipt["computed_at"] = datetime.now(timezone.utc).isoformat()
RECEIPT_PATH.write_text(json.dumps(receipt, indent=2, default=str))
print(f"\nFull receipt: {RECEIPT_PATH}")
print(f"SHA256: {receipt['receipt_sha256']}")
return 0 if not failures else 1
if __name__ == "__main__":
sys.exit(main())