Porting from SimPy 2 to 3¶
Porting from SimPy 2 to SimPy 3 is not overly complicated. A lot of changes merely comprise copy/paste.
This guide describes the conceptual and API changes between both SimPy versions and shows you how to change your code for SimPy 3.
In SimPy 2, you had to decide at import-time whether you wanted to use a normal
SimPy.Simulation), a real-time simulation
SimPy.SimulationRT) or something else. You usually had to import
Process and some of the SimPy
passivate, for example) from that package.
In SimPy 3, you usually need to import much less classes and modules (for
example, all keywords are gone). In most use cases you will now only need to
from Simpy.Simulation import Simulation, Process, hold
SimPy 2 encapsulated the simulation state in a
Simulation* class (e.g.,
class also had a
simulate() method that executed a normal simulation,
a real-time simulation or something else (depending on the particular class).
There was a global
Simulation instance that was automatically created when
you imported SimPy. You could also instantiate it on your own to uses SimPy’s
object-orient API. This led to some confusion and problems, because you had to
Simulation instance around when you were using the object-oriented
API but not if you were using the procedural API.
To execute a simulation, you call the environment’s
# Procedural API from SimPy.Simulation import initialize, simulate initialize() # Start processes simulate(until=10)
# Object-oriented API from SimPy.Simulation import Simulation sim = Simulation() # Start processes sim.simulate(until=10)
import simpy env = simpy.Environment() # Start processes env.run(until=10)
Defining a Process¶
Processes had to inherit the
Process base class in SimPy 2. Subclasses had
to implement at least a so called Process Execution Method (PEM) (which is
basically a generator function) and in most cases
__init__(). Each process
needed to know the
Simulation instance it belonged to. This reference was
passed implicitly in the procedural API and had to be passed explicitly in the
object-oriented API. Apart from some internal problems, this made it quite
cumbersome to define a simple process.
Processes were started by passing the
Process and a generator instance
created by the generator function to either the global
or the corresponding
A process in SimPy 3 is a Python generator (no matter if it’s defined on module
level or as an instance method) wrapped in a
instance. The generator usually requires a reference to a
Environment to interact with, but this is completely
# Procedural API from Simpy.Simulation import Process class MyProcess(Process): def __init__(self, another_param): super().__init__() self.another_param = another_param def generator_function(self): """Implement the process' behavior.""" yield something initialize() proc = Process('Spam') activate(proc, proc.generator_function())
# Object-oriented API from SimPy.Simulation import Simulation, Process class MyProcess(Process): def __init__(self, sim, another_param): super().__init__(sim=sim) self.another_param = another_param def generator_function(self): """Implement the process' behaviour.""" yield something sim = Simulation() proc = Process(sim, 'Spam') sim.activate(proc, proc.generator_function())
import simpy def generator_function(env, another_param): """Implement the process' behavior.""" yield something env = simpy.Environment() proc = env.process(generator_function(env, 'Spam'))
SimPy Keywords (
In SimPy 2, processes created new events by yielding a SimPy Keyword and some
additional parameters (at least
self). These keywords had to be imported
SimPy.Simulation* if they were used. Internally, the keywords were
mapped to a function that generated the according event.
Generally, whenever a process yields an event, the execution of the process is
suspended and resumed once the event has been triggered. To motivate this
understanding, some of the events were renamed. For example, the
keyword meant to wait until some time has passed. In terms of events this means
that a timeout has happened. Therefore
hold has been replaced by a
yield hold, self, duration yield passivate, self yield request, self, resource yield release, self, resource yield waitevent, self, event yield waitevent, self, [event_a, event_b, event_c] yield queueevent, self, event_list yield get, self, level, amount yield put, self, level, amount
yield env.timeout(duration) # hold: renamed yield env.event() # passivate: renamed yield resource.request() # Request is now bound to class Resource resource.release() # Release no longer needs to be yielded yield event # waitevent: just yield the event yield env.all_of([event_a, event_b, event_c]) # waitvent yield env.any_of([event_a, event_b, event_c]) # queuevent yield container.get(amount) # Level is now called Container yield container.put(amount) yield event_a | event_b # Wait for either a or b. This is new. yield event_a & event_b # Wait for a and b. This is new. yield env.process(calculation(env)) # Wait for the process calculation to # to finish.
Partially supported features¶
waituntil keyword is not completely supported anymore:
yield waituntil, self, cond_func
SimPy 2 was evaluating
cond_func after every event, which was
computationally very expensive. One possible workaround is for example the
following process, which evaluates
def waituntil(env, cond_func, delay=1): while not cond_func(): yield env.timeout(delay) # Usage: yield waituntil(env, cond_func)
In SimPy 2,
interrupt() was a method of the interrupting process. The
victim of the interrupt had to be passed as an argument.
The victim was not directly notified of the interrupt but had to check if the
interrupted flag was set. Afterwards, it had to reset the interrupt via
interruptReset(). You could manually set the
of the victim.
Explicitly checking for an interrupt is obviously error prone as it is too easy to be forgotten.
In SimPy 3, you call
interrupt() on the victim
process. You can optionally supply a cause. An
Interrupt is then thrown into the victim process,
which has to handle the interrupt via
try: ... except Interrupt: ....
class Interrupter(Process): def __init__(self, victim): super().__init__() self.victim = victim def run(self): yield hold, self, 1 self.interrupt(self.victim_proc) self.victim_proc.interruptCause = 'Spam' class Victim(Process): def run(self): yield hold, self, 10 if self.interrupted: cause = self.interruptCause self.interruptReset()
def interrupter(env, victim_proc): yield env.timeout(1) victim_proc.interrupt('Spam') def victim(env): try: yield env.timeout(10) except Interrupt as interrupt: cause = interrupt.cause