├── .gitignore ├── run.sh ├── sample_trace.png ├── convertToPDF.py ├── nbstreamreader.py ├── SimpleAgent.py ├── Communicator.py ├── Elevator.py ├── README.md ├── sim.py └── Enivronment.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | *.pyc 3 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | python SimpleAgent.py $1 $2 $3 $4 $5 $6 2 | -------------------------------------------------------------------------------- /sample_trace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suragnair/lift-sim/HEAD/sample_trace.png -------------------------------------------------------------------------------- /convertToPDF.py: -------------------------------------------------------------------------------- 1 | from reportlab.pdfgen import canvas 2 | from reportlab.lib.pagesizes import A4, letter 3 | import sys 4 | 5 | point = 1 6 | textsize = 12 7 | inch = 72 8 | 9 | def convert(fileName, outName): 10 | print('converting %s to pdf' %fileName) 11 | ptr = open(fileName, 'r') # text file I need to convert 12 | episodes = ptr.read().split('\n\n=======') 13 | episodes = ['=======' + x for x in episodes] 14 | episodes[0] = episodes[0][7:] 15 | height = len(episodes[1].split('\n')) + 3 16 | width = max([len(x) for x in episodes[1].split('\n')]) 17 | 18 | ptr.close() 19 | 20 | c = canvas.Canvas(outName , pagesize=(width*textsize*0.65 , height*textsize)) 21 | 22 | for ep in episodes: 23 | v = (height - 2) * textsize 24 | c.setStrokeColorRGB(0,0,0) 25 | c.setFillColorRGB(0,0,0) 26 | c.setFont('Courier', textsize * point) 27 | for line in ep.split('\n'): 28 | c.drawString( 0.3 * inch, v, line) 29 | v -= textsize * point 30 | c.showPage() 31 | c.save() 32 | 33 | 34 | if __name__ == '__main__': 35 | convert(sys.argv[1], sys.argv[2]) 36 | -------------------------------------------------------------------------------- /nbstreamreader.py: -------------------------------------------------------------------------------- 1 | from threading import Thread 2 | from Queue import Queue, Empty 3 | 4 | class NonBlockingStreamReader: 5 | 6 | def __init__(self, stream): 7 | ''' 8 | stream: the stream to read from. 9 | Usually a process' stdout or stderr. 10 | ''' 11 | 12 | self._s = stream 13 | self._q = Queue() 14 | 15 | def _populateQueue(stream, queue): 16 | ''' 17 | Collect lines from 'stream' and put them in 'quque'. 18 | ''' 19 | while True: 20 | line = stream.readline() 21 | if line: 22 | queue.put(line) 23 | else: 24 | # raise UnexpectedEndOfStream 25 | pass 26 | 27 | self._t = Thread(target = _populateQueue, 28 | args = (self._s, self._q)) 29 | self._t.daemon = True 30 | self._t.start() #start collecting lines from the stream 31 | 32 | def readline(self, timeout = None): 33 | try: 34 | return self._q.get(block = timeout is not None, 35 | timeout = timeout) 36 | except Empty: 37 | return None 38 | 39 | # class UnexpectedEndOfStream(Exception): pass -------------------------------------------------------------------------------- /SimpleAgent.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | 4 | def get_params(): 5 | parser = argparse.ArgumentParser(description='Stationary Controller') 6 | parser.add_argument('N', metavar='5', type=int, help='number of floors') 7 | parser.add_argument('K', metavar='2', type=int, help='number of elevators') 8 | parser.add_argument('p', metavar='0.8', type=float, help='prob person arrives') 9 | parser.add_argument('q', metavar='0.7', type=float, help='prob person arrives at ground floor, if s/he arrives') 10 | parser.add_argument('r', metavar='0.9', type=float, help='prob person gets down at first floor') 11 | parser.add_argument('t', metavar='1', type=float, help='time unit') 12 | 13 | args = parser.parse_args() 14 | return args 15 | 16 | def simpleAgent(args): 17 | ready = sys.stdin.readline().strip() 18 | 19 | repeat = ['AU','AOU']*(args.N-1) 20 | repeat[-1] = 'AOD' 21 | repeat += ['AD','AOD']*(args.N-1) 22 | repeat[-1] = 'AOU' 23 | 24 | i = 0 25 | 26 | while(True): 27 | actions = ['AOU' + str(k+1) for k in range(args.K)] 28 | 29 | for l in range(args.K): 30 | if i>(args.N/args.K+1)*l*2: 31 | actions[l] = repeat[(i - (args.N/args.K+1)*l*2 - 1) % len(repeat)] + str(l+1) 32 | 33 | i+=1 34 | # sys.stderr.write(' '.join(actions) + '\n') 35 | sys.stdout.write(' '.join(actions) + '\n') 36 | sys.stdout.flush() 37 | updates = sys.stdin.readline().strip() 38 | 39 | 40 | if __name__=="__main__": 41 | args = get_params() 42 | 43 | sys.stdout.write('0\n') 44 | sys.stdout.flush() 45 | simpleAgent(args) 46 | 47 | -------------------------------------------------------------------------------- /Communicator.py: -------------------------------------------------------------------------------- 1 | from subprocess import Popen, PIPE 2 | from nbstreamreader import NonBlockingStreamReader as NBSR 3 | from sys import platform 4 | import os 5 | 6 | class Communicator(object): 7 | def __init__(self): 8 | self.ChildProcess = None 9 | 10 | def isChildProcessNotNone(self): 11 | if(self.ChildProcess is None): 12 | return False 13 | else: 14 | return True 15 | 16 | def CreateChildProcess(self,Execution_Command,Executable_File, args_list): 17 | if platform == "darwin" or platform == "linux" or platform == "linux2": 18 | self.ChildProcess = Popen ([Execution_Command, Executable_File] + args_list, stdin = PIPE, stdout = PIPE, bufsize=0,preexec_fn=os.setsid) 19 | else: 20 | self.ChildProcess = Popen ([Execution_Command, Executable_File] + args_list, stdin = PIPE, stdout = PIPE, bufsize=0) 21 | self.ModifiedOutStream = NBSR(self.ChildProcess.stdout) 22 | 23 | def RecvDataOnPipe(self,TIMEOUT): 24 | data = None 25 | if(self.isChildProcessNotNone()): 26 | try: 27 | data = self.ModifiedOutStream.readline(TIMEOUT) 28 | except: 29 | pass 30 | return data 31 | 32 | def SendDataOnPipe(self,data): 33 | success_flag = False 34 | if(self.isChildProcessNotNone()): 35 | try: 36 | self.ChildProcess.stdin.write(data) 37 | success_flag = True 38 | except: 39 | pass 40 | return success_flag 41 | 42 | def closeChildProcess(self): 43 | if(self.isChildProcessNotNone()): 44 | if platform == "darwin" or platform == "linux" or platform == "linux2": 45 | try: 46 | os.killpg(os.getpgid(self.ChildProcess.pid), 15) 47 | except: 48 | pass 49 | else: 50 | self.ChildProcess.kill() 51 | self.ChildProcess = None 52 | 53 | 54 | if __name__ == '__main__': 55 | c = Communicator() 56 | c.CreateChildProcess('sh','run.sh') 57 | counter = 1 58 | # snair: testing Communicator 59 | try: 60 | while(counter != 100): 61 | c.SendDataOnPipe(str(counter) + '\n') 62 | data = c.RecvDataOnPipe(1) 63 | print "Parent Recieved",data 64 | data = data.strip() 65 | counter += 1 66 | 67 | c.SendDataOnPipe("end") 68 | 69 | except: 70 | c.closeChildProcess() 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Elevator.py: -------------------------------------------------------------------------------- 1 | class Elevator(object): 2 | """ 3 | - state representation of the elevator 4 | """ 5 | def __init__(self, N, K): 6 | self.N = N # number of floors 7 | self.K = K # number of elevators 8 | 9 | self.pos = [0]*K # initial positions of all elevators 10 | self.BU = [0]*N # button up on each floor (always 0 for top floor) 11 | self.BD = [0]*N # button down on each floor (always 0 for first floor) 12 | self.BF = [[0]*N for i in range(K)] # floor buttons pressed inside elevator, for each elevator 13 | self.LU = [0]*K # light up indicator for each lift for its current floor (always 0 for top floor) 14 | self.LD = [0]*K # light down indicator for each lift for its current floor (always 0 for first floor) 15 | 16 | def __str__(self): 17 | """ 18 | - returns a string expression of the current state of the elevator 19 | """ 20 | state = '' 21 | state += ' '.join([str(x) for x in self.pos]) + ' ' 22 | state += ''.join([str(x) + ' ' + str(y) + ' ' for x, y in zip(self.BU,self.BD)]) 23 | for e in self.BF: 24 | state += ' '.join([str(x) for x in e]) 25 | state += ' ' 26 | state += ' '.join([str(x) for x in self.LU]) + ' ' 27 | state += ' '.join([str(x) for x in self.LD]) + ' ' 28 | 29 | return state 30 | 31 | # state modifiers 32 | 33 | def modify_pos(self, k, delta): 34 | """ 35 | - change position of kth lift by delta (+/- 1) 36 | - validity checks in Simulator 37 | """ 38 | self.pos[k] += delta 39 | 40 | def modify_floor_button(self, n, direction, status): 41 | """ 42 | - n : floor number 43 | - direction : 'U' for up button and 'D' for down button 44 | - status : 0 to clear and 1 to press 45 | - returns if status was toggled 46 | """ 47 | 48 | toggled = True 49 | 50 | if direction == 'U': 51 | if self.BU[n] == status: 52 | toggled = False 53 | self.BU[n] = status 54 | if direction == 'D': 55 | if self.BD[n] == status: 56 | toggled = False 57 | self.BD[n] = status 58 | 59 | return toggled 60 | 61 | def modify_elevator_button(self, k, n, status): 62 | """ 63 | - k : elevator number 64 | - n : floor number 65 | - status : 0 to clear and 1 to press 66 | - returns if status was toggled 67 | """ 68 | toggled = True 69 | if self.BF[k][n] == status: 70 | toggled = False 71 | self.BF[k][n] = status 72 | 73 | return toggled 74 | 75 | def reset_lights(self): 76 | self.LU = [0] * self.K 77 | self.LD = [0] * self.K 78 | 79 | def modify_lights(self, k, direction, status): 80 | """ 81 | - k : lift number 82 | - direction : 'U' for up button and 'D' for down button 83 | - status : 0 to clear and 1 to press 84 | """ 85 | if direction == 'U': 86 | self.LU[k] = status 87 | if direction == 'D': 88 | self.LD[k] = status -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lift-sim 2 | ## Simulator for Elevator Control 3 | 4 | A simplified elevator simulator that follows the rules specified in the problem statement [here](http://www.cse.iitd.ac.in/~mausam/courses/col333/autumn2016/A4/A4.pdf). 5 | 6 | This simulator has been created for Assignment 4 of the Fall 2016 course COL333 (Artificial Intelligence) at IIT-Delhi. Before solving your MDP, don't forget that honesty is the best policy. 7 | 8 | ## Usage Instructions 9 | 10 | To run the simulator: 11 | ```bash 12 | python sim.py run.sh

