Sample solutions for Virtual Orrery

Below are 3 possible solution to the orrery challenge exercise, with different levels of complexity.

import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from IPython.display import HTML, display
import random
## Individual planets (Earth, Mercury)

au = 1.496e8
year = 365.256

# Orbital constants for Earth and Mercury
d_e = 1 * au
p_e = 1 * year
d_m = 0.387 * au
p_m = 87.969

# 1 Year of timepoints, at 5 day intervals
t = np.arange(0, year, 5)

# Position coordinates for Earth and Mercury for each timepoint in t
x_e = d_e * np.cos(2*np.pi*t/p_e)
y_e = d_e * np.sin(2*np.pi*t/p_e)
x_m = d_m * np.cos(2*np.pi*t/p_m)
y_m = d_m * np.sin(2*np.pi*t/p_m)


# Set up the figure
filename = "animation.gif"
interval = 100
frames = len(t)

def ganimate(frame):
    plt.cla()
    # Plot a yellow circle in the middle to represent the Sun
    plt.plot(0,0, 'oy')
    # Plot orbital positions of Earth and Mercury for current frame
    plt.scatter(x_e[frame], y_e[frame])
    plt.scatter(x_m[frame], y_m[frame])
    # Set the axis limits to the minimum and maximum x,y coordinates of Earth
    plt.xlim(min(x_e), max(x_e))
    plt.ylim(min(y_e), max(y_e))

fig = plt.figure(figsize=(5, 5))
anim = animation.FuncAnimation(fig, ganimate, frames=frames, interval=interval)
anim.save(filename, writer='imagemagick')
plt.close()

__counter__ = str(random.randint(0,2e9))
display(HTML('<img src="' + filename + '?' + __counter__ + '">'))

If we want to plot many planets, using arrays and loops can save us many lines of code

## Inner planets: Using arrays and for loops

au = 1.496e8
year = 365.256

# Inner planets only
distances = np.array([[0.387, 0.723, 1, 1.524]]) * au
periods = np.array([[87.969, 224.701, 1*year, 686.98]])

# Make animation last for full orbit of Mars, 10 day intervals
# (N.B.: [:, np.newaxis] is needed for succesful broadcasting)
t = np.arange(0, np.amax(periods), 10)[:, np.newaxis]

# Compute orbital coordinates
x_planets = distances * np.cos(2*np.pi*t/periods)
y_planets = distances * np.sin(2*np.pi*t/periods)

# Set up figure
filename = "animation.gif"
interval = 100
frames = len(t)

def ganimate(frame):
    plt.cla()
    
    # Plot a yellow circle in the middle to represent the Sun
    plt.plot(0,0, 'oy')
    
    # Create a line highlighting the orbit of Earth
    plt.plot(x_planets[:,2], y_planets[:,2], linewidth=0.5)
    
    # For each planet, plot its current position for the frame
    for p in range(max(periods.shape)):
        plt.scatter(x_planets[frame, p], y_planets[frame, p])
    
    # Set axis limits to correspond to the minimum and maximum x and y positions
    # out of all planet orbits
    plt.xlim(np.amin(x_planets), np.amax(x_planets))
    plt.ylim(np.amin(y_planets), np.amax(y_planets))
    
fig = plt.figure(figsize=(5, 5))

anim = animation.FuncAnimation(fig, ganimate, frames=frames, interval=interval)
anim.save(filename, writer='imagemagick')
plt.close()

__counter__ = str(random.randint(0,2e9))
display(HTML('<img src="' + filename + '?' + __counter__ + '">'))

Finally, if we want to animate the entire solar system, we’ll need to make sure we have a sensible number of frames.

Use np.arange to create an array of values with an interval which you define yourself: np.arange(start, stop, step)

Use np.linspace to create an array with a defined number of evenly spaced values: np.linspace(start, stop, nvals).

## Entire solar system

au = 1.496e8
year = 365.256

distances = np.array([[0.387, 0.723, 1, 1.524,        # Inner planets
                       5.203, 9.537, 19.191, 30.069,  # Outer planets
                       39.482]]) * au                 # Pluto :)
periods = np.array([[87.969, 224.701, 1*year, 686.98,
                     11.862*year, 29.457*year, 84.011*year, 164.79*year,
                     247.94*year]])

# Make animation last for full orbit of Pluto, Using np.linspace() to generate 200 values
# (N.B.: [:, np.newaxis] is needed for succesful broadcasting)
t = np.linspace(0, np.amax(periods), 200)[:, np.newaxis]

# Compute orbital coordinates
x_planets = distances * np.cos(2*np.pi*t/periods)
y_planets = distances * np.sin(2*np.pi*t/periods)

# Set up figure
filename = "animation.gif"
interval = 100
frames = len(t)

def ganimate(frame):
    plt.cla()
    
    # Plot a yellow circle in the middle to represent the Sun
    plt.plot(0,0, 'oy')
    
    # For each planet, plot its current position for the frame
    for p in range(max(periods.shape)):
        plt.scatter(x_planets[frame, p], y_planets[frame, p])
    
    # Set axis limits to correspond to the minimum and maximum x and y positions
    # out of all planet orbits
    plt.xlim(np.amin(x_planets), np.amax(x_planets))
    plt.ylim(np.amin(y_planets), np.amax(y_planets))
    
fig = plt.figure(figsize=(8, 8))
anim = animation.FuncAnimation(fig, ganimate, frames=frames, interval=interval)
anim.save(filename, writer='imagemagick')
plt.close()

__counter__ = str(random.randint(0,2e9))
display(HTML('<img src="' + filename + '?' + __counter__ + '">'))