def plot_orbit(self, r_a_initial, v_a_initial, n_orbits=100): """ Plotting routine for a standard, two picture, orbital plot. :param r_a_initial: (list) Initial position vector of the asteroid. :param v_a_initial: (list) Initial velocity vector of the asteroid :param n_orbits: (int) Number of orbits of Jupiter to plot. :return: None """ # Define asteroid and simulate its trajectory for n_orbits orbits of Jupiter asteroid = Asteroid(r_a_initial, v_a_initial) t, r_a, v_a = asteroid.solve_orbit(n_orbits) # Plot overview of orbit (axis 1) fig, (ax1, ax2) = plt.subplots(1, 2, figsize=[3.0 * 4, 2.8 * 2]) ax1.set_xlim((-7, 7)) ax1.set_ylim((-7, 7)) ax1.plot(r_a[0], r_a[1]) self.plot_extras(ax1) ax1.set_xlabel("x /AU") ax1.set_ylabel("y /AU") # Plot zoomed in view of asteroid orbit (axis 2) ax2.plot(r_a[0], r_a[1]) ax2.set_xlabel("x /AU") ax2.set_ylabel("y /AU") # Plot starting position on axis 2 ax2.plot(r_a[0][0], r_a[1][0], 'r+') # Save figure plt.savefig("fig.png")
def pooled_process_velocity(args): """ Evaluate wander based on input list of initial velocity values. :param args: (list) List containing initial conditions of the form (v_x, v_y, r_lagrange_point). Each item is to be evaluated and have its wander calculated. :return: (list) List of calculated ranges of wander in the same order as the input list. """ # Iterate through each item in args, calculate wander and append to r_max_list r_max_list = [] for item in args: # Save start time of function time_start = time() # Unpack item and set initial conditions x, y, r_lagrange_point = item r_a_initial = [r_lagrange_point[0], r_lagrange_point[1], 0] v_a_initial = [x, y, 0] # Simulate asteroid for 100 orbits asteroid = Asteroid(r_a_initial, v_a_initial) t, r_a, v_a = asteroid.solve_orbit(100) # Calculate r_max, the maximum distance of the asteroid from the Lagrange point r_max = np.amax(np.sqrt((r_a[0] - r_lagrange_point[0]) ** 2 + (r_a[1] - r_lagrange_point[1]) ** 2)) r_max_list.append(r_max) # Output runtime for each loop to provide feedback during long calculation print(f"Took {time()-time_start}s") return r_max_list
def evaluate_mass_wander(self): """ Evaluate wander of an asteroid from L4 for a range of planetary masses. :return: None. """ # Define initial velocity vector v_a_initial = [0, 0, 0] # Populate an array of masses to iterate over # masses = np.linspace(0.0001, 0.05, num=100) * constants.MASS_SUN masses = np.logspace(-5, -2, num=100) # Evaluate masses between a given range of solar masses. r_max_list = [] for mass in masses: # Recalculate Lagrange point (L4) position r_lagrange = [ constants.R * ((constants.MASS_SUN - mass) / (constants.MASS_SUN + mass)) * np.cos(np.pi / 3), constants.R * np.sin(np.pi / 3), 0 ] # Perturb Lagrange point # r_lagrange = np.array(r_lagrange) + np.array([-0.0006 * constants.R, +0.0006 * constants.R, 0]) # Define a new asteroid with specified planet mass asteroid = Asteroid(r_lagrange, v_a_initial, planet_mass=mass) # Simulate asteroid over 500 orbits of Jupiter t, r_a, v_a = asteroid.solve_orbit(500) # Find range of wander r_max r_max = np.amax( np.sqrt((r_a[0] - r_lagrange[0])**2 + (r_a[1] - r_lagrange[1])**2)) # Append range of wander to list to be plotted r_max_list.append(r_max) # Print output as loop executes for debugging print(r_max, mass) # Open file and dump results to it for future evaluation with open("out.txt", "w") as f: f.write( json.dumps({ "r_max_list": r_max_list, "masses": masses.tolist() }))
def animate(self): """ Animation routine for a standard orbit, slightly perturbed from L4. Used to create "animation1.mp4". :return: None. """ # Define initial conditions r_a_initial = [ constants.R * ((constants.MASS_SUN - constants.MASS_JUPITER) / (constants.MASS_SUN + constants.MASS_JUPITER)) * np.cos(np.pi / 3), constants.R * np.sin(np.pi / 3), 0 ] v_a_initial = [0, 0, 0] r_a_initial = np.array(r_a_initial) + np.array( [-0.001 * constants.R, +0.001 * constants.R, 0]) # Create asteroid object and simulate for 60 orbits asteroid = Asteroid(r_a_initial, v_a_initial) t, r_a, v_a = asteroid.solve_orbit(60) # Create figure and subplots fig, (ax1, ax2) = plt.subplots(1, 2, figsize=[3.2 * 4, 2.8 * 2]) # Set axis limits and label axes ax1.set_xlim((-7, 7)) ax1.set_ylim((-7, 7)) ax2.set_xlim((np.min(r_a[0]), np.max(r_a[0]))) ax2.set_ylim((np.min(r_a[1]), np.max(r_a[1]))) ax1.set_xlabel("x /AU") ax1.set_ylabel("y /AU") ax2.set_xlabel("x /AU") ax2.set_ylabel("y /AU") # Plot Sun and Jupiter self.plot_extras(ax1) # Define variables to plot asteroid loci line, = ax1.plot([], []) line_zoomed, = ax2.plot([], []) point, = ax1.plot([], [], "ro", markersize=3) # Define graphic to show orbit location sun_circle = plt.Circle((-6, 6), 0.15, color="k") radius_line = plt.Circle((-6, 6), 0.8, color="k", fill=False, linewidth=0.2) ax1.add_artist(sun_circle) ax1.add_artist(radius_line) text = ax1.text(-4.8, 6, "t = 0 Yr") # Define orbit properties for animation omega = np.sqrt(constants.G * (constants.MASS_SUN + constants.MASS_JUPITER) / constants.R**3) period = 2 * np.pi / omega initial_angle = np.pi / 2 # Additional phase to make graphic plot match large plot at t=0 # Define animation function def animate(i): line.set_data(r_a[0][0:i], r_a[1][0:i]) line_zoomed.set_data(r_a[0][0:i], r_a[1][0:i]) time = t[i] angle = ((time % period) / period) * 2 * np.pi + initial_angle x = -6 + 0.8 * np.sin(angle) y = 6 + 0.8 * np.cos(angle) point.set_data(x, y) text.set_text(f"t = {int(t[i])} Yr") return line, line_zoomed, point, text # Calculate interval and number of frames needed FPS = 60.0 # Frames per second in Hz ANIM_LENGTH = 20.0 # Animation length in seconds interval = 1 / FPS frames = int(ANIM_LENGTH * FPS) # Define animation animation = matplotlib.animation.FuncAnimation(fig, animate, frames=frames, interval=interval, blit=True) # Save animation plt.rcParams['animation.ffmpeg_path'] = constants.FFMPEG_PATH FFWriter = matplotlib.animation.writers['ffmpeg'] writer = FFWriter( fps=FPS, metadata=dict(artist='Cambridge Computing Project 2020'), bitrate=2000) animation.save('animation1.mp4', writer=writer)
def evaluate_energy_conservation(self, r_a_initial, v_a_initial, n_orbits): """ Test energy conservation for a given number of orbits and initial conditions. :param r_a_initial: (list) Initial position vector of the asteroid. :param v_a_initial: (list) Initial velocity vector of the asteroid :param n_orbits: (int) Number of orbits of Jupiter to plot. :return: None. """ # Define asteroid object with initial conditions asteroid = Asteroid(r_a_initial, v_a_initial) # Simulate orbit for n_orbits t, r_a, v_a = asteroid.solve_orbit(n_orbits) # Transpose r_a and v_a for future convenience r_a = np.transpose(r_a) v_a = np.transpose(v_a) # Define omega and orbital period omega = np.sqrt(constants.G * (constants.MASS_SUN + constants.MASS_JUPITER) / constants.R**3) period = 2 * np.pi / omega # Iterate over each time sample and calculate total energy energy_list = [] for i in range(len(t)): # Define vectors for potential calculation r_a_to_s = self.r_s - r_a[i] r_a_to_j = self.r_j - r_a[i] r = np.linalg.norm(r_a[i]) # Calculate gravitational potential energy graviatational_potential = -constants.G * ( constants.MASS_SUN / np.linalg.norm(r_a_to_s) + constants.MASS_JUPITER / np.linalg.norm(r_a_to_j)) # Calculate kinetic energy due to rotating frame kinetic_energy_rot = 0.5 * r**2 * omega**2 # Calculate kinetic energy in the rotating frame kinetic_energy_frame = 0.5 * np.linalg.norm(v_a[i])**2 # Calculate total kinetic energy kinetic_energy = -kinetic_energy_rot + kinetic_energy_frame # Append total energy to energy_list energy_list.append(kinetic_energy + graviatational_potential) fig, ax1 = plt.subplots() # Plot all energies as a percentage deviation from initial energy ax1.plot(t / period, (np.array(energy_list) / energy_list[0] - 1) * 100) # Plot expected theoretical energy line ax1.hlines(0, np.min(t), np.max(t / period)) # Set axis labels ax1.set_xlabel("Number of orbits /No Units") ax1.set_ylabel("|Percentage deviation from expected energy| /%") # Save plot plt.savefig("fig.png")