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:
            raise("Passenger not found on elevator!")
        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:
            raise("Passenger not found on current floor!")
        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
        self.add_passengers_randomly(num_passengers)

    def add_passengers_randomly(self, number):
        """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()