{############################################################################# #### File name: ELEVATOR.PAS #### Module name: MSC-elevator. #### Support modules reqd: . #### Run time environment: Digital Research's CP/M #### 8080 CPU. #### Compile time environment: Digital Research's Pascal/MT+ 5.2 #### Copyright (c) 1981 by Lawrence Adkins. #### Program development history: #### 29-Apr-82 Program created. #### 1-May-82 Changes made to allow scheduling of non-busy elevators, and #### to idle those that are both non_busy and not scheduled. #### 3-May-82 More improvements to the scheduling algorithms. #### (Last update on: 03-May-82) ##############################################################################} {############################################################################## #### This program simulates a system of several elevators all in operation #### within a high-rise building. The program cannot do any special I/O, #### and is therefore unable to replace the conventional relay networks #### presently controlling most elevators. The program runs in batch mode. #### 1-May changes: Any passengerless, unassigned elevator #### (during each time interval) will be assigned any most extreme floor #### (should one exist) where a floor call has been made which originated #### at a floor in the direction of the elevator, but which indicated that the #### direction had to be reverse. I.e, curr_floor = 6, direction = down, call #### from floor 2 to to go up. Passengerless elevators which see #### no floor calls originating and indicating the current direction (i.e., #### curr_floor = 6, direction = down, call from 3 to go down), did not prev- #### iously assign themselves, nor cannot currently assign themselves a floor #### would be required to cease motion. Once any elevator opens its doors at #### a floor, and both button lights go out, that floor's assigned elevator #### field must be reset to zero. ##############################################################################} PROGRAM elevator_system (* VAR input, output: text *); CONST bottom_floor = 1; { lowest floor elevator goes to } top_floor = 15; { highest floor elevator goes to } max_elevators = 3; { number of elevators in the system } TYPE motion = (down, up); { the only two ways an elevator moves } action = (door_open, in_motion); { ... and the only two states... } t_elevator_info = RECORD curr_floor : bottom_floor..top_floor; fire_exit : bottom_floor..top_floor; direction : motion; status : action; button_bank: ARRAY [bottom_floor..top_floor] OF boolean; END; t_floor_info = RECORD up_button : boolean; down_button: boolean; prob_up_button_pushed: real; prob_down_button_pushed: real; prob_this_floor_is_destination: real; nb_elev_serving_this_request: 0..max_elevators; END; VAR elev_cars : ARRAY [1..max_elevators] OF t_elevator_info; { our three elevator cars and associated shafts } elev_foyers: ARRAY [bottom_floor..top_floor] OF t_floor_info; { our foyers on fifteen stories each with three elevator entrances and a pair of buttons } clock: integer; { measures intervals of (lets say) five seconds } bldg_on_fire: boolean; { at this point always false, but if there was a sensor, would bring elevators to exit floor } max_time: integer; { our elevator cannnot run forever- it must cease to to function when clock exceeds this value } shaft: integer; { a temporary holding an elevator/shaft number } seed_for_random_number_generator: real; infil: text; outfil: text; {############################################################################## #### Assumed structure of input text data file... #### The first three inputs are integers indicating the floor where each #### elevator will be initially positioned. (Preferably fire exit floors.) #### The next fifteen pairs of inputs are real floating point numbers #### representing the probabilities of the 'UP' and 'DOWN' buttons in #### that order) being pushed for the respective floors 1..15. #### The next fifteen inputs are real floating point numbers representing #### the probabilities of the buttons in the elevators, for floors 1 thru #### 15, respectively, whenever passengers enter the elevator. #### The next input is an integer representing the number of time units for #### which to run the simulation. #### The last expected input is an integer between 1 and 65256 to be used #### as an initial seed for the random number generator. ##############################################################################} PROCEDURE initialize (VAR input, output: text); FORWARD; PROCEDURE init_elev_cars (VAR input, output: text); FORWARD; PROCEDURE print_legend (VAR output: text); FORWARD; PROCEDURE init_up_down_probabilities (VAR input, output: text); FORWARD; PROCEDURE init_floor_selection_probabilities (VAR input,output:text);FORWARD; PROCEDURE print_status_board (VAR output: text); FORWARD; PROCEDURE print_elev_car_status (VAR output: text); FORWARD; PROCEDURE h1andle_requests_for_elevator_usage (VAR output: text); FORWARD; FUNCTION random_num (VAR seed: real): real; FORWARD; PROCEDURE perform_a_single_operation (shaft: integer; VAR output:text);FORWARD; FUNCTION listed_in_floor_table (shaft: integer; VAR floor: integer) : boolean; FORWARD; FUNCTION must_continue_in_same_direction (shaft: integer): boolean; FORWARD; FUNCTION against_stops (shaft: integer): boolean; FORWARD; FUNCTION no_passengers (shaft: integer): boolean; FORWARD; FUNCTION reason_to_open_door (shaft: integer): boolean; FORWARD; FUNCTION other_floor_calls (shaft: integer): boolean; FORWARD; PROCEDURE h2andle_an_emergency (shaft: integer; VAR output: text); FORWARD; PROCEDURE open_elevator_door (shaft: integer; VAR output: text); FORWARD; PROCEDURE pick_destination_floors (shaft: integer;VAR output:text);FORWARD; FUNCTION could_provide_service (shaft: integer): boolean; FORWARD; {############################################################################## #### Initialize probabilities by reading from data file. #### Data file is read from only at initialization time. #### Range checking is done on most read-in values, with errors #### generating messages and replacing with arbitrary permissable values. #### Initialize elevator system data structures. ##############################################################################} PROCEDURE initialize (* VAR input, output: text *); BEGIN writeln (output, ' ---- SIMULATION OF AN ELEVATOR SYSTEM. ---- '); writeln (output); print_legend (output); writeln (output); init_elev_cars (input, output); writeln (output); init_up_down_probabilities (input, output); writeln (output); init_floor_selection_probabilities (input, output); writeln (output); read (input, max_time); IF (max_time < 0) OR (max_time > 1000) THEN BEGIN writeln (output, 'Data error. Illegal MAX_TIME of ', max_time:6); max_time := 5 END; writeln (output, 'MAX_TIME is ', max_time:6); read (input, seed_for_random_number_generator); IF (seed_for_random_number_generator < 1.0) OR (seed_for_random_number_gernerator > 65256.0) THEN BEGIN writeln (output, 'Data error. Illegal SEED of ', seed_for_random_number_generator:15); seed_for_random_number_generator := 127.0 END; writeln (output, 'Random number generator SEED is ', seed_for_random_number_generator:15); bldg_on_fire := false END; PROCEDURE print_legend (* VAR output: text *); BEGIN writeln (output, 'In the maps which follow the following legend applies:'); writeln (output, ' Given this sample output,'); writeln (output, '1 floor 1 2 3 4 5 6 7 8 9 10...'); writeln (output, '2'); writeln (output, '3 up calls > . . . . > . . . .'); writeln (output, '4'); writeln (output, '5 car 1 -> . 1 . . . . . . . +'); writeln (output, '6 car 2 -> . . . . . . . 2 + .'); writeln (output, '7 car 3 <- + . . < . . . . . .'); writeln (output,'8'); writeln (output, '9 down calls . . . < . . . . . .'); writeln (output); writeln (output,'where, in lines 3 and 9 the arrows represent calls, and'); writeln (output,'dots represent no calls. In lines 5 thru 7, plusses'); writeln (output,'represent buttons pushed within the elevator, digits'); writeln (output,'represent elevators which have opened their doors, while'); writeln (output,'arrows here represent elevators passing by a floor w/o'); writeln (output,'stopping. Dots here indicate no elevators or buttons '); writeln (output,'not pushed from within the elevator.'); END; {############################################################################## #### Initialization of the elev_cars array. Probabilities read from input #### file, other fields initialized with predetermined values. ##############################################################################} PROCEDURE init_elev_cars (* VAR input, output: text *); VAR shaft, floor, pres_floor: integer; BEGIN writeln (output, 'Initial elevator floor assignments. '); writeln (output, 'elevator':10, 'floor':10); FOR shaft := 1 TO max_elevators DO WITH elev_cars [shaft] DO BEGIN read (input, pres_floor); IF (pres_floor < bottom_floor) OR (pres_floor > top_floor) THEN BEGIN writeln (output, 'Data error. Illegal initial floor # ', pres_floor:3, ' for elevator ', shaft:2); pres_floor := bottom_floor END; curr_floor := pres_floor; writeln (output, shaft:10, curr_floor:10); direction := up; status := door_open; fire_exit := curr_floor; FOR floor := bottom_floor TO top_floor DO button_bank [floor] := false END END; {############################################################################## #### Initialize most portions of the elev_foyer array, with probabilities #### read from the input file, and other fields assigned predetermined values. ##############################################################################} PROCEDURE init_up_down_probabilities (* VAR input, output: text *); VAR floor: integer; BEGIN writeln (output, 'Initial elevator foyer up-down button probability', ' assignments.'); writeln (output, 'floor':10, 'up':15, 'down':15); FOR floor := bottom_floor TO top_floor DO WITH elev_foyers [floor] DO BEGIN up_button := false; down_button := false; nb_elev_serving_this_request := 0; read (input, prob_up_button_pushed, prob_down_button_pushed); IF (prob_up_button_pushed < 0.0) OR (prob_up_button_pushed > 1.0) THEN BEGIN writeln (output, 'Data error. Illegal UP selection probability', ' of ', prob_up_button_pushed:15, ' for floor ', floor:3); prob_up_button_pushed := 0.0 END; IF (prob_down_button_pushed < 0.0) OR (prob_down_button_pushed > 1.0) THEN BEGIN writeln (output, 'Data error. Illegal DOWN selection', ' probability of ', prob_down_button_pushed:15, ' for floor ', floor:3); prob_down_button_pushed := 0.0 END; writeln (output, floor:10, prob_up_button_pushed:15, prob_down_button_pushed:15) END END; {############################################################################## #### Read one more set of probabilities from the input text file. ##############################################################################} PROCEDURE init_floor_selection_probabilities (* VAR input, output: text *); VAR floor: integer; BEGIN writeln (output, 'Initial floor selection probability assignments. '); writeln (output, 'floor':10, 'selection':15); FOR floor := bottom_floor TO top_floor DO WITH elev_foyers [floor] DO BEGIN read (input, prob_this_floor_is_destination); IF (prob_this_floor_is_destination < 0.0) OR (prob_this_floor_is_destination > 1.0) THEN BEGIN writeln (output, 'Data error. Illegal FLOOR selection ', 'probability of ', prob_this_floor_is_destination:15, ' for floor ', floor:3); prob_this_floor_is_destination := 0.0 END; writeln (output, floor:10, prob_this_floor_is_destination:15) END END; {############################################################################## #### Give a sort of pictorial view of the status of the elevator system, #### showing present positions, floors where buttons were pushed, etc. #### Given this sample output, ###1 floor 1 2 3 4 5 6 7 8 9 10... ###2 ###3 up calls > . . . . > . . . . ###4 ###5 car 1 -> . 1 . . . . . . . + ###6 car 2 -> . . . . . . . 2 + . ###7 car 3 <- . . . < . . . . . . ###8 ###9 down calls . . . < . . . . . . #### #### where, in lines 3 and 9 the arrows represent calls, and dots represent #### no calls. In lines 5 thru 7, plusses represent buttons pushed within #### the elevator, digits represent elevators which have opened their doors, #### while arrows here represent elevators passing by a floor w/o stopping. ##############################################################################} PROCEDURE print_status_board (* VAR output: text *); CONST s = 3; { so that board fits on 80 column screen. } VAR floor: integer; BEGIN writeln (output); writeln (output); write (output, 'floor':15); FOR floor := bottom_floor TO top_floor DO write (output, floor:4); writeln (output); writeln (output); write (output, 'up calls':15); FOR floor := bottom_floor TO top_floor DO WITH elev_foyers [floor] DO IF up_button THEN write (output, '>':s) ELSE write (output, '.':s); writeln (output); writeln (output); print_elev_car_status (output); write (output, 'down calls':15); FOR floor := bottom_floor TO top_floor DO WITH elev_foyers [floor] DO IF down_button THEN write (output, '<':s) ELSE write (output, '.':s); writeln (output); writeln (output); writeln (output) END; {############################################################################## #### Print a line pertaining to the status of a single elevator car. ##############################################################################} PROCEDURE print_elev_car_status (* VAR output: text *); CONST s = 3; { so that board fits in 80 cols on screen } VAR shaft, floor: integer; BEGIN FOR shaft := 1 TO max_elevators DO WITH elev_cars [shaft] DO BEGIN write (output, 'car ':8, shaft:2); IF direction = up THEN write (output, '->':5) ELSE write (output, '<-':5); FOR floor := bottom_floor TO top_floor DO IF (curr_floor = floor) THEN IF status = door_open THEN write (output, shaft:4) ELSE IF direction = up THEN write (output, '>':s) ELSE write (output, '<':s) ELSE IF button_bank [floor] THEN write (output, '+':s) ELSE write (output, '.':s); writeln (output) END; writeln (output) END; {############################################################################## #### Figure out which buttons were pressed in every foyer (what I call the #### hall space the elevators open into). ##############################################################################} PROCEDURE h1andle_requests_for_elevator_usage (* VAR output: text *); VAR floor,shaft: integer; r: real; b: boolean; BEGIN FOR floor := bottom_floor TO top_floor DO WITH elev_foyers [floor] DO BEGIN r := random_num (seed_for_random_number_generator); IF r < prob_up_button_pushed THEN BEGIN up_button := true; writeln (output, 'Up call on floor ', floor:4) END; r := random_num (seed_for_random_number_generator); IF r < prob_down_button_pushed THEN BEGIN down_button := true; writeln (output, 'Down call on floor ', floor:4) END END; FOR shaft := 1 TO max_elevators DO b:= could_provide_service (shaft) END; {############################################################################## #### Generate the pseudo random number such that 0 <= random_num <= 1 ##############################################################################} FUNCTION random_num (* VAR seed: real): real *); CONST modulus = 65536.0; multiplier = 25173.0; increment = 13849.0; VAR temp: real; BEGIN temp := ((multiplier * seed) + increment); { MOD modulus } temp := temp / modulus; seed := (temp - trunc (temp)) * modulus; random_num := seed / modulus END; {############################################################################## #### Do what is considered a legal operation for an elevator within the #### time interval assumed (say, 5 secs.) ##############################################################################} PROCEDURE perform_a_single_operation (* shaft: integer; VAR output: text *); VAR i, floor1: integer; busy : boolean; BEGIN busy := true; i := 0; REPEAT i := i + 1; WITH elev_cars [shaft] DO IF bldg_on_fire THEN h2andle_an_emergency (shaft, output) ELSE IF reason_to_open_door (shaft) THEN open_elevator_door (shaft, output) ELSE IF must_continue_in_same_direction (shaft) OR (no_passengers (shaft) AND NOT against_stops (shaft) AND listed_in_floor_table (shaft, floor1)) THEN BEGIN { move a floor up or down } status := in_motion; IF direction = down THEN curr_floor := curr_floor - 1 ELSE curr_floor := curr_floor + 1 END ELSE IF busy THEN BEGIN { turn around, but dont move } busy := false; status := in_motion; IF direction = down THEN direction := up ELSE direction := down END UNTIL busy OR (i = 2) END; {############################################################################## #### Return true if elevator (shaft) scheduled to stop at any floor along its #### path ##############################################################################} FUNCTION listed_in_floor_table (* shaft:integer; VAR floor:integer):boolean *); VAR b: boolean; BEGIN floor := bottom_floor - 1; REPEAT floor := floor + 1; b := elev_foyers [floor].nb_elev_serving_this_request = shaft UNTIL (floor >= top_floor) OR b; listed_in_floor_table := b; END; {############################################################################## #### Determine if we must travel further before turning direction. #### Normally, when the elevator is busy, we return true only if there are #### people in the foyers of the floors in the elevator's current direction #### waiting to travel further on in the same direction. ##############################################################################} FUNCTION must_continue_in_same_direction (* shaft: integer) : boolean *); VAR b: boolean; floor: integer; BEGIN b:= false; IF NOT against_stops (shaft) THEN WITH elev_cars[shaft] DO IF direction = up THEN FOR floor := curr_floor+1 TO top_floor DO b := b OR button_bank [floor] OR elev_foyers [floor].up_button ELSE FOR floor := curr_floor-1 DOWNTO bottom_floor DO b := b OR button_bank [floor] OR elev_foyers [floor].down_button; must_continue_in_same_direction := b; END; {############################################################################## #### Determine if the elevator cannot go any further in the same direction. ##############################################################################} FUNCTION against_stops (* shaft: integer): boolean *); BEGIN WITH elev_cars [shaft] DO against_stops := ((curr_floor = bottom_floor) AND (direction = down)) OR ((curr_floor = top_floor ) AND (direction = up)) END; {############################################################################## #### Return true if NO buttons remain lit (unserviced) within the elevator car ##############################################################################} FUNCTION no_passengers (* shaft: integer): boolean *); VAR floor: integer; b: boolean; BEGIN b := false; WITH elev_cars [shaft] DO FOR floor := bottom_floor TO top_floor DO b := b OR button_bank [floor]; no_passengers := NOT b; END; {############################################################################## #### Return true if there stands good reason why the door should be opened. #### Either a passenger wants to get out, or people outside want a ride along #### the current direction. ##############################################################################} FUNCTION reason_to_open_door (* shaft: integer): boolean *); BEGIN WITH elev_cars [shaft] DO reason_to_open_door := button_bank [curr_floor] OR ((direction = up) AND elev_foyers [curr_floor]. up_button) OR ((direction = down) AND elev_foyers [curr_floor]. down_button) END; {############################################################################## #### Return true if floor calls are pending that could not be detected by the #### must_continue_in_same_direction function. #### The calls examined are those originating in the current direction of the #### elevator, but whose direction indicate that the people want to travel #### in the reverse direction. ##############################################################################} FUNCTION other_floor_calls (* shaft: integer): boolean *); VAR b: boolean; floor: integer; BEGIN b:= false; IF NOT against_stops (shaft) THEN WITH elev_cars[shaft] DO IF direction = up THEN FOR floor := curr_floor+1 TO top_floor DO b := b OR elev_foyers [floor].down_button ELSE FOR floor := curr_floor-1 DOWNTO bottom_floor DO b := b OR elev_foyers [floor].up_button; other_floor_calls := b; END; {############################################################################## #### Routine executed whenever a building emergency occurs. ##############################################################################} PROCEDURE h2andle_an_emergency (* shaft: integer; VAR output: text *); BEGIN WITH elev_cars [shaft] DO BEGIN button_bank [fire_exit] := true; IF curr_floor = fire_exit THEN BEGIN open_elevator_door (shaft, output); writeln (output, '*** Elevator # ',shaft:3, ' out of service. ***') END ELSE IF curr_floor > fire_exit THEN BEGIN status := in_motion; direction := down; curr_floor := curr_floor - 1 END ELSE BEGIN status := in_motion; direction := up; curr_floor := curr_floor + 1 END END END; {############################################################################## #### Open the doors on the elevator and simulate the actions of the new #### passengers pushing floor buttons. ##############################################################################} PROCEDURE open_elevator_door (* shaft: integer; VAR output: text *); VAR current_floor: integer; BEGIN WITH elev_cars [shaft] DO BEGIN status := door_open; current_floor := curr_floor END; WITH elev_foyers [current_floor] DO BEGIN IF up_button OR down_button THEN pick_destination_floors (shaft, output); IF elev_cars [shaft]. direction = up THEN up_button := false ELSE down_button := false; IF NOT (up_button OR down_button) THEN BEGIN nb_elev_serving_this_request := 0; END END; WITH elev_cars [shaft] DO BEGIN IF button_bank [curr_floor] THEN BEGIN button_bank [curr_floor] := false; writeln ('Passengers let off elevator ',shaft:2, ' on floor ', curr_floor:4) END END END; {############################################################################## #### Routine to simulate the action of passengers selecting the floors where #### they want to go. If this were my elevator, Any floor selection would #### be recognized (but perhaps not untilany of those in the path of the #### present direction were visited first.) ##############################################################################} PROCEDURE pick_destination_floors (* shaft: integer; VAR output: text *); CONST this_was_my_elevator = false; VAR floor : integer; PROCEDURE push_floor_selection_button; VAR r: real; BEGIN WITH elev_cars [shaft] DO BEGIN r := random_num (seed_for_random_number_generator); IF r < elev_foyers [floor]. prob_this_floor_is_destination THEN BEGIN button_bank [floor] := true; write (output, floor:3, ' , ') END END END; BEGIN WITH elev_cars [shaft] DO BEGIN write (output, 'Passengers picked up by elevator ',shaft:2, ' at floor ', curr_floor:4,' to go to floors '); IF this_was_my_elevator THEN FOR floor := bottom_floor TO top_floor DO push_floor_selection_button ELSE IF NOT against_stops (shaft) THEN IF direction = up THEN FOR floor := curr_floor + 1 TO top_floor DO push_floor_selection_button ELSE FOR floor := curr_floor - 1 DOWNTO bottom_floor DO push_floor_selection_button; writeln (output) END END; {########################################################################## #### Figure out if the elevator has no passengers, whether there is a floor #### call from in your direction to go in the reverse direction, and then #### assign the closest elevator that meets these qualifications to the #### floor call closest to either the top or bottom of the shaft. ##########################################################################} FUNCTION could_provide_service (* shaft: integer): boolean *); VAR b, b1: boolean; floor: integer; FUNCTION find_floor_unassigned_to_elevator (scale: integer): boolean; VAR b: boolean; elev_assigned_previously: boolean; BEGIN REPEAT floor := floor + scale; WITH elev_foyers [floor] DO BEGIN elev_assigned_previously := NOT no_passengers (shaft) OR ((nb_elev_serving_this_request > 0) AND (abs (elev_cars [nb_elev_serving_this_request]. curr_floor - floor) < abs (elev_cars [shaft]. curr_floor - floor))); IF NOT elev_assigned_previously AND (((elev_cars [shaft].direction = up) AND (down_button)) OR ((elev_cars [shaft].direction = down) AND (up_button))) THEN nb_elev_serving_this_request := shaft; b := (nb_elev_serving_this_request = shaft) END UNTIL b OR elev_assigned_previously OR (floor = elev_cars [shaft]. curr_floor); find_floor_unassigned_to_elevator := b END; BEGIN b := (NOT listed_in_floor_table (shaft, floor)) AND other_floor_calls (shaft); IF b THEN IF elev_cars [shaft]. direction = up THEN BEGIN floor := top_floor + 1; b1 := find_floor_unassigned_to_elevator (-1) END ELSE BEGIN floor := bottom_floor - 1; b1 := find_floor_unassigned_to_elevator (+1) END; could_provide_service := b AND b1; END; BEGIN { main program } assign (infil, 'b:elevator.dat'); reset (infil); assign (outfil, 'CON:'); rewrite (outfil); initialize (infil, outfil); {init probability and other stuff } clock := 1; print_status_board (outfil); REPEAT { for each time interval,...} writeln (outfil); writeln (outfil, ' ':30, 'Time Interval: ', clock:4); h1andle_requests_for_elevator_usage (outfil); FOR shaft := 1 TO max_elevators DO perform_a_single_operation (shaft, outfil); clock := clock + 1; print_status_board (outfil); UNTIL clock > max_time; { sorry, even computerized els go out of order } writeln (outfil, 'Simulation complete. ') END.