M. K. Hunter
M. K. Hunter

Reputation: 1828

Starting a simpy simulation run using a callback

How can I write some simulation code (including waiting for events) from a callback?

Reducing my process to the simplest possible example, I want to start some executable code when an event occurs in my simpy simulation. What I did was set that executable code as a callback for an event. That executable code itself has some simpy event handling in it, specifically a timeout. In my real code, this happens within a process, but this simplified version, where event is in main displays the same problem behavior:

import simpy
if __name__ == '__main__':
    env = simpy.Environment()

    def simulationRun(event):
        print("Starting a run at time [%i]" % env.now)
        yield env.timeout(5)
        print("Ending a run at time [%i]" % env.now)

    event = env.timeout(5)
    event.callbacks.append(simulationRun)

    print("Starting simulation.")
    env.run(20)
    print("End of simulation.")

My expected output is:

Starting simulation.
Starting a run at time [5]
Ending a run at time [10]
End of simulation.

However, my actual output is:

Starting simulation.
End of simulation.

When I remove the yield command, I get:

Starting simulation.
Starting a run at time [5]
Ending a run at time [5]
End of simulation.

Clearly the callback is set correctly but the whole point is to simulate the timeout in simulationRun. How can I run some simulation code (including waiting for an event) in code from a callback?

This uses Python 3.4.2 and simpy.

Upvotes: 0

Views: 1763

Answers (2)

Stefan Scherfke
Stefan Scherfke

Reputation: 3232

You should not work with callbacks directly. Best practise is to use another process that starts the simulation_run() for you:

import simpy


def simulation_run(env):
    print("Starting a run at time [%i]" % env.now)
    yield env.timeout(5)
    print("Ending a run at time [%i]" % env.now)


def starter(env):
    yield env.timeout(5)
    env.process(simulation_run(env))


if __name__ == '__main__':
    env = simpy.Environment()
    env.process(starter(env))
    print("Starting simulation.")
    env.run(20)
    print("End of simulation.")

Since this is a relatively common pattern, we have a utility function built into SimPy which does exactly this:

import simpy
import simpy.util


def simulation_run(env):
    print("Starting a run at time [%i]" % env.now)
    yield env.timeout(5)
    print("Ending a run at time [%i]" % env.now)


if __name__ == '__main__':
    env = simpy.Environment()
    simpy.util.start_delayed(env, simulation_run(env), delay=5)
    print("Starting simulation.")
    env.run(20)
    print("End of simulation.")

Note: You should always pass a reference to the Environment into your processes instead of using the one from the global scope. It may seem tedious, but you can run into any kind of problems if you later restructure or refacor your code and the global env is no longer what you expected or no longer available.

Upvotes: 2

M. K. Hunter
M. K. Hunter

Reputation: 1828

The problem is that the callback is not a process, therefore it is not running in the simpy simulation engine in such a way that it can be stopped and restarted by the engine. What you need is a process. You can make your current simulationRun a process by starting that code from a separate callback function, as shown below.

import simpy  

if __name__ == '__main__':
    env = simpy.Environment()

    def simulationRun():
        print("Starting a run at time [%i]" % env.now)
        yield env.timeout(5)
        print("Ending a run at time [%i]" % env.now)

    def callback(event):
        s = simulationRun()
        env.process(s)

    event = env.timeout(5)
    event.callbacks.append(callback)

    print("Starting simulation.")
    env.run(20)
    print("End of simulation.")

Upvotes: 1

Related Questions