Simulation Structures and Modelling in SimPy | Towards Data Science

Python is famous for its simple syntax and numerous libraries which make this language capable of doing almost anything. Python’s range of operations varies from moderately easy tasks like developing web and desktop applications to complex tasks like building machine learning and artificial intelligence projects.
We see systems everywhere, from an embedded system in a digital alarm clock to big systems like airports and railway stations. To run these systems efficiently and optimally, we need simulations as they provide us with readings to rely on. Engagement with surroundings and randomness are two crucial factors of systems and simulations. This article will focus on such functions of python libraries so that we can simulate a scenario as per our requirements. To achieve this goal and create the algorithm, we will use the following two built-in libraries of Python:
SimPy
Random
Scenario: To follow social distancing, as a station master you have to design a plan to minimize the number of people on the platform and keep the maximum people out in their vehicles or waiting rooms (Let’s assume that the rooms have proper measures to keep the travelers safe).
Plan: We will start by assigning arrival and departure times to the trains. A booth should be made to distribute platform tickets to passengers boarding the next arriving train.
I am assuming that you already have Python installed and have a basic knowledge. If you haven’t worked with Python before, you might want to check out this tutorial.
I prefer running the program in the shell. You can use Jupyter Notebooks as well. The first step is to install and import the required libraries. Open the Command Prompt and write:
pip install SimPypip install random
After successful installation, import the libraries using the import function:
import SimPyimport random
Before you start coding the main algorithm, it is necessary to make a clear image of how should your simulation look like. This step is most important for making a good simulation model. One should spend maximum time brainstorming, structuring, and collecting data for this step. Collecting statistical data from a trustworthy source for every uncertain moment in simulation is important. If this is compromised, the frequency of occurrence of one or more events can increase or decrease. This can result in generating false values. According to our plan, the simulation must run in the following manner:
Passenger arrives at the Railway Station and heads to the platform ticket counter. — If he is boarding the next arriving train, he gets a platform ticket. — Else, he is requested to wait in the lounge.
Passenger arrives at the Railway Station and heads to the platform ticket counter. — If he is boarding the next arriving train, he gets a platform ticket. — Else, he is requested to wait in the lounge.
2. The train arrives at the platform, passengers board the train and it departs after 10 minutes. There is also a probability that the train will delay by some minutes.
3. Passengers waiting in the lounge head to the ticket counter and the eligible ones head to the platform. Meanwhile, more passengers arrive at the station.In this way, we can minimize the number of passengers on the platform.
We will start by defining the main class which will contain a blueprint of simulation and the procedures carried out at the railway station. This class will contain some functions that will generate the required environment. This class is named as Station.
class Station(object):    def __init__(self, env, num_worker):        self.env = env        self.worker = simpy.Resource(env, num_worker)
In this code, __init__() is a reserved method in python classes. It is called when an object is created from the class. This method allows us to define the attributes of a class. We proceed by creating some SimPy resources essential for our simulation. The variable ‘env’ is predefined and helps us to generate a virtual environment for simulation. Now think it this way, there are some resources that are provided by the station to the passengers. In this situation, it’s the workers present in the ticket counter. The number of workers will be denoted by num_worker variable.
Before proceeding, we need to learn about some more functions of SimPy and Random library. The first one being:
random.randint(a,b)
This function helps us generate a random integer between two integers a and b (both included). This will aid us in simulating random events in our process. You can use it with conditional statements by specifying a trigger condition for events.
p = random.randint(1,8)if p == 3 :    print('Event Triggered')    #Probability = 0.125
The second function is:
yield self.env.timeout(a)
This is used to run a process (env in this case) for a duration of a minute(s). We can further use this with the randint function to run the environment for any random integral value of time between a and b.
yield self.env.timeout(random.randint(a,b))
Now you are ready to code the function that will classify the passengers and sell platform tickets accordingly. It will also generate the schedule of trains, delay them (if needed), and print the number of passengers on the platform and in the lounge. The task will be accomplished by using the above functions with conditional statements and loops as per requirement. SimPy will help in generating and running the environment.
The first step is to define the function and its arguments. After that, all the necessary variables that govern the arrival, departure (after 10 minutes of arrival), and delay of trains will be declared.
def ticket(self, passenger):    sch_train = random.randint(0,45)     dep_train = sch_train + 10     print('Next train arriving after',sch_train,'minutes')
Note: The function ticket takes self and passenger as its attributes. The passenger attribute will be defined later in the run_station function.
The next task is to set a condition to delay the train. In this case, the train will delay for all even possibilities between 0 and 10.
del_ = random.randint(0,10)del_f = del_%2if del_f == 0:            del_t = random.randint(5,20)            print('Train is delayed by',del_t,'minutes')            sch_train = sch_train + del_t            dep_train = sch_train + 10            print('Train will arrive after',sch_train,'minutes\n')        else:            print('No delay\n')
The variable del_ generates required cases and del_f is the remainder after those case values are divided by 2. To be an even number, the value of del_f must be zero. When it is equal to zero, the train is delayed by del_t minutes which is again a random amount of time between 5 and 20 minutes. Consequently, the value of arrival and departure time is increased. If the difference between the current time and delayed arrival time (new arrival time after the delay is announced) is less than 10, the platform ticket will not be generated (This will be coded in the next step).
num_pass = 0            for i in range(passenger):            time = random.randint(5,60) #time after which passenger's train is scheduled            if time<=dep_train:                yield self.env.timeout(random.randint(1,3))                print('Platform ticket generated')                num_pass +=1            else:                print('Cannot generate platform ticket')print('\nPassengers on platorm:', num_pass)        p = passenger - num_pass        print('Passengers in waiting lounge:', p,'\n')
Initially, num_pass (number of passengers on the platform) is equal to 0. The variable time stores random values of time in minutes after which the train is scheduled. A conditional ‘if’ statement checks whether the passenger is eligible for a platform ticket or not. The time a worker takes to complete this process is between 1 and 3 minutes. Subsequently, the platform ticket is generated, num_pass is incremented by 1, and the value of num_pass is printed.The process of ticket generation is completed. Now we will code a function which schedules the arrival of passengers in the station.
def station_arrival(env, passenger, station):    arrival_time = env.now    with station.worker.request() as req:        yield req        yield env.process(station.ticket(passenger))
Note: This function calls out for another function in the main class. This gives a hint that it will be called again somewhere as a substitute to avoid calling too many functions at once.
The variable arrival_time stores the value of the current time of the environment generated by env.now command. The next line requests the main class (Station) to let the simulation use its resource (num_worker). The last line of this request yields triggers the function of ticket generation (env.process(station.ticket(passenger)) defined in the main class. The last thing we require is a function that calls all other functions in order and generates value for passenger variable in a ‘for’ loop as it is an attribute for both functions defined above.
def run_station(env, num_worker):    station = Station(env, num_worker)    a = 5    for passenger in range(20,40):        env.process(station_arrival(env, passenger, station))while a > 0:        yield env.timeout(0.7)        passenger += 1        env.process(station_arrival(env, passenger, station))
The station_arrival function is called in a ‘for’ loop that asks the environment for a resource and then initiates the ticket generation process after the conditions are fulfilled. Finally, a ‘while’ loop executes the whole process and increments the passenger in every step. In order to run the simulation, we just need to call the run_station function.
With this, the simulation structure is ready to be executed in the main function that will assign the env variable as a SimPy environment and defines the simulation time (env.run(until=200)).
def main():    random.seed(30)    n = input('Enter no. of workers: ')    num_worker = int(n)    env = simpy.Environment()    env.process(run_station(env, num_worker))    env.run(until=200)if __name__ == "__main__":    main()
The effectiveness of the simulation depends on how well the program is structured and how accurate are the probability determining factors. It requires a lot of brainstorming. In this case, all the numbers generated using randint give a rough estimate. We can structure them accurately based on studies/surveys conducted on the same topic an then determine the correct probability of an event. This field has great potential to become a research topic.
For more assistance, download the source code from GitHub.
This is a self-made scenario of maintaining social distancing on platforms. The solution provided to the issue might not be the best but the main motive of framing this situation was to describe basic functions of SimPy library, need for simulations, and demonstrate the coding procedure behind the simple simulation cases.
With the help of the SimPy framework, we can understand a lot about structuring programs and processes. We can now simulate various systems and their subprocesses by defining and scheduling various functions in an environment. This helps us understand the randomness in daily life processes. Now we have a clear understanding of the system and how basic probabilities govern the complexity and accuracy of a simulation.
These simulations can help individuals in making various decisions by calculating costs and other factors. Simulations are an effective way of generating useful and accurate data if they are structured correctly. Moreover, they can also be used in data prediction with the help of data analytics.