Skip to content

trevorbye/ProcessSimToolkit

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Process Simulation Toolkit

This is a simplified tool for running stochastic discrete-event process simulations that follow a Server/Queue model. Models are easily built in a building-block format, with easy access to the classes and functions to create custom logic for more-complicated scenarios. Plotting output is included for a histogram output of total system time, as well as live plotting for output mean stabilization used for determining optimal simulation runs.

The primary algorithm for moving customers between servers and queues can be found here. Modifications can be made to this algorithm to create custom interaction behavior.



Lemonade Stand Tutorial

Full code for this tutorial can be accessed here.

Getting Started


This is a tutorial for simulating the flow of customers through a lemonade stand. Although a simple example, it serves to illustrate the design pattern for this toolkit, and simply adding more Servers/Queues will allow for significantly more complex simulations. To get started, fork this entire project.

After you have access to all classes, begin by importing the necessary libraries and classes:

from UtilityClasses.ServerEntity import Server
from UtilityClasses.QueueEntity import PriorityQueue
from UtilityClasses.TimeRandomizerEntity import TimeRandomizer
from UtilityClasses.ServerAndQueueWrapperEntity import ServerAndQueueWrapper
from SimulationClasses.SimulationApplication import SimApplication

import datetime

The UtilityClasses directory contains various classes used as the building blocks for creating a simulation. The Server class represents a capacity-constrained resource in your system that processes customers and holds them for a certain period of time. This time is stochastically generated by the TimeRandomizer class, which allows you to define different distributions to draw samples from. After customers are processed by a server, they are typically transitioned into a PriorityQueue where wait time is accumulated while waiting for the next server to be available (in this tutorial there is only one server for brevity). ServerAndQueueWrapper is simply a wrapper object for either a Server or a PriorityQueue, and allows all system resources to be added to one list in sequence. SimApplication is the main configuration class, and accepts parameters for the start and end time of each run, total simulation runs, and switches for turning on/off chart output. Importing datetime is necesary to define these time parameters.


Building the System


Begin by defining an empty list, which will hold all Server and PriorityQueue objects we will build. Note: this list represents the sequence of events in the simulation, thus objects must be added in order of occurrence within the system.

server_queue_wrapper_list = []

Next we will begin building the logic that stochastically introduces customers into the system. First, we must define a TimeRandomizer object that will determine the distribution and its parameters used for this sampling. In this case, we will use a normal distribution and state that customers arrive to the lemonade stand with a mean of every 20 seconds and a standard deviation of 5 seconds:

arrival_time_randomizer = TimeRandomizer("normal", normal_mean=20, normal_stddev=5)

After defining the randomizer, we will build the arrival server. The first parameter is capacity for the server, and must always be 1 for an arrival server. The second parameter is an object of type TimeRandomizer, which we have defined as arrival_time_randomizer. The third parameter is an optional text description for the server.

arrival_server = Server(1, arrival_time_randomizer, description="Arrival Server")

Now that the server object is built, wrap it in a ServerAndQueueWrapper object and add it to server_queue_wrapper_list:

arrival_wrapper = ServerAndQueueWrapper(server_object=arrival_server)
server_queue_wrapper_list.append(arrival_wrapper)

In general, a queue should immediately follow a server. Servers can be chained in succession, but if a customer cannot move from one server to the other due to capacity constraints, it will remain in the first server with a zero duration and will cause the simulation to run infinitely as no other events will be able to be processed. Thus, we will build a queue for the lemonade stand line, and it is as simple as defining a PriorityQueue object with an optional description parameter:

arrival_queue = PriorityQueue(description="Lemonade Stand Line")

Now, customers that are created by our arrival server will be inserted into the arrival_queue to await being served lemonade. Similar to creating the arrival server, wrap the arrival_queue and append to server_queue_wrapper_list:

queue_wrapper = ServerAndQueueWrapper(queue_object=arrival_queue)
server_queue_wrapper_list.append(queue_wrapper)

The next step is to build a server that represents the customers being served lemonade. As soon as this server has available capacity, the next customer in line in the arrival_queue will leave the queue and enter the server for getting their lemonade. Again, we must first define a TimeRandomizer object to generate random samples for how long it takes for each customer to be served lemonade. This time, we will use a triangular distribution with min=30, mode=40, max=70:

main_process_randomizer = TimeRandomizer("triangular", triangle_min=30, triangle_mode=40, triangle_max=70)

Since this is not an arrival server, capacity can be set at any level. Considering we have a quick rate of customers arriving, we will initially set the server capacity at 4 (4 employees serving lemonade). We'll also wrap the object and add to our list of processes:

main_process_server = Server(4, main_process_randomizer, "Main process Server (serving Lemonade)")
main_process_wrapper = ServerAndQueueWrapper(server_object=main_process_server)
server_queue_wrapper_list.append(main_process_wrapper)

Simulation Configuration


Now that we have built a list of Server and PriorityQueue objects, we are ready to build the configuration object. First it is necessary to define the start and end time for each run using the datetime library. Note: a longer duration for each run iteration will take longer to run the model, however, increasingly complicated simulations can take longer to reach true system behavior, therefore longer run durations may be necessary depending on your model.

start_time = datetime.datetime(year=2018, month=7, day=1, hour=12, minute=0, second=0)
end_time = datetime.datetime(year=2018, month=7, day=2, hour=12, minute=0, second=0)

At this point we can construct the SimApplication object and run the model. This class accepts as parameters our start and end time for each run, total runs to perform, our list of queues and servers, as well as two boolean values for optional chart output. For now we will only configure output_plot=True and leave mean_stabilization_tracking=False, which will create a histogram of the simulation output. As an inital value, set number of runs equal to 50:

sim = SimApplication(start_time, end_time, 50, server_queue_wrapper_list,
                     output_plot=True, mean_stabilization_tracking=False)

Now simply call the run_sim() method to run the model:

sim.run_sim()

Plot Output

Running the model will produce a histogram plot of the duration (in seconds) customers spent in the system to get served lemonade:

Histogram Output Plot


In order to determine if run size for the model is sufficient, we can toggle the mean stabilization plot. A simple visual method of determining optimal run size is tracking an output statistic (typically mean or standard deviation), and monitoring when it stops experiencing significant changes. The differences are small between each run, but by ensuring stability of an output statistic, you can ensure your model has experienced all possible extreme behaviors and events.

To toggle this plotting capability, modify the mean_stabilization_tracking parameter and set to True (this will override and turn off the histogram plot). Additionally, increase the runs to 500 to track a longer run duration. Run the model again and live plotting will begin:

sim = SimApplication(start_time, end_time, 500, server_queue_wrapper_list,
                     output_plot=True, mean_stabilization_tracking=True)
sim.run_sim()

This plot will update every simulation iteration while it is runnning, thus allowing output stabilization to be tracked in real-time. The full run produces this plot:

Mean Stabilization Output Plot


For this model, the mean stabilizes at approximately 375 runs, thus it can be concluded that 375 runs is optimal for this model and any runs beyond that value will not affect output data significantly.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published