Jump to content

Mathematical model & Simulation of a Lobby with Elevators


Freak
 Share

Recommended Posts

So for my mathematical modeling class, we had to do a final project where we created a probabilistic model in one of a few different available topics. Mine was for the "rush hour" of an office lobby when all the employees arrive in the morning. All I was given were some basic immutable conditions, and the rest was up to me! Those conditions were:

  • The time between employee arrivals varies between 0 and 30 seconds in a probabilistic manner.
  • There are 4 elevators for 12 floors.
  • Elevators wait 15 seconds after a person enters them before closing doors (this resets after every entry)

I'm going to walk you through my though process and my formulation of what I consider to be a pretty snazzy model!

 

So the first question on my mind and immediately what I interpreted to be the most important issue was the probabilistic arrival time of the employees. I could easily just make people equally as likely to arrive at any time in that 30 second interval, but that's very uninspired and boring. Instead I thought for a minute and decided that I wanted to start with a parabolic distribution like this:

Ji9P5kS.png

Where "α" is a normalizing constant to make sure that the cumulative probability for the relevant domain is 1.

The reason being that I felt arrivals in the real world would likely be semi-grouped. This is because of things like traffic lights, carpools, public transport, cross walk signals, etc... People are forced to wait at various checkpoints in their commutes, and then all given the green light at the same time. Therefore after any given person's arrival, it's more likely that the next person is either going to arrive very shortly, or a while afterward.

I then tried to see how I could improve it.

  1. I didn't like that the probability for an arrival after 15 seconds was 0 because not only does that mean we'll never have a 15 second wait period, but we also will probably never have any that are between 10-20 seconds because the probabilities are so low. For that reason I needed a base probability constant to add to the equation.
  2. Additionally, I also wanted to experiment with the idea of a variable shift value. Instead of having the parabola constantly centered at 15 seconds, I could have its global minimum vary. It could be centered at 13 when arrivals should be more sparse, and at 17 when things are busier. This is a better model for an actual rush hour because in the middle of a rush hour it's busier than the beginning or end.

So I made the following changes:

JVPh0pR.png

This is just the graph of T(t) not of P(x).

"a" is another normalizing constant that I solved for by setting T(0) to 0 so that it would accurately span the entire duration of the simulation. The shift value was 2400 on T(t) because the simulation will run for 80 minutes and that's 4800 seconds. "c2" is the maximum value of the time variable shift. For P(x) "mi" is a constant which represents the minimum shift value and "c" is the base probability value.

From there I just needed to substitute the finished T(t) function into P(x), assign values to my constants, and solve for "α"

u5ReU6s.png

and when you graph that final equation you get:

AhWkLsK.png

Where any ZX slice of the surface represents the probability distribution for arrival interval at any given point in the simulation.

Finally, I also had the idea to include an option for employees to take the stairs with roughly a flipped exponential distribution for the probabilities that any given employee will use them (e.g. 66% chance if on floor 2, 43% for floor 3, 27% for floor 4, etc...)

 

Finally I had to write a program for a Monte Carlo simulation to actually implement the model:

Main.Java

Spoiler

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.Random;

public class Main {
	public static Random rand = new Random();
	public static Person finishedHead = null;
	
