Shared1.2 | Class ID 16123 | 557066.ipynbOpen in CoCalc
import numpy as np
import pandas as pd
import copy
import seaborn as sns
import matplotlib.pyplot as plt

class Elevator(object):
"""An Elevator is a generic class that can move one floor up and down and
let the passengers off and on simultaneously with the help of a convenience function called "open_door".
"""

def __init__(self, building, capacity = 10):
""" Initializes an instance of a class with a certain capacity identified by the user.
Each object is characterized by the number of passengers, its current floor, the building
where it is situated, and a list of passengers inside the elevator.
"""
self.capacity = capacity   # number of people elevator can hold
self.current_floor = 1     # elevator starts at ground floor: 1
self.building = building
self.num_passengers = 0

# self.passengers is a dictionary from a destination floor number
# to a list of Passenger objects (the Passengers that need to get off at floor i)
self.passengers = {
i : [] for i in range(1, building.num_floors + 1)
}

def move(self, direction):
"""Moves the elevator one floor up or down. """
self.current_floor += direction # direction is integer +1 or -1

def let_off(self, passenger, timestep = None):
"""Removes passenger from the elevator and adds them to the building.
If passenger has arrived on their destination floor, add them to arrived_passengers.
Raises an error if passenger is not on the elevator.
"""
try:
self.passengers[passenger.end_floor].remove(passenger)
except:
self.num_passengers -= 1

if passenger.end_floor == self.current_floor:
self.building.arrived_passengers.append(passenger)
else:
self.building.waiting_passengers[self.current_floor].append(passenger)
self.building.num_passengers += 1

if timestep is not None:
passenger.travel_time += timestep - passenger.last_boarding
passenger.last_deboarding = timestep

return

def let_on(self, passenger, timestep = None):
"""Adds passenger to the elevator from the current floor
Raises an error if passenger is not on the elevator's current_floor
"""
try:
self.building.waiting_passengers[self.current_floor].remove(passenger)
except:
self.building.num_passengers -= 1

self.passengers[passenger.end_floor].append(passenger)
self.num_passengers += 1

if timestep is not None:
passenger.wait_time += timestep - passenger.last_deboarding
passenger.last_boarding = timestep

return

def open_doors(self, timestep = None):
"""A convenience function for letting people on and off floors simultaneously"""

# passengers get off
for passenger in self.passengers[self.current_floor]:
self.let_off(passenger, timestep)

# passengers get on
floor_lst = self.building.waiting_passengers[self.current_floor]
while self.capacity > self.num_passengers and floor_lst:
passenger = floor_lst[-1]
self.let_on(passenger, timestep)

return

def not_empty(self):
"""Returns True if elevator is not empty."""
return self.num_passengers > 0

class Building(object):
"""The building generates and stores the passengers waiting for the elevator."""

def __init__(self, num_floors, num_passengers):
"""By default requires user specified number of floors and passengers."""
self.num_floors = num_floors
self.arrived_passengers = []
self.waiting_passengers = {
i : [] for i in range(1, num_floors + 1)
}
self.num_passengers = 0

"""Generates random passengers added to each floor."""
for i in range(number):
start = np.random.randint(1, self.num_floors + 1)
end = start
while end == start:
end = np.random.randint(1, self.num_floors + 1)
passenger = Passenger(start,end)
self.waiting_passengers[passenger.start_floor].append(passenger)
self.num_passengers += number

def not_empty(self):
"""Returns True if building is not empty."""
return self.num_passengers > 0

class Passenger(object):
"""Initiates passengers with start and end floor, called in building class."""

def __init__(self, start_floor, end_floor):
"""Initializes variables for keeping track of wait_ and travel_time
Allows for multiple boardings/deboardings from the elevator
"""
self.start_floor = start_floor
self.end_floor = end_floor
self.last_boarding = 0
self.last_deboarding = 0
self.wait_time = 0
self.travel_time = 0

# Main Program

def display_state_and_collect_analytics(elevator, building, analytics):
"""Displays the current elevator and building state.
Collects relevant data about the performance of the elevator.
"""
print building.num_passengers

