Simulation of a M/M/1 queue using processes

This simulation is adapted from the Bank Renege example in Simjulia documentation: http://simjuliajl.readthedocs.io/en/stable/examples/1_bank_renege.html

In [1]:
using SimJulia
using Distributions
using RandomStreams

Let's first simulate a fixed number of clients.

In [4]:
const RANDOM_SEED = 200
const NEW_CUSTOMERS = 5  # Total number of customers
const INTERVAL_CUSTOMERS = 2.0  # Generate new customers roughly every x seconds
const MEAN_SERVICE = 1.9

function source(env::Environment, number::Int, interval::Float64, counter::Resource)
    d = Exponential(interval)
    for i in 1:number
        yield(Timeout(env, rand(d)))
        Process(env, customer, i, counter, MEAN_SERVICE)
    end
end

function customer(env::Environment, idx::Int, counter::Resource, time_in_system::Float64)
    # Record the arrival time in the system
    arrive = now(env)
    println("$arrive: arrival of customer $idx")
    yield(Request(counter))
    # The simulation clock now contains the time when the client goes to the server.
    wait = now(env) - arrive
    # Record the waiting time
    waits[idx] = wait
    println("$(now(env)): customer $idx has waited $wait")
    yield(Timeout(env, rand(Exponential(time_in_system))))
    println("$(now(env)): customer $idx: finished")
    yield(Release(counter))
end
WARNING: Method definition source(SimJulia.Environment, Int64, Float64, SimJulia.Resource) in module Main at In[2]:7 overwritten at In[4]:7.
WARNING: Method definition customer(SimJulia.Environment, Int64, SimJulia.Resource, Float64) in module Main at In[2]:16 overwritten at In[4]:16.
Out[4]:
customer (generic function with 1 method)
In [5]:
# Setup and start the simulation
println("M/M/1 with processes")
waits = Array(Float64, NEW_CUSTOMERS)
srand(RANDOM_SEED)
env = Environment()
M/M/1 with processes
Out[5]:
SimJulia.Environment(0.0,Base.Collections.PriorityQueue{SimJulia.BaseEvent,SimJulia.EventKey,Base.Order.ForwardOrdering}(),0,0,Nullable{SimJulia.Process}())
In [6]:
# Start processes and run
counter = Resource(env, 1)
Process(env, source, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter)
run(env)
1.2050403125807367: arrival of customer 1
1.2050403125807367: customer 1 has waited 0.0
1.4181503510677775: arrival of customer 2
1.7579446126823792: arrival of customer 3
3.3655840436959545: arrival of customer 4
3.5932863161203605: customer 1: finished
3.5932863161203605: customer 2 has waited 2.1751359650525828
3.6479776093878162: customer 2: finished
3.6479776093878162: customer 3 has waited 1.890032996705437
4.429115262189869: customer 3: finished
4.429115262189869: customer 4 has waited 1.0635312184939147
6.231843986748496: arrival of customer 5
11.115163310168295: customer 4: finished
11.115163310168295: customer 5 has waited 4.8833193234198
14.890451525141378: customer 5: finished

We can compute the mean waiting time by

In [7]:
mean(waits)
Out[7]:
2.002403900734347

However, most of the time, we do not know the number of client. We first set the end of simulation event by specifying an time horizon when running the simulation.

In [8]:
Process(env, source, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter)
run(env, 5.0)
16.75032685090032: arrival of customer 1
16.75032685090032: customer 1 has waited 0.0
18.006294710192993: arrival of customer 2
18.19956590875879: customer 1: finished
18.19956590875879: customer 2 has waited 0.193271198565796
19.03895740537856: customer 2: finished

We observe however that the simulation time has not been reset to 0. A simple solution is to create a new simulation environment. This also requires to set the resource again.

In [9]:
env = Environment()
counter = Resource(env, 1)
Process(env, source, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter)
run(env, 5.0)
3.560848405790721: arrival of customer 1
3.560848405790721: customer 1 has waited 0.0
3.96822071626369: customer 1: finished

The random draws are different but we can produce the same as previously by using the same seed, i.e. the same initial state.