13 | ``` 14 | 15 | run.sh should be a bash script which runs your code. It should take the arguments ```N, K, p, q, r, t_u``` as specified in the problem statement. 16 | 17 | Optional arguments: 18 | -ep \ : Number of episodes to play out (Default: 1000) 19 | -log \ : Name of output log file (Default: 'simulation.txt') 20 | -mode \ : 'CUI' for a command line visualisation, 'None' for no visualisation (Default: 'CUI') 21 | 22 | You should read subsequent updates from stdin and write actions to stdout. Debug messages can be written to stderr. On running your executable for the first time, you should send a '0' when ready. The initial state has all elevators on the first floor with no buttons pressed. 23 | 24 | You may have a look at SimpleAgent.py and run.sh for an example agent and script. 25 | 26 | Note: To create an agent in C++, write a program that: 27 | - reads from stdin (cin) the buttons pressed in the form of a single string. Note that the state of individual buttons is delimited by spaces. For example, if in a time step, a person shows up on the 4th floor and decides to go up, the update received would be 'BU4'. If along with this, a person enters the 2nd elevator and presses the button for the 3rd floor, the update would be 'BU4 B32'. Hence every message in the state received will be either of the form B<D/U><floor number> or B<destination><elevator number>. 28 | 29 | The state of a button will only be sent if it's not already pressed. 30 | 31 | - writes actions to stdout (cout) in the form of a string, with one action per elevator separated by spaces. For example, read the actions outputted by simpleAgent inside simpleAgent.py. 32 | 33 | 34 | ## Sample Print Trace: 35 | 36 | ![alt tag](https://raw.githubusercontent.com/suragnair/lift-sim/master/sample_trace.png) 37 | 38 | - Actions taken : action taken by the controller based on the previous state. Based on these actions, a simulation is performed and printed below. 39 | - People Waiting Up/Down : number of people waiting to go up and down on each floor (note that this is not explicitly available to the agent). 40 | - Floor Up/Down Buttons : In the above Episode, the BD is pressed for floors 3 and 5, and BU is pressed for floor 1. 41 | - Elevators : A dot shows which floor the elevator is on. If it is open, then a gap is shown with the direction of the light. 42 | - Elevator Buttons : Buttons pressed inside each elevator are shown with 'o's. For Elevator 1, button for the 5th floor is pressed while the button for the 1st floor is pressed for Elevator 2. 43 | - Update sent : The additional updates that transpired in the last time step. In this case, the button 'BD3' was pressed since someone arrived at floor 3. If no one arrives (or presses an already pressed floor button), a '0' is sent, followed by new lift buttons pressed (if any). 44 | 45 | ## Log to PDF 46 | 47 | To convert a .txt log to a pdf for easier viewing: 48 | 49 | ```bash 50 | python convertToPDF.py 51 | ``` 52 | 53 | Note: reportlab is a prerequisite and can be installed using pip: 54 | 55 | ```bash 56 | pip install reportlab 57 | ``` 58 | -------------------------------------------------------------------------------- /sim.py: -------------------------------------------------------------------------------- 1 | from Communicator import Communicator 2 | from Enivronment import Environment 3 | import os, argparse 4 | 5 | 6 | class Interactor(Communicator): 7 | 8 | def __init__(self): 9 | super(Interactor, self).__init__() 10 | pass 11 | 12 | def CheckExeFile(self, Execution_Command, Executable_File): 13 | """ Checks the Existence of the Executable File and 14 | if the extension of the file matches the command used to run it 15 | Args: 16 | Execution_Command : Command used to execute the Executable File (sh, python ./ etc) 17 | Executable_File : The Executable File 18 | Returns: 19 | None 20 | """ 21 | Extension = Executable_File.split('.') 22 | if (len(Extension) == 1): 23 | return False 24 | Extension = Extension[-1] 25 | if (os.path.isfile(Executable_File)): 26 | if (Execution_Command == './' or Execution_Command == 'sh'): 27 | if (Extension == 'sh' or Extension == 'o'): 28 | return True 29 | else: 30 | return False 31 | elif (Execution_Command == 'java'): 32 | if (Extension == 'java'): 33 | return True 34 | else: 35 | return False 36 | elif (Execution_Command == 'python'): 37 | if (Extension == 'py'): 38 | return True 39 | else: 40 | return False 41 | else: 42 | return False 43 | 44 | def CreateChildProcess(self, Execution_Command, Executable_File, args_list): 45 | """ Creates a Process, with which the Simulator communicates. 46 | Checks the existance of the Executable_File and some basic 47 | checks for whether the Execution_Command used to run the code 48 | matches the extension of the Executable File 49 | Prints if error is found 50 | Args: 51 | Execution_Command : Command used to execute the Executable File (sh, python ./ etc) 52 | Executable_File : The Executable File 53 | args_list : list of arguments (N, K, p, q, r, t_u) 54 | Returns: 55 | None 56 | """ 57 | if (self.CheckExeFile(Execution_Command, Executable_File)): 58 | super(Interactor, self).CreateChildProcess(Execution_Command, Executable_File, args_list) 59 | else: 60 | print 'ERROR : EITHER FILE ', Executable_File, ' DOES NOT EXIST', 61 | print 'OR THE EXECUTION COMMAND TO RUN THE FILE ', Execution_Command, ' IS INCORRECT' 62 | 63 | def RecvDataFromProcess(self, t_u, first_time=False): 64 | """ 65 | Receives Data from the process. Waits 30 minutes for the first '0', then t_u every time 66 | For both the above cases, prints the error msg and closes the connection to the process. 67 | 68 | Args: 69 | t_u : time unit as specified in the problem statement 70 | first_time : if receiving data for the first time, timeout = 30 minutes and t_u from then 71 | 72 | Returns: 73 | retData : array of length K with action for every elevator 74 | string "ERROR" in case of an error 75 | """ 76 | FIRST_TIMEOUT = 30*60 77 | 78 | if first_time: 79 | data = super(Interactor, self).RecvDataOnPipe(FIRST_TIMEOUT) 80 | else: 81 | data = super(Interactor, self).RecvDataOnPipe(t_u) 82 | 83 | retData = None 84 | if (data == None): 85 | print 'ERROR : AGENT TIMED OUT' 86 | super(Interactor, self).closeChildProcess() 87 | retData = 'ERROR' 88 | 89 | else: 90 | if data.strip() == '0': 91 | retData = '0' 92 | else: 93 | retData = data.strip().split(' ') 94 | 95 | return retData 96 | 97 | def SendData2Process(self, data): 98 | """ Sends Data (State) to the process. Handles the case if the process being communicated with has closed. 99 | Args: 100 | data : string data, to send the process (buttons pressed) 101 | Returns: 102 | success_flag : A boolean flag to denote the data transfer to the process was successful or not. 103 | """ 104 | 105 | if data[-1] != '\n': 106 | data += '\n' 107 | success_flag = super(Interactor, self).SendDataOnPipe(data) 108 | if not success_flag: 109 | print 'ERROR : FAILED TO SEND DATA TO PROCESS' 110 | super(Interactor, self).closeChildProcess() 111 | return success_flag 112 | 113 | 114 | def simulate(args): 115 | args_list = [str(args.N), str(args.K), str(args.p), str(args.q), str(args.r), str(args.t)] 116 | interactor = Interactor() 117 | 118 | if args.exe.endswith('.py'): 119 | interactor.CreateChildProcess('python', args.exe, args_list) 120 | elif args.exe.endswith('.sh'): 121 | interactor.CreateChildProcess('sh', args.exe, args_list) 122 | else: 123 | interactor.CreateChildProcess('sh', args.exe, args_list) 124 | 125 | ready = interactor.RecvDataFromProcess(args.t, first_time=True) 126 | 127 | if ready != '0': 128 | return 129 | 130 | env = Environment(args.N, args.K, args.p, args.q, args.r, args.t) 131 | interactor.SendData2Process('0') 132 | if args.mode != 'None': 133 | print(env) 134 | sim_log = str(env) + '\n' 135 | 136 | for episode in range(args.ep): 137 | actions = interactor.RecvDataFromProcess(args.t, first_time=False) 138 | if actions == 'ERROR': 139 | sim_log += 'ERROR\n' 140 | break 141 | 142 | new_buttons_pressed = env.apply_action([''.join([i for i in x if not i.isdigit()]) for x in actions]) 143 | if new_buttons_pressed == 'INVALID ACTION' or len(actions) != args.K: 144 | print('~'*len(new_buttons_pressed) + '\n' + new_buttons_pressed + '\n' + '~'*len(new_buttons_pressed)) 145 | sim_log += 'INVALID ACTION\n' 146 | break 147 | 148 | interactor.SendData2Process(new_buttons_pressed) 149 | 150 | sim_log += '\n' + '=' * len('EPISODE ' + str(episode + 1)) + '\n' 151 | sim_log += 'EPISODE ' + str(episode + 1) + '\n' 152 | sim_log += '=' * len('EPISODE ' + str(episode + 1)) + '\n' 153 | sim_log += '\n' + '=> Actions taken : ' + ' '.join(actions) + '\n' 154 | sim_log += str(env) + '\n' 155 | sim_log += '=> Updates sent : ' + new_buttons_pressed + '\n' 156 | 157 | if args.mode != 'None': 158 | print('') 159 | print('=' * len('EPISODE ' + str(episode + 1))) 160 | print('EPISODE ' + str(episode+1)) 161 | print('='*len('EPISODE ' + str(episode+1))+'\n') 162 | print('=> Actions taken : ' + ' '.join(actions)) 163 | print(env) 164 | print('=> Update sent : ' + new_buttons_pressed) 165 | 166 | print('FINAL TOTAL COST (at the end of ' + str(episode+1) + ' simulations) : ' + str(env.total_cost)) 167 | 168 | interactor.closeChildProcess() 169 | f = open(args.log, 'w') 170 | f.write(sim_log) 171 | f.close() 172 | 173 | 174 | if __name__=="__main__": 175 | parser = argparse.ArgumentParser(description='Elevator Control Simulator') 176 | parser.add_argument('exe', metavar='run.sh', type=str, help='your executable') 177 | parser.add_argument('N', metavar='5', type=int, help='number of floors') 178 | parser.add_argument('K', metavar='2', type=int, help='number of elevators') 179 | parser.add_argument('p', metavar='0.8', type=float, help='prob person arrives') 180 | parser.add_argument('q', metavar='0.7', type=float, help='prob person arrives at ground floor, if s/he arrives') 181 | parser.add_argument('r', metavar='0.9', type=float, help='prob person gets down at first floor') 182 | parser.add_argument('t', metavar='1', type=float, help='time unit') 183 | parser.add_argument('-ep', dest='ep', type=int, default=1000, help='number of episodes to play, default 1000') 184 | parser.add_argument('-mode', dest='mode', type=str, default='CUI', help='display settings') 185 | parser.add_argument('-log', dest='log', type=str, default='simulation.txt', help='name for simulation log file, default simulation.txt') 186 | args = parser.parse_args() 187 | 188 | simulate(args) -------------------------------------------------------------------------------- /Enivronment.py: -------------------------------------------------------------------------------- 1 | import random 2 | from Elevator import Elevator 3 | 4 | 5 | class Person(object): 6 | def __init__(self, id, N, q, r): 7 | self.id = id # id to avoid duplicates 8 | self.elev_num = -1 9 | 10 | if random.random() < q: 11 | self.start = 0 # storing first floor as 0 for convenience 12 | else: 13 | self.start = random.randint(1, N - 1) 14 | 15 | if self.start == 0: 16 | self.dest = random.randint(1, N - 1) 17 | else: 18 | if random.random() < r: 19 | self.dest = 0 20 | else: 21 | self.dest = random.choice(range(1, self.start) + range(self.start + 1, N)) 22 | 23 | if self.start < self.dest: 24 | self.direction = 'U' 25 | else: 26 | self.direction = 'D' 27 | 28 | 29 | class Environment(object): 30 | """ 31 | - plays out the simulation as specified in the problem statement for one time unit 32 | - method to simulate an action on existing state 33 | - method to print current state (not equivalent to state representation of elevator in Elevator.py) 34 | """ 35 | 36 | # CONSTANTS 37 | WAIT_TIME_COST_FACTOR = 2 38 | UP_DOWN_COST_FACTOR = 1 39 | 40 | def __init__(self, N, K, p, q, r, t_u): 41 | self.N = N # number of floors 42 | self.K = K # number of elevators 43 | self.p = p # probability person arrives at a given time step 44 | self.q = q # probability person arrives at first floor 45 | self.r = r # probability person wants to get down at first floor 46 | self.t_u = t_u # one time unit 47 | 48 | self.elev = Elevator(N, K) 49 | self.total_cost = 0 50 | self.total_people_served = 0 51 | self.people_in_sys = [] 52 | 53 | def apply_action(self, action): 54 | """ 55 | - action is a K length list with actions of the form 'AU', 'AD', 'AOU', 'AOD', 'AS' for each elevator 56 | - executes the action and simulates the next state 57 | - returns the new buttons pressed after simulation 58 | - updates the cost 59 | """ 60 | 61 | # reset lights, will set depending on action 62 | self.elev.reset_lights() 63 | 64 | # update costs 65 | self.total_cost += Environment.WAIT_TIME_COST_FACTOR * len(self.people_in_sys) # cost for people carried over from last time step 66 | self.total_cost += Environment.UP_DOWN_COST_FACTOR * len([x for x in action if x=='AU' or x=='AD']) # cost for all lifts moved 67 | 68 | new_buttons_pressed = '' 69 | 70 | # move lifts 71 | for k in range(self.K): 72 | if action[k] == 'AU': 73 | self.elev.modify_pos(k, 1) 74 | if action[k] == 'AD': 75 | self.elev.modify_pos(k, -1) 76 | if not 0 <= self.elev.pos[k] < self.N: 77 | return 'INVALID ACTION' 78 | 79 | # embarkation, disembarkation 80 | for k in range(self.K): 81 | if action[k] == 'AOU' or action[k] == 'AOD': # remove people with this dest, unpress floor and lift buttons 82 | self.people_in_sys = [x for x in self.people_in_sys if not (x.elev_num == k and x.dest == self.elev.pos[k])] 83 | self.elev.modify_elevator_button(k, self.elev.pos[k], 0) 84 | self.elev.modify_floor_button(self.elev.pos[k], action[k][-1], 0) 85 | 86 | # add people to the elevator who want to go in the direction of lift, press their buttons 87 | for i in range(len(self.people_in_sys)): 88 | if (self.people_in_sys[i].elev_num == -1 and self.people_in_sys[i].start == self.elev.pos[k] and 89 | self.people_in_sys[i].direction == action[k][-1]): 90 | self.people_in_sys[i].elev_num = k 91 | unpressed = self.elev.modify_elevator_button(k, self.people_in_sys[i].dest, 1) 92 | 93 | if unpressed: # may have been pressed by someone already in list, or multiple times by people entering => add once 94 | new_buttons_pressed += 'B_' + str(self.people_in_sys[i].dest + 1) + '_' + str(k+1) + ' ' 95 | 96 | # set lights 97 | if action[k] == 'AOU': 98 | self.elev.modify_lights(k, 'U', 1) 99 | if action[k] == 'AOD': 100 | self.elev.modify_lights(k, 'D', 1) 101 | 102 | floor_button_pressed = False 103 | 104 | # person arrival 105 | if random.random() < self.p: # person arrives 106 | self.total_people_served += 1 107 | new_person = Person(self.total_people_served, self.N, self.q, self.r) 108 | unpressed = self.elev.modify_floor_button(new_person.start, new_person.direction, 1) 109 | if unpressed: 110 | new_buttons_pressed = 'B' + new_person.direction + '_' + str(new_person.start+1) + ' ' + new_buttons_pressed 111 | floor_button_pressed = True 112 | 113 | self.people_in_sys.append(new_person) 114 | 115 | if not floor_button_pressed: 116 | new_buttons_pressed = '0 ' + new_buttons_pressed 117 | 118 | return new_buttons_pressed 119 | 120 | def __str__(self): 121 | """ 122 | - returns a (hopefully kinda sorta pretty) text based representation of the current environment 123 | - notice that while this representation prints the number of people at each floor, it is not informed 124 | as part of the elevator state to the agent 125 | - e.g. if the BU3 has been pressed and another person comes in at the third floor to go up, BU3 will 126 | not be sent again to the agent 127 | """ 128 | 129 | left_margin = 25 130 | lift_width = 5 131 | 132 | state = '' 133 | state += '-'*(left_margin + (lift_width+1)*self.N + 24) + '\n' 134 | state += 'FLOOR' + ' '*(left_margin-5) + ' '*(lift_width/2 + 1) 135 | for i in range(self.N): 136 | # complex rule for accounting for different string lengths for more than 100 floors 137 | state += str(i+1) + ' '*(lift_width - len(str(i+1)) - (len(str(i+2))-len(str(i+1)))*((len(str(i+2))-1)/2) + 1 ) 138 | state += '\n' 139 | 140 | # people waiting 141 | waiting_up = [0]*self.N 142 | waiting_down = [0] * self.N 143 | people_in_lift = [0] * self.K 144 | for person in self.people_in_sys: 145 | if person.elev_num == -1: 146 | if person.direction == 'U': 147 | waiting_up[person.start] += 1 148 | else: 149 | waiting_down[person.start] += 1 150 | else: 151 | people_in_lift[person.elev_num] += 1 152 | 153 | state += 'PEOPLE WAITING UP/DOWN' + ' '*(left_margin-22) + ' '*(lift_width/2) 154 | for u,d in zip(waiting_up,waiting_down): 155 | state += str(u) + '/' +str(d) + ' '*(lift_width-len(str(u)+str(d))) 156 | state += '\n' 157 | 158 | # up and down buttons 159 | state += 'FLOOR UP BUTTON' + ' '*(left_margin + lift_width/2 - 15) 160 | for i in range(self.N): 161 | if self.elev.BU[i]: 162 | state += '-->' 163 | else: 164 | state += ' '*3 165 | state += ' '*(lift_width-2) 166 | state += '\n' 167 | 168 | state += 'FLOOR DOWN BUTTON' + ' ' * (left_margin + lift_width / 2 - 17) 169 | for i in range(self.N): 170 | if self.elev.BD[i]: 171 | state += '<--' 172 | else: 173 | state += ' ' * 3 174 | state += ' ' * (lift_width - 2) 175 | state += '\n' 176 | 177 | # lifts 178 | for i in range(self.K): 179 | state += ' '*(left_margin + 1) 180 | for j in range(self.N): 181 | if self.elev.pos[i] == j and self.elev.LU[i]: 182 | state += ' ' * (lift_width / 2 - 1) + '-->' + ' ' * (lift_width / 2) 183 | elif self.elev.pos[i] == j and self.elev.LD[i]: 184 | state += ' ' * (lift_width / 2 - 1) + '<--' + ' ' * (lift_width / 2) 185 | else: 186 | state += ' ' * (lift_width + 1) 187 | state += '\n' 188 | state += ' '*left_margin + '-' 189 | for j in range(self.N): 190 | if self.elev.pos[i] == j and (self.elev.LU[i] or self.elev.LD[i]): 191 | state += '-'*(lift_width/2-1) + ' '*3 + '-'*(lift_width/2) 192 | else: 193 | state += '-'*(lift_width+1) 194 | state += '\n' 195 | state += 'ELEVATOR ' + str(i+1) + ' '*(left_margin - 9 - len(str(i+1))) 196 | 197 | for j in range(self.N): 198 | if self.elev.pos[i] == j: 199 | state += '|' + ' '*(lift_width/2) + '.' + ' '*(lift_width/2) 200 | else: 201 | state += '|' + ' '*lift_width 202 | state += '|' + ' '*5 + 'PEOPLE IN LIFT : ' + str(people_in_lift[i]) + '\n' 203 | 204 | state += ' ' * left_margin + '-' * ((lift_width + 1) * self.N + 1) + '\n' 205 | state += 'BUTTONS PRESSED' + ' '*(left_margin-15 + lift_width/2 + 1) 206 | for j in range(self.N): 207 | if self.elev.BF[i][j]: 208 | state += 'o' + ' '*lift_width 209 | else: 210 | state += ' '*(lift_width+1) 211 | state += '\n' 212 | 213 | state += '\n' 214 | state += 'TOTAL PEOPLE IN SYSTEM : ' + str(len(self.people_in_sys)) + '\n' 215 | state += 'TOTAL CUMULATIVE COST : ' + str(self.total_cost) + '\n' 216 | state += '-' * (left_margin + (lift_width + 1) * self.N + 24) 217 | 218 | return state --------------------------------------------------------------------------------