codex/inverse-problem-nullspace.py

inverse-problem-nullspace.py
"""Write inverse-problem-nullspace.png into cwd.

Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Run from _media; only NumPy, Matplotlib and the standard library are used.
"""

from pathlib import Path

import matplotlib

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


def main():
    weak = np.array([[0.55, 0.45], [0.45, 0.55]])
    lost = np.full((2, 2), 0.5)
    square = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]])
    t = np.linspace(0, 1, 5)
    samples = np.column_stack([t, 1 - t])
    np.testing.assert_allclose(samples @ lost.T, 0.5)
    np.testing.assert_allclose(weak @ [1, -1], [0.1, -0.1])
    np.testing.assert_allclose(lost @ [1, -1], [0, 0])
    colors = ["#1765a8", "#257e65", "#94640a", "#bf5b16", "#793a95"]
    plt.rcParams.update({"font.size": 11, "axes.titlesize": 13})
    fig, axes = plt.subplots(1, 3, figsize=(10.8, 4.6), facecolor="white")
    fig.subplots_adjust(left=0.06, right=0.98, bottom=0.22, top=0.74, wspace=0.33)
    fig.suptitle("Weak information is different from lost information", y=0.98, fontsize=16)
    fig.text(0.5, 0.875, "The same unit square and five inputs with x₁ + x₂ = 1 are mapped in each panel.", ha="center")
    matrices = [np.eye(2), weak, lost]
    titles = ["Input pixels", "Weak difference mode", "Lost difference mode"]
    subtitles = ["x = (t, 1 − t)", "A = [[0.55, 0.45], [0.45, 0.55]]", "B = [[0.5, 0.5], [0.5, 0.5]]"]
    for i, (ax, matrix, title, subtitle) in enumerate(zip(axes, matrices, titles, subtitles)):
        boundary = square @ matrix.T
        points = samples @ matrix.T
        ax.fill(boundary[:, 0], boundary[:, 1], color="#edf1f5")
        ax.plot(boundary[:, 0], boundary[:, 1], color="#717d89", lw=1.5)
        if i < 2:
            ax.plot(points[:, 0], points[:, 1], color="#555555", lw=1, ls="--")
            ax.scatter(points[:, 0], points[:, 1], s=65, c=colors, edgecolors="white", linewidths=0.8, zorder=4)
        else:
            ax.scatter([0.5], [0.5], s=110, facecolor="white", edgecolor="#793a95", linewidth=2, zorder=4)
            ax.annotate("All five inputs\ngive (0.5, 0.5)", (0.5, 0.5), xytext=(0.08, 0.83), arrowprops={"arrowstyle": "->", "color": "#555555"}, fontsize=10)
        if i == 1:
            ax.annotate("(0.55, 0.45)", (0.55, 0.45), xytext=(0.42, 0.12), arrowprops={"arrowstyle": "->", "color": "#555555"}, fontsize=10)
            ax.annotate("(0.45, 0.55)", (0.45, 0.55), xytext=(0.03, 0.85), arrowprops={"arrowstyle": "->", "color": "#555555"}, fontsize=10)
        ax.set_title(title, pad=28)
        ax.text(0.5, 1.045, subtitle, transform=ax.transAxes, ha="center", fontsize=9)
        ax.set_xlim(-0.08, 1.08)
        ax.set_ylim(-0.08, 1.08)
        ax.set_aspect("equal")
        ax.set_xticks([0, 0.5, 1])
        ax.set_yticks([0, 0.5, 1])
        ax.set_xlabel("x₁" if i == 0 else "y₁")
        ax.set_ylabel("x₂" if i == 0 else "y₂")
        ax.set_facecolor("white")
        ax.grid(color="#e3e7eb", lw=0.6)
        ax.spines[["top", "right"]].set_visible(False)
    fig.text(0.5, 0.055, "Difference (1, −1):    A scales it by 0.1; B sends it to zero.    Both preserve the mean mode (1, 1).", ha="center")
    fig.savefig(Path.cwd() / "inverse-problem-nullspace.png", dpi=110, facecolor="white")
    plt.close(fig)


if __name__ == "__main__":
    main()