codexinertial-cart-force-balance.py
inertial-cart-force-balance.py
"""Draw the ideal balanced cart and two nonrotating reference frames.
Tested with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
Run from _media; writes inertial-cart-force-balance.png to the current directory.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch, Rectangle
def arrow(ax, start, end, color, label, label_position):
ax.add_patch(FancyArrowPatch(start, end, arrowstyle="-|>",
mutation_scale=19, linewidth=2.5, color=color))
ax.text(*label_position, label, color=color, fontsize=12, ha="center")
def main():
plt.rcParams.update({"font.size": 11, "font.family": "DejaVu Sans"})
fig = plt.figure(figsize=(10, 6.8), dpi=120, facecolor="white")
grid = fig.add_gridspec(2, 2, height_ratios=[1.25, 1], hspace=0.28)
ax = fig.add_subplot(grid[0, :])
ax.set(xlim=(0, 10), ylim=(0, 5))
ax.axis("off")
ax.set_title("One cart: nonzero individual forces, zero net force", fontsize=15, pad=10)
ax.plot([0.4, 9.6], [1.3, 1.3], color="#707070", linewidth=2)
ax.add_patch(Rectangle((4.1, 1.4), 1.8, 1.1, facecolor="#e4ecf6",
edgecolor="#334e70", linewidth=2))
ax.text(5, 1.92, "2 kg cart", ha="center", va="center", fontsize=13)
arrow(ax, (4.1, 1.95), (1.9, 1.95), "#b3482f", "−6 N", (2.8, 2.2))
arrow(ax, (5.9, 1.95), (8.1, 1.95), "#b3482f", "+6 N", (7.2, 2.2))
arrow(ax, (5, 2.5), (5, 3.65), "#b3482f", "support", (4.2, 3.55))
arrow(ax, (5, 1.4), (5, 0.22), "#b3482f", "weight", (4.2, 0.35))
arrow(ax, (6.5, 3.2), (8.6, 3.2), "#176a9b", "v = +1.5 m/s", (7.55, 3.55))
ax.text(0.5, 4.25, "Laboratory inertial frame\nHorizontal forces balance; vertical forces balance.",
fontsize=11, va="center")
ax.text(8.4, 0.6, "+x →", ha="center")
for column, title, origin, velocity, acceleration in [
(0, "Constant-velocity origin", "R(t) = Ut, U = +1.5 m/s",
"v′ = v − U = 0", "a′ = 0: cart remains at rest"),
(1, "Accelerating origin", "R(t) = ½At², A = +0.50 m/s²",
"v′ = 1.5 − 0.50t (m/s)", "a′ = −0.50 m/s²"),
]:
panel = fig.add_subplot(grid[1, column])
panel.axis("off")
panel.set_title(title, fontsize=14, pad=12)
panel.text(0.5, 0.87, origin, ha="center", transform=panel.transAxes)
panel.plot([0.1, 0.9], [0.57, 0.57], color="#707070", transform=panel.transAxes)
panel.add_patch(Rectangle((0.4, 0.58), 0.2, 0.18, transform=panel.transAxes,
facecolor="#e4ecf6", edgecolor="#334e70"))
panel.text(0.5, 0.41, velocity, ha="center", transform=panel.transAxes, color="#176a9b")
panel.text(0.5, 0.26, acceleration, ha="center", transform=panel.transAxes)
panel.text(0.5, 0.08, "Actual net force = 0" if column == 0 else
"Actual net force = 0; effective term = −mA", ha="center",
fontsize=10, transform=panel.transAxes)
fig.subplots_adjust(top=0.92, bottom=0.08, left=0.055, right=0.95)
fig.text(0.5, 0.025, "Stipulated ideal model. Blue arrow is velocity, not another force.",
ha="center", fontsize=10)
fig.savefig(Path.cwd() / "inertial-cart-force-balance.png", facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()