Spatial velocity

%matplotlib inline
from matplotlib import rc

rc("animation", html="jshtml")

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# Parameters
R1 = 1.0  # small wheel radius
R2 = 2.0  # large wheel radius
omega = 1.5  # angular speed (rad/s)
fps = 60
interval = 1000 / fps  # ms
T = 2 * np.pi / omega

fig, ax = plt.subplots(figsize=(10, 4))
ax.set_aspect("equal")
ax.set_xlim(-R2 * 1.5, R2 * 3)
ax.set_ylim(-R2 * 1.2, R2 * 1.2)
ax.axis("off")

# Add wheels
wheel1 = plt.Circle((0, 0), R1, fill=False, lw=2, color="red", alpha=0.7)
wheel2 = plt.Circle((R2 * 1.7, 0), R2, fill=False, lw=2, color="blue", alpha=0.7)
ax.add_patch(wheel1)
ax.add_patch(wheel2)

# Points
(point1,) = ax.plot([], [], "ro", markersize=8)
(point2,) = ax.plot([], [], "bo", markersize=8)

# Radius lines
(radius_line1,) = ax.plot([], [], "r-", lw=2)
(radius_line2,) = ax.plot([], [], "b-", lw=2)

# Tangent (velocity) vectors
(velocity_line1,) = ax.plot([], [], "r--", lw=2)
(velocity_line2,) = ax.plot([], [], "b--", lw=2)

# Texts for displaying velocity magnitude
velocity_text1 = ax.text(-R2 * 0.8, R2 * 1.0, "", color="red", fontsize=11)
velocity_text2 = ax.text(R2 * 2.0, R2 * 1.0, "", color="blue", fontsize=11)


def init():
    point1.set_data([], [])
    point2.set_data([], [])
    radius_line1.set_data([], [])
    radius_line2.set_data([], [])
    velocity_line1.set_data([], [])
    velocity_line2.set_data([], [])
    velocity_text1.set_text("")
    velocity_text2.set_text("")
    return (point1, point2, radius_line1, radius_line2, velocity_line1, velocity_line2, velocity_text1, velocity_text2)


def animate(frame):
    t = frame / fps
    theta = omega * t

    # Wheel 1 (small, at (0,0))
    x1 = R1 * np.cos(theta)
    y1 = R1 * np.sin(theta)
    point1.set_data([x1], [y1])
    radius_line1.set_data([0, x1], [0, y1])
    # Velocity (tangent) vector
    vx1 = -omega * R1 * np.sin(theta)
    vy1 = omega * R1 * np.cos(theta)
    vnorm1 = np.hypot(vx1, vy1)
    velocity_line1.set_data([x1, x1 + vx1 * 0.5], [y1, y1 + vy1 * 0.5])
    velocity_text1.set_text(f"Small: v=({vx1:.2f}, {vy1:.2f}), |v|={vnorm1:.2f}")

    # Wheel 2 (large, center at (R2*1.7, 0))
    cx2, cy2 = R2 * 1.7, 0
    x2 = cx2 + R2 * np.cos(theta)
    y2 = cy2 + R2 * np.sin(theta)
    point2.set_data([x2], [y2])
    radius_line2.set_data([cx2, x2], [cy2, y2])
    vx2 = -omega * R2 * np.sin(theta)
    vy2 = omega * R2 * np.cos(theta)
    vnorm2 = np.hypot(vx2, vy2)
    velocity_line2.set_data([x2, x2 + vx2 * 0.5], [y2, y2 + vy2 * 0.5])
    velocity_text2.set_text(f"Large: v=({vx2:.2f}, {vy2:.2f}), |v|={vnorm2:.2f}")

    return (point1, point2, radius_line1, radius_line2, velocity_line1, velocity_line2, velocity_text1, velocity_text2)


anim = FuncAnimation(fig, animate, init_func=init, frames=int(T * fps), interval=interval, blit=True)

anim  # Show animation in Jupyter