2
2
"""
3
3
Taxi simulator
4
4
5
- Sample run with two cars, random seed = 4::
5
+ Sample run with two cars, random seed = 4. This is a valid doctest.
6
6
7
7
>>> main(num_taxis=2, seed=10)
8
8
taxi: 0 Event(time=0, proc=0, action='leave garage')
23
23
taxi: 1 Event(time=76, proc=1, action='going home')
24
24
*** end of events ***
25
25
26
+ See explanation and longer sample run at the end of this module.
27
+
26
28
"""
27
29
28
30
import sys
@@ -43,18 +45,20 @@ def compute_delay(interval):
43
45
"""Compute action delay using exponential distribution"""
44
46
return int (random .expovariate (1 / interval )) + 1
45
47
46
-
47
- def taxi_process (ident , trips , start_time = 0 ):
48
+ # BEGIN TAXI_PROCESS
49
+ def taxi_process (ident , trips , start_time = 0 ): # <1>
48
50
"""Yield to simulator issuing event at each state change"""
49
- time = yield Event (start_time , ident , 'leave garage' )
50
- for i in range (trips ):
51
- prowling_ends = time + compute_delay (SEARCH_INTERVAL )
52
- time = yield Event (prowling_ends , ident , 'pick up passenger' )
51
+ time = yield Event (start_time , ident , 'leave garage' ) # <2>
52
+ for i in range (trips ): # <3>
53
+ prowling_ends = time + compute_delay (SEARCH_INTERVAL ) # <4>
54
+ time = yield Event (prowling_ends , ident , 'pick up passenger' ) # <5>
53
55
54
- trip_ends = time + compute_delay (TRIP_DURATION )
55
- time = yield Event (trip_ends , ident , 'drop off passenger' )
56
+ trip_ends = time + compute_delay (TRIP_DURATION ) # <6>
57
+ time = yield Event (trip_ends , ident , 'drop off passenger' ) # <7>
56
58
57
- yield Event (trip_ends + 1 , ident , 'going home' )
59
+ yield Event (time + 1 , ident , 'going home' ) # <8>
60
+ # <9>
61
+ # END TAXI_PROCESS
58
62
59
63
# BEGIN TAXI_SIMULATOR
60
64
class Simulator :
@@ -129,10 +133,93 @@ def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
129
133
130
134
131
135
"""
132
- Sample run:
136
+ Notes for the ``taxi_process`` coroutine::
137
+
138
+ <1> `taxi_process` will be called once per taxi, creating a generator
139
+ object to represent its operations. `ident` is the number of the taxi
140
+ (eg. 0, 1, 2 in the sample run); `trips` is the number of trips this
141
+ taxi will make before going home; `start_time` is when the taxi
142
+ leaves the garage.
143
+
144
+ <2> The first `Event` yielded is `'leave garage'`. This suspends the
145
+ coroutine, and lets the simulation main loop proceed to the next
146
+ scheduled event. When it's time to reactivate this process, the main
147
+ loop will `send` the current simulation time, which is assigned to
148
+ `time`.
149
+
150
+ <3> This block will be repeated once for each trip.
151
+
152
+ <4> The ending time of the search for a passenger is computed.
153
+
154
+ <5> An `Event` signaling passenger pick up is yielded. The coroutine
155
+ pauses here. When the time comes to reactivate this coroutine,
156
+ the main loop will again `send` the current time.
157
+
158
+ <6> The ending time of the trip is computed, taking into account the
159
+ current `time`.
160
+
161
+ <7> An `Event` signaling passenger drop off is yielded. Coroutine
162
+ suspended again, waiting for the main loop to send the time of when
163
+ it's time to continue.
164
+
165
+ <8> The `for` loop ends after the given number of trips, and a final
166
+ `'going home'` event is yielded, to happen 1 minute after the current
167
+ time. The coroutine will suspend for the last time. When reactivated,
168
+ it will be sent the time from the simulation main loop, but here I
169
+ don't assign it to any variable because it will not be useful.
170
+
171
+ <9> When the coroutine falls off the end, the coroutine object raises
172
+ `StopIteration`.
173
+
174
+
175
+ Notes for the ``Simulator.run`` method::
176
+
177
+ <1> The simulation `end_time` is the only required argument for `run`.
178
+
179
+ <2> Use `sorted` to retrieve the `self.procs` items ordered by the
180
+ integer key; we don't care about the key, so assign it to `_`.
181
+
182
+ <3> `next(proc)` primes each coroutine by advancing it to the first
183
+ yield, so it's ready to be sent data. An `Event` is yielded.
184
+
185
+ <4> Add each event to the `self.events` `PriorityQueue`. The first
186
+ event for each taxi is `'leave garage'`, as seen in the sample run
187
+ (ex_taxi_process>>).
188
+
189
+ <5> Main loop of the simulation: run until the current `time` equals
190
+ or exceeds the `end_time`.
191
+
192
+ <6> The main loop may also exit if there are no pending events in the
193
+ queue.
194
+
195
+ <7> Get `Event` with the smallest `time` in the queue; this is the
196
+ `current_event`.
197
+
198
+ <8> Display the `Event`, identifying the taxi and adding indentation
199
+ according to the taxi id.
200
+
201
+ <9> Update the simulation time with the time of the `current_event`.
202
+
203
+ <10> Retrieve the coroutine for this taxi from the `self.procs`
204
+ dictionary.
205
+
206
+ <11> Send the `time` to the coroutine. The coroutine will yield the
207
+ `next_event` or raise `StopIteration` it's finished.
208
+
209
+ <12> If `StopIteration` was raised, delete the coroutine from the
210
+ `self.procs` dictionary.
211
+
212
+ <13> Otherwise, put the `next_event` in the queue.
213
+
214
+ <14> If the loop exits because the simulation time passed, display the
215
+ number of events pending (which may be zero by coincidence,
216
+ sometimes).
217
+
218
+
219
+ Sample run from the command line::
133
220
134
221
# BEGIN TAXI_SAMPLE_RUN
135
- $ $ clear; python3 taxi_sim.py -t 3 -s 19
222
+ $ clear; python3 taxi_sim.py -t 3 -s 19
136
223
taxi: 0 Event(time=0, proc=0, action='leave garage')
137
224
taxi: 0 Event(time=5, proc=0, action='pick up passenger')
138
225
taxi: 1 Event(time=10, proc=1, action='leave garage')
0 commit comments