The first thing we will do is set up an interface for the controller and the
threads to communicate between each other. We will have one event that will
say if the program is running. This event will be shared by all the threads,
when it is cleared, that will signal each thread to exit. We will need another
event for each thread to tell them when to run and when to stop. We will call
this event simulate
. These events will need to be passed to the subjects
before they run, so preferably at creation. Once the events have been created
and initialised, and the subjects have been created, they can be started.
<MD Setup>= running = self.running = threading.Event() running.set() simulate = threading.Event() self.subject = self.simulator = Simulate.Simulate(running, simulate) self.simulator.start() simulate = threading.Event() self.minimise = Minimise.Minimise(running, simulate) self.minimise.start() self.vpRenderer = VPRenderer.VPRenderer() self.textRenderer = TextRenderer.TextRenderer()
This code is written to a file (or else not used).
Now we need to redefine the start callback. The start button has now become a start/stop button, so the first thing it needs to do is determine whether it is stopping the simulation, or starting the simulation. It does this by checking whether the current subjects simulate event is set. If it is set, all the callback must to do is clear it, thus stopping the simulation.
If however the current subjects simulate event is not set, then it needs to
start the simulation. It does this by simply calling set()
, and the
subject will start simulating.
<Start Callback>= def startClicked(self, widget, data=None): if self.subject.simulate.isSet(): self.subject.simulate.clear() else: if self.function.get_history() == 0: self.subject = self.simulator elif self.function.get_history() == 1: self.subject = self.minimise try: n = int(self.nside.get_text()) self.subject.nside = (n, n, n) r = float(self.rside.get_text()) self.subject.rside = (r, r, r) self.subject.tstep = float(self.tstep.get_text()) except ValueError: pass else: self.vpRenderer.setSubject(self.subject) self.textRenderer.setSubject(self.subject) self.subject.simulate.set()
This code is written to a file (or else not used).
Quitting from the program is something that needs to be taken great care of.
Problems can arise when threads are already waiting, or if they run into a wait
statement on an unset event. Firstly, we clear the running event. Secondly,
we need to set all the simulate events for each subject, so as to ensure that
no threads are waiting for them to be set. Finally, we use the join()
statement on each thread to ensure that each thread exits. The join()
statement won't return until the run()
routine of that thread has finished.
<Quit Callback>= def quit(self, widget, data=None): self.running.clear() self.simulator.simulate.set() self.minimise.simulate.set() self.simulator.join() self.minimise.join() gtk.main_quit()
This code is written to a file (or else not used).
As each subject will be threaded, it is best to do as much of the
implementation as possible in the subject class. The subject class itself will
inherit from Thread
, and it should also receive the events and store them.
<Subject.py>= import threading class Subject(threading.Thread): def __init__(self, running, simulate): threading.Thread.__init__(self) self.running = running self.simulate = simulate self.observers = [] self.state = "" self.atoms = [] self.totalPotential = 0 self.totalKinetic = 0 self.totalEnergy = 0 self.nside = 0 self.rside = 0 <Subject Methods>
This code is written to a file (or else not used).
The use of the events to start and stop the simulation needs to be incorporated
into each subject as follows. Notice the self.simulate.wait()
is placed
both before the main running loop, and as the last statement in the main
running loop. This ensures that no code is executed that we don't want
executed after the running event is cleared.
<Minimise.py>= from Subject import Subject import math class Minimise(Subject): def run(self): self.simulate.wait() while self.running.isSet(): tol = 0.01 self.atoms = self.createSimpleCube(self.rside, self.nside) <Initialise Velocity> self.force = self.evalForce(self.atoms) self.totalPotential = self.evalPotential(self.atoms) self.totalEnergy = self.totalPotential self.state = "initialise" self.notify() self.state = "run" forcetol = 0.01 * len(self.atoms) totalForce = forcetol + 1 while (self.running.isSet() and self.simulate.isSet() and totalForce > forcetol): alpha = self.goldenSectionSearch(self.atoms, self.force, tol) self.atoms = self.nextAtoms(self.atoms, alpha, self.force) oldPotential = self.totalPotential self.force = self.evalForce(self.atoms) tol = abs((self.totalPotential - oldPotential) / self.totalPotential) * 10 totalForce = 0 for f in self.force: totalForce = totalForce + f[0] ** 2 + f[1] ** 2 + f[2] ** 2 self.totalEnergy = self.totalPotential self.notify() print "The minimum potential is %e" % self.totalPotential self.simulate.clear() self.simulate.wait() <Evaluate Force> <Evaluate Potential> <Create Simple Cube> <Golden Section Search> <Next Atoms>
This code is written to a file (or else not used).
<Simulate.py>= from Subject import Subject import math class Simulate(Subject): def __init__(self, running, simulate): self.tstep = 0.01 Subject.__init__(self, running, simulate) def run(self): self.simulate.wait() while self.running.isSet(): self.atoms = self.createSimpleCube(self.rside, self.nside) <Initialise Velocity> newForce = oldForce = self.evalForce(self.atoms) self.totalEnergy = self.totalPotential + self.totalKinetic self.state = "initialise" self.notify() self.state = "run" while self.simulate.isSet() and self.running.isSet(): <Update Coordinates> newForce = self.evalForce(self.atoms) <Update Velocity> oldForce = newForce self.totalEnergy = self.totalPotential + self.totalKinetic self.notify() self.simulate.wait() <Evaluate Force> <Create Simple Cube>
This code is written to a file (or else not used).