codex/information-projection-probability-simplex.py

information-projection-probability-simplex.py
"""Original finite-alphabet mean-constrained information projection diagram."""

from math import sqrt
from pathlib import Path

import matplotlib

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


def main():
    height = sqrt(3) / 2
    triples = np.array([(a / 70, b / 70, (70 - a - b) / 70)
        for a in range(71) for b in range(71 - a)])
    x = triples[:, 1] / 2 + triples[:, 2]
    y = height * triples[:, 1]
    with np.errstate(divide="ignore", invalid="ignore"):
        d = np.sum(np.where(triples > 0, triples * np.log(3 * triples), 0), axis=1)
    t = (1 + sqrt(13)) / 2
    q = np.array([1, t, t * t]) / (1 + t + t * t)
    assert abs(q[2] - q[0] - 0.5) < 1e-12
    fig, ax = plt.subplots(figsize=(9, 5.5), dpi=120, facecolor="white")
    contours = ax.tricontourf(x, y, d, levels=np.linspace(0, np.log(3), 18), cmap="Blues")
    ax.plot([0, 0.5, 1, 0], [0, height, 0, 0], color="#244b66", linewidth=1.6)
    ax.plot([0.75, 0.75], [0, height / 2], color="#287846", linewidth=2.4,
        label="Mean constraint q(1) − q(−1) = 1/2")
    ax.annotate("Mean = 1/2", xy=(0.75, height / 2), xytext=(0.87, 0.58),
        fontsize=11, arrowprops={"arrowstyle": "->", "color": "#287846"})
    ax.plot(0.5, height / 3, "o", color="#843977", markersize=8)
    ax.annotate("Reference p\n(1/3, 1/3, 1/3)", xy=(0.5, height / 3),
        xytext=(0.13, 0.40), fontsize=11,
        arrowprops={"arrowstyle": "->", "color": "#843977"})
    qx, qy = q[1] / 2 + q[2], height * q[1]
    ax.plot(qx, qy, "o", color="#19713e", markersize=8)
    ax.annotate("Information projection q*\n(0.116, 0.268, 0.616)", xy=(qx, qy),
        xytext=(0.80, 0.31), fontsize=10,
        arrowprops={"arrowstyle": "->", "color": "#19713e"})
    ax.text(0, -0.045, "q(−1) = 1", ha="center", fontsize=11)
    ax.text(1, -0.045, "q(1) = 1", ha="center", fontsize=11)
    ax.text(0.5, height + 0.035, "q(0) = 1", ha="center", fontsize=11)
    ax.set(xlim=(-0.09, 1.27), ylim=(-0.11, height + 0.12), aspect="equal",
        title="A mean constraint selects the least-divergent probability law")
    ax.axis("off")
    fig.colorbar(contours, ax=ax, shrink=0.7, pad=0.02, label="D(q ∥ p), natural-log units (nats)")
    fig.text(0.5, 0.035, "Every point in the triangle is a probability distribution on {−1, 0, 1}.",
        ha="center", fontsize=11)
    fig.tight_layout(rect=(0, 0.07, 1, 1))
    fig.savefig(Path(__file__).stem + ".png", facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()