codex/static-friction-adjustable-response.py

static-friction-adjustable-response.py
"""Plot an original static-friction feasibility model.

Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; output is static-friction-adjustable-response.png in CWD.
"""
from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


MASS = 2.0
GRAVITY = 9.8
MU_STATIC = 0.4
LIMIT = MU_STATIC * MASS * GRAVITY


def main():
    plt.rcParams.update({"font.size": 12, "font.family": "DejaVu Sans"})
    fig, ax = plt.subplots(figsize=(9, 5.8), dpi=120, facecolor="white")
    ax.set_facecolor("white")
    ax.axhspan(-LIMIT, LIMIT, color="#e1eef8", label="Admissible static friction")
    for y in (-LIMIT, LIMIT):
        ax.axhline(y, color="#47728e", lw=1.4, ls=":")
    applied = np.linspace(-LIMIT, LIMIT, 300)
    ax.plot(applied, -applied, lw=3, color="#15699b", label=r"Rest requires $f_s=-F$")
    for sign in (-1, 1):
        outside = sign * np.linspace(LIMIT, 11, 120)
        ax.plot(outside, -outside, color="#b34730", ls="--", lw=2)
    for f in (2.0, 6.0):
        ax.scatter(f, -f, s=65, color="#15699b", edgecolors="white", zorder=5)
        ax.annotate(f"F = {f:g} N: rest", (f, -f), xytext=((f-2.2, -f-2.2) if f == 2 else (f+1.2, -f+2.2)),
                    arrowprops={"arrowstyle": "-", "color": "#34505f"}, fontsize=11)
    ax.scatter([10, -10], [-10, 10], marker="x", s=100, lw=2.5,
               color="#b34730", zorder=6, label="Demand exceeds bound: no rest")
    ax.scatter([LIMIT, -LIMIT], [-LIMIT, LIMIT], color="#15699b", s=45, zorder=6)
    ax.text(-10.6, -LIMIT + 0.5, r"$f_s=-7.84\,\mathrm{N}$", fontsize=11, color="#335a72")
    ax.text(3.1, LIMIT + 0.45, r"$f_s=+7.84\,\mathrm{N}$", fontsize=11, color="#335a72")
    ax.text(0.98, 0.03, "Dashed demand is not a sliding-force law.",
            transform=ax.transAxes, ha="right", fontsize=10,
            bbox={"facecolor": "white", "edgecolor": "none", "alpha": 1})
    ax.axhline(0, color="#83929a", lw=0.7)
    ax.axvline(0, color="#83929a", lw=0.7)
    ax.set(xlim=(-11.3, 11.3), ylim=(-12, 12),
           xlabel="Applied horizontal force F (N); right is positive",
           ylabel=r"Required signed static friction $f_s$ (N)",
           title="Static friction adjusts within a bound")
    ax.set_xticks([-10, -6, -2, 0, 2, 6, 10])
    ax.set_yticks([-10, -7.84, 0, 7.84, 10])
    ax.grid(alpha=0.18)
    ax.legend(loc="upper center", bbox_to_anchor=(0.5, -0.18),
              frameon=False, fontsize=10, ncol=1)
    fig.text(0.5, 0.905, r"Stipulated model: $m=2\,\mathrm{kg}$, $g=9.8\,\mathrm{m/s^2}$, $\mu_s=0.40$; $N=19.6\,\mathrm{N}$",
             ha="center", fontsize=11)
    fig.subplots_adjust(left=0.12, right=0.97, bottom=0.26, top=0.85)
    fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()