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