EventSimulation Tutorial

# EventSimulation Tutorial

## Installation

In REPL run `Pkg.add("EventSimulation")` to install the package and next `using EventSimulation` to start using it.

## First simulation

This is a bare minimum simulation using EventSimulation:

``````using EventSimulation

function arrival(s)
t = s.now
println("Arrived at ", s.now)
register!(s, x -> println("Left at \$(x.now) that arrived at \$t"), 1.5)
end

s = Scheduler()
for t in 1.0:5.0
register!(s, arrival, t)
end

go!(s)``````

In this example five customers arrive at times 1, 2, ..., 5 and stay in the system for 1.5 time units. Observe that in `arrival` we use a closure to define anonymous function that is registered. In its body `x.now` will be taken from the state of the scheduler when the anonymous function is invoked but `t` is fixed in enclosing scope of `arrival` function as the time of the arrival.

When using EventSimulation working with closures is often the simplest way to develop a simulation so it is important that you understand this example.

## Defining infinite source of events

Now we add an infinite stream of customers arriving to the system:

``````using EventSimulation

function arrival(s)
t = s.now
println("Arrived at ", s.now)
register!(s, x -> println("Left at \$(x.now) that arrived at \$t"), 1.5)
end

s = Scheduler()
repeat_register!(s, arrival, x -> 1.0)

go!(s, 7)``````

In this example we show how `arrival` function can be scheduled to be repeatedly put into event queue in time deltas defined by anonymous function `x -> 1.0`.

Aslo observe that we have passed second argument `7` to function `go!` which will force unconditional termination of the simulation after this moment.

Exercise: Think what would happen if the termination time would be omitted in the expression `go!(s, 7)`. How you could use function `terminate!` inside definition of `arrival` to get a similar effect. What would be the difference?

## Defining custom simulation state

Now let us add a simple counter of number of customers in the system:

``````using EventSimulation
using Random

mutable struct CounterState <: AbstractState
count::Int
end

function arrival(s)
function departure(x)
x.state.count -= 1
println("Left at \$(x.now) that arrived at \$t and left ",
x.state.count, " other customers")
end

t = s.now
println("Arrived at ", s.now, " and met ", s.state.count, " other customers")
register!(s, departure, 1.0)
s.state.count += 1
end

s = Scheduler(CounterState(0))

Random.seed!(1)
repeat_register!(s, arrival, x -> rand())
go!(s, 10)``````

Observe that all functions in EventSimulation receive `Scheduler` as an argument and therefore they have acces to current simulation time and simulation state.

Additionally we have changed customer arrival behavior to random.

Exercise: Try changing customer's arrival rate to the exponential distribution using `randexp` function. Next change time in system distribution to the Gamma distribution. You can find it in `Distributions` package.

## Monitoring simulation

Let us calculate how many customers are present in the above system using custom `monitor`.

``````using EventSimulation
using Random

mutable struct CounterState <: AbstractState
count::Int
total_time::Float64
customer_time::Float64
end

function arrival(s)
register!(s, departure, 1.0)
s.state.count += 1
end

function departure(s)
s.state.count -= 1
end

function monitor(s, Δ)
s.state.total_time += Δ
s.state.customer_time += Δ * s.state.count
end

cs = CounterState(0, 0.0, 0.0)
s = Scheduler(cs, Float64, monitor)

Random.seed!(1)
repeat_register!(s, arrival, x -> rand())
go!(s, 100_000)
println("Average number of customers in a system is ",
cs.customer_time/cs.total_time)``````

Observe that `monitor` usually will modify simulation state to gather the simulation statistics. Other approaches could use global variables or variables defined in closure of `monitor`, but using simulation state is the recommended approach.

Exercise: Think if the obtained result is in line with Little's law. Try changing arrival rate and time in the system to check it. As a more advanced exercise make `monitor` collect data only when simulation time is greater or equal than 100 (i.e. discarding simulation burn-in period).

## Introduction to resources

