codex/kinetic-energy-work-and-power.py

kinetic-energy-work-and-power.py
"""Draw the ideal constant-force example; write the PNG basename to cwd.

Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
All curves are exact expressions for a 2 kg point mass initially at rest.
"""

import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({"font.size": 12, "savefig.facecolor": "white"})
    mass, force, distance = 2.0, 3.0, 4.0
    acceleration = force / mass
    final_time = np.sqrt(2 * distance / acceleration)
    x = np.linspace(0, distance, 401)
    velocity = np.sqrt(2 * force * x / mass)
    energy = force * x
    t = np.linspace(0, final_time, 401)
    power = force * acceleration * t
    mean_power = force * distance / final_time
    fig, axes = plt.subplots(1, 3, figsize=(12, 5.2), facecolor="white")
    fig.subplots_adjust(left=0.065, right=0.975, bottom=0.19, top=0.73, wspace=0.37)
    fig.suptitle("One constant force: motion, energy, and transfer rate", fontsize=18, y=0.97)
    fig.text(0.5, 0.865, "Mass 2 kg; net force 3 N to the right; starts at rest; travels 4 m",
             ha="center", fontsize=13)
    fig.text(0.5, 0.795, r"$a=1.5$ m/s²; $t_f=\sqrt{16/3}\approx 2.309401$ s", ha="center")
    blue, orange, green = "#2369a4", "#b85c17", "#34734b"
    axes[0].plot(x, velocity, color=blue, lw=2.7)
    axes[0].set(xlabel="Distance x (m)", ylabel="Rightward velocity v (m/s)",
                xlim=(0, 4.2), ylim=(0, 4), xticks=[0, 1, 2, 3, 4])
    axes[0].set_title(r"$v(x)=\sqrt{2Fx/m}$", pad=12)
    axes[0].scatter([4], [velocity[-1]], color=blue, s=45, zorder=3)
    axes[0].text(0.45, 3.6, "Final: 3.4641 m/s", color=blue, fontsize=11)
    axes[1].plot(x, energy, color=orange, lw=2.7)
    axes[1].set(xlabel="Distance x (m)", ylabel="Kinetic energy K (J)",
                xlim=(0, 4.2), ylim=(0, 14), xticks=[0, 1, 2, 3, 4], yticks=[0, 4, 8, 12])
    axes[1].set_title(r"$K(x)=Fx$", pad=12)
    axes[1].scatter([4], [12], color=orange, s=45, zorder=3)
    axes[1].text(0.45, 12.6, "Final: 12 J", color=orange, fontsize=11)
    axes[2].plot(t, power, color=green, lw=2.7)
    axes[2].fill_between(t, 0, power, color=green, alpha=0.16)
    axes[2].axhline(mean_power, color="#74518e", lw=2, ls="--")
    axes[2].set(xlabel="Time t (s)", ylabel="Power P (W)",
                xlim=(0, final_time * 1.045), ylim=(0, 12), xticks=[0, 1, 2])
    axes[2].set_title(r"$P(t)=Fat$", pad=12)
    axes[2].scatter([final_time], [power[-1]], color=green, s=45, zorder=3)
    axes[2].text(0.16, 10.8, "Final: 10.3923 W", color=green, fontsize=11)
    axes[2].text(0.14, 5.6, "Mean: 5.19615 W", color="#74518e", fontsize=11,
                 bbox={"facecolor": "white", "edgecolor": "none", "pad": 2})
    axes[2].text(1.28, 1.45, "Area: 12 J", color=green, fontsize=11)
    for ax in axes:
        ax.set_facecolor("white")
        ax.grid(alpha=0.18)
        ax.spines[["top", "right"]].set_visible(False)
    fig.text(0.5, 0.045, "Velocity and energy use separate units. The mean-power line gives the same work over the same time.",
             ha="center", fontsize=11)
    fig.savefig("kinetic-energy-work-and-power.png", dpi=120,
                facecolor="white", transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()