next up previous
Next: Exercises Up: Adding a Graphical User Previous: Threading

Running the GTK main loop and simulation loop simultaneously

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).

*


next up previous
Next: Exercises Up: Adding a Graphical User Previous: Threading
James Roper 2004-02-12