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))
endAnother 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.