In [42]:
import matplotlib.pyplot as plt
import numpy as np
%matplotlib inline
You've been using classes all of the time, without even knowing it. Everything in Python is an object. You have some data (number, text, etc.)with some methods (or functions) which are internal to the object, and which you can use on that data. Lets look at a few examples...
In [43]:
a = 10.1
In [44]:
type(a)
Out[44]:
How can I see what methods an object of type float has?
In [45]:
print(dir(a)) # Show all of the methods of a
In [46]:
a.is_integer()
Out[46]:
They're hidden methods - we'll talk more about these later.
Define some key things:
Aside: If you're a C++/Java programmer, 'self' is exactly equivalent to 'this', but functions must have self as an argument, as it is passed in implicitly as the first argument of any method call in Python.
In [47]:
# Create a class by using class keyword followed by name.
class MyClass:
# The 'self' variable ALWAYS needs to be the first variable given to any class method.
def __init__(self, message):
# Here we create a new variable inside "self" called "mess" and save the argument "message"
# passed from the constructor to it.
self.mess = message
def say(self):
print(self.mess)
# Don't normally need to write a destructor - one is created by Python automatically. However we do it here
# just to show you that it can be done:
def __del__(self):
print("Deleting object of type MyClass")
Use the same syntax as we use to call a function, BUT the arguments get passed in to the "__init__" function. Note that you ignore the self object, as Python sorts this out.
In [48]:
a = MyClass("Hello")
print(a.mess)
How do I access data stored in the class? with the ".", followed by the name.
In [49]:
# But, we also defined a method called "say" which does the same thing:
a.say()
What happens though if we reuse the variable name 'a'?
Aside:
In [50]:
print(a)
So, what happens if we either choose to store something else under the name 'a', or tell Python to delte it?
In [51]:
del a
a = MyClass('Hello')
a = 2
In [ ]:
In [52]:
mess = "Hello"
def say(mess):
print(mess)
say(mess)
What information does it need to know about itself?
In [53]:
class Box:
def __init__(self, length):
self.length = length
What we're going to try and do is add particles to the box, which have some properties:
An initial velocity
$r(t + \delta t) \approx r(t) + v(t)\delta t$
In [54]:
class Particle:
def __init__(self, r0, v0):
"""
r0 = initial position
v0 = initial speed
"""
self.r = r0
self.v = v0
def step(self, dt, L):
"""
Move the particle
dt = timestep
L = length of the containing box
"""
self.r = self.r + self.v * dt
if self.r >= L:
self.r -= L
elif self.r < 0:
self.r += L
Lets just check this, if a Particle is in a box of length 10, has r0 = 0, v0=5, then after 1 step of length 3, the position should be at position 5:
In [55]:
p = Particle(0, 5)
p.step(3, 10)
print(p.r)
Lets add a place to store the particles to the box class, and add a method to add particles to the box:
In [56]:
class Box:
def __init__(self, length):
self.length = length
self.particles = []
def add_particle(particle):
self.particles.append(particle)
Tasks (30-40 minutes):
1) Add a method that calculates the average position of Particles in the box (Hint: you might have to think about what to do when there are no particles!)
2) Add a method that makes all of the particles step forwards, and keep track of how much time has passed in the box class.
3) Add a method which plots the current position of the particles in the box.
4) Write a method that writes the current positions and velocities to a CSV file.
5) Write a method that can load a CSV file of positions and velocities, create particles with these and then add them to the Box list of particles. (Hint: Look up the documentation for the module 'csv')
In [ ]:
In [57]:
class Box:
def __init__(self, length):
self.length = length
self.particles = []
self.t = 0
def add_particle(self, particle):
self.particles.append(particle)
def step(self, dt):
for particle in self.particles:
particle.step(dt, self.length)
def write(self, filename):
f = open(filename, 'w')
for particle in self.particles:
f.write('{},{}\n'.format(particle.r, particle.v))
f.close()
def plot(self):
for particle in self.particles:
plt.scatter(particle.r, 0)
def load(self, filename):
f = open(filename, 'r')
csvfile = csv.reader(f)
for position, velocity in csvfile:
p = Particle(position, velocity)
self.add_particle(p)
In [58]:
b = Box(10)
for i in range(10):
p = Particle(i/2, i/3)
b.add_particle(p)
b.write('test.csv')
In [1]:
!cat test.csv
In [59]:
class Particle:
def __init__(self, r0, v0):
"""
r0 = initial position
v0 = initial speed
"""
self._r = r0
self._v = v0
def step(self, dt, L):
"""
Move the particle
dt = timestep
L = length of the containing box
"""
self._r = self._r + self._v * dt
if self._r >= L:
self._r -= L
elif self._r < 0:
self._r += L
@property
def r(self):
return self._r
@r.setter
def r_setter(self, value):
self._r = value
@property
def v(self):
return self._v
@r.setter
def r_setter(self, value):
self._v = value
In [60]:
class SlowParticle(Particle):
def __init__(self, r0, v0, slowing_factor):
Particle.__init__(self, r0, v0)
self.factor = slowing_factor
def step(self, dt, L):
"""
Move the particle, but change so that if the particle bounces off of a wall,
it slows down by 50%
dt = timestep
L = length of the containing box
"""
self._r = self._r + self._v * dt
if self._r >= L:
self._r -= L
self._v /= factor
elif self._r < 0:
self._r += L
self._v /= factor
Remember earlier, when we did:
In [61]:
a = 1.0
print(dir(a))
Notice that there is a method "__add__" - we can define these special methods to allow our class to do things that you can ordinarily do with built in types.
In [81]:
class Box:
def __init__(self, length):
self.length = length
self.particles = []
self.t = 0
def __add__(self, other):
if self.length == other.length:
b = Box(self.length)
for p in self.particles:
b.add_particle(p)
for p in other.particles:
b.add_particle(p)
return b
else:
return ValueError('To add two boxes they must be of the same length')
def mean_position(self):
l = np.sum([p.r for p in self.particles])/len(self.particles)
return l
def add_particle(self, particle):
self.particles.append(particle)
def step(self, dt):
for particle in self.particles:
particle.step(dt, self.length)
def write(self, filename):
f = open(filename, 'w')
for particle in self.particles:
f.write('{},{}\n'.format(particle.r, particle.v))
f.close()
def plot(self):
for particle in self.particles:
plt.scatter(particle.r, 0)
def load(self, filename):
f = open(filename, 'r')
csvfile = csv.reader(f)
for position, velocity in csvfile:
p = Particle(position, velocity)
self.add_particle(p)
def __repr__(self):
if len(self.particles) == 1:
return 'Box containing 1 particle'
else:
return 'Box containing {} particles'.format(len(self.particles))
Now we've created an 'add' method, we can, create two boxes and add these together!
In [63]:
a = Box(10)
a.add_particle(Particle(10, 10))
b = Box(10)
b.add_particle(Particle(15, 10))
c = a + b
print(a)
print(b)
print(c)
In [66]:
a.mean_position(), b.mean_position(), c.mean_position()
Out[66]:
In [67]:
a.step(0.5)
In [68]:
a.mean_position(), b.mean_position(), c.mean_position()
Out[68]:
Why has the mean position of particles in Box C changed? Look at the memory address of the particles:
In [70]:
a.particles, c.particles
Out[70]:
Boxes are pointing to the SAME particles! If we don't want this to happen, we need to write a 'copy' constructor for the class - a function which knows how to create an identical copy of the particle!
We can do this by using the 'deepcopy' function in the 'copy' module, and redefine the particle and slow particle classes:
In [84]:
import copy
class Particle:
def __init__(self, r0, v0):
"""
r0 = initial position
v0 = initial speed
"""
self.r = r0
self.v = v0
def step(self, dt, L):
"""
Move the particle
dt = timestep
L = length of the containing box
"""
self.r = self.r + self.v * dt
if self.r >= L:
self.r -= L
elif self.r < 0:
self.r += L
def copy(self):
return copy.deepcopy(self)
Then, we should change the Box class's 'add' method, to use this copy operation rather than just append the child particles of the existing boxes:
In [85]:
class Box:
def __init__(self, length):
self.length = length
self.particles = []
self.t = 0
def __add__(self, other):
if self.length == other.length:
b = Box(self.length)
for p in self.particles:
b.add_particle(p)
for p in other.particles:
b.add_particle(p)
return b
else:
return ValueError('To add two boxes they must be of the same length')
def mean_position(self):
l = np.sum([p.r for p in self.particles])/len(self.particles)
return l
def add_particle(self, particle):
self.particles.append(particle.copy())
def step(self, dt):
for particle in self.particles:
particle.step(dt, self.length)
def write(self, filename):
f = open(filename, 'w')
for particle in self.particles:
f.write('{},{}\n'.format(particle.r, particle.v))
f.close()
def plot(self):
for particle in self.particles:
plt.scatter(particle.r, 0)
def load(self, filename):
f = open(filename, 'r')
csvfile = csv.reader(f)
for position, velocity, ptype in csvfile:
p = Particle(position, velocity)
self.add_particle(p)
def __repr__(self):
if len(self.particles) == 1:
return 'Box containing 1 particle'
else:
return 'Box containing {} particles'.format(len(self.particles))
In [ ]:
In [86]:
a = Box(10)
a.add_particle(Particle(10, 10))
b = Box(10)
b.add_particle(Particle(15, 10))
c = a + b
print(a)
print(b)
print(c)