File: porting_from_simpy2.rst

package info (click to toggle)
python-simpy3 3.0.11-3
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 1,080 kB
  • sloc: python: 2,885; makefile: 138
file content (322 lines) | stat: -rw-r--r-- 9,934 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
.. _porting_from_simpy2:

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


Imports
=======

In SimPy 2, you had to decide at import-time whether you wanted to use a normal
simulation (``SimPy.Simulation``), a real-time simulation
(``SimPy.SimulationRT``) or something else. You usually had to import
``Simulation`` (or ``SimulationRT``), ``Process`` and some of the SimPy
keywords (``hold`` or ``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
import :mod:`simpy`.


**SimPy 2**

.. code-block:: python

    from Simpy.Simulation import Simulation, Process, hold


**SimPy 3**

.. code-block:: python

    import simpy


The ``Simulation*`` classes
===========================

SimPy 2 encapsulated the simulation state in a ``Simulation*`` class (e.g.,
``Simulation``, ``SimulationRT`` or ``SimulationTrace``). This
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
pass the ``Simulation`` instance around when you were using the object-oriented
API but not if you were using the procedural API.

In SimPy 3, an :class:`~simpy.core.Environment` replaces ``Simulation`` and
:class:`~simpy.rt.RealtimeEnvironment` replaces ``SimulationRT``. You always
need to instantiate an environment. There's no more global state.

To execute a simulation, you call the environment's
:meth:`~simpy.core.Environment.run()` method.

**SimPy 2**

.. code-block:: python

    # Procedural API
    from SimPy.Simulation import initialize, simulate

    initialize()
    # Start processes
    simulate(until=10)

.. code-block:: python

    # Object-oriented API
    from SimPy.Simulation import Simulation

    sim = Simulation()
    # Start processes
    sim.simulate(until=10)


**SimPy3**

.. code-block:: python

    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 ``activate()`` function
or the corresponding ``Simulation`` method.

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 :class:`~simpy.events.Process`
instance. The generator usually requires a reference to a
:class:`~simpy.core.Environment` to interact with, but this is completely
optional.

Processes are can be started by creating a :class:`~simpy.events.Process`
instance and passing the generator to it. The environment provides a shortcut
for this: :meth:`~simpy.core.Environment.process()`.

**SimPy 2**

.. code-block:: python

    # 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())


.. code-block:: python

    # 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())


**SimPy 3**

.. code-block:: python

    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 (``hold`` etc.)
==============================

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
from ``SimPy.Simulation*`` if they were used. Internally, the keywords were
mapped to a function that generated the according event.

In SimPy 3, you directly yield :mod:`~simpy.events` if you want to wait for an
event to occur. You can instantiate an event directly or use the shortcuts
provided by :class:`~simpy.core.Environment`.

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 ``hold``
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
:class:`~simpy.events.Timeout` event.

.. note::

    :class:`~simpy.events.Process` is also an :class:`~simpy.events.Event`. If
    you want to wait for a process to finish, simply yield it.


**SimPy 2**

.. code-block:: python

    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


**SimPy 3**

.. code-block:: python

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

The following ``waituntil`` keyword is not completely supported anymore:

.. code-block:: python

    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 ``cond_func`` periodically:

.. code-block:: python

    def waituntil(env, cond_func, delay=1):
        while not cond_func():
            yield env.timeout(delay)

    # Usage:
    yield waituntil(env, cond_func)


Interrupts
==========

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 ``interruptCause`` attribute
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 :meth:`~simpy.events.Process.interrupt()` on the victim
process. You can optionally supply a cause. An
:exc:`~simpy.exceptions.Interrupt` is then thrown into the victim process,
which has to handle the interrupt via ``try: ... except Interrupt: ...``.


**SimPy 2**

.. code-block:: python

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


**SimPy 3**

.. code-block:: python

    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


Conclusion
==========

This guide is by no means complete. If you run into problems, please have
a look at the other :doc:`guides <index>`, the :doc:`examples
<../examples/index>` or the :doc:`../api_reference/index`. You are also very
welcome to submit improvements. Just create a pull request at `bitbucket
<https://bitbucket.org/simpy/simpy/>`_.