The discrete-event simulator

The qns.simulator modules provides a discrete-event driven simulator. The simulator organize many events and invokes these events at a pacific time to drive the simulation. Events are bound to a discrete occur time. We start from introducing the time structure.

Time structure

The time is discrete in SimQN, that is, the smallest time slot is \(1/accuracy\), where accuracy is the number of time slots per second. In SimQN, the accuracy can be set to meet the simulation’s need. Usually, a larger accuracy brings more simulation overhead.

The discrete time in SimQN is a Time object, and it can be described in seconds and number of time slots:

from qns.simulator.ts import Time

default_time_accuracy = 1,000,000

t1 = Time(1) # time slot 1, a.k.a 1/1,000,000 second
t2 = Time(sec=1.1) # time 1.1 seconds, a.k.a 1,100,000 time slots

print(t1.sec) # output: 1e-6

Also, Time is comparable.

assert(t1 < t2)
assert(t1 <= t2)

t3 = Time(1,100,000)
assert(t1 == t3)

Events in simulation

Event has a occur time t and an invoke function. The invoke function will be called at time t. Just like Time, Event``s are also comparable based on the occur time. ``Event object has also an optional attribution by to represent the entity that generates this event.

from qns.simulator.event import Event

# PrintEvent will print "event happened" if invoked
class PrintEvent(Event):
    def invoke(self) -> None:
        print("event happened")

# te will happen at 1 second
te = PrintEvent(t=Time(sec=1), name="test event", by = None)

# get te's occur time
print(te.t)

# invoke the event manually
te.invoke() # invoke the event

# cannel the event
te.cancel() # cancel the event
assert(te.is_cancelled == True)

# The events are comparable
te2 = PrintEvent(t=Time(sec=2), name="test event 2")
assert(te < te2)

To make it easier of building an event, function func_to_event can wrap any functions to an event.

from qns.simulator.event import Event, func_to_event

# this is a function to print message
def print_msg(msg):
    print(msg)

# func_to_event wrap the print_msg to an event. It is invoked at 6 seconds, and the msg is "hello, world"
print_event = func_to_event(Time(sec = 6, print_msg, "hello, world"))

The Simulator

The simulator maintains an event pool that can get the most recent event in order, then the simulator invokes this event. After every events is handled, the simulation finishes. By default, the event pool is implemented from a minimum heap so that getting the most recent event and inserting events can be done quickly.

The simulator is initiated by a start time ts, an end time te, and the optional time accuracy. The simulation will run between ts and te. During the simulation, the current time is in variable tc.

# start time is 0 second, end time is 60 seconds.
s = Simulator(0, 60)

start_time = s.ts # get the start time
end_time = s.te # get the end time
current_time = s.tc # get the current time

It is possible to insert an event to the simulator by method add_event, and the simulation can start by method run

# start time is 0 second, end time is 60 seconds.
s = Simulator(0, 60)

print_event = func_to_event(Time(sec = 6, print_msg, "hello, world"))

# add a new event
s.add_event(print_event)

# run the simulation
s.run()