Now we will consider two streams of agents. Supplier provides one unit of good every one unit of time. There are customers that arrive randomly and want to buy a random amount of good. Maximally two customers can wait in the line.

``````using EventSimulation
using Random

mutable struct GoodState <: AbstractState
good::SimResource{Float64}
end

function delivery(s)
provide!(s, s.state.good, 1.0)
printstyled("Delivered 1.0 at ", s.now, "\n", color=:green)
end

function customer(s)
function leave(x)
println("Left at ", x.now, " with quantity ", round(demand, digits=4))
end
demand = rand()
print("Arrived at ", round(s.now, digits=4),
" with demand ", round(demand, digits=4))
if !request!(s, s.state.good, demand, leave)[1]
printstyled(" but line was too long and left with nothing\n", color=:red)
else
println(" and went into a queue")
end
end

function balance(s)
printstyled("Amount of good in storage: ", round(s.state.good.quantity, digits=4),
" at time ", s.now, "\n", color=:magenta)
end

s = Scheduler(GoodState(SimResource{Float64}(max_requests=2)))

Random.seed!(1)
repeat_register!(s, delivery, x -> 1.0)
repeat_register!(s, customer, x -> rand())
repeat_register!(s, balance, x -> x.now == 0 ? 1.000001 : 1.0)
go!(s, 5)``````

Observe that fulfillment of pending requests by `SimResource` is immediate. This means that the amount is passed to a request from the container before the request event executed.

Exercise: We have used a fixed value of `0.000001` as an increment over an integer number to invoke `balance`. Rewrite the example using `PriorityTime` type in such a way that `balance` is invoked at integer times but with priority low enough.

## `Nothing` delta in `repeat_register!`

In general EventSimulation does not check the values passed to the engine in order to maximize execution speed. The only exception is `interval` argument of `repeat_register!` function. If it returns a value that is `nothing` then the repeated scheduling of events is interrupted. Here is a simple example

``````using EventSimulation
using Statistics

mutable struct Jumps <: AbstractState
n::Int
end

jump(s) = s.state.n+=1
next(s) = s.now >= 1.0 ? nothing : rand()

function main()
s = Scheduler(Jumps(0))
repeat_register!(s, jump, next)
go!(s)
s.state.n
end

println(mean(main() for i in 1:10^6))``````

The above code implements a well known puzzle. Assume that we have a bug that gets hungry every `rand()` time units and eats then. Assume that the fist time it eats is 0. What is the expected number of times it will eat till time reaches 1.0? Run the code to learn the answer (if you did not know the puzzle before). Observe that returning `nothing` from `next` forces the simulation to stop.

## Bulk execution of events

EventSimulation allows you to plan execution of a batch of actions at the same time. They can be either executed in a predefined order or in random order (this example actually does not require DES, but is a MWE of `bulk_register!`).

``````using EventSimulation
using Statistics
using Random

mutable struct Airplane <: AbstractState
free::Set{Int}
ok::Bool
end

function sit(s, i)
s.state.ok = i in s.state.free
pop!(s.state.free, s.state.ok ? i : rand(s.state.free))
end

function main(n)
s = Scheduler(Airplane(Set(1:n), false))
tickets = randperm(n)
tickets[1] = rand(1:n)
bulk_register!(s, tickets, sit, 0.0)
go!(s)
s.state.ok
end

for n in [10, 50, 250]
println(n, "\t", mean(main(n) for i in 1:10^4))
end``````

Another simple puzzle. We have an airplane with `n` seats and `n` customers. Every customer has a ticket with seat number. It is represented by a vector `tickets`. Customers enter the airplane in the order of the vector `tickets` and everyone tries to sit on ones own seat. Unfortunately `tickets[1] = rand(1:n)` so the firs customer has forgotten the seat number and has chosen a random seat. When the following customers enter the airplane and find their seat taken they pick a random free seat. The question is what is the probability that last customer that enters the plane finds the seat taken as a function of `n`. Run the simulation to find out if you do not know the answer!

## Next steps

This is the end of this introductory tutorial. More advanced features are covered in examples contained in `/examples/` directory.