codex/borel-cantelli-independent-and-shared-uniform-traces.py

borel-cantelli-independent-and-shared-uniform-traces.py
"""Generate finite fresh-input and shared-input event traces in the current directory.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media with MPLBACKEND=Agg and a writable MPLCONFIGDIR.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    n = np.arange(1, 121)
    probabilities = 1 / (n + 1)
    # Two reproducible, independent streams; neither searches for particular traces.
    shared_rng = np.random.default_rng(18)
    fresh_rng = np.random.default_rng(19)
    shared_u = shared_rng.random(6)
    independent = fresh_rng.random((6, n.size)) <= probabilities
    shared = shared_u[:, None] <= probabilities
    assert np.all(shared[:, 1:] <= shared[:, :-1])
    assert np.array_equal(shared.sum(axis=1), np.minimum(120, np.maximum(0, np.floor(1 / shared_u).astype(int) - 1)))

    plt.rcParams.update({"font.size": 11, "axes.titlesize": 12, "figure.facecolor": "white"})
    fig, axes = plt.subplots(2, 1, figsize=(10, 6.1), layout="constrained", sharex=True)
    for ax, hits, color, title in zip(
        axes, [independent, shared], ["#2463a0", "#b55318"],
        [r"Fresh input at every index: $A_n=\{U_n\leq1/(n+1)\}$",
         r"One shared input per row: $A_n=\{U\leq1/(n+1)\}$"],
    ):
        for row, trace in enumerate(hits):
            y = 6 - row
            ax.hlines(y, 1, 120, color="#dedede", lw=1)
            ax.scatter(n[trace], np.full(trace.sum(), y), color=color, s=30, zorder=3)
        ax.set_title(title, loc="left")
        ax.set_ylim(0.4, 6.6)
        ax.set_yticks(np.arange(1, 7))
        ax.set_ylabel("Simulated row")
        ax.set_xscale("log")
        ax.spines[["top", "right"]].set_visible(False)
        ax.grid(axis="x", alpha=0.18)
    axes[1].set_yticklabels([f"{6-i}  (U={shared_u[5-i]:.3f})" for i in range(6)])
    axes[0].set_yticklabels([str(6-i) for i in range(6)])
    axes[1].set_xlim(0.9, 140)
    axes[1].set_xticks([1, 2, 5, 10, 20, 50, 120], labels=["1", "2", "5", "10", "20", "50", "120"])
    axes[1].set_xlabel("Event index n (logarithmic scale); dot = occurrence")
    fig.suptitle("Same probabilities, different dependence", fontsize=15)
    fig.supxlabel("Only 120 indices are shown: finite traces do not prove infinite-time claims.", fontsize=10)
    fig.savefig(Path(__file__).stem + ".png", dpi=110, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()