	public static void main(String[] args){
		Person queueHead, allHead, tempPerson;
		Elevator[] theElevators = new Elevator[4];
		boolean check;	
		double time;
		
		double endQueueNum;
		double totalQueue;
		double totalElevator;
		int totalPeople;
		double maxWait;
		double totalNonZero;
		int nonZeroNum;
		double totalClose;
		
		double nonZeroSum = 0;
		double endQueueSum = 0;
		double queueAverageSum = 0;
		double elevatorAverageSum = 0;
		double maxWaitSum = 0;
		double afterCloseSum = 0;
		int elevatorPeople = 0;
		double ratioSum = 0;
		int runNum = 5000;
		for(int k = 0; k < runNum; ++k){
			queueHead = null;
			allHead = new Person();
			for(int j = 0; j < theElevators.length; ++j){
				theElevators[j] = new Elevator();
			}

			allHead.setArrivalTime(0);
			generatePeople(allHead, 0);
			do{
				check = true;	
				//do elevator checks
				for(int i = 0; i < theElevators.length; ++i){
					if(theElevators[i].isOccupied() && theElevators[i].isAvailable()){
						if((allHead.getArrivalTime()-theElevators[i].getLastArrival()) > 15){
							theElevators[i].setAvailable(false);
							theElevators[i].setTimeAvailable(theElevators[i].getLastArrival()+15);
						}
					}
					if(!theElevators[i].isAvailable() && theElevators[i].getTimeAvailable() < allHead.getArrivalTime()){ //When an elevator gets back
						theElevators[i].setAvailable(true);
						for(int j = 0; j<20; j++){
							if(queueHead != null){
								queueHead.setQueueTime((theElevators[i].getTimeAvailable() + 15 + j) - queueHead.getArrivalTime());
								queueHead.setElevatorStartTime((theElevators[i].getTimeAvailable() + 15 + j));
								theElevators[i].addOccupant(pop(queueHead));
								queueHead = queueHead.getNextPerson();
							}else{
								break;
							}
							if(j == 19){ //if the elevator got completely filled by queue members upon arriving
								theElevators[i].setAvailable(false);
								theElevators[i].setTimeAvailable(theElevators[i].getTimeAvailable() + 35); //35 = 15+20
							}
						}
					}
				}
				
				//do the person stuff
				if(allHead.useStairs()){
					finishedPush(pop(allHead));
					allHead = allHead.getNextPerson();
				}else{
					for(int i = 0; i < theElevators.length; ++i){
						if(theElevators[i].isAvailable()){
							time = allHead.getArrivalTime();
							allHead.setElevatorStartTime(time);//fdgfdg
							theElevators[i].addOccupant(pop(allHead));
							allHead = allHead.getNextPerson();
							if(theElevators[i].getOccupantsNum() >= 20){ //if it's full, then send off
								theElevators[i].setAvailable(false);
								theElevators[i].setTimeAvailable(time);
							}
							check = false;
							break;
						}
					}
					if(check){
						if(queueHead == null){
							queueHead = pop(allHead);
							allHead = allHead.getNextPerson();
						}else{
							tempPerson = queueHead;
							while(tempPerson.getNextPerson() != null){
								tempPerson = tempPerson.getNextPerson();
							}
							tempPerson.setNextPerson(pop(allHead));
							allHead = allHead.getNextPerson();
						}
					}
				}
				if(allHead == null){
					break;
				}
			}while(allHead.getNextPerson() != null);

			for(int i = 0; i < theElevators.length; ++i){
				theElevators[i].setTimeAvailable(4800);
			}
			endQueueNum = 0;
			while(queueHead != null){
				endQueueNum++;
				queueHead = queueHead.getNextPerson();
			}
			
			tempPerson = finishedHead;
			totalQueue = 0;
			totalElevator = 0;
			totalPeople = 0;
			elevatorPeople = 0;
			maxWait = 0;
			totalNonZero = 0;
			nonZeroNum = 0;
			totalClose = 0;
			while(tempPerson != null){
				if(tempPerson.getQueueTime() > maxWait){
					maxWait = tempPerson.getQueueTime();
				}
				if(tempPerson.getQueueTime() != 0 && !Double.isNaN(tempPerson.getQueueTime())){
					totalNonZero += tempPerson.getQueueTime();
					nonZeroNum++;
				}
				totalQueue += tempPerson.getQueueTime();
				if(tempPerson.useStairs() == false){
					elevatorPeople++;
					totalElevator += tempPerson.getElevatorTime();
					totalClose += tempPerson.getElevatorExtraTime();
				}
				totalPeople++;
				tempPerson = tempPerson.getNextPerson();
			}
			nonZeroSum += (totalNonZero / nonZeroNum);
			endQueueSum += endQueueNum;
			maxWaitSum += maxWait;
			queueAverageSum += (totalQueue/totalPeople);
			elevatorAverageSum += (totalElevator/elevatorPeople);
			afterCloseSum += (totalClose/elevatorPeople);
			ratioSum += ((double)(nonZeroNum)/totalPeople)*100;
		}
		System.out.println("Average Queue Time: " + (queueAverageSum/runNum));
		System.out.println("Average Non-Zero Queue Time: " + (nonZeroSum/runNum));
		System.out.println("Average Elevator Time: " + (elevatorAverageSum/runNum));
		System.out.println("Average Elevator Time After Close: " + (afterCloseSum/runNum));
		System.out.println("Average Max Queue Time: " + (maxWaitSum/runNum));
		System.out.println("Average Queue Length At End: " + (endQueueSum/runNum));
		System.out.println("Average Percent of People Who Queued: " + (ratioSum/runNum));
	}
	
