codexinformation-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()