By this point, you're pronably quite used to importing libraries. Today you'll learn to make them yourself. A library is made of a number of modules. A number is just any file with a .py at the end, i.e., a python script outside of a Jupyter notebook. You can use any text editor to edit them, but some nice ones include Sublime Text and Atom. With a little configuration, they can be configured so that they highlight and autocomplete your code. This setup will be covered in a separate notebook.
A module consists of a number of functions and classes which you can call from your main block of code. This allows your main block of code to remain nice, clean and organised. It also allows for easier collaboration and sharing of code; in some cases the main block of code may not be relevant and all that they need is the module, so that they can solve a different problem. Try copy and pasting the below code, from the previous lesson, into a file and call it OrbitModule.py . Save it to the same directory (save it in the same folder) as this notebook. The name of a module is defined by the name of the file.
# coding: utf-8
from __future__ import division, print_function
from vpython import *
import numpy as np
import copy
class PhysicsError (Exception):
"""
Error type defined for if two Particles get too close to simulate well
"""
def __init__(self, exception_type):
self.exception_type = exception_type
def __str__(self):
return repr(self.exception_type)
class Particle(sphere):
"""
Class which describes a Particle under the influence of some force. Subclasses vpython sphere so drawing is no effort.
"""
G = 1
def __init__(self,pos = vector(0,0,0), velocity = vector(0,0,0), mass = 0.0, radius =0.0, color = color.red):
"""
Parameters
----------
pos : vpython vector
Initial position of Particle
velocity : vpython vector
Initial velocity of Particle
mass: float
Mass of Particle (default = 0)
radius : float
Radius of Particle
color: vpython color
Color of particle
"""
sphere.__init__(self,pos = pos, velocity = velocity, radius = radius, make_trail = True, color = color)
self.velocity = velocity
self.mass = mass
def force_felt_by(self,other,if_at = None):
'''
Parameters
----------
other: Particle
The particle which feels the force
if_at: vpython vector
If this parameter is used, the function gives the force the 'other' particle would feel if it were at this position
Subclass Particle and change this to implement custom forces, then everything else should work.
Default(The one implemented here) is gravitational
'''
if not if_at:
if_at = other.pos
position_difference = if_at - self.pos
determinant = position_difference.mag
if determinant == 0:
return vector(0,0,0)
velocity_determinant = self.velocity.mag
g_force_scalar = (-1*self.G*self.mass*other.mass)/(determinant**3)
g_force_vector = g_force_scalar * position_difference
if determinant < self.radius + other.radius:
raise PhysicsError("Collision ")
return g_force_vector
def increment_by(self,pos_increment, velocity_increment):
'''
Function to increment coordinates and velocity at the same time.
Parameters
----------
pos_increment: vpython vector
Increment for pos
velocity_increment: vpython vector
Increment for velocity
'''
self.pos += pos_increment
self.velocity += velocity_increment
class System (object):
"""Class which describes a system composed of a number of Particles."""
def __init__(self,dt,G = 1):
"""
Parameters
----------
dt: float
Specifies time increments to take
G: float
Gravitational constant, set to 1 by default
"""
planets = []
self.dt = dt
self.G = G
def runge_kutta_move_time(self):
"""Move time forwards by one step using RK4. Assumes gravitational field doesn't change significantly with time during one time step."""
old_planets = self.planets
try:
for counter_1,planet_1 in enumerate(self.planets):
k1 = vector(0,0,0)
k2 = vector(0,0,0)
k3 = vector(0,0,0)
k4 = vector(0,0,0)
for counter_2,planet_2 in enumerate(old_planets):
if counter_2 != counter_1:
k1 += planet_2.force_felt_by(planet_1, if_at = None)/planet_1.mass
imagpos = planet_1.pos + (self.dt/2)*k1
for counter_2,planet_2 in enumerate(old_planets):
if counter_2 != counter_1:
k2 += planet_2.force_felt_by(planet_1, if_at = imagpos)/planet_1.mass
imagpos = planet_1.pos + (self.dt/2)*k2
for counter_2,planet_2 in enumerate(old_planets):
if counter_2 != counter_1:
k3 += planet_2.force_felt_by(planet_1, if_at = imagpos)/planet_1.mass
imagpos = planet_1.pos + (self.dt)*k3
for counter_2,planet_2 in enumerate(old_planets):
if counter_2 != counter_1:
k3 += planet_2.force_felt_by(planet_1, if_at = imagpos)/planet_1.mass
x_increment = planet_1.velocity*self.dt
v_increment = (self.dt/6)*(k1 + 2*k2 + 2*k3 + k4)
self.planets[counter_1].increment_by(x_increment,v_increment)
except PhysicsError:
print("Collision")
We can now import the module and use it.
import OrbitModule as om
from vpython import *
scene1 = canvas(title = "Orbits!")
scene1.caption = """Right button drag or Ctrl-drag to rotate "camera" to view scene.
To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.
On a two-button mouse, middle is left + right.
Touch screen: pinch/extend to zoom, swipe or two-finger rotate."""
scene1.forward = vector(0,0,1)
# Initialise 3 planets
giant_planet = om.Particle(pos = vector(-10.,0.,0.),
velocity = vector(0., 0., 0.),
mass = 200, radius = 5, color = color.blue)
dwarf_planet = om.Particle(pos = vector(15.,0.,0.),
velocity = vector(0., 0., 3.25),
mass = 10, radius = 5, color = color.green)
really_big_planet = om.Particle(pos = vector(-100,-200,0),
velocity = vector(3, 0, 0),
mass = 2000, radius = 20)
dt = 0.1
system = om.System(dt)
planets_array = [giant_planet, dwarf_planet, really_big_planet]
system.planets = planets_array
# Step the system's time forwards 50 times a second.
while True:
rate(50)
system.runge_kutta_move_time()
If you keep on going with scientific computing, along the way you will no doubt create some useful, reusable code. Put these in modules and if you've documented how to use them well, they will facilitate you greatly in future projects.
Libraries are just folders with a file called __init__.py . If you put any other .py files in the same folder they'll be imported as part of the module. You could access these by writing
import library.module