	public static Person pop(Person p){
		Person tempPerson = new Person(p);
		tempPerson.setNextPerson(null);
		return tempPerson;
	}
	public static void finishedPush(Person toAdd){
		if(finishedHead == null){
			finishedHead = toAdd;
		}else{
			Person tempPerson = finishedHead;
			toAdd.setNextPerson(tempPerson);
			finishedHead = toAdd;
		}
	}
	
	public static void generatePeople(Person head, double time){
		Person next = new Person();
		time += extraTime(time);
		if(time <= 4800){
			next.setArrivalTime(time);
			head.setNextPerson(next);
			generatePeople(head.getNextPerson(), time);
		}
	}
	
	public static double extraTime(double time){
		double testVal;
		double condition;
		double alpha = (1.2/(Math.pow(13 + (4*Math.pow(time - 2400,2))/5760000,3) - Math.pow((4*Math.pow(time - 2400,2))/5760000 - 17,3)));
		
		while(true){
			testVal = 30*rand.nextDouble();
			condition = rand.nextDouble();
			if(condition <= (alpha*Math.pow(testVal + (4/5760000)*Math.pow(time-2400,2) -17,2)) + .02){
				break;
			}
		}
		return testVal;
	}
	
	public static void distritubtionCheck(){
		String text = "";
		File file = new File("dist.txt");
		try{
			file.createNewFile();
		}catch(IOException e){
			e.printStackTrace();
		}
		for(int i = 0; i<10000; ++i){
			text += extraTime(3600) + "\n";
		}
		try (PrintStream out = new PrintStream(new FileOutputStream("dist.txt"))) {
		    out.print(text);
		}catch(FileNotFoundException e){
			e.printStackTrace();
		}
	}
}

 

Person.Java

Spoiler


public class Person {
	private int floor;
	private double arrivalTime;
	private Person nextPerson;
	private boolean useStairs = false;
	
	private double elevatorStartTime;
	private double elevatorExtraTime;
	private double elevatorTime;
	private double queueTime = 0;
	
	public Person(){
		nextPerson = null;
		floor = Main.rand.nextInt(12) + 1; //Choose a random floor 1-12
		if(floor == 1){
			useStairs = true;
		}else if(floor == 2 && Main.rand.nextDouble() <= .66){
			useStairs = true;
		}else if(floor == 3 && Main.rand.nextDouble() <= .43){
			useStairs = true;
		}else if(floor == 4 && Main.rand.nextDouble() <= .27){
			useStairs = true;
		}else if(floor == 5 && Main.rand.nextDouble() <= .16){
			useStairs = true;
		}else if(floor == 6 && Main.rand.nextDouble() <= .08){
			useStairs = true;
		}else if(floor == 7 && Main.rand.nextDouble() <= .03){
			useStairs = true;
		}
	}
	public Person(Person p){
		nextPerson = p.getNextPerson();
		floor = p.getFloor();
		useStairs = p.useStairs();
		arrivalTime = p.getArrivalTime();
		queueTime = p.getQueueTime();
		elevatorStartTime = p.getElevatorStartTime();
		elevatorExtraTime = p.getElevatorExtraTime();
	}
	
/*---------------------------------------
 * 				Getters
 */
	public double getElevatorTime(){
		return elevatorTime;
	}
	public double getQueueTime(){
		return queueTime;
	}
	public Person getNextPerson(){
		return nextPerson;
	}
	public int getFloor(){
		return floor;
	}
	public double getArrivalTime(){
		return arrivalTime;
	}
	public boolean useStairs(){
		return useStairs;
	}
	public double getElevatorStartTime(){
		return elevatorStartTime;
	}
	public double getElevatorExtraTime(){
		return elevatorExtraTime;
	}
	
/*---------------------------------------
 * 				Setters
 */	
	public void setNextPerson(Person p){
		nextPerson = p;
	}
	public void setArrivalTime(double t){
		arrivalTime = t;
	}
	
