codex/bell-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()