# Anonymous functions

In the previous tutorial, we created module, PyParticles, that allowed to us to recreate our particle simulation relatively easily. However, before we can use this module to create a range of different simulations, we need to be able to choose which behaviours the particles exhibit. In this tutorial, we will:

• Introduce lambda for creating anonymous functions
• Give the Environment class a dictionary of functions we can choose from

Like the previous tutorial, this one's not specific for Pygame. It introduces Python's anonymous functions, which are quite an advanced programming technique. It took a while to work out how and when to use them, but I think we now have a prefect example of how they can be useful. As usual, the final code is at the bottom of this post.

## Particle functions

At present the Particle's `move()` function is responsible for changes in velocity due to gravity (or not if you commented it out) and drag. In the next tutorial, we'll model a cloud of gas in space, so we don't want to include drag or a gravitational force pulling all particles in the same direction (nor do we want the particle to bounce off the boundary of the screen). We therefore need to make these functions independent. To start with, move the code controlling gravity out of the `move()` function into a separate `accelerate()` function within the Particle class:

```def accelerate(self, vector):
(self.angle, self.speed) = addVectors((self.angle, self.speed), vector)```

We don't necessarily need to move `drag()` into a separate function because by setting the `mass_of_air` variable to 0, drag becomes 1, so there's no reduction in speed. However, it's inefficient to multiply each particle's speed by 1 every tick of the simulation; it would be better if we could ignore drag unless specified otherwise. If we create a separate `drag()` function, then we could rewrite the Environment `update()` method like so:

```def update(self):
for particle in self.particles:
particle.move()
if self.mass_of_air != 0:
particle.experienceDrag()
if self.acceleration:
particle.accelerate(self.acceleration)
if self.hasBoundaries:
self.bounce(particle)```

This `update()` method tests whether to we should bother calculating drag. It also test whether to accelerate particles due to some constant force, such as gravity, which would be set as a vector belonging to the Environment class called `self.acceleration`. Finally it tests whether the particles should bounce off the walls or continue off into space. This is determined by a boolean (i.e. true or false), `self.hasBoundaries`.

The problem is that now we testing whether we should call these various functions for each of the particles, every tick of the simulation. If we have 100 particles, then that's 300 `if` statements every tick! We could improve the efficiency somewhat (to 3 `if` statements per tick) by putting each of the calls in a separate loop, with the test outside, like so:

```for particle in self.particles:
particle.move()

if self.mass_of_air != 0:
for particle in self.particles:
particle.experienceDrag()

if self.acceleration:
for particle in self.particles:
particle.accelerate(self.acceleration)

if self.hasBoundaries:
for particle in self.particles:
self.bounce(particle)```

However, we still have to carry out each of the tests each tick of the simulation and the code is just inelegant. Ideally we should only have to carry out the test once at the start of the simulation.

## Variable functions

My solution to this problem (and others probably exist), is to give the Environment object a list of the particle functions we want to use (move, drag, bounce etc.), and call each of them for each particle:

```for particle in self.particles:
for f in self.particle_functions:
f(particle)```

But how do we get our functions into a list? We can't just type:

`self.particle_functions = [particle.experienceDrag, self.bounce(particle)]`

We want to define the function list at the beginning of the simulation, before we've even defined `particle`. Even if we had defined the Environment object's list of particles, we need some way to refer each specific particle object. Furthermore, if we add `self.bounce(particle)`to the list, what we actual add is the result of calling `self.bounce(particle)`, which is `None` because the function doesn't return anything.

The solution is to use the lamdba function to create anonymous functions. For convenience, I put the functions in a dictionary, so they can be referred to easily. To the Environment class's `__init__()` function, add:

```self.particle_functions = []
self.function_dict = {
'move': lambda p: p.move(),
'drag': lambda p: p.experienceDrag(),
'bounce': lambda p: self.bounce(p),
'accelerate': lambda p: p.accelerate(self.acceleration)}```

Lambda allows us to create a single line function without a name. Just as with normal functions they can take a parameter, which is given before the colon. For example, if we now type:

`self.function_dict['move'](self.particles[0])`

We will call `lambda p: p.move()` with the first particle in the Environment object's particle as the parameter. This will take the particle object (referred to as p in the lambda function) and call its `move()` function.

We can now give the Environment class a function to add specific functions to its `particle_functions` list.

```def addFunctions(self, function_list):
for f in function_list:
if f in self.function_dict:
self.particle_functions.append(self.function_dict[f])
else:
print "No such function: %s" % f```

Now, when we start a new simulation, it's incredibly simple to define which functions to include:

```env = PyParticles.Environment((width, height))
env.acceleration = (math.pi, 0.002)```

When the Environment's `update()` function is called, it will now call the move, accelerate and drag functions for each of its particles.

## Two-particle functions

Sadly, the situation is not quite so simple, because we should also like to define which two-particle functions are called. For example, we might not want the particles to collide and in the next tutorial we'll want to add an `attract()` function which will take two particles as parameters. The following adaptation is required:

```self.particle_functions1 = []
self.particle_functions2 = []
self.function_dict = {
'move': (1, lambda p: p.move()),
'drag': (1, lambda p: p.experienceDrag()),
'bounce': (1, lambda p: self.bounce(p)),
'accelerate': (1, lambda p: p.accelerate(self.acceleration)),
'collide': (2, lambda p1, p2: collide(p1, p2))}

for func in function_list:
(n, f) = self.function_dict.get(func, (-1, None))
if n == 1:
self.particle_functions1.append(f)
elif n == 2:
self.particle_functions2.append(f)
else:
print "No such function: %s" % func```

Now the function dictionary values are a tuple that indicates whether the function takes 1 or 2 parameters, which we use to determine which of two function lists to add the function to. The Environment `update()` function should now be changed to:

```def update(self):
for i, particle in enumerate(self.particles):
for f in self.particle_functions1:
f(particle)
for particle2 in self.particles[i+1:]:
for f in self.particle_functions2:
f(particle, particle2)```

Now the `collide()` function can be included or ignored in the same way as for the other particle functions. The following tutorial will have an example of how our updated PyParticles module can be used.

## Final note on efficiency

Note, our simulation still isn't as efficient as it could be since if we want to move the particles and make them experience drag, then we must make two function calls instead of one as before. Furthermore, we carry out the nested loop in the `update()` function regardless of whether we include any two-particle functions. This is a limitation of writing flexible code. If processing speed becomes an issue, then it's best to write a custom program. However, the PyParticles module is a good place to start prototyping a simulation and testing parameters; you can start weeding out further inefficiencies once you've got an interesting simulation running. PyParticles should be efficient enough to get you started.

These last two tutorial may have seemed like a lot of work to essentially get back where we started (and if you made it this far then I'm seriously impressed), but hopefully the following tutorials will demonstrate just how flexible our code now is. We can now use our moduel to create a range of apparently very different programs with relatively little effort. We'll start in the next tutorial, by making a simulation of a gas cloud collasping under its own gravity to form a solar system.

AttachmentSize
PyParticles2.txt5.89 KB