	public void setFloorHigh(){
		floor = 100;
	}
	public void setElevatorTime(double t){
		elevatorTime = t;
	}
	public void setQueueTime(double q){
		queueTime = q;
	}
	public void setElevatorStartTime(double t){
		elevatorStartTime = t;
	}
	public void setElevatorExtraTime(double t){
		elevatorExtraTime = t;
	}
}

 

Elevator.Java

Spoiler



public class Elevator {
	private Person[] occupants = {};
	private int occupantsNum = 0;
	private boolean available;
	private double lastArrival;
	private double nextTimeAvailable; //okay?
	
	public Elevator(){
		available = true;
	}
	
/*---------------------------------------
 * 				Getters
 */
	public double getTimeAvailable(){
		return nextTimeAvailable;
	}
	public boolean isAvailable(){	
		return available;
	}
	public double getLastArrival(){
		return lastArrival;
	}
	public boolean isOccupied(){
		if(occupants.length == 0){
			return false;
		}else{
			return true;
		}
	}
	public int getOccupantsNum(){
		return occupantsNum;
	}
	
/*---------------------------------------
 * 				Setters
 */
	public void addOccupant(Person newOccupant){
		Person[] newOccupants = new Person[occupants.length + 1];
		for(int i = 0; i < occupants.length; ++i){
			newOccupants[i] = occupants[i];
		}
		newOccupants[occupants.length] = newOccupant;
		occupants = newOccupants;
		lastArrival = newOccupant.getArrivalTime();
		occupantsNum++;
	}
	public void setAvailable(boolean b){
		available = b;
	}
	public void setTimeAvailable(double time){
		double extraTime = 0;
		int currentFloor = 0;
		
		Person[] tempOccupants = new Person[occupants.length]; //Selection sort them in order of floor number
		int lowest = -1;
		for(int i = 0; i < occupants.length; ++i){
			for(int j = 0; j < occupants.length; ++j){
				if(lowest == -1){
					lowest = j;
				}else if(occupants[lowest].getFloor() > occupants[j].getFloor()){
					lowest = j;
				}
			}
			tempOccupants[i] = occupants[lowest];
			occupants[lowest] = new Person();
			occupants[lowest].setFloorHigh(); //this is a dirty fix...
		}
		occupants = tempOccupants;
		
		for(int i = 0; i < occupants.length; ++i){
			extraTime += occupants[i].getFloor() - 1 - currentFloor;
			if(occupants[i].getFloor() - 1 - currentFloor != 0){
				extraTime += 15;
			}
			extraTime += 1;
			occupants[i].setElevatorTime((time - occupants[i].getElevatorStartTime()) + extraTime);
			occupants[i].setElevatorExtraTime(extraTime);
			Main.finishedPush(occupants[i]);
			currentFloor = occupants[i].getFloor() - 1;
		}
		extraTime += currentFloor;
		nextTimeAvailable = time + extraTime;
		Person[] test = {};
		occupants = test;
		occupantsNum = 0;
	}
}

 

Parts of the code might be slightly ham-fisted because I needed to just get it to work, but it works perfectly fine.

And these were the results after running the simulation 5000 times:

YgpCVk1.png

What's quite interesting is that you can actually see the time variable shift in these results. Only about 0.1% of people actually had to wait in line for an elevator, but the average amount of time that people spent in line if they did have to wait was 23 seconds rather than something inconsequential. This implies that the model did its job and toward the middle of the simulation a line formed, and then it dispersed when the rush hour died down.

  • I Like This! 1
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...