{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# 4. Advanced Libraries\n", "\n", "### Making a module\n", "\n", "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.\n", "\n", "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "# coding: utf-8\n", "from __future__ import division, print_function\n", "from vpython import *\n", "import numpy as np\n", "import copy\n", "\n", "class PhysicsError (Exception):\n", " \"\"\"\n", " Error type defined for if two Particles get too close to simulate well\n", " \"\"\"\n", " def __init__(self, exception_type):\n", " self.exception_type = exception_type\n", " def __str__(self):\n", " return repr(self.exception_type)\n", "\n", "class Particle(sphere):\n", " \"\"\"\n", " Class which describes a Particle under the influence of some force. Subclasses vpython sphere so drawing is no effort.\n", " \"\"\"\n", "\n", " G = 1\n", " def __init__(self,pos = vector(0,0,0), velocity = vector(0,0,0), mass = 0.0, radius =0.0, color = color.red):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " pos : vpython vector\n", " Initial position of Particle\n", " velocity : vpython vector\n", " Initial velocity of Particle\n", " mass: float\n", " Mass of Particle (default = 0)\n", " radius : float\n", " Radius of Particle\n", " color: vpython color\n", " Color of particle\n", " \"\"\"\n", " sphere.__init__(self,pos = pos, velocity = velocity, radius = radius, make_trail = True, color = color)\n", " self.velocity = velocity\n", " self.mass = mass\n", " def force_felt_by(self,other,if_at = None):\n", " '''\n", " Parameters\n", " ----------\n", " other: Particle\n", " The particle which feels the force\n", " if_at: vpython vector\n", " If this parameter is used, the function gives the force the 'other' particle would feel if it were at this position\n", "\n", " Subclass Particle and change this to implement custom forces, then everything else should work.\n", " Default(The one implemented here) is gravitational\n", " '''\n", " if not if_at:\n", " if_at = other.pos\n", " position_difference = if_at - self.pos\n", " determinant = position_difference.mag\n", " if determinant == 0:\n", " return vector(0,0,0)\n", " velocity_determinant = self.velocity.mag\n", " g_force_scalar = (-1*self.G*self.mass*other.mass)/(determinant**3)\n", " g_force_vector = g_force_scalar * position_difference\n", " if determinant < self.radius + other.radius:\n", " raise PhysicsError(\"Collision \")\n", " return g_force_vector\n", "\n", " def increment_by(self,pos_increment, velocity_increment):\n", " '''\n", " Function to increment coordinates and velocity at the same time.\n", " Parameters\n", " ----------\n", " pos_increment: vpython vector\n", " Increment for pos\n", " velocity_increment: vpython vector\n", " Increment for velocity\n", " '''\n", " self.pos += pos_increment\n", " self.velocity += velocity_increment\n", "\n", "class System (object):\n", " \"\"\"Class which describes a system composed of a number of Particles.\"\"\"\n", " def __init__(self,dt,G = 1):\n", " \"\"\"\n", " Parameters\n", " ----------\n", " dt: float\n", " Specifies time increments to take\n", " G: float\n", " Gravitational constant, set to 1 by default\n", " \"\"\"\n", " planets = []\n", " self.dt = dt\n", " self.G = G\n", "\n", " def runge_kutta_move_time(self):\n", " \"\"\"Move time forwards by one step using RK4. Assumes gravitational field doesn't change significantly with time during one time step.\"\"\"\n", " old_planets = self.planets\n", " try:\n", " for counter_1,planet_1 in enumerate(self.planets):\n", " k1 = vector(0,0,0)\n", " k2 = vector(0,0,0)\n", " k3 = vector(0,0,0)\n", " k4 = vector(0,0,0)\n", " for counter_2,planet_2 in enumerate(old_planets):\n", " if counter_2 != counter_1:\n", " k1 += planet_2.force_felt_by(planet_1, if_at = None)/planet_1.mass\n", " imagpos = planet_1.pos + (self.dt/2)*k1\n", " for counter_2,planet_2 in enumerate(old_planets):\n", " if counter_2 != counter_1:\n", " k2 += planet_2.force_felt_by(planet_1, if_at = imagpos)/planet_1.mass\n", " imagpos = planet_1.pos + (self.dt/2)*k2\n", " for counter_2,planet_2 in enumerate(old_planets):\n", " if counter_2 != counter_1:\n", " k3 += planet_2.force_felt_by(planet_1, if_at = imagpos)/planet_1.mass\n", " imagpos = planet_1.pos + (self.dt)*k3\n", " for counter_2,planet_2 in enumerate(old_planets):\n", " if counter_2 != counter_1:\n", " k3 += planet_2.force_felt_by(planet_1, if_at = imagpos)/planet_1.mass\n", " x_increment = planet_1.velocity*self.dt\n", " v_increment = (self.dt/6)*(k1 + 2*k2 + 2*k3 + k4)\n", " self.planets[counter_1].increment_by(x_increment,v_increment)\n", " except PhysicsError:\n", " print(\"Collision\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can now import the module and use it." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "import OrbitModule as om\n", "from vpython import *\n", "\n", "scene1 = canvas(title = \"Orbits!\")\n", "scene1.caption = \"\"\"Right button drag or Ctrl-drag to rotate \"camera\" to view scene.\n", "To zoom, drag with middle button or Alt/Option depressed, or use scroll wheel.\n", " On a two-button mouse, middle is left + right.\n", "Touch screen: pinch/extend to zoom, swipe or two-finger rotate.\"\"\"\n", "scene1.forward = vector(0,0,1)\n", "# Initialise 3 planets\n", "giant_planet = om.Particle(pos = vector(-10.,0.,0.),\n", " velocity = vector(0., 0., 0.), \n", " mass = 200, radius = 5, color = color.blue)\n", "dwarf_planet = om.Particle(pos = vector(15.,0.,0.), \n", " velocity = vector(0., 0., 3.25), \n", " mass = 10, radius = 5, color = color.green)\n", "really_big_planet = om.Particle(pos = vector(-100,-200,0), \n", " velocity = vector(3, 0, 0), \n", " mass = 2000, radius = 20)\n", "dt = 0.1\n", "system = om.System(dt)\n", "planets_array = [giant_planet, dwarf_planet, really_big_planet]\n", "system.planets = planets_array\n", "# Step the system's time forwards 50 times a second.\n", "while True:\n", " rate(50)\n", " system.runge_kutta_move_time()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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.\n", "\n", "### Libraries\n", "\n", "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 \n", "\n", "import library.module" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.5.1" } }, "nbformat": 4, "nbformat_minor": 0 }