codeximpulse-force-pulses.py
impulse-force-pulses.py
"""Draw three stipulated positive net-force pulses with equal impulse.
Tested with Python 3.14.4, NumPy 2.3.5, Matplotlib 3.10.7.
Write impulse-force-pulses.png to the current working directory.
"""
import matplotlib.pyplot as plt
import numpy as np
def main():
triangle_t = np.linspace(0.0, 0.5, 501)
triangle_f = 24.0 * (1.0 - np.abs(triangle_t - 0.25) / 0.25)
impulses = np.array([12.0 * 0.5, np.trapezoid(triangle_f, triangle_t), 3.0 * 2.0])
delta_v = impulses / 2.0
np.testing.assert_allclose(impulses, [6.0, 6.0, 6.0])
np.testing.assert_allclose(delta_v, [3.0, 3.0, 3.0])
print("Pulse impulses (N s):", impulses.tolist())
print("Velocity changes for 2 kg (m/s):", delta_v.tolist())
plt.rcParams.update({"font.size": 12, "savefig.facecolor": "white"})
fig, axes = plt.subplots(1, 3, figsize=(11.5, 5.6), facecolor="white",
sharex=True, sharey=True)
fig.subplots_adjust(left=0.07, right=0.985, bottom=0.27,
top=0.73, wspace=0.17)
fig.suptitle("Equal impulse does not mean equal peak force", fontsize=18, y=0.965)
fig.text(0.5, 0.86,
"Three rightward net-force pulses, each with area 6 N s; all axes use the same scale",
ha="center", fontsize=12)
pulses = [([0.0, 0.5], [12.0, 12.0], "#276b9b", "Short rectangle", "12 N × 0.5 s = 6 N s"),
(triangle_t, triangle_f, "#cf7323", "Short triangle", "½ × 24 N × 0.5 s = 6 N s"),
([0.0, 2.0], [3.0, 3.0], "#387f54", "Long rectangle", "3 N × 2 s = 6 N s")]
for ax, (t, force, color, title, area_label) in zip(axes, pulses, strict=True):
ax.fill_between(t, 0, force, color=color, alpha=0.24)
ax.plot(t, force, color=color, lw=2.8)
ax.plot([t[0], t[0]], [0, force[0]], color=color, lw=2.8)
ax.plot([t[-1], t[-1]], [force[-1], 0], color=color, lw=2.8)
ax.plot([t[-1], 2.15], [0, 0], color=color, lw=2.8)
peak_idx = int(np.argmax(force))
peak_x, peak = t[peak_idx], force[peak_idx]
ax.annotate(f"Peak {peak:g} N", xy=(peak_x, peak),
xytext=(0.95, max(peak + 3.0, 9.0)), fontsize=11,
ha="center", color=color,
arrowprops={"arrowstyle": "->", "color": color, "lw": 1.3})
ax.set(title=title, xlabel="Time t (s)", xlim=(-0.04, 2.15), ylim=(0, 29),
xticks=[0, 0.5, 1, 1.5, 2], yticks=[0, 6, 12, 18, 24])
ax.text(0.5, -0.25, area_label, transform=ax.transAxes,
ha="center", fontsize=11, color=color)
ax.set_facecolor("white")
ax.set_axisbelow(True)
ax.grid(alpha=0.17)
ax.spines[["top", "right"]].set_visible(False)
ax.title.set_fontsize(13)
axes[0].set_ylabel("Net force F (N)")
fig.text(0.5, 0.105,
r"Same fixed mass $m=2$ kg: $\Delta v=J/m=6/2=3$ m/s for every pulse",
ha="center", fontsize=13)
fig.text(0.5, 0.04,
"These are stipulated examples. Pulse shapes affect the motion during the interval, even when the final change is equal.",
ha="center", fontsize=10)
fig.savefig("impulse-force-pulses.png", dpi=120, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()