codex/kinetic-friction-constant-speed-force-balance.py

kinetic-friction-constant-speed-force-balance.py
"""Draw an original horizontal sliding force balance, not measured data.

Writes a matching-basename opaque white PNG to CWD. Tested with Python
3.14.4, Matplotlib 3.10.7 and NumPy 2.3.5. Force arrows share a length scale;
the dashed velocity arrow has no force scale. The block already slides.
"""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle


def main():
    mass, gravity, mu = 2.0, 9.8, 0.30
    normal = mass * gravity
    sliding_force = mu * normal
    fig, ax = plt.subplots(figsize=(8.6, 5.6), dpi=120)
    fig.patch.set_facecolor("white")
    ax.set_facecolor("white")
    ax.set(xlim=(-3.5, 3.5), ylim=(-2.7, 2.7), aspect="equal")
    ax.axis("off")
    ax.add_patch(Rectangle((-0.8, -0.38), 1.6, 0.76,
                           facecolor="#f1e0c3", edgecolor="#604c30", lw=1.5))
    ax.text(0, 0.15, "2 kg block", ha="center", fontsize=12)
    ax.plot([-3.25, 3.25], [-0.4, -0.4], color="#767676", lw=2, zorder=0)
    ax.text(2.05, -0.69, "stationary horizontal surface", ha="center", fontsize=10)
    scale = 0.077
    arrows = [((0, 0), (0, normal * scale), "#2166ac"),
              ((0, 0), (0, -normal * scale), "#2166ac"),
              ((0.8, 0), (0.8 + sliding_force * scale, 0), "#a43b31"),
              ((-0.8, 0), (-0.8 - sliding_force * scale, 0), "#a43b31")]
    for start, end, color in arrows:
        ax.annotate("", xy=end, xytext=start,
                    arrowprops=dict(arrowstyle="-|>", color=color, lw=2.6,
                                    mutation_scale=18))
    ax.text(0.15, 1.1, f"N = {normal:.1f} N", color="#2166ac", fontsize=12)
    ax.text(0.15, -1.35, f"mg = {normal:.1f} N", color="#2166ac", fontsize=12)
    ax.text(1.65, 0.2, f"pull F = {sliding_force:.2f} N", ha="center",
            color="#a43b31", fontsize=12)
    ax.text(-1.7, 0.2, f"friction |fₖ| = {sliding_force:.2f} N", ha="center",
            color="#a43b31", fontsize=12)
    ax.annotate("", xy=(2.65, 1.62), xytext=(1.15, 1.62),
                arrowprops=dict(arrowstyle="->", color="#35784f", lw=2,
                                linestyle="--"))
    ax.text(1.9, 1.83, "constant velocity →", color="#35784f",
            ha="center", fontsize=12)
    ax.text(0, -2.04, "F − |fₖ| = 0     •     N − mg = 0     •     a = 0",
            ha="center", fontsize=13)
    ax.text(0, -2.46, "Stipulated model: μₖ = 0.30, g = 9.8 m/s²; already sliding",
            ha="center", color="#555555", fontsize=11)
    ax.set_title("Constant-speed sliding: balanced forces", fontsize=16, pad=13)
    fig.subplots_adjust(left=0.035, right=0.965, top=0.9, bottom=0.04)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"),
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()