codex/tangent-normal-vector-decomposition.py

tangent-normal-vector-decomposition.py
"""Orthogonal projections at (1,1) on a parabola; PNG to current directory."""

from pathlib import Path

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


def main():
    point = np.array([1., 1.])
    tangent = np.array([1., 2.]) / np.sqrt(5)
    normal = np.array([-2., 1.]) / np.sqrt(5)
    vector = np.array([3., 1.])
    along = (vector @ tangent) * tangent
    across = (vector @ normal) * normal
    assert np.allclose(along, [1, 2])
    assert np.allclose(across, [2, -1])
    assert np.allclose(along + across, vector)
    assert np.isclose(along @ across, 0)
    assert np.isclose(np.linalg.norm(vector)**2,
                      np.linalg.norm(along)**2 + np.linalg.norm(across)**2)
    fig, ax = plt.subplots(figsize=(9.2, 6.3), dpi=110)
    fig.patch.set_facecolor("white")
    x = np.linspace(-1.1, 2.3, 250)
    ax.plot(x, x*x, color="#777777", lw=2, label=r"Curve $y=x^2$")
    ax.scatter(*point, color="#343434", s=45, zorder=5)
    for displacement, color in [(vector, "#343434"),
                                (along, "#245f9b"), (across, "#c56521")]:
        ax.annotate("", point + displacement, point,
                    arrowprops=dict(arrowstyle="-|>", color=color,
                                    lw=2.5, mutation_scale=18))
    for endpoint in [point+along, point+across]:
        ax.plot([endpoint[0], (point+vector)[0]],
                [endpoint[1], (point+vector)[1]],
                color="#aaaaaa", ls="--", lw=1.3)
    ax.text(.5, .55, "P = (1, 1)", fontsize=11)
    ax.text(2.08, 3.15, "Tangent part\n(1, 2)", color="#245f9b", fontsize=12)
    ax.text(3.1, -.45, "Normal part\n(2, −1)", color="#c56521", fontsize=12)
    ax.text(4.05, 2.13, "w = (3, 1)", fontsize=12)
    ax.set_xlim(-1.3, 5.4)
    ax.set_ylim(-.9, 5.2)
    ax.set_aspect("equal")
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_title("A vector splits along the local tangent and normal", fontsize=15)
    ax.grid(alpha=.16)
    ax.legend(loc="upper left", fontsize=10)
    fig.text(.5, .035,
             "Arrows share the base point P; dashed connectors show vector addition. "
             "The two components are perpendicular.",
             ha="center", fontsize=10.5)
    fig.subplots_adjust(left=.07, right=.98, bottom=.13, top=.9)
    fig.savefig(Path.cwd() / "tangent-normal-vector-decomposition.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()