codex/orthogonal-projection-metric-comparison.py

orthogonal-projection-metric-comparison.py
"""Original circles/ellipses of projection error; PNG basename to CWD."""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    fig, axes = plt.subplots(1, 2, figsize=(9.2, 5), dpi=120)
    fig.patch.set_facecolor("white")
    x = np.array([3., 1.])
    e = np.array([1., 2.])
    theta = np.linspace(0, 2*np.pi, 601)
    cases = [
        (np.array([1., 1.]), "Ordinary dot product", "(1, 2)",
         r"$\|x-p\|^2=5$"),
        (np.array([4., 1.]), "Weight first-coordinate errors by 4",
         "(7/4, 7/2)", r"$\|x-p\|_M^2=25/2$"),
    ]
    for ax, (weights, title, label, footer) in zip(axes, cases):
        p = e*np.sum(weights*x*e)/np.sum(weights*e*e)
        residual = x-p
        squared_distance = np.sum(weights*residual**2)
        radius = np.sqrt(squared_distance/weights)
        contour = x[:, None] + radius[:, None]*np.array(
            [np.cos(theta), np.sin(theta)])
        ax.plot(contour[0], contour[1], color="#2878a5", lw=2)
        t = np.linspace(-1.5, 2.55, 201)
        ax.plot(t, 2*t, color="#888888", lw=2)
        ax.plot([p[0], x[0]], [p[1], x[1]], color="#28865b",
                lw=2, ls="--")
        ax.scatter([x[0]], [x[1]], color="#2878a5", s=45, zorder=4)
        ax.scatter([p[0]], [p[1]], color="#c05b20", s=50, zorder=4)
        ax.text(3.12, 1.05, "x = (3, 1)", fontsize=11)
        ax.annotate("p = " + label, p,
                    xytext=(.06, .89 if weights[0] == 4 else .7),
                    textcoords="axes fraction", fontsize=11,
                    color="#a74a12",
                    arrowprops={"arrowstyle": "->", "color": "#a74a12"})
        ax.text(.5, -.25, footer, transform=ax.transAxes,
                ha="center", fontsize=12)
        ax.set_xlim(-.6, 5.5)
        ax.set_ylim(-3, 5)
        ax.set_aspect("equal")
        ax.set_xlabel("First coordinate")
        ax.set_ylabel("Second coordinate")
        ax.set_title(title, fontsize=11.5, pad=12)
        ax.grid(alpha=.17)
        ax.spines[["top", "right"]].set_visible(False)
    fig.suptitle("The smallest error contour touches the allowed line at the optimum",
                 fontsize=13, y=.98)
    fig.subplots_adjust(left=.07, right=.98, top=.83, bottom=.25, wspace=.23)
    fig.savefig(Path.cwd()/Path(__file__).with_suffix(".png").name,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()