""" Taxi simulator ============== Driving a taxi from the console:: >>> from taxi_sim import taxi_process >>> taxi = taxi_process(ident=13, trips=2, start_time=0) >>> next(taxi) Event(time=0, proc=13, action='leave garage') >>> taxi.send(_.time + 7) Event(time=7, proc=13, action='pick up passenger') >>> taxi.send(_.time + 23) Event(time=30, proc=13, action='drop off passenger') >>> taxi.send(_.time + 5) Event(time=35, proc=13, action='pick up passenger') >>> taxi.send(_.time + 48) Event(time=83, proc=13, action='drop off passenger') >>> taxi.send(_.time + 1) Event(time=84, proc=13, action='going home') >>> taxi.send(_.time + 10) Traceback (most recent call last): File "", line 1, in StopIteration Sample run with two cars, random seed 10. This is a valid doctest:: >>> main(num_taxis=2, seed=10) taxi: 0 Event(time=0, proc=0, action='leave garage') taxi: 0 Event(time=5, proc=0, action='pick up passenger') taxi: 1 Event(time=5, proc=1, action='leave garage') taxi: 1 Event(time=10, proc=1, action='pick up passenger') taxi: 1 Event(time=15, proc=1, action='drop off passenger') taxi: 0 Event(time=17, proc=0, action='drop off passenger') taxi: 1 Event(time=24, proc=1, action='pick up passenger') taxi: 0 Event(time=26, proc=0, action='pick up passenger') taxi: 0 Event(time=30, proc=0, action='drop off passenger') taxi: 0 Event(time=34, proc=0, action='going home') taxi: 1 Event(time=46, proc=1, action='drop off passenger') taxi: 1 Event(time=48, proc=1, action='pick up passenger') taxi: 1 Event(time=110, proc=1, action='drop off passenger') taxi: 1 Event(time=139, proc=1, action='pick up passenger') taxi: 1 Event(time=140, proc=1, action='drop off passenger') taxi: 1 Event(time=150, proc=1, action='going home') *** end of events *** See longer sample run at the end of this module. """ import argparse import collections import random import queue DEFAULT_NUMBER_OF_TAXIS = 3 DEFAULT_END_TIME = 180 SEARCH_DURATION = 5 TRIP_DURATION = 20 DEPARTURE_INTERVAL = 5 Event = collections.namedtuple('Event', 'time proc action') # tag::TAXI_PROCESS[] def taxi_process(ident, trips, start_time=0): # <1> """Yield to simulator issuing event at each state change""" time = yield Event(start_time, ident, 'leave garage') # <2> for i in range(trips): # <3> time = yield Event(time, ident, 'pick up passenger') # <4> time = yield Event(time, ident, 'drop off passenger') # <5> yield Event(time, ident, 'going home') # <6> # end of taxi process # <7> # end::TAXI_PROCESS[] # tag::TAXI_SIMULATOR[] class Simulator: def __init__(self, procs_map): self.events = queue.PriorityQueue() self.procs = dict(procs_map) def run(self, end_time): # <1> """Schedule and display events until time is up""" # schedule the first event for each cab for _, proc in sorted(self.procs.items()): # <2> first_event = next(proc) # <3> self.events.put(first_event) # <4> # main loop of the simulation sim_time = 0 # <5> while sim_time < end_time: # <6> if self.events.empty(): # <7> print('*** end of events ***') break current_event = self.events.get() # <8> sim_time, proc_id, previous_action = current_event # <9> print('taxi:', proc_id, proc_id * ' ', current_event) # <10> active_proc = self.procs[proc_id] # <11> next_time = sim_time + compute_duration(previous_action) # <12> try: next_event = active_proc.send(next_time) # <13> except StopIteration: del self.procs[proc_id] # <14> else: self.events.put(next_event) # <15> else: # <16> msg = '*** end of simulation time: {} events pending ***' print(msg.format(self.events.qsize())) # end::TAXI_SIMULATOR[] def compute_duration(previous_action): """Compute action duration using exponential distribution""" if previous_action in ['leave garage', 'drop off passenger']: # new state is prowling interval = SEARCH_DURATION elif previous_action == 'pick up passenger': # new state is trip interval = TRIP_DURATION elif previous_action == 'going home': interval = 1 else: raise ValueError(f'Unknown previous_action: {previous_action}') return int(random.expovariate(1 / interval)) + 1 def main(end_time=DEFAULT_END_TIME, num_taxis=DEFAULT_NUMBER_OF_TAXIS, seed=None): """Initialize random generator, build procs and run simulation""" if seed is not None: random.seed(seed) # get reproducible results taxis = {i: taxi_process(i, (i + 1) * 2, i * DEPARTURE_INTERVAL) for i in range(num_taxis)} sim = Simulator(taxis) sim.run(end_time) if __name__ == '__main__': parser = argparse.ArgumentParser( description='Taxi fleet simulator.') parser.add_argument('-e', '--end-time', type=int, default=DEFAULT_END_TIME, help='simulation end time; default = %s' % DEFAULT_END_TIME) parser.add_argument('-t', '--taxis', type=int, default=DEFAULT_NUMBER_OF_TAXIS, help='number of taxis running; default = %s' % DEFAULT_NUMBER_OF_TAXIS) parser.add_argument('-s', '--seed', type=int, default=None, help='random generator seed (for testing)') args = parser.parse_args() main(args.end_time, args.taxis, args.seed) """ Sample run from the command line, seed=3, maximum elapsed time=120:: # tag::TAXI_SAMPLE_RUN[] $ python3 taxi_sim.py -s 3 -e 120 taxi: 0 Event(time=0, proc=0, action='leave garage') taxi: 0 Event(time=2, proc=0, action='pick up passenger') taxi: 1 Event(time=5, proc=1, action='leave garage') taxi: 1 Event(time=8, proc=1, action='pick up passenger') taxi: 2 Event(time=10, proc=2, action='leave garage') taxi: 2 Event(time=15, proc=2, action='pick up passenger') taxi: 2 Event(time=17, proc=2, action='drop off passenger') taxi: 0 Event(time=18, proc=0, action='drop off passenger') taxi: 2 Event(time=18, proc=2, action='pick up passenger') taxi: 2 Event(time=25, proc=2, action='drop off passenger') taxi: 1 Event(time=27, proc=1, action='drop off passenger') taxi: 2 Event(time=27, proc=2, action='pick up passenger') taxi: 0 Event(time=28, proc=0, action='pick up passenger') taxi: 2 Event(time=40, proc=2, action='drop off passenger') taxi: 2 Event(time=44, proc=2, action='pick up passenger') taxi: 1 Event(time=55, proc=1, action='pick up passenger') taxi: 1 Event(time=59, proc=1, action='drop off passenger') taxi: 0 Event(time=65, proc=0, action='drop off passenger') taxi: 1 Event(time=65, proc=1, action='pick up passenger') taxi: 2 Event(time=65, proc=2, action='drop off passenger') taxi: 2 Event(time=72, proc=2, action='pick up passenger') taxi: 0 Event(time=76, proc=0, action='going home') taxi: 1 Event(time=80, proc=1, action='drop off passenger') taxi: 1 Event(time=88, proc=1, action='pick up passenger') taxi: 2 Event(time=95, proc=2, action='drop off passenger') taxi: 2 Event(time=97, proc=2, action='pick up passenger') taxi: 2 Event(time=98, proc=2, action='drop off passenger') taxi: 1 Event(time=106, proc=1, action='drop off passenger') taxi: 2 Event(time=109, proc=2, action='going home') taxi: 1 Event(time=110, proc=1, action='going home') *** end of events *** # end::TAXI_SAMPLE_RUN[] """