codex/harmonic-hitting-probability-profiles.py

harmonic-hitting-probability-profiles.py
"""Draw exact state values; run from _media to write the matching PNG.

Tested with Python 3.14.4, Matplotlib 3.10.7 and NumPy 2.3.5.
"""

import os
import tempfile
from pathlib import Path

os.environ.setdefault("MPLCONFIGDIR", str(Path(tempfile.gettempdir()) / "codex-wiki-matplotlib"))

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
    states = np.arange(5)
    fair = states / 4
    r = 2 / 3
    biased = (1 - r ** states) / (1 - r ** 4)
    duration = states * (4 - states)
    fig, (left, right) = plt.subplots(1, 2, figsize=(9.6, 4.6), dpi=120, facecolor="white")
    blue, orange, green = "#1769aa", "#b54b14", "#247044"
    for ax in (left, right):
        ax.set_facecolor("white")
        ax.set_xlim(-0.15, 4.15)
        ax.set_xticks(states)
        ax.set_xlabel(r"Starting state $i$")
        ax.grid(alpha=0.25)
        ax.spines[["top", "right"]].set_visible(False)
    left.plot(states, fair, "o--", color=blue, linewidth=1.8, markersize=6,
              label=r"Fair: $p=0.5$, $h_i=i/4$")
    left.plot(states, biased, "s-", color=orange, linewidth=1.8, markersize=6,
              label=r"Upward bias: $p=0.6$")
    left.set_title("Which endpoint?", fontsize=14, pad=10)
    left.set_ylabel(r"Success probability $h_i=\Pr_i(T_4<T_0)$")
    left.set_ylim(-0.04, 1.08)
    left.set_yticks([0, 0.25, 0.5, 0.75, 1])
    left.annotate(r"$h_2=9/13\approx0.6923$", (2, biased[2]), xytext=(0.4, 0.92),
                  color=orange, arrowprops={"arrowstyle": "->", "color": orange}, fontsize=11)
    left.legend(loc="lower right", fontsize=10, framealpha=1)
    right.plot(states, duration, "o-", color=green, linewidth=1.8, markersize=7)
    right.set_title("How many steps? (fair walk)", fontsize=14, pad=10)
    right.set_ylabel(r"Expected steps $t_i=\mathbb{E}_i T$")
    right.set_ylim(-0.15, 4.75)
    right.set_yticks([0, 1, 2, 3, 4])
    for i, value in enumerate(duration):
        right.annotate(str(value), (i, value), xytext=(0, 9), textcoords="offset points",
                       ha="center", color=green, fontsize=12)
    right.text(0.5, 0.17, r"$t_i=i(4-i)$", transform=right.transAxes,
               ha="center", color=green, fontsize=14)
    fig.suptitle("Absorption: probability and duration answer different questions", fontsize=15, y=0.97)
    fig.text(0.5, 0.035, "Dots are the five state values; connecting lines are visual guides. Endpoints are absorbing.",
             ha="center", fontsize=10)
    fig.subplots_adjust(left=0.075, right=0.97, top=0.81, bottom=0.17, wspace=0.36)
    fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()