codextail-probabilities-die-endpoints.py
tail-probabilities-die-endpoints.py
"""Exact fair-die atoms and upper tails; Python 3.14 / NumPy 2.3 / Matplotlib 3.10.
Run from _media. Closed dots include endpoints; white dots exclude them.
All probabilities are analytic model values, rather than measurements.
"""
from pathlib import Path
import matplotlib.pyplot as plt
import numpy as np
def main():
plt.rcParams.update({"font.size": 11, "axes.spines.top": False,
"axes.spines.right": False, "figure.facecolor": "white"})
blue, orange, grey = "#236a9e", "#bd5b15", "#b5bcc3"
fig, axes = plt.subplots(1, 3, figsize=(11.4, 4.2), layout="constrained")
fig.suptitle("Fair die: an endpoint carries probability 1/6", fontsize=16)
values = np.arange(1, 7)
ax = axes[0]
for value in values:
color = orange if value >= 4 else grey
ax.vlines(value, 0, 1 / 6, color=color, linewidth=3)
ax.plot(value, 1 / 6, "o", color=color, markersize=7)
ax.axvline(4, color=orange, linestyle=":", linewidth=1.5)
ax.text(3.5, 0.245, "At threshold 4:\n3 atoms included", ha="center",
bbox={"facecolor": "white", "edgecolor": "none", "pad": 2})
ax.set(title="Probability mass", ylabel=r"$P(D=d)$", ylim=(0, 0.31),
xlim=(0.5, 6.5), xlabel="Die outcome d")
ax.set_yticks([0, 1 / 6], ["0", "1/6"])
# The two functions share all plateaus and differ only at integer thresholds.
for ax, inclusive, color in zip(axes[1:], [True, False], [orange, blue]):
edges = np.arange(0, 8)
for left, right in zip(edges[:-1], edges[1:]):
height = np.count_nonzero(values > left) / 6
ax.hlines(height, left, right, color=color, linewidth=2.2)
for value in values:
upper = np.count_nonzero(values >= value) / 6
lower = np.count_nonzero(values > value) / 6
closed, opened = (upper, lower) if inclusive else (lower, upper)
ax.plot(value, opened, "o", markerfacecolor="white",
markeredgecolor=color, markersize=7, markeredgewidth=1.5)
ax.plot(value, closed, "o", color=color, markersize=7)
ax.axvline(4, color=color, linestyle=":", linewidth=1.1)
ax.set(title=r"Inclusive tail $P(D\geq t)$" if inclusive
else r"Strict tail $P(D>t)$", xlim=(0.5, 6.5),
ylim=(-0.045, 1.075), xlabel="Threshold t", ylabel="Probability")
ax.set_yticks([0, 1 / 3, 1 / 2, 1], ["0", "1/3", "1/2", "1"])
ax.annotate("1/2" if inclusive else "1/3",
xy=(4, 1 / 2 if inclusive else 1 / 3), xytext=(4.7, 0.81),
arrowprops={"arrowstyle": "->", "color": color}, color=color)
for ax in axes:
ax.set_xticks(values)
ax.grid(axis="y", color="#e6e9ec", linewidth=0.7)
ax.set_axisbelow(True)
fig.text(0.5, -0.025, "Exact mathematical model • filled dot: value at the threshold • white dot: excluded endpoint",
ha="center", fontsize=10)
fig.savefig(Path(__file__).with_suffix(".png").name, dpi=100,
facecolor="white", transparent=False, bbox_inches="tight")
plt.close(fig)
if __name__ == "__main__":
main()