def run_simulation(elevator,building,strategy,log_progress=True):
"""Runs the simulation for a given strategy.
Continues to call the given strategy until
all passengers are processed.
"""
is_moving_up = True
timestep = 0
analytics = {'waiting_times':[]}
while building.not_empty() or elevator.not_empty():
timestep,is_moving_up = strategy(elevator,building,timestep,is_moving_up)
if log_progress:
display_state_and_collect_analytics(elevator, building, analytics)
analytics['tot_time'] = timestep
analytics['arrived_passengers'] = building.arrived_passengers

return analytics

def basic_controller(elevator, building,timestep,is_moving_up):
"""Implements basic elevator strategy.
Elevator goes up and down the building,
stopping at every floor along the way.
"""
elevator.open_doors(timestep = timestep)
timestep += 1

# switch directions if the elevator reaches the top or bottom of the building
if elevator.current_floor == building.num_floors:
is_moving_up = False
elif elevator.current_floor == 1:
is_moving_up = True

# elevator moves up or down
elevator.move(1 if is_moving_up else -1)

timestep += 1

return(timestep,is_moving_up)

def secondary_controller(elevator, building, timestep,is_moving_up):
"""Implements an elevator strategy where the
elevator goes up and down the building,
stopping at every floor where someone needs to get off or on.
"""
if elevator.current_floor == building.num_floors:
is_moving_up = False
elif elevator.current_floor == 1:
is_moving_up = True

on_floor = building.waiting_passengers[elevator.current_floor]
off_floor = elevator.passengers[elevator.current_floor]
space = True if elevator.num_passengers < elevator.capacity else False

if (off_floor) or (on_floor and space):
elevator.open_doors(timestep = timestep)
else:
# switch directions if the elevator reaches the top or bottom of the building
elevator.move(1 if is_moving_up else -1)

# elevator moves up or down
timestep += 1
return(timestep,is_moving_up)

if __name__ == "__main__":

# create building object with random passenger set up
building1 = Building(num_floors=20, num_passengers=100)

# an identical copy of the building
building2 = copy.deepcopy(building1)

# two elevators, each with its own associated building
elevator1 = Elevator(building1, capacity=10)
elevator2 = Elevator(building2, capacity=10)

# run simulations with each of the two strategies
results1 = run_simulation(elevator1,building1,basic_controller)
results2 = run_simulation(elevator2,building2,secondary_controller)

94 90 89 88 87 87 86 86 86 85 84 83 81 80 79 79 79 78 77 76 76 75 75 75 75 75 74 73 72 72 71 71 71 70 70 68 66 65 65 64 63 63 63 63 62 62 62 62 62 62 61 61 60 59 59 58 57 55 55 55 55 55 55 55 54 54 53 53 53 52 52 52 52 51 51 49 49 48 48 48 48 46 45 45 45 45 44 43 43 42 41 41 41 41 40 40 38 38 38 38 38 38 38 37 36 36 35 35 34 33 33 33 33 33 33 33 33 33 33 33 31 29 28 27 27 26 26 25 25 25 24 24 24 24 24 23 21 21 21 20 20 19 19 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 17 16 14 11 11 11 11 10 9 8 7 7 7 7 7 7 7 4 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 94 94 90 90 89 89 88 88 87 87 87 86 86 86 86 85 85 84 84 83 83 81 80 80 79 79 78 78 78 78 77 77 76 76 75 74 74 74 73 73 73 73 73 73 73 72 72 71 70 70 70 69 69 68 68 68 67 67 67 67 65 65 63 62 62 62 59 58 58 58 58 58 58 58 58 57 56 56 56 56 56 55 55 54 54 53 52 52 52 51 50 50 49 49 49 48 48 47 47 46 46 46 46 43 43 43 43 43 43 43 43 43 42 42 42 41 41 40 39 39 39 38 37 37 36 35 35 35 34 34 34 34 34 34 33 33 33 33 33 33 33 33 33 32 32 31 31 28 28 27 27 26 25 25 25 25 23 22 22 22 22 20 19 19 19 18 18 16 15 15 15 15 15 15 15 15 15 14 14 13 13 12 12 12 11 11 11 11 11 11 11 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 9 8 8 7 7 7 7 7 7 3 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

