1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
| import numpy as np import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation, PillowWriter from matplotlib.patches import Ellipse
a = 1.0 e = 0.05 kappa_R = 1 kappa_phi = 1.1 eta = 0.0 phi0 = 0.0
T_max = 7 * np.pi / kappa_phi n_frames = 700 t = np.linspace(0, T_max, n_frames)
def star_positions(t): R = a + a * e * np.cos(kappa_R * t + eta) phi = kappa_phi * t - (2 * kappa_phi * e / kappa_R) * np.sin(kappa_R * t + eta) + phi0 x = R * np.cos(phi) y = R * np.sin(phi) return x, y
x_star, y_star = star_positions(t)
x_guide = a * np.cos(kappa_phi * t) y_guide = a * np.sin(kappa_phi * t)
theta_circle = np.linspace(0, 2*np.pi, 200) x_guide_circle = a * np.cos(theta_circle) y_guide_circle = a * np.sin(theta_circle)
radial_semiaxis = a * e tangential_semiaxis = a * (2 * kappa_phi * e / kappa_R)
r_star = np.sqrt(x_star**2 + y_star**2) periapsis_indices = [] for i in range(1, len(r_star)-1): if r_star[i] < r_star[i-1] and r_star[i] < r_star[i+1]: periapsis_indices.append(i) if len(periapsis_indices) == 0: min_idx = np.argmin(r_star) periapsis_indices = [min_idx]
dpi = 200 fig, ax = plt.subplots(figsize=(8, 8), dpi=dpi) ax.set_aspect('equal') margin = 1.5 * a * (1 + e) ax.set_xlim(-margin, margin) ax.set_ylim(-margin, margin) ax.set_xlabel('x', fontsize=12) ax.set_ylabel('y', fontsize=12) ax.set_title('Epicyclic Motion: Star, Guiding Centre, and Epicycle Ellipse', fontsize=14) ax.grid(alpha=0.3)
ax.plot(x_star, y_star, color='lightgray', linewidth=1.5, label='star orbit path') ax.plot(x_guide_circle, y_guide_circle, '--', color='gray', linewidth=1.5, label='guiding centre orbit')
star_point, = ax.plot([], [], 'ro', markersize=4, label='star') star_trail, = ax.plot([], [], 'r-', linewidth=1.5, alpha=0.7) guide_point, = ax.plot([], [], 'bo', markersize=3, label='guiding centre')
epicycle_ellipse = Ellipse((0, 0), width=2*radial_semiaxis, height=2*tangential_semiaxis, angle=0, facecolor='cyan', edgecolor='blue', alpha=0.3, linewidth=1.5) ax.add_patch(epicycle_ellipse)
periapsis_markers = ax.scatter([], [], s=25, c='green', marker='o', zorder=5, label='periapsis')
time_text = ax.text(0.02, 0.95, '', transform=ax.transAxes, fontsize=12, bbox=dict(facecolor='white', alpha=0.7, edgecolor='none'))
ax.legend(loc='upper right', fontsize=10)
def init(): star_point.set_data([], []) star_trail.set_data([], []) guide_point.set_data([], []) epicycle_ellipse.center = (0, 0) epicycle_ellipse.angle = 0 periapsis_markers.set_offsets(np.empty((0, 2))) time_text.set_text('') return star_point, star_trail, guide_point, epicycle_ellipse, periapsis_markers, time_text
def update(frame): star_point.set_data([x_star[frame]], [y_star[frame]]) start_idx = max(0, frame - 49) star_trail.set_data(x_star[start_idx:frame+1], y_star[start_idx:frame+1]) gx, gy = x_guide[frame], y_guide[frame] guide_point.set_data([gx], [gy]) epicycle_ellipse.center = (gx, gy) angle_deg = np.degrees(np.arctan2(gy, gx)) epicycle_ellipse.angle = angle_deg coords = [(x_star[i], y_star[i]) for i in periapsis_indices if i <= frame] if coords: periapsis_markers.set_offsets(np.array(coords)) else: periapsis_markers.set_offsets(np.empty((0, 2))) time_text.set_text(f't = {t[frame]:.2f}') return star_point, star_trail, guide_point, epicycle_ellipse, periapsis_markers, time_text
ani = FuncAnimation(fig, update, frames=n_frames, init_func=init, blit=True, interval=50)
ani.save('epicyclic_orbit_with_ellipse.gif', writer=PillowWriter(fps=20), dpi=dpi) plt.close(fig) print("GIF saved as 'epicyclic_orbit_with_ellipse.gif'")
|