22"""
33Taxi simulator
44
5- Sample run with two cars, random seed = 4::
5+ Sample run with two cars, random seed = 4. This is a valid doctest.
66
77 >>> main(num_taxis=2, seed=10)
88 taxi: 0 Event(time=0, proc=0, action='leave garage')
2323 taxi: 1 Event(time=76, proc=1, action='going home')
2424 *** end of events ***
2525
26+ See explanation and longer sample run at the end of this module.
27+
2628"""
2729
2830import sys
@@ -43,18 +45,20 @@ def compute_delay(interval):
4345 """Compute action delay using exponential distribution"""
4446 return int (random .expovariate (1 / interval )) + 1
4547
46-
47- def taxi_process (ident , trips , start_time = 0 ):
48+ # BEGIN TAXI_PROCESS
49+ def taxi_process (ident , trips , start_time = 0 ): # <1>
4850 """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>
5355
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>
5658
57- yield Event (trip_ends + 1 , ident , 'going home' )
59+ yield Event (time + 1 , ident , 'going home' ) # <8>
60+ # <9>
61+ # END TAXI_PROCESS
5862
5963# BEGIN TAXI_SIMULATOR
6064class Simulator :
@@ -129,10 +133,93 @@ def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS,
129133
130134
131135"""
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::
133220
134221# BEGIN TAXI_SAMPLE_RUN
135- $ $ clear; python3 taxi_sim.py -t 3 -s 19
222+ $ clear; python3 taxi_sim.py -t 3 -s 19
136223taxi: 0 Event(time=0, proc=0, action='leave garage')
137224taxi: 0 Event(time=5, proc=0, action='pick up passenger')
138225taxi: 1 Event(time=10, proc=1, action='leave garage')
0 commit comments