## Efficiency Comparison¶

def extract_times(results,strategy):
"""Extract efficiency metrics from stored passenger objects."""

waited = [] # times waiting for elevator
travelled = [] # times on elevator
totals = [] # total times travelled

# add every passengers data to aggregate lists
for passenger in results['arrived_passengers']:
waited.append(passenger.wait_time)
travelled.append(passenger.travel_time)
totals.append(passenger.wait_time+passenger.travel_time)

# update dictionary with extracted data
strategy["runtimes"].append(results["tot_time"])
strategy["waits"].append(waited)
strategy["travels"].append(travelled)
strategy["totals"].append(totals)

def compare_strategies(strategies,runs=100):
"""Run simulation 'runs' times for each strategy and extract efficiency metrics."""

metrics = []
# create a metric dictionary for each strategy
for strategy in strategies:
metrics.append({"runtimes":[],"waits":[],"travels":[],"totals":[]})

# run the simulation for each strategy, 'runs' times
for sim_run in range(runs):
basic_building = Building(num_floors=20, num_passengers=100)
buildings = []
elevators = []
results = []
for i,strategy in enumerate(strategies):
building = copy.deepcopy(basic_building)
elevator = Elevator(building, capacity=10)
results = run_simulation(elevator,building,strategy,log_progress=False)
extract_times(results,metrics[i])

return(metrics)

# collect efficiency measures for each strategy developed
strategies = [basic_controller,secondary_controller]
strategy1,strategy2 = compare_strategies(strategies,1000)

# simple tabular summary of simulation runtimes under each strategy

pd.DataFrame([strategy1["runtimes"],strategy2["runtimes"]],
index=['Strategy 1','Strategy 2']).T.describe()

Strategy 1 Strategy 2
count 1000.000000 1000.000000
mean 451.926000 279.687000
std 41.834683 19.346869
min 324.000000 229.000000
25% 420.000000 260.000000
50% 458.000000 279.000000
75% 474.000000 296.000000
max 610.000000 341.000000
plt.figure(figsize=(8,4))
sns.distplot(strategy1["runtimes"],label='Strategy 1',kde=False)
sns.distplot(strategy2["runtimes"],label='Strategy 2',kde=False)
plt.title('Histograms of Simulation Runtimes')
plt.ylabel('Number of Simulations')
plt.xlabel('Runtime (time to process all passengers)')
plt.legend()
plt.show()

def all_waiting_times(times):
"""Simple helper function to merge list of lists."""
return([time for simulation in times for time in simulation])

plt.figure(figsize=(8,4))
sns.distplot(all_waiting_times(strategy1['waits']),label='Strategy 1',kde=False,bins=10)
sns.distplot(all_waiting_times(strategy2['waits']),label='Strategy 2',kde=False,bins=10)
plt.title('Histograms of Individual Waiting Times')
plt.ylabel('Number of Passengers')
plt.xlabel('Waiting Time (time waiting for elevator)')
plt.legend()
plt.show()

plt.figure(figsize=(8,4))
sns.distplot(all_waiting_times(strategy1['travels']),label='Strategy 1',kde=False,bins=10)
sns.distplot(all_waiting_times(strategy2['travels']),label='Strategy 2',kde=False,bins=10)
plt.title('Histograms of Individual Travel Times')
plt.ylabel('Number of Passengers')
plt.xlabel('Travel Time (time on elevator)')
plt.legend()
plt.show()

plt.figure(figsize=(8,4))
sns.distplot(all_waiting_times(strategy1['totals']),label='Strategy 1',kde=False,bins=10)
sns.distplot(all_waiting_times(strategy2['totals']),label='Strategy 2',kde=False,bins=10)
plt.title('Histograms of Individual Time in Simulation')
plt.ylabel('Number of Passengers')
plt.xlabel('Total Time (time in simulation)')
plt.legend()
plt.show()