1============
2Environments
3============
4
5.. currentmodule:: simpy.core
6
7A simulation environment manages the simulation time as well as the scheduling
8and processing of events. It also provides means to step through or execute the
9simulation.
10
11The base class for all environments is :class:`~simpy.core.BaseEnvironment`.
12"Normal" simulations usually use its subclass
13:class:`~simpy.core.Environment`. For real-time simulations, SimPy provides a
14:class:`~simpy.rt.RealtimeEnvironment` (more on that in
15:doc:`real-time-simulations`).
16
17
18.. _simulation-control:
19
20Simulation control
21==================
22
23SimPy is very flexible in terms of simulation execution. You can run your
24simulation until there are no more events, until a certain simulation time is
25reached, or until a certain event is triggered. You can also step through the
26simulation event by event. Furthermore, you can mix these things as you like.
27
28For example, you could run your simulation until an interesting event occurs.
29You could then step through the simulation event by event for a while; and
30finally run the simulation until there are no more events left and your processes
31have all terminated.
32
33The most important method here is :meth:`Environment.run()`:
34
35- If you call it without any argument (``env.run()``), it steps through the
36  simulation until there are no more events left.
37
38  .. warning::
39
40     If your processes run forever (``while True: yield env.timeout(1)``), this
41     method will never terminate (unless you kill your script by e.g., pressing
42     :kbd:`Ctrl-C`).
43
44- In most cases it is advisable to stop your simulation when it reaches
45  a certain simulation time. Therefore, you can pass the desired time via the
46  *until* parameter, e.g.: ``env.run(until=10)``.
47
48  The simulation will then stop when the internal clock reaches 10 but will not
49  process any events scheduled for time 10. This is similar to a new
50  environment where the clock is 0 but (obviously) no events have yet been
51  processed.
52
53  If you want to integrate your simulation in a GUI and want to draw a
54  process bar, you can repeatedly call this function with increasing *until*
55  values and update your progress bar after each call:
56
57  .. code-block:: python
58
59     for i in range(100):
60         env.run(until=i)
61         progressbar.update(i)
62
63- Instead of passing a number to ``run()``, you can also pass any event to it.
64  ``run()`` will then return when the event has been processed.
65
66  Assuming that the current time is 0, ``env.run(until=env.timeout(5))`` is
67  equivalent to ``env.run(until=5)``.
68
69  You can also pass other types of events (remember, that
70  a :class:`~simpy.events.Process` is an event, too)::
71
72     >>> import simpy
73     >>>
74     >>> def my_proc(env):
75     ...     yield env.timeout(1)
76     ...     return 'Monty Python’s Flying Circus'
77     >>>
78     >>> env = simpy.Environment()
79     >>> proc = env.process(my_proc(env))
80     >>> env.run(until=proc)
81     'Monty Python’s Flying Circus'
82
83.. _simulation-step:
84
85To step through the simulation event by event, the environment offers
86:meth:`~Environment.peek()` and :meth:`~Environment.step()`.
87
88``peek()`` returns the time of the next scheduled event or *infinity*
89(``float('inf')``) if no future events are scheduled.
90
91``step()`` processes the next scheduled event. It raises an
92:class:`EmptySchedule` exception if no event is available.
93
94In a typical use case, you use these methods in a loop like:
95
96.. code-block:: python
97
98   until = 10
99   while env.peek() < until:
100      env.step()
101
102
103State access
104============
105
106The environment allows you to get the current simulation time via the
107:attr:`Environment.now` property. The simulation time is a number without unit
108and is increased via :class:`~simpy.events.Timeout` events.
109
110By default, ``now`` starts at 0, but you can pass an ``initial_time`` to the
111:class:`Environment` to use something else.
112
113.. note::
114
115   Although the simulation time is technically unitless, you can pretend that
116   it is, for example, in seconds and use it like a timestamp returned by
117   :func:`time.time()` to calculate a date or the day of the week.
118
119The property :attr:`Environment.active_process` is comparable to
120:func:`os.getpid()` and is either ``None`` or pointing at the currently active
121:class:`~simpy.events.Process`. A process is *active* when its process function
122is being executed. It becomes *inactive* (or suspended) when it yields an
123event.
124
125Thus, it only makes sense to access this property from within a process
126function or a function that is called by your process function::
127
128   >>> def subfunc(env):
129   ...     print(env.active_process)  # will print "p1"
130   >>>
131   >>> def my_proc(env):
132   ...     while True:
133   ...         print(env.active_process)  # will print "p1"
134   ...         subfunc(env)
135   ...         yield env.timeout(1)
136   >>>
137   >>> env = simpy.Environment()
138   >>> p1 = env.process(my_proc(env))
139   >>> env.active_process  # None
140   >>> env.step()
141   <Process(my_proc) object at 0x...>
142   <Process(my_proc) object at 0x...>
143   >>> env.active_process  # None
144
145An exemplary use case for this is the resource system: If a process function
146calls :meth:`~simpy.resources.resource.Resource.request()` to request
147a resource, the resource determines the requesting process via
148``env.active_process``. Take a `look at the code`__ to see how we do this :-).
149
150__ https://gitlab.com/team-simpy/simpy/-/blob/master/src/simpy/resources/base.py
151
152
153Event creation
154==============
155
156To create events, you normally have to import :mod:`simpy.events`, instantiate
157the event class and pass a reference to the environment to it. To reduce the
158amount of typing, the :class:`Environment` provides some shortcuts for event
159creation. For example, :meth:`Environment.event()` is equivalent to
160``simpy.events.Event(env)``.
161
162Other shortcuts are:
163
164- :meth:`Environment.process()`
165- :meth:`Environment.timeout()`
166- :meth:`Environment.all_of()`
167- :meth:`Environment.any_of()`
168
169More details on what the events do can be found in the :doc:`guide to events
170<events>`.
171
172
173Miscellaneous
174=============
175
176Since Python 3.3, a generator function can have a return value:
177
178.. code-block:: python
179
180   def my_proc(env):
181       yield env.timeout(1)
182       return 42
183
184In SimPy, this can be used to provide return values for processes that can be
185used by other processes:
186
187.. code-block:: python
188
189   def other_proc(env):
190       ret_val = yield env.process(my_proc(env))
191       assert ret_val == 42
192
193Internally, Python passes the return value as parameter to the
194:exc:`StopIteration` exception that it raises when a generator is exhausted. So
195in Python 2.7 and 3.2 you could replace the ``return 42`` with a ``raise
196StopIteration(42)`` to achieve the same result.
197
198To keep your code more readable, the environment provides the method
199:meth:`~Environment.exit()` to do exactly this:
200
201.. code-block:: python
202
203   def my_proc(env):
204       yield env.timeout(1)
205       env.exit(42)  # Py2 equivalent to "return 42"
206