codexdouble-pendulum-point-mass-geometry.py
double-pendulum-point-mass-geometry.py
"""Draw the equal-point-mass double pendulum; emit a PNG in the current directory."""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Arc
import numpy as np
def main():
plt.rcParams.update({"font.size": 12, "figure.facecolor": "white"})
fig = plt.figure(figsize=(8, 5.12), dpi=125)
ax = fig.add_axes((0.06, 0.13, 0.51, 0.76))
theta = np.deg2rad([28, -22])
rods = np.column_stack((np.sin(theta), -np.cos(theta)))
points = np.vstack(([0, 0], np.cumsum(rods, axis=0)))
colors = ["#246ba0", "#bd5127"]
ax.plot([-0.28, 0.28], [0.08, 0.08], color="#444444", lw=5)
for i, color in enumerate(colors):
start, end = points[i:i + 2]
ax.plot(*np.stack((start, end)).T, color=color, lw=4, zorder=3)
ax.plot([start[0], start[0]], [start[1], start[1] - 0.65],
"--", color="#aaaaaa", lw=1.4)
mid = (start + end) / 2
ax.text(mid[0] + (0.08 if i == 0 else 0.16), mid[1],
r"$\ell$", color=color, fontsize=17)
angles = sorted([-90, -90 + np.rad2deg(theta[i])])
ax.add_patch(Arc(start, 0.68, 0.68, theta1=angles[0],
theta2=angles[1], color=color, lw=2))
ax.text(start[0] + (0.11 if i == 0 else -0.21),
start[1] - 0.48, rf"$\theta_{i + 1}$", color=color)
ax.scatter(*end, s=270, color=color, edgecolor="white", lw=1.5, zorder=4)
ax.text(end[0] + 0.14, end[1] - 0.05, r"$m$", fontsize=17, color=color)
ax.scatter(0, 0, s=50, color="#333333", zorder=5)
ax.text(-0.09, 0.17, "fixed pivot", ha="center")
ax.annotate("", (0.97, -0.8), (0.97, -0.23),
arrowprops={"arrowstyle": "->", "lw": 2, "color": "#555555"})
ax.text(1.02, -0.52, r"$g$", va="center", fontsize=17)
origin = np.array([-0.52, -1.6])
for delta, label in [(np.array([0.3, 0]), "$x$"), (np.array([0, 0.3]), "$y$")]:
ax.annotate("", origin + delta, origin,
arrowprops={"arrowstyle": "->", "lw": 1.3, "color": "#555555"})
ax.text(*(origin + delta + [0.02, 0.02]), label, fontsize=13)
ax.set(xlim=(-0.72, 1.27), ylim=(-2.13, 0.32), aspect="equal")
ax.axis("off")
fig.suptitle("Double pendulum: geometry before dynamics", y=0.96, fontsize=18)
fig.text(0.60, 0.77, "Two equal point masses\nTwo massless rigid rods", linespacing=1.6)
fig.text(0.60, 0.57, r"$x_1=\ell\sin\theta_1$" + "\n" + r"$y_1=-\ell\cos\theta_1$",
linespacing=1.8, color=colors[0])
fig.text(0.60, 0.34, r"$x_2=\ell(\sin\theta_1+\sin\theta_2)$" + "\n" +
r"$y_2=-\ell(\cos\theta_1+\cos\theta_2)$", linespacing=1.8,
color=colors[1], fontsize=11)
fig.text(0.5, 0.05, "Each angle uses its own downward vertical; the rod bend is a difference.",
ha="center", fontsize=11)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white")
plt.close(fig)
if __name__ == "__main__":
main()