In [10]:
srand(RANDOM_SEED)
env = Environment()
counter = Resource(env, 1)
Process(env, source, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter)
run(env, 5.0)
1.2050403125807367: arrival of customer 1
1.2050403125807367: customer 1 has waited 0.0
1.4181503510677775: arrival of customer 2
1.7579446126823792: arrival of customer 3
3.3655840436959545: arrival of customer 4
3.5932863161203605: customer 1: finished
3.5932863161203605: customer 2 has waited 2.1751359650525828
3.6479776093878162: customer 2: finished
3.6479776093878162: customer 3 has waited 1.890032996705437
4.429115262189869: customer 3: finished
4.429115262189869: customer 4 has waited 1.0635312184939147

However, a possible issue is that Customer 4 never finishes his service. If we want to allow the customer to complete his experience, we have to modify the source function. We can circumvent it by redefining the source function so that no customer is generated after a horizon limit, but we do not put a limit when calling the run function.

In [11]:
function source!(env::Environment, number::Int, interval::Float64, counter::Resource, limit::Float64, nserved::Array{Int64,1})
    nserved[1] = 0
    d = Exponential(interval)
    for i in 1:number
        yield(Timeout(env, rand(d)))
        if (now(env) > limit) break end
        Process(env, customer, i, counter, MEAN_SERVICE)
        nserved[1] += 1
    end
end
Out[11]:
source! (generic function with 1 method)
In [12]:
nserved = [ 0 ]
srand(RANDOM_SEED)
env = Environment()
counter = Resource(env, 1)
Process(env, source!, NEW_CUSTOMERS, INTERVAL_CUSTOMERS, counter, 5.0, nserved)
run(env)
1.2050403125807367: arrival of customer 1
1.2050403125807367: customer 1 has waited 0.0
1.4181503510677775: arrival of customer 2
1.7579446126823792: arrival of customer 3
3.3655840436959545: arrival of customer 4
3.5932863161203605: customer 1: finished
3.5932863161203605: customer 2 has waited 2.1751359650525828
3.6479776093878162: customer 2: finished
3.6479776093878162: customer 3 has waited 1.890032996705437
4.429115262189869: customer 3: finished
4.429115262189869: customer 4 has waited 1.0635312184939147
11.115163310168295: customer 4: finished

This raises the question: should we flush the entities in the system at the end of the horizon of allow the entities in the system to complete their process? It depends on the context!

In our case, the mean waiting time is

In [13]:
mean(waits[1:nserved[1]])
Out[13]:
1.2821750450629836
In [14]:
function new_source!(env::Environment, interval::Float64, counter::Resource, limit::Float64, nserved::Array{Int64,1})
    nserved[1] = 0
    i = 0
    d = Exponential(interval)
    while (true)
        yield(Timeout(env, rand(d)))
        if (now(env) > limit) break end
        i += 1
        Process(env, new_customer, i, counter, MEAN_SERVICE, new_waits)
    end
    nserved[1] = i
 end
Out[14]:
new_source! (generic function with 1 method)
In [15]:
function new_customer(env::Environment, idx::Int, counter::Resource, time_in_system::Float64, waits::Array{Float64,1})
    # Record the arrival time in the system
    arrive = now(env)
    println("$arrive: arrival of customer $idx")
    yield(Request(counter))
    # The simulation clock now contains the time when the client goes to the server.
    wait = now(env) - arrive
    # Record the waiting time
    waits = push!(waits, wait)
    println("$(now(env)): customer $idx has waited $wait")
    yield(Timeout(env, rand(Exponential(time_in_system))))
    println("$(now(env)): customer $idx: finished")
    yield(Release(counter))
end
Out[15]:
new_customer (generic function with 1 method)
In [16]:
nserved = [ 0 ]
srand(RANDOM_SEED)
env = Environment()
counter = Resource(env, 1)
new_waits = Float64[]
Process(env, new_source!, INTERVAL_CUSTOMERS, counter, 5.0, nserved)
run(env)
1.2050403125807367: arrival of customer 1
1.2050403125807367: customer 1 has waited 0.0
1.4181503510677775: arrival of customer 2
1.7579446126823792: arrival of customer 3
3.3655840436959545: arrival of customer 4
3.5932863161203605: customer 1: finished
3.5932863161203605: customer 2 has waited 2.1751359650525828
3.6479776093878162: customer 2: finished
3.6479776093878162: customer 3 has waited 1.890032996705437
4.429115262189869: customer 3: finished
4.429115262189869: customer 4 has waited 1.0635312184939147
11.115163310168295: customer 4: finished
In [17]:
new_waits
Out[17]:
4-element Array{Float64,1}:
 0.0    
 2.17514
 1.89003
 1.06353
In [18]:
nserved[1]
Out[18]:
4
In [ ]: