codex/negative-entropy-window-duration.py

negative-entropy-window-duration.py
"""Exact negative-window probabilities; Python 3.14 and project plotting versions.
Run from _media to write the matching opaque PNG in the current directory.
"""
from math import comb
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np

p, q = 0.75, 0.25
n = np.arange(1, 102, 2)
negative = np.array([sum(comb(int(m), k)*p**k*q**(int(m)-k) for k in range((int(m)+1)//2)) for m in n])
fig, axes = plt.subplots(1, 2, figsize=(10, 4), layout="constrained", facecolor="white")
axes[0].semilogy(n, negative, color="#c54d36", lw=2, label=r"Exact $\Pr(S_n<0)$ (odd $n$)")
axes[0].semilogy(n, (2*np.sqrt(p*q))**n, color="0.4", ls="--", label=r"Upper bound $(2\sqrt{pq})^n$")
axes[0].set(xlabel=r"Window duration in steps $n$", ylabel="Probability of a negative total", title="Long negative totals become rare")
axes[0].legend(fontsize=9)
axes[0].grid(alpha=0.2)
axes[1].plot(n, p*n, color="#2676a8", lw=2, label=r"Positive increments: $np$")
axes[1].plot(n, q*n, color="#c54d36", lw=2, label=r"Negative increments: $nq$")
axes[1].set(xlabel=r"Window duration in steps $n$", ylabel="Expected number of increments", title="Negative increments still accumulate", ylim=(0, 80))
axes[1].legend(fontsize=9, loc="upper left")
axes[1].text(0.04, 0.62, r"Negative fraction stays $q=1/4$" + "\n" + r"Mean total: $n\ln(3)/2>0$", transform=axes[1].transAxes)
axes[1].grid(alpha=0.2)
fig.suptitle("Exact biased-step model, p = 3/4: negative increments versus negative windows", fontsize=13)
fig.savefig(Path(__file__).stem + ".png", dpi=120, facecolor="white", transparent=False)
plt.close(fig)