The first question you might ask, is why the heck would you build your own simulation framework in Python? There’s a million of them, right?
I wouldn’t say a million. SimPy seems to be number one right now. There are a few others. And SimPy is a nice little framework. It has events that process, queues that automatically increment, all kinds of things. But after spending a few weeks trying to do what I want to do, the SimPy was a little to SimPle. I tried all kinds of ways to process the complicated set of events and state changes that needed to occur in SimPy, but for a medium-sized, industrial discrete event simulation, SimPy just didn’t have the fidelity and flexibility that you might need.
Here’s the system I need to simulate: I have a bunch of patrol cars roaming around the city, answering calls from people who need help. Obviously, we want to place our patrol cars to minimize the amount of time it takes to respond to a call. So I have a heuristic that places the patrol cars in the best spots to respond to a call, depending on what day of the week it is, and what hour of the day it is. The calls on Saturday night, come in at a different frequency, and different places than the calls on Thursday morning. So we need a dynamic solution where the patrol cars move to a new patrol position every hour. We also need the ability for the cars to be dispatched to the nearest call. And we need the ability for the cars to be able to go from call to call, in case they get too busy and the calls go into a queue. And the cars need to have several different states. This is where SimPy failed. The cars in SimPy are a resource, once a resource is assigned, you have limited-to-no control over that resource until it’s released by the requesting entity, in this case, the caller. There was just no simple way to do this in SimPy. I’m sure I could have figured it out if I banged my head on my desk a few more times, but if I had known the limitations of the framework, and how simple it was to create my own, I never would have gone down that road.
So, without further ado, let’s get started with the most important ingredient of our discrete event simulation, a clock. Without a clock, you can’t have discrete events, the notion of time is inherent in an event. An event will always take place, at a certain time. It may not be on-time, in fact if it’s a flight I’m on, it will almost never be on-time, but eventually, there will be a time where either the flight will take off, or it will be canceled.
But to go back to our simulation, we need a clock to control the state of these events. So the first thing I’m going to do, is create the simplest clock possible in my Python script, a for loop. Because my patrol car simulation’s basic unit of time measurement is the minute, I’m going to pretend that each iteration of my for loop is one minute. You can, of course change this, the unit of measurement is arbitrary, it can be a second, a millisecond, or a year, whatever works for your own needs. Here’s what it looks like:
#our unit of time here, is going to be #one minute, and we're going to run for one week SIM_TIME=7*24*60 ####START SIM RUN for i in range(1, SIM_TIME): print("Sim minute: %d" % i)
And here’s a little bit of sample output:
Sim minute: 1
Sim minute: 2
Sim minute: 3
Sim minute: 4
Sim minute: 5
Sim minute: 6
Sim minute: 7
Sim minute: 8
Sim minute: 9
Sim minute: 10
…
Very simple, right? A very basic simulation of a clock, but not very useful. For my patrol car simulation, the cars will be relocated every hour, that’s every sixty minutes for those of you that work for my favorite airline. How do we tell our simulation to do something every hour? We’ll use the modulo operator in Python : %. It looks something like this:
#our unit of time here, is going to be #one minute, and we're going to run for one week SIM_TIME=7*24*60 ####START SIM RUN hour=0 for i in range(1, SIM_TIME): print("Sim minute: %d" % i) if i % 60 == 0: print("Another hour has passed. Last hour %d" % hour) hour+=1 print("This hour: %d" % hour)
Now the output looks like this:
Sim minute: 56
Sim minute: 57
Sim minute: 58
Sim minute: 59
Sim minute: 60
Another hour has passed. Last hour 0
This hour: 1
Sim minute: 61
…
So now we have a way of making something happen every hour, which we need to move our patrol cars. Counting minutes is fine, and we can probably write our entire simulation by counting minutes, but it makes readability and debugging a lot harder. Let’s make our sim more readable and compatible with the outside world by making our clock look more like the outside world. First I’ll set up a couple of arrays that I can use to track hours and days:
DOW=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"] hour_array=["00","01", "02", "03", "04", "05", "06", "07","08", "09", "10", "11", "12", "13", "14","15", "16", "17", "18", "19", "20", "21","22", "23"]
As you’ve notices my hours are going to be on the twenty-four hour clock system, as most law enforcement agencies use. I’m going to put these together in an class called a ScheduleHour, which looks something like this:
class ScheduleHour(object): def __init__(self, day, hour, index): self.day = day self.hour = hour self.index = index
ScheduleHour is going to be used to frame a weekly schedule to be used as a guide, AKA a schedule. To translate between the schedule and the clock, I’m going to use another class called DayHourMinute. This will look very similar, but everything’s a string:
class DayHourMinute(object): def __init__(self, day_string, hour_string, minute_string): self.day=day_string self.hour=hour_string self.minute=minute_string
So here’s what it looks like now, when I put it all together:
#our unit of time here, is going to be #one minute, and we're going to run for one week SIM_TIME=7*24*60 DOW=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"] hour_array=["00","01", "02", "03", "04", "05", "06", "07","08", "09", "10", "11", "12", "13", "14","15", "16", "17", "18", "19", "20", "21","22", "23"] current_day_hour_minute=None class DayHourMinute(object): def __init__(self, day_string, hour_string, minute_string): self.day=day_string self.hour=hour_string self.minute=minute_string class ScheduleHour(object): def __init__(self, day, hour, index): self.day = day self.hour = hour self.index = index ####START SIM RUN hour=0 schedule = [] h=0 for this_day in DOW: for this_hour in hour_array: temp_hour = ScheduleHour(this_day, this_hour, h) schedule.append(temp_hour) h += 1 for i in range(1, SIM_TIME): if i % 60 == 0: print("Another hour has passed. Last hour %d" % hour) hour+=1 print("This hour: %d" % hour) day_index = DOW.index(schedule[hour].day) current_day_hour_minute = DayHourMinute(schedule[hour].day, schedule[hour].hour, str(i - int(schedule[hour].hour) * 60 - (1440 * day_index))) print("Day %s Hour %s Minute %s " % (current_day_hour_minute.day, current_day_hour_minute.hour, current_day_hour_minute.minute))
And here’s what the output looks like:
…
Day Sun Hour 00 Minute 57
Day Sun Hour 00 Minute 58
Day Sun Hour 00 Minute 59
Another hour has passed. Last hour 0
This hour: 1
Day Sun Hour 01 Minute 0
Day Sun Hour 01 Minute 1
Day Sun Hour 01 Minute 2
…
Much more readable, isn’t it? Notice the minute count goes from 0 to 59, instead of 1 to 60. This sim tutorial will be continued. Here’s the source code for this tutorial:
https://github.com/jenholm/MontePy/blob/master/Sim3.py