codex/mean-value-theorem-secant-and-tangent.py

mean-value-theorem-secant-and-tangent.py
"""Plot the distinct parallel secant and tangent promised by the MVT.

Run from _media to save the opaque PNG there. Python 3.14; NumPy/Matplotlib.
"""
from pathlib import Path

import matplotlib

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


def main():
    plt.rcParams.update({"font.size": 12, "axes.spines.top": False,
                         "axes.spines.right": False})
    fig, ax = plt.subplots(figsize=(7.2, 4.8), dpi=120, constrained_layout=True)
    x = np.linspace(0.85, 3.15, 450)
    ax.plot(x, x**2, color="#235789", linewidth=2.8, label=r"$f(x)=x^2$")
    ax.plot(x, 4*x-3, color="#287a51", linewidth=2.2,
            linestyle="--", label=r"secant: $y=4x-3$")
    ax.plot(x, 4*x-4, color="#b63c45", linewidth=2.2,
            label=r"tangent: $y=4x-4$")
    ax.scatter([1, 3], [1, 9], color="#287a51", s=55, zorder=5)
    ax.scatter([2], [4], color="#b63c45", s=55, zorder=5)
    ax.annotate(r"$(1,1)$", (1, 1), xytext=(1.10, 0.35))
    ax.annotate(r"$(3,9)$", (3, 9), xytext=(2.60, 9.35))
    ax.annotate(r"$c=2$: $f'(2)=4$", (2, 4), xytext=(2.20, 2.05),
                arrowprops={"arrowstyle": "->", "color": "#555555"})
    ax.text(0.045, 0.63, "Both lines have slope 4\nTheir heights differ by 1",
            transform=ax.transAxes, fontsize=11,
            bbox={"facecolor": "white", "edgecolor": "#cccccc", "pad": 6})
    ax.set(xlim=(0.85, 3.15), ylim=(-0.9, 10), xlabel=r"$x$", ylabel=r"$y$",
           title="Mean value theorem: matching slopes")
    ax.set_xticks([1, 2, 3])
    ax.grid(alpha=0.16)
    ax.legend(loc="upper left", fontsize=11)
    ax.set_facecolor("white")
    fig.savefig(Path.cwd() / "mean-value-theorem-secant-and-tangent.png",
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()