codexbell-vs-classical-correlations.py
bell-vs-classical-correlations.py
"""Compute Bell/mixture probabilities and save an opaque PNG in the cwd.
Run from _media with: python3 ../bell-vs-classical-correlations.py
Uses the repository's numpy/matplotlib environment; tested on Python 3.14.
"""
from pathlib import Path
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np
def joint_probabilities(rho, basis):
"""Rows are Alice's outcomes; columns are Bob's outcomes."""
transform = np.kron(basis, basis)
return np.diag(transform.conj().T @ rho @ transform).real.reshape(2, 2)
def reductions(rho):
tensor = rho.reshape(2, 2, 2, 2)
return np.trace(tensor, axis1=1, axis2=3), np.trace(tensor, axis1=0, axis2=2)
def entropy(rho):
spectrum = np.linalg.eigvalsh(rho)
positive = spectrum[spectrum > 1e-12]
return float(-np.sum(positive * np.log2(positive)))
def main():
phi = np.array([1, 0, 0, 1], dtype=float) / np.sqrt(2)
bell = np.outer(phi, phi)
mixture = np.diag([0.5, 0, 0, 0.5])
z_basis = np.eye(2)
x_basis = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
correlated = np.diag([0.5, 0.5])
uniform = np.full((2, 2), 0.25)
tables = [[joint_probabilities(rho, basis) for rho in (bell, mixture)]
for basis in (z_basis, x_basis)]
for actual, expected in zip(
(tables[0][0], tables[0][1], tables[1][0], tables[1][1]),
(correlated, correlated, correlated, uniform),
):
np.testing.assert_allclose(actual, expected, atol=1e-12)
np.testing.assert_allclose(actual.sum(axis=0), [0.5, 0.5], atol=1e-12)
np.testing.assert_allclose(actual.sum(axis=1), [0.5, 0.5], atol=1e-12)
for rho in (bell, mixture):
for reduced in reductions(rho):
np.testing.assert_allclose(reduced, np.eye(2) / 2, atol=1e-12)
np.testing.assert_allclose(entropy(reduced), 1, atol=1e-12)
np.testing.assert_allclose([entropy(bell), entropy(mixture)], [0, 1], atol=1e-12)
# Complete dephasing on Alice removes blocks connecting different A values.
dephased = bell.reshape(2, 2, 2, 2) * np.eye(2)[:, None, :, None]
np.testing.assert_allclose(dephased.reshape(4, 4), mixture, atol=1e-12)
plt.rcParams.update({"font.size": 11, "figure.facecolor": "white",
"axes.facecolor": "white", "savefig.facecolor": "white"})
fig, axes = plt.subplots(2, 2, figsize=(8, 7))
fig.subplots_adjust(left=0.12, right=0.94, top=0.82, bottom=0.22,
hspace=0.64, wspace=0.36)
fig.suptitle("Same local randomness, different joint correlations", fontsize=15, y=0.97)
headings = [r"Bell state $|\Phi^+\rangle$", "Classical mixture: 00 or 11"]
for column, heading in enumerate(headings):
position = axes[0, column].get_position()
fig.text((position.x0 + position.x1) / 2, 0.875, heading,
ha="center", weight="bold", fontsize=12)
for row, labels in enumerate((('0', '1'), ('+', '−'))):
for column in range(2):
ax = axes[row, column]
table = tables[row][column]
ax.imshow(table, cmap="Blues", vmin=0, vmax=0.5, interpolation="nearest")
ax.set_xticks([0, 1], labels)
ax.set_yticks([0, 1], labels)
ax.set_xlabel("Bob's outcome", labelpad=3)
ax.set_ylabel("Alice's outcome", labelpad=3)
ax.set_title("Both measure " + ("Z" if row == 0 else "X"), pad=8)
ax.tick_params(length=0)
for i in range(2):
for j in range(2):
ax.text(j, i, f"{table[i, j]:.2f}", ha="center", va="center",
fontsize=17, color="white" if table[i, j] > 0.3 else "#12354a")
ax.set_xticks([0.5], minor=True)
ax.set_yticks([0.5], minor=True)
ax.grid(which="minor", color="white", linewidth=2)
ax.tick_params(which="minor", length=0)
for spine in ax.spines.values():
spine.set_visible(False)
fig.text(0.5, 0.057, r"Every panel: each local outcome has probability $1/2$."
"\n" + r"Both preparations: $\rho_A=\rho_B=I_2/2$.",
ha="center", fontsize=11, linespacing=1.6)
output = Path(Path(__file__).stem + ".png")
fig.savefig(output, dpi=120, facecolor="white", transparent=False)
plt.close(fig)
print(f"Saved {output}: probabilities, marginals, entropies and dephasing verified.")
if __name__ == "__main__":
main()