From 965bd51bb95b344dec460f56aee7cf18c446e8a1 Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Thu, 3 Aug 2023 02:38:32 +0000 Subject: [PATCH] build based on bb72add --- dev/api/index.html | 4 ++-- dev/examples/Latency/index.html | 2 +- dev/examples/mmc/index.html | 2 +- dev/examples/ross/index.html | 2 +- dev/guides/basics/index.html | 2 +- dev/guides/environments/index.html | 2 +- dev/guides/events/index.html | 2 +- dev/index.html | 2 +- dev/search/index.html | 2 +- dev/search_index.js | 2 +- dev/tutorial/index.html | 2 +- 11 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev/api/index.html b/dev/api/index.html index 5df9cc0..5e3ad3e 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -1,5 +1,5 @@ -API · ConcurrentSim

API

ConcurrentSim.ContainerType
Container{N}(env::Environment, capacity::N=one(N); level::N=zero(N))

A "Container" resource object, storing up to capacity units of a resource (of type N).

There is a Resource alias for Container{Int}.

Resource() with default capacity of 1 is very similar to a typical lock. The request and unlock functions are a convenient way to interact with such a "lock", in a way mostly compatible with other discrete event and concurrency frameworks.

See Store for a more channel-like resource.

Think of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).

source
ConcurrentSim.StoreType
Store{T}(env::Environment; capacity::UInt=typemax(UInt))

A store is a resource that can hold a number of items of type T. It is similar to a Base.Channel with a finite capacity (put! blocks after reaching capacity). The put! and take! functions are a convenient way to interact with such a "channel" in a way mostly compatible with other discrete event and concurrency frameworks.

See Container for a more lock-like resource.

Think of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).

source
Base.put!Method
put!(sto::Store, item::T)

Put an item into the store. Returns the put event, blocking if the store is full.

source
ConcurrentSim.requestMethod
request(res::Container)

Locks the Container (or Resources) and return the lock event. If the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.

source
ConcurrentSim.tryrequestMethod
tryrequest(res::Container)

If the Container (or Resource) is not locked, locks it and return the lock event. Returns false if the Container is locked, similarly to the meaning of trylock for Base.ReentrantLock.

If the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.

julia> sim = Simulation(); res = Resource(sim);
+API · ConcurrentSim

API

ConcurrentSim.ContainerType
Container{N<:Real, T<:Number}(env::Environment, capacity::N=one(N); level::N=zero(N))

A "Container" resource object, storing up to capacity units of a resource (of type N).

There is a Resource alias for Container{Int, Int}.

Resource() with default capacity of 1 is very similar to a typical lock. The request and unlock functions are a convenient way to interact with such a "lock", in a way mostly compatible with other discrete event and concurrency frameworks.

See Store for a more channel-like resource.

Think of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).

source
ConcurrentSim.StoreType
Store{N, T<:Number}(env::Environment; capacity::UInt=typemax(UInt))

A store is a resource that can hold a number of items of type N. It is similar to a Base.Channel with a finite capacity (put! blocks after reaching capacity). The put! and take! functions are a convenient way to interact with such a "channel" in a way mostly compatible with other discrete event and concurrency frameworks.

See Container for a more lock-like resource.

Think of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).

source
Base.put!Method
put!(sto::Store, item::T)

Put an item into the store. Returns the put event, blocking if the store is full.

source
ConcurrentSim.requestMethod
request(res::Container)

Locks the Container (or Resources) and return the lock event. If the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.

source
ConcurrentSim.tryrequestMethod
tryrequest(res::Container)

If the Container (or Resource) is not locked, locks it and return the lock event. Returns false if the Container is locked, similarly to the meaning of trylock for Base.ReentrantLock.

If the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.

julia> sim = Simulation(); res = Resource(sim);
 
 julia> ev = tryrequest(res)
 ConcurrentSim.Put 1
@@ -8,4 +8,4 @@
 ConcurrentSim.Put
 
 julia> tryrequest(res)
-false
source
Base.unlockMethod
unlock(res::Container)

Unlocks the Container and return the unlock event.

source
Base.take!Function
take!(::Store)

An alias for get(::Store) for easier interoperability with the Base.Channel interface. Blocks if the store is empty.

source
+false
source
Base.unlockMethod
unlock(res::Container)

Unlocks the Container and return the unlock event.

source
Base.take!Function
take!(::Store)

An alias for get(::Store) for easier interoperability with the Base.Channel interface. Blocks if the store is empty.

source
diff --git a/dev/examples/Latency/index.html b/dev/examples/Latency/index.html index 50c8cc1..af6913c 100644 --- a/dev/examples/Latency/index.html +++ b/dev/examples/Latency/index.html @@ -81,4 +81,4 @@ Received this at 80.0 while sender sent this at 70.0 Received this at 85.0 while sender sent this at 75.0 Received this at 90.0 while sender sent this at 80.0 -Received this at 95.0 while sender sent this at 85.0 +Received this at 95.0 while sender sent this at 85.0 diff --git a/dev/examples/mmc/index.html b/dev/examples/mmc/index.html index 82aa871..945d5bd 100644 --- a/dev/examples/mmc/index.html +++ b/dev/examples/mmc/index.html @@ -62,4 +62,4 @@ # Customer 8 exited service: 8.683396356977969 # Customer 10 entered service: 8.683396356977969 # Customer 9 exited service: 8.7501656586875 -# Customer 10 exited service: 9.049670951561666 +# Customer 10 exited service: 9.049670951561666 diff --git a/dev/examples/ross/index.html b/dev/examples/ross/index.html index 8fa6db4..4b3dcdc 100644 --- a/dev/examples/ross/index.html +++ b/dev/examples/ross/index.html @@ -68,4 +68,4 @@ At time 30844.62667837361: No more spares! At time 1601.2524911974856: No more spares! At time 824.1048708405848: No more spares! -Average crash time: 16664.247588264083 +Average crash time: 16664.247588264083 diff --git a/dev/guides/basics/index.html b/dev/guides/basics/index.html index 2928294..0ab0433 100644 --- a/dev/guides/basics/index.html +++ b/dev/guides/basics/index.html @@ -14,4 +14,4 @@ # output -now=1.0, value=42

The example process function above first creates a timeout event. It passes the environment, a delay, and a value to it. The timeout schedules itself at now + delay (that’s why the environment is required); other event types usually schedule themselves at the current simulation time.

The process function then yields the event and thus gets suspended. It is resumed, when ConcurrentSim processes the timeout event. The process function also receives the event’s value (42) – this is, however, optional, so @yield event would have been okay if the you were not interested in the value or if the event had no value at all.

Finally, the process function prints the current simulation time (that is accessible via the now function) and the timeout’s value.

If all required process functions are defined, you can instantiate all objects for your simulation. In most cases, you start by creating an instance of Environment, e.g. a Simulation, because you’ll need to pass it around a lot when creating everything else.

Starting a process function involves two things:

+now=1.0, value=42

The example process function above first creates a timeout event. It passes the environment, a delay, and a value to it. The timeout schedules itself at now + delay (that’s why the environment is required); other event types usually schedule themselves at the current simulation time.

The process function then yields the event and thus gets suspended. It is resumed, when ConcurrentSim processes the timeout event. The process function also receives the event’s value (42) – this is, however, optional, so @yield event would have been okay if the you were not interested in the value or if the event had no value at all.

Finally, the process function prints the current simulation time (that is accessible via the now function) and the timeout’s value.

If all required process functions are defined, you can instantiate all objects for your simulation. In most cases, you start by creating an instance of Environment, e.g. a Simulation, because you’ll need to pass it around a lot when creating everything else.

Starting a process function involves two things:

diff --git a/dev/guides/environments/index.html b/dev/guides/environments/index.html index 4fcbc30..4c5c660 100644 --- a/dev/guides/environments/index.html +++ b/dev/guides/environments/index.html @@ -57,4 +57,4 @@ end

In ConcurrentSim, this can be used to provide return values for processes that can be used by other processes:

@resumable function other_proc(env::Environment)
   ret_val = @yield @process my_proc(env)
   @assert ret_val == 150
-end
+end diff --git a/dev/guides/events/index.html b/dev/guides/events/index.html index c4c3040..6205889 100644 --- a/dev/guides/events/index.html +++ b/dev/guides/events/index.html @@ -19,4 +19,4 @@ ConcurrentSim.Event 1 julia> run(sim) -Called back from ConcurrentSim.Event 1 +Called back from ConcurrentSim.Event 1 diff --git a/dev/index.html b/dev/index.html index b4fe279..1ac6c1e 100644 --- a/dev/index.html +++ b/dev/index.html @@ -26,4 +26,4 @@ fast 0.5 slow 1.0 fast 1.0 -fast 1.5 +fast 1.5 diff --git a/dev/search/index.html b/dev/search/index.html index 830aba4..0995802 100644 --- a/dev/search/index.html +++ b/dev/search/index.html @@ -1,2 +1,2 @@ -Search · ConcurrentSim

Loading search...

    +Search · ConcurrentSim

    Loading search...

      diff --git a/dev/search_index.js b/dev/search_index.js index 7c4e40d..edb2871 100644 --- a/dev/search_index.js +++ b/dev/search_index.js @@ -1,3 +1,3 @@ var documenterSearchIndex = {"docs": -[{"location":"guides/environments/#Environments","page":"Environments","title":"Environments","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"A simulation environment manages the simulation time as well as the scheduling and processing of events. It also provides means to step through or execute the simulation.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The base type for all environments is Environment. “Normal” simulations use its subtype Simulation.","category":"page"},{"location":"guides/environments/#Simulation-control","page":"Environments","title":"Simulation control","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"ConcurrentSim is very flexible in terms of simulation execution. You can run your simulation until there are no more events, until a certain simulation time is reached, or until a certain event is triggered. You can also step through the simulation event by event. Furthermore, you can mix these things as you like.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"For example, you could run your simulation until an interesting event occurs. You could then step through the simulation event by event for a while; and finally run the simulation until there are no more events left and your processes have all terminated.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The most important function here is run:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"If you call it with an instance of the environment as the only argument (run(env)), it steps through the simulation until there are no more events left. If your processes run forever, this function will never terminate (unless you kill your script by e.g., pressing Ctrl-C).\nIn most cases it is advisable to stop your simulation when it reaches a certain simulation time. Therefore, you can pass the desired time via a second argument, e.g.: run(env, 10).\nThe simulation will then stop when the internal clock reaches 10 but will not process any events scheduled for time 10. This is similar to a new environment where the clock is 0 but (obviously) no events have yet been processed.\nIf you want to integrate your simulation in a GUI and want to draw a process bar, you can repeatedly call this function with increasing until values and update your progress bar after each call:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"sim = Simulation()\nfor t in 1:100\n run(sim, t)\n update(progressbar, t)\nend","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"Instead of passing a number as second argument to run, you can also pass any event to it. run will then return when the event has been processed.\nAssuming that the current time is 0, run(env, timeout(env, 5)) is equivalent to run(env, 5).\nYou can also pass other types of events (remember, that a Process is an event, too):","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"using ResumableFunctions\nusing ConcurrentSim\n\n@resumable function my_process(env::Environment)\n @yield timeout(env, 1)\n \"Monty Python's Flying Circus\"\nend\n\nsim = Simulation()\nproc = @process my_process(sim)\nrun(sim, proc)\n\n# output\n\n\"Monty Python's Flying Circus\"","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"To step through the simulation event by event, the environment offers step. This function processes the next scheduled event. It raises an EmptySchedule exception if no event is available.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"In a typical use case, you use this function in a loop like:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"while now(sim) < 10\n step(sim)\nend","category":"page"},{"location":"guides/environments/#State-access","page":"Environments","title":"State access","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The environment allows you to get the current simulation time via the function now. The simulation time is a number without unit and is increased via timeout events.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"By default, the simulation starts at time 0, but you can pass an initial_time to the Simulation constructor to use something else.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"Note","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"note: Note\nAlthough the simulation time is technically unitless, you can pretend that it is, for example, in milliseconds and use it like a timestamp returned by Base.Dates.datetime2epochm to calculate a date or the day of the week. The Simulation constructor and the run function accept as argument a Base.Dates.DateTime and the timeout constructor a Base.Dates.Delay. Together with the convenience function nowDateTime a simulation can transparantly schedule its events in seconds, minutes, hours, days, ...","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The function active_process is comparable to Base.Libc.getpid and returns the current active Process. If no process is active, a NullException is thrown. A process is active when its process function is being executed. It becomes inactive (or suspended) when it yields an event.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"Thus, it only makes sense to call this function from within a process function or a function that is called by your process function:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> function subfunc(env::Environment)\n println(active_process(env))\n end\nsubfunc (generic function with 1 method)\n\njulia> @resumable function my_proc(env::Environment)\n while true\n println(active_process(env))\n subfunc(env)\n @yield timeout(env, 1)\n end\n end\nmy_proc (generic function with 1 method)\n\njulia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process my_proc(sim)\nConcurrentSim.Process 1\n\njulia> isnothing(active_process(sim))\ntrue\n\njulia> ConcurrentSim.step(sim)\nConcurrentSim.Process 1\nConcurrentSim.Process 1\n\njulia> isnothing(active_process(sim))\ntrue","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"An exemplary use case for this is the resource system: If a process function calls request to request (lock) a Resource, the resource determines the requesting process via active_process.","category":"page"},{"location":"guides/environments/#Miscellaneous","page":"Environments","title":"Miscellaneous","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"A generator function can have a return value:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"@resumable function my_proc(env::Environment)\n @yield timeout(sim, 1)\n 150\nend","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"In ConcurrentSim, this can be used to provide return values for processes that can be used by other processes:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"@resumable function other_proc(env::Environment)\n ret_val = @yield @process my_proc(env)\n @assert ret_val == 150\nend","category":"page"},{"location":"api/#API","page":"API","title":"API","text":"","category":"section"},{"location":"api/","page":"API","title":"API","text":"Modules = [ConcurrentSim]\nPrivate = false","category":"page"},{"location":"api/#ConcurrentSim.ConcurrentSim","page":"API","title":"ConcurrentSim.ConcurrentSim","text":"Main module for ConcurrentSim.jl – a discrete event process oriented simulation framework for Julia.\n\n\n\n\n\n","category":"module"},{"location":"api/#ConcurrentSim.Container","page":"API","title":"ConcurrentSim.Container","text":"Container{N}(env::Environment, capacity::N=one(N); level::N=zero(N))\n\nA \"Container\" resource object, storing up to capacity units of a resource (of type N).\n\nThere is a Resource alias for Container{Int}.\n\nResource() with default capacity of 1 is very similar to a typical lock. The request and unlock functions are a convenient way to interact with such a \"lock\", in a way mostly compatible with other discrete event and concurrency frameworks.\n\nSee Store for a more channel-like resource.\n\nThink of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).\n\n\n\n\n\n","category":"type"},{"location":"api/#ConcurrentSim.Store","page":"API","title":"ConcurrentSim.Store","text":"Store{T}(env::Environment; capacity::UInt=typemax(UInt))\n\nA store is a resource that can hold a number of items of type T. It is similar to a Base.Channel with a finite capacity (put! blocks after reaching capacity). The put! and take! functions are a convenient way to interact with such a \"channel\" in a way mostly compatible with other discrete event and concurrency frameworks.\n\nSee Container for a more lock-like resource.\n\nThink of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).\n\n\n\n\n\n","category":"type"},{"location":"api/#Base.put!-Union{Tuple{T}, Tuple{Store{T}, T}} where T","page":"API","title":"Base.put!","text":"put!(sto::Store, item::T)\n\nPut an item into the store. Returns the put event, blocking if the store is full.\n\n\n\n\n\n","category":"method"},{"location":"api/#ConcurrentSim.request-Tuple{Container}","page":"API","title":"ConcurrentSim.request","text":"request(res::Container)\n\nLocks the Container (or Resources) and return the lock event. If the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.\n\n\n\n\n\n","category":"method"},{"location":"api/#ConcurrentSim.tryrequest-Tuple{Container}","page":"API","title":"ConcurrentSim.tryrequest","text":"tryrequest(res::Container)\n\nIf the Container (or Resource) is not locked, locks it and return the lock event. Returns false if the Container is locked, similarly to the meaning of trylock for Base.ReentrantLock.\n\nIf the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.\n\njulia> sim = Simulation(); res = Resource(sim);\n\njulia> ev = tryrequest(res)\nConcurrentSim.Put 1\n\njulia> typeof(ev)\nConcurrentSim.Put\n\njulia> tryrequest(res)\nfalse\n\n\n\n\n\n","category":"method"},{"location":"api/","page":"API","title":"API","text":"unlock(res::Container; priority::Int=0)\ntake!(sto::Store, filter::Function=get_any_item; priority::Int=0)","category":"page"},{"location":"api/#Base.unlock-Tuple{Container}","page":"API","title":"Base.unlock","text":"unlock(res::Container)\n\nUnlocks the Container and return the unlock event.\n\n\n\n\n\n","category":"method"},{"location":"api/#Base.take!","page":"API","title":"Base.take!","text":"take!(::Store)\n\nAn alias for get(::Store) for easier interoperability with the Base.Channel interface. Blocks if the store is empty.\n\n\n\n\n\n","category":"function"},{"location":"guides/basics/#ConcurrentSim-basics","page":"Basics","title":"ConcurrentSim basics","text":"","category":"section"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"This guide describes the basic concepts of ConcurrentSim: How does it work? What are processes, events and the environment? What can I do with them?","category":"page"},{"location":"guides/basics/#How-ConcurrentSim-works","page":"Basics","title":"How ConcurrentSim works","text":"","category":"section"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"If you break ConcurrentSim down, it is just an asynchronous event dispatcher. You generate events and schedule them at a given simulation time. Events are sorted by priority, simulation time, and an increasing event id. An event also has a list of callbacks, which are executed when the event is triggered and processed by the event loop. Events may also have a return value.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The components involved in this are the Environment, events and the process functions that you write.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Process functions implement your simulation model, that is, they define the behavior of your simulation. They are @resumable functions that @yield instances of AbstractEvent.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The environment stores these events in its event list and keeps track of the current simulation time.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"If a process function yields an event, ConcurrentSim adds the process to the event’s callbacks and suspends the process until the event is triggered and processed. When a process waiting for an event is resumed, it will also receive the event’s value.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Here is a very simple example that illustrates all this:","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"using ResumableFunctions\nusing ConcurrentSim\n\n@resumable function example(env::Environment)\n event = timeout(env, 1, value=42)\n value = @yield event\n println(\"now=\", now(env), \", value=\", value)\nend\n\nsim = Simulation()\n@process example(sim)\nrun(sim)\n\n# output\n\nnow=1.0, value=42","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The example process function above first creates a timeout event. It passes the environment, a delay, and a value to it. The timeout schedules itself at now + delay (that’s why the environment is required); other event types usually schedule themselves at the current simulation time.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The process function then yields the event and thus gets suspended. It is resumed, when ConcurrentSim processes the timeout event. The process function also receives the event’s value (42) – this is, however, optional, so @yield event would have been okay if the you were not interested in the value or if the event had no value at all.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Finally, the process function prints the current simulation time (that is accessible via the now function) and the timeout’s value.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"If all required process functions are defined, you can instantiate all objects for your simulation. In most cases, you start by creating an instance of Environment, e.g. a Simulation, because you’ll need to pass it around a lot when creating everything else.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Starting a process function involves two things:","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"You have to call the macro @process with as argument a call to the process function. (This will not execute any code of that function yet.) This will schedule an initialisation event at the current simulation time which starts the execution of the process function. The process instance is also an event that is triggered when the process function returns.\nFinally, you can start ConcurrentSim’s event loop. By default, it will run as long as there are events in the event list, but you can also let it stop earlier by providing an until argument.","category":"page"},{"location":"guides/events/#Events","page":"Events","title":"Events","text":"","category":"section"},{"location":"guides/events/","page":"Events","title":"Events","text":"ConcurrentSim includes an extensive set of event types for various purposes. All of them are descendants of AbstractEvent. Here the following events are discussed:","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"Event\ntimeout\nOperator","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"The guide to resources describes the various resource events.","category":"page"},{"location":"guides/events/#Event-basics","page":"Events","title":"Event basics","text":"","category":"section"},{"location":"guides/events/","page":"Events","title":"Events","text":"ConcurrentSim events are very similar – if not identical — to deferreds, futures or promises. Instances of the type AbstractEvent are used to describe any kind of events. Events can be in one of the following states. An event:","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"might happen (idle),\nis going to happen (scheduled) or\nhas happened (processed).","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"They traverse these states exactly once in that order. Events are also tightly bound to time and time causes events to advance their state.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"Initially, events are idle and the function state returns ConcurrentSim.idle.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"If an event gets scheduled at a given time, it is inserted into ConcurrentSim’s event queue. The function state returns ConcurrentSim.scheduled.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"As long as the event is not processed, you can add callbacks to an event. Callbacks are function having an AbstractEvent as first parameter.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"An event becomes processed when ConcurrentSim pops it from the event queue and calls all of its callbacks. It is now no longer possible to add callbacks. The function state returns ConcurrentSim.processed.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"Events also have a value. The value can be set before or when the event is scheduled and can be retrieved via the function value or, within a process, by yielding the event (value = @yield event).","category":"page"},{"location":"guides/events/#Adding-callbacks-to-an-event","page":"Events","title":"Adding callbacks to an event","text":"","category":"section"},{"location":"guides/events/","page":"Events","title":"Events","text":"“What? Callbacks? I’ve never seen no callbacks!”, you might think if you have worked your way through the tutorial.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"That’s on purpose. The most common way to add a callback to an event is yielding it from your process function (@yield event). This will add the process’ resume function as a callback. That’s how your process gets resumed when it yielded an event.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"However, you can add any function to the list of callbacks as long as it accepts AbstractEvent or a descendant as first parameter:","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"julia> using ConcurrentSim\n\njulia> function my_callback(ev::AbstractEvent)\n println(\"Called back from \", ev)\n end\nmy_callback (generic function with 1 method)\n\njulia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> ev = Event(sim)\nConcurrentSim.Event 1\n\njulia> @callback my_callback(ev)\n#1 (generic function with 1 method)\n\njulia> succeed(ev)\nConcurrentSim.Event 1\n\njulia> run(sim)\nCalled back from ConcurrentSim.Event 1","category":"page"},{"location":"examples/Latency/#Event-Latency","page":"Latency","title":"Event Latency","text":"","category":"section"},{"location":"examples/Latency/#Description","page":"Latency","title":"Description","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"In this example we show how to separate the time delay between processes from the processes themselves. We model a communications channel, called a Cable, where a sender sends messages regularly each SEND_PERIOD time units and a receiver listens each RECEIVE_PERIOD. The messages in the cable have a delay fo DELAY_DURATION until they reach the recevier.","category":"page"},{"location":"examples/Latency/#Load-Packages","page":"Latency","title":"Load Packages","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"using ConcurrentSim\nusing ResumableFunctions\nimport Base: put!, take!\n\n# output","category":"page"},{"location":"examples/Latency/#Define-Constants","page":"Latency","title":"Define Constants","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"const SIM_DURATION = 100.\nconst SEND_PERIOD = 5.0\nconst RECEIVE_PERIOD = 3.0;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/#Define-Cable-model","page":"Latency","title":"Define Cable model","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The Cable contains reference to the simulation it is part of, the delay that messages experience, and a store that contains the sent messages","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"mutable struct Cable\n env::Simulation\n delay::Float64\n store::Store{String}\n \n function Cable(env::Simulation, delay::Float64)\n return new(env, delay, Store{String}(env))\n end\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The latency function is a generator which yields two events: first a timeout that represents the transmission delay, then a put event when the message gets stored in the store.","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"@resumable function latency(env::Simulation, cable::Cable, value::String)\n @yield timeout(cable.env, cable.delay)\n @yield put!(cable.store, value)\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The put! and take! functions allow interaction with the cable (note that these are not @resumable because they need to return the result of the operation and not the operation itself).","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"function put!(cable::Cable, value::String)\n @process latency(cable.env, cable, value) # results in the scheduling of all events generated by latency\nend\n\nfunction take!(cable::Cable); output = false\n take!(cable.store) # returns an element stored in the cable store\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The sender and receiver generators yield events to the simulator.","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"@resumable function sender(env::Simulation, cable::Cable)\n while true\n @yield timeout(env, SEND_PERIOD)\n value = \"sender sent this at $(now(env))\"\n put!(cable, value)\n end\nend\n\n@resumable function receiver(env::Simulation, cable::Cable)\n while true\n @yield timeout(env, RECEIVE_PERIOD)\n msg = @yield take!(cable)\n println(\"Received this at $(now(env)) while $msg\")\n end\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"Create simulation, register events, and run!","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"env = Simulation()\ncable = Cable(env, 10.)\n@process sender(env, cable)\n@process receiver(env, cable)\n\nrun(env, SIM_DURATION)\n\n# output\n\nReceived this at 15.0 while sender sent this at 5.0\nReceived this at 20.0 while sender sent this at 10.0\nReceived this at 25.0 while sender sent this at 15.0\nReceived this at 30.0 while sender sent this at 20.0\nReceived this at 35.0 while sender sent this at 25.0\nReceived this at 40.0 while sender sent this at 30.0\nReceived this at 45.0 while sender sent this at 35.0\nReceived this at 50.0 while sender sent this at 40.0\nReceived this at 55.0 while sender sent this at 45.0\nReceived this at 60.0 while sender sent this at 50.0\nReceived this at 65.0 while sender sent this at 55.0\nReceived this at 70.0 while sender sent this at 60.0\nReceived this at 75.0 while sender sent this at 65.0\nReceived this at 80.0 while sender sent this at 70.0\nReceived this at 85.0 while sender sent this at 75.0\nReceived this at 90.0 while sender sent this at 80.0\nReceived this at 95.0 while sender sent this at 85.0","category":"page"},{"location":"examples/mmc/#Multi-server-Queue","page":"Multi-server Queue","title":"Multi-server Queue","text":"","category":"section"},{"location":"examples/mmc/#Description","page":"Multi-server Queue","title":"Description","text":"","category":"section"},{"location":"examples/mmc/","page":"Multi-server Queue","title":"Multi-server Queue","text":"An M/M/c queue is a basic queue with c identical servers, exponentially distributed interarrival times, and exponentially distributed service times for each server. The arrival rate is defined as λ such that the interarrival time distribution has mean 1/λ. Similarly, the service rate is defined as μ such that the service time distribution has mean 1/μ (for each server). The overall traffic intensity of the queue is ρ = λ / (c * μ). If the traffic intensity exceeds one, the queue is unstable and the queue length will grow indefinitely. ","category":"page"},{"location":"examples/mmc/#Code","page":"Multi-server Queue","title":"Code","text":"","category":"section"},{"location":"examples/mmc/","page":"Multi-server Queue","title":"Multi-server Queue","text":"#set simulation parameters\nRandom.seed!(8710) # set random number seed for reproducibility\nnum_customers = 10 # total number of customers generated\n\n# set queue parameters\nnum_servers = 2 # number of servers\nmu = 1.0 / 2 # service rate\nlam = 0.9 # arrival rate\narrival_dist = Exponential(1 / lam) # interarrival time distriubtion\nservice_dist = Exponential(1 / mu) # service time distribution\n\n# define customer behavior\n@resumable function customer(env::Environment, server::Resource, id::Integer, t_a::Float64, d_s::Distribution)\n @yield timeout(env, t_a) # customer arrives\n println(\"Customer $id arrived: \", now(env))\n @yield request(server) # customer starts service\n println(\"Customer $id entered service: \", now(env))\n @yield timeout(env, rand(d_s)) # server is busy\n @yield unlock(server) # customer exits service\n println(\"Customer $id exited service: \", now(env))\nend\n\n# setup and run simulation\nsim = Simulation() # initialize simulation environment\nserver = Resource(sim, num_servers) # initialize servers\narrival_time = 0.0\nfor i = 1:num_customers # initialize customers\n arrival_time += rand(arrival_dist)\n @process customer(sim, server, i, arrival_time, service_dist)\nend\nrun(sim) # run simulation\n\n## output\n#\n# Customer 1 arrived: 0.1229193244813443\n# Customer 1 entered service: 0.1229193244813443\n# Customer 2 arrived: 0.22607641035584877\n# Customer 2 entered service: 0.22607641035584877\n# Customer 3 arrived: 0.4570009029409502\n# Customer 2 exited service: 1.7657345101378559\n# Customer 3 entered service: 1.7657345101378559\n# Customer 1 exited service: 2.154824561031012\n# Customer 3 exited service: 2.2765287086137764\n# Customer 4 arrived: 2.3661687470062995\n# Customer 4 entered service: 2.3661687470062995\n# Customer 5 arrived: 2.6110816119637885\n# Customer 5 entered service: 2.6110816119637885\n# Customer 5 exited service: 2.8017888690417583\n# Customer 6 arrived: 3.019540357955037\n# Customer 6 entered service: 3.019540357955037\n# Customer 6 exited service: 3.351151832298383\n# Customer 7 arrived: 3.5254699872847612\n# Customer 7 entered service: 3.5254699872847612\n# Customer 7 exited service: 4.261422043181396\n# Customer 4 exited service: 4.602071952938201\n# Customer 8 arrived: 7.27536704811686\n# Customer 8 entered service: 7.27536704811686\n# Customer 9 arrived: 7.491176033637809\n# Customer 9 entered service: 7.491176033637809\n# Customer 10 arrived: 8.39098457094977\n# Customer 8 exited service: 8.683396356977969\n# Customer 10 entered service: 8.683396356977969\n# Customer 9 exited service: 8.7501656586875\n# Customer 10 exited service: 9.049670951561666","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"ConcurrentSim is a discrete-event process-oriented simulation framework written in Julia inspired by the Python library SimPy. Its process dispatcher is based on semi-coroutines scheduling as implemented in ResumableFunctions or ResumableFunctions. A Process in ConcurrentSim is defined by a @resumable function yielding Events. ConcurrentSim provides three types of shared resources to model limited capacity congestion points: Resources, Containers and Stores. The API is modeled after the SimPy API but some specific Julia semantics are used.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The documentation contains a tutorial, topical guides explaining key concepts, a number of examples and the API reference. The tutorial, the topical guides and some examples are borrowed from SimPy to allow a direct comparison and an easy migration path for users. The differences between ConcurrentSim and SimPy are clearly documented.","category":"page"},{"location":"","page":"Home","title":"Home","text":"ConcurrentSim.jl used to be known as SimJulia.jl until 2023. It is one of the longest-lived Julia packages.","category":"page"},{"location":"#Example","page":"Home","title":"Example","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"A short example simulating two clocks ticking in different time intervals looks like this:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function clock(sim::Simulation, name::String, tick::Float64)\n while true\n println(name, \" \", now(sim))\n @yield timeout(sim, tick)\n end\n end\nclock (generic function with 1 method)\n\njulia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process clock(sim, \"fast\", 0.5)\nConcurrentSim.Process 1\n\njulia> @process clock(sim, \"slow\", 1.0)\nConcurrentSim.Process 3\n\njulia> run(sim, 2)\nfast 0.0\nslow 0.0\nfast 0.5\nslow 1.0\nfast 1.0\nfast 1.5","category":"page"},{"location":"tutorial/#Tutorial","page":"Tutorial","title":"Tutorial","text":"","category":"section"},{"location":"tutorial/#Basic-Concepts","page":"Tutorial","title":"Basic Concepts","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Simjulia is a discrete-event simulation library. The behavior of active components (like vehicles, customers or messages) is modeled with processes. All processes live in an environment. They interact with the environment and with each other via events.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Processes are described by @resumable functions. You can call them process function. During their lifetime, they create events and @yield them in order to wait for them to be triggered.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"note: Note\nDetailed information about the @resumable and the @yield macros can be found in the documentation of ResumableFunctions.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"When a process yields an event, the process gets suspended. ConcurrentSim resumes the process, when the event occurs (we say that the event is triggered). Multiple processes can wait for the same event. ConcurrentSim resumes them in the same order in which they yielded that event.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"An important event type is the timeout. Events of this type are scheduled after a certain amount of (simulated) time has passed. They allow a process to sleep (or hold its state) for the given time. A timeout and all other events can be created by calling a constructor having the environment as first argument.","category":"page"},{"location":"tutorial/#Our-First-Process","page":"Tutorial","title":"Our First Process","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Our first example will be a car process. The car will alternately drive and park for a while. When it starts driving (or parking), it will print the current simulation time.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"So let’s start:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function car(env::Environment)\n while true\n println(\"Start parking at \", now(env))\n parking_duration = 5\n @yield timeout(env, parking_duration)\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(env, trip_duration)\n end\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Our car process requires a reference to an Environment in order to create new events. The car‘s behavior is described in an infinite loop. Remember, the car function is a @resumable function. Though it will never terminate, it will pass the control flow back to the simulation once a @yield statement is reached. Once the yielded event is triggered (“it occurs”), the simulation will resume the function at this statement.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"As said before, our car switches between the states parking and driving. It announces its new state by printing a message and the current simulation time (as returned by the function call now). It then calls the constructor timeout to create a timeout event. This event describes the point in time the car is done parking (or driving, respectively). By yielding the event, it signals the simulation that it wants to wait for the event to occur.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Now that the behavior of our car has been modeled, lets create an instance of it and see how it behaves:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment)\n while true\n println(\"Start parking at \", now(env))\n parking_duration = 5\n @yield timeout(env, parking_duration)\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(env, trip_duration)\n end\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process car(sim)\nConcurrentSim.Process 1\n\njulia> run(sim, 15)\nStart parking at 0.0\nStart driving at 5.0\nStart parking at 7.0\nStart driving at 12.0\nStart parking at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The first thing we need to do is to create an environment, e.g. an instance of Simulation. The macro @process having as argument a car process function call creates a process that is initialised and added to the environment automatically.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Note, that at this time, none of the code of our process function is being executed. Its execution is merely scheduled at the current simulation time.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The Process returned by the @process macro can be used for process interactions.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Finally, we start the simulation by calling run and passing an end time to it.","category":"page"},{"location":"tutorial/#Process-Interaction","page":"Tutorial","title":"Process Interaction","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The Process instance that is returned by @process macro can be utilized for process interactions. The two most common examples for this are to wait for another process to finish and to interrupt another process while it is waiting for an event.","category":"page"},{"location":"tutorial/#Waiting-for-a-Process","page":"Tutorial","title":"Waiting for a Process","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"As it happens, a ConcurrentSim Process can be used like an event. If you yield it, you are resumed once the process has finished. Imagine a car-wash simulation where cars enter the car-wash and wait for the washing process to finish, or an airport simulation where passengers have to wait until a security check finishes.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Lets assume that the car from our last example is an electric vehicle. Electric vehicles usually take a lot of time charging their batteries after a trip. They have to wait until their battery is charged before they can start driving again.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"We can model this with an additional charge process for our car. Therefore, we redefine our car process function and add a charge process function.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"A new charge process is started every time the vehicle starts parking. By yielding the Process instance that the @process macro returns, the run process starts waiting for it to finish:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\ncharge (generic function with 1 method)\n\njulia> @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n @yield charge_process\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n\n using ConcurrentSim\n\n @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\n\n @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n @yield charge_process\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Starting the simulation is straightforward again: We create a Simulation, one (or more) cars and finally call run.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process car(sim)\nConcurrentSim.Process 1\n\njulia> run(sim, 15)\nStart parking and charging at 0.0\nStart driving at 5.0\nStart parking and charging at 7.0\nStart driving at 12.0\nStart parking and charging at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/#Interrupting-Another-Process","page":"Tutorial","title":"Interrupting Another Process","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Imagine, you don’t want to wait until your electric vehicle is fully charged but want to interrupt the charging process and just start driving instead.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"ConcurrentSim allows you to interrupt a running process by calling the interrupt function:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function driver(env::Environment, car_process::Process)\n @yield timeout(env, 3)\n @yield interrupt(car_process)\n end\ndriver (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The driver process has a reference to the car process. After waiting for 3 time steps, it interrupts that process.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Interrupts are thrown into process functions as Interrupt exceptions that can (should) be handled by the interrupted process. The process can then decide what to do next (e.g., continuing to wait for the original event or yielding a new event):","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\ncharge (generic function with 1 method)\n\njulia> @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n try\n @yield charge_process\n catch\n println(\"Was interrupted. Hopefully, the battery is full enough ...\")\n end\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"When you compare the output of this simulation with the previous example, you’ll notice that the car now starts driving at time 3 instead of 5:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function driver(env::Environment, car_process::Process)\n @yield timeout(env, 3)\n @yield interrupt(car_process)\n end\n\n @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\n\n @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n try\n @yield charge_process\n catch\n println(\"Was interrupted. Hopefully, the battery is full enough ...\")\n end\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> car_process = @process car(sim)\nConcurrentSim.Process 1\n\njulia> @process driver(sim, car_process)\nConcurrentSim.Process 3\n\njulia> run(sim, 15)\nStart parking and charging at 0.0\nWas interrupted. Hopefully, the battery is full enough ...\nStart driving at 3.0\nStart parking and charging at 5.0\nStart driving at 10.0\nStart parking and charging at 12.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/#Shared-Resources","page":"Tutorial","title":"Shared Resources","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"ConcurrentSim offers three types of resources that help you modeling problems, where multiple processes want to use a resource of limited capacity (e.g., cars at a fuel station with a limited number of fuel pumps) or classical producer-consumer problems.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"In this section, we’ll briefly introduce ConcurrentSim’s Resource class.","category":"page"},{"location":"tutorial/#Basic-Resource-Usage","page":"Tutorial","title":"Basic Resource Usage","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"We’ll slightly modify our electric vehicle process car that we introduced in the last sections.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The car will now drive to a battery charging station (BCS) and request (lock) one of its two charging spots. If both of these spots are currently in use, it waits until one of them becomes available again. It then starts charging its battery and leaves the station afterwards:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The resource’s request function generates an event that lets you wait until the resource becomes available again. If you are resumed, you “own” the resource until you release it with unlock.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"You are responsible to call unlock once you are done using the resource. When you unlock (release) a resource, the next waiting process is resumed and now “owns” one of the resource’s slots. The basic Resource sorts waiting processes in a FIFO (first in—first out) way.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"A resource needs a reference to an Environment and a capacity when it is created:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> bcs = Resource(sim, 2)\nConcurrentSim.Resource","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"We can now create the car processes and pass a reference to our resource as well as some additional parameters to them","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\n\n sim = Simulation()\n bcs = Resource(sim, 2)\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> for i in 1:4\n @process car(sim, i, bcs, 2i, 5)\n end\n\njulia> run(sim)\n1 arriving at 2.0\n1 starting to charge at 2.0\n2 arriving at 4.0\n2 starting to charge at 4.0\n3 arriving at 6.0\n1 leaving the bcs at 7.0\n3 starting to charge at 7.0\n4 arriving at 8.0\n2 leaving the bcs at 9.0\n4 starting to charge at 9.0\n3 leaving the bcs at 12.0\n4 leaving the bcs at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Finally, we can start the simulation. Since the car processes all terminate on their own in this simulation, we don’t need to specify an until time — the simulation will automatically stop when there are no more events left:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\n\n sim = Simulation()\n bcs = Resource(sim, 2)\n for i in 1:4\n @process car(sim, i, bcs, 2i, 5)\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> run(sim)\n1 arriving at 2.0\n1 starting to charge at 2.0\n2 arriving at 4.0\n2 starting to charge at 4.0\n3 arriving at 6.0\n1 leaving the bcs at 7.0\n3 starting to charge at 7.0\n4 arriving at 8.0\n2 leaving the bcs at 9.0\n4 starting to charge at 9.0\n3 leaving the bcs at 12.0\n4 leaving the bcs at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that the first two cars can start charging immediately after they arrive at the BCS, while cars 3 and 4 have to wait.","category":"page"},{"location":"examples/ross/#Ross,-Simulation-5th-edition:","page":"Ross","title":"Ross, Simulation 5th edition:","text":"","category":"section"},{"location":"examples/ross/#A-repair-problem","page":"Ross","title":"A repair problem","text":"","category":"section"},{"location":"examples/ross/#Source","page":"Ross","title":"Source","text":"","category":"section"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"Ross, Simulation 5th edition, Section 7.7, p. 124-126","category":"page"},{"location":"examples/ross/#Description","page":"Ross","title":"Description","text":"","category":"section"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"A system needs n working machines to be operational. To guard against machine breakdown, additional machines are kept available as spares. Whenever a machine breaks down it is immediately replaced by a spare and is itself sent to the repair facility, which consists of a single repairperson who repairs failed machines one at a time. Once a failed machine has been repaired it becomes available as a spare to be used when the need arises. All repair times are independent random variables having the common distribution function G. Each time a machine is put into use the amount of time it functions before breaking down is a random variable, independent of the past, having distribution function F.","category":"page"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"The system is said to “crash” when a machine fails and no spares are available. Assuming that there are initially n + s functional machines of which n are put in use and s are kept as spares, we are interested in simulating this system so as to approximate ET, where T is the time at which the system crashes.","category":"page"},{"location":"examples/ross/#Code","page":"Ross","title":"Code","text":"","category":"section"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"using ResumableFunctions\nusing ConcurrentSim\n\nusing Distributions\nusing Random\nusing StableRNGs\n\nconst RUNS = 5\nconst N = 10\nconst S = 3\nconst SEED = 150\nconst LAMBDA = 100\nconst MU = 1\n\nconst rng = StableRNG(42) # setting a random seed for reproducibility\nconst F = Exponential(LAMBDA)\nconst G = Exponential(MU)\n\n@resumable function machine(env::Environment, repair_facility::Resource, spares::Store{Process})\n while true\n try @yield timeout(env, Inf) catch end\n @yield timeout(env, rand(rng, F))\n get_spare = take!(spares)\n @yield get_spare | timeout(env)\n if state(get_spare) != ConcurrentSim.idle \n @yield interrupt(value(get_spare))\n else\n throw(StopSimulation(\"No more spares!\"))\n end\n @yield request(repair_facility)\n @yield timeout(env, rand(rng, G))\n @yield unlock(repair_facility)\n @yield put!(spares, active_process(env))\n end\nend\n\n@resumable function start_sim(env::Environment, repair_facility::Resource, spares::Store{Process})\n for i in 1:N\n proc = @process machine(env, repair_facility, spares)\n @yield interrupt(proc)\n end\n for i in 1:S\n proc = @process machine(env, repair_facility, spares)\n @yield put!(spares, proc) \n end\nend\n\nfunction sim_repair()\n sim = Simulation()\n repair_facility = Resource(sim)\n spares = Store{Process}(sim)\n @process start_sim(sim, repair_facility, spares)\n msg = run(sim)\n stop_time = now(sim)\n println(\"At time $stop_time: $msg\")\n stop_time\nend\n\nresults = Float64[]\nfor i in 1:RUNS push!(results, sim_repair()) end\nprintln(\"Average crash time: \", sum(results)/RUNS)\n\n# output\n\nAt time 12715.718224958666: No more spares!\nAt time 37335.53567595007: No more spares!\nAt time 30844.62667837361: No more spares!\nAt time 1601.2524911974856: No more spares!\nAt time 824.1048708405848: No more spares!\nAverage crash time: 16664.247588264083","category":"page"}] +[{"location":"guides/environments/#Environments","page":"Environments","title":"Environments","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"A simulation environment manages the simulation time as well as the scheduling and processing of events. It also provides means to step through or execute the simulation.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The base type for all environments is Environment. “Normal” simulations use its subtype Simulation.","category":"page"},{"location":"guides/environments/#Simulation-control","page":"Environments","title":"Simulation control","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"ConcurrentSim is very flexible in terms of simulation execution. You can run your simulation until there are no more events, until a certain simulation time is reached, or until a certain event is triggered. You can also step through the simulation event by event. Furthermore, you can mix these things as you like.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"For example, you could run your simulation until an interesting event occurs. You could then step through the simulation event by event for a while; and finally run the simulation until there are no more events left and your processes have all terminated.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The most important function here is run:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"If you call it with an instance of the environment as the only argument (run(env)), it steps through the simulation until there are no more events left. If your processes run forever, this function will never terminate (unless you kill your script by e.g., pressing Ctrl-C).\nIn most cases it is advisable to stop your simulation when it reaches a certain simulation time. Therefore, you can pass the desired time via a second argument, e.g.: run(env, 10).\nThe simulation will then stop when the internal clock reaches 10 but will not process any events scheduled for time 10. This is similar to a new environment where the clock is 0 but (obviously) no events have yet been processed.\nIf you want to integrate your simulation in a GUI and want to draw a process bar, you can repeatedly call this function with increasing until values and update your progress bar after each call:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"sim = Simulation()\nfor t in 1:100\n run(sim, t)\n update(progressbar, t)\nend","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"Instead of passing a number as second argument to run, you can also pass any event to it. run will then return when the event has been processed.\nAssuming that the current time is 0, run(env, timeout(env, 5)) is equivalent to run(env, 5).\nYou can also pass other types of events (remember, that a Process is an event, too):","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"using ResumableFunctions\nusing ConcurrentSim\n\n@resumable function my_process(env::Environment)\n @yield timeout(env, 1)\n \"Monty Python's Flying Circus\"\nend\n\nsim = Simulation()\nproc = @process my_process(sim)\nrun(sim, proc)\n\n# output\n\n\"Monty Python's Flying Circus\"","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"To step through the simulation event by event, the environment offers step. This function processes the next scheduled event. It raises an EmptySchedule exception if no event is available.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"In a typical use case, you use this function in a loop like:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"while now(sim) < 10\n step(sim)\nend","category":"page"},{"location":"guides/environments/#State-access","page":"Environments","title":"State access","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The environment allows you to get the current simulation time via the function now. The simulation time is a number without unit and is increased via timeout events.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"By default, the simulation starts at time 0, but you can pass an initial_time to the Simulation constructor to use something else.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"Note","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"note: Note\nAlthough the simulation time is technically unitless, you can pretend that it is, for example, in milliseconds and use it like a timestamp returned by Base.Dates.datetime2epochm to calculate a date or the day of the week. The Simulation constructor and the run function accept as argument a Base.Dates.DateTime and the timeout constructor a Base.Dates.Delay. Together with the convenience function nowDateTime a simulation can transparantly schedule its events in seconds, minutes, hours, days, ...","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"The function active_process is comparable to Base.Libc.getpid and returns the current active Process. If no process is active, a NullException is thrown. A process is active when its process function is being executed. It becomes inactive (or suspended) when it yields an event.","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"Thus, it only makes sense to call this function from within a process function or a function that is called by your process function:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> function subfunc(env::Environment)\n println(active_process(env))\n end\nsubfunc (generic function with 1 method)\n\njulia> @resumable function my_proc(env::Environment)\n while true\n println(active_process(env))\n subfunc(env)\n @yield timeout(env, 1)\n end\n end\nmy_proc (generic function with 1 method)\n\njulia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process my_proc(sim)\nConcurrentSim.Process 1\n\njulia> isnothing(active_process(sim))\ntrue\n\njulia> ConcurrentSim.step(sim)\nConcurrentSim.Process 1\nConcurrentSim.Process 1\n\njulia> isnothing(active_process(sim))\ntrue","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"An exemplary use case for this is the resource system: If a process function calls request to request (lock) a Resource, the resource determines the requesting process via active_process.","category":"page"},{"location":"guides/environments/#Miscellaneous","page":"Environments","title":"Miscellaneous","text":"","category":"section"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"A generator function can have a return value:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"@resumable function my_proc(env::Environment)\n @yield timeout(sim, 1)\n 150\nend","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"In ConcurrentSim, this can be used to provide return values for processes that can be used by other processes:","category":"page"},{"location":"guides/environments/","page":"Environments","title":"Environments","text":"@resumable function other_proc(env::Environment)\n ret_val = @yield @process my_proc(env)\n @assert ret_val == 150\nend","category":"page"},{"location":"api/#API","page":"API","title":"API","text":"","category":"section"},{"location":"api/","page":"API","title":"API","text":"Modules = [ConcurrentSim]\nPrivate = false","category":"page"},{"location":"api/#ConcurrentSim.ConcurrentSim","page":"API","title":"ConcurrentSim.ConcurrentSim","text":"Main module for ConcurrentSim.jl – a discrete event process oriented simulation framework for Julia.\n\n\n\n\n\n","category":"module"},{"location":"api/#ConcurrentSim.Container","page":"API","title":"ConcurrentSim.Container","text":"Container{N<:Real, T<:Number}(env::Environment, capacity::N=one(N); level::N=zero(N))\n\nA \"Container\" resource object, storing up to capacity units of a resource (of type N).\n\nThere is a Resource alias for Container{Int, Int}.\n\nResource() with default capacity of 1 is very similar to a typical lock. The request and unlock functions are a convenient way to interact with such a \"lock\", in a way mostly compatible with other discrete event and concurrency frameworks.\n\nSee Store for a more channel-like resource.\n\nThink of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).\n\n\n\n\n\n","category":"type"},{"location":"api/#ConcurrentSim.Store","page":"API","title":"ConcurrentSim.Store","text":"Store{N, T<:Number}(env::Environment; capacity::UInt=typemax(UInt))\n\nA store is a resource that can hold a number of items of type N. It is similar to a Base.Channel with a finite capacity (put! blocks after reaching capacity). The put! and take! functions are a convenient way to interact with such a \"channel\" in a way mostly compatible with other discrete event and concurrency frameworks.\n\nSee Container for a more lock-like resource.\n\nThink of Resource and Container as locks and of Store as channels. They block only if empty (on taking) or full (on storing).\n\n\n\n\n\n","category":"type"},{"location":"api/#Base.put!-Union{Tuple{T}, Tuple{N}, Tuple{Store{N, T}, N}} where {N, T<:Number}","page":"API","title":"Base.put!","text":"put!(sto::Store, item::T)\n\nPut an item into the store. Returns the put event, blocking if the store is full.\n\n\n\n\n\n","category":"method"},{"location":"api/#ConcurrentSim.request-Tuple{Resource}","page":"API","title":"ConcurrentSim.request","text":"request(res::Container)\n\nLocks the Container (or Resources) and return the lock event. If the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.\n\n\n\n\n\n","category":"method"},{"location":"api/#ConcurrentSim.tryrequest-Tuple{Container}","page":"API","title":"ConcurrentSim.tryrequest","text":"tryrequest(res::Container)\n\nIf the Container (or Resource) is not locked, locks it and return the lock event. Returns false if the Container is locked, similarly to the meaning of trylock for Base.ReentrantLock.\n\nIf the capacity of the Container is greater than 1, multiple requests can be made before blocking occurs.\n\njulia> sim = Simulation(); res = Resource(sim);\n\njulia> ev = tryrequest(res)\nConcurrentSim.Put 1\n\njulia> typeof(ev)\nConcurrentSim.Put\n\njulia> tryrequest(res)\nfalse\n\n\n\n\n\n","category":"method"},{"location":"api/","page":"API","title":"API","text":"unlock(res::Resource; priority::Number=0)\ntake!(sto::Store, filter::Function=get_any_item; priority::Int=0)","category":"page"},{"location":"api/#Base.unlock-Tuple{Resource}","page":"API","title":"Base.unlock","text":"unlock(res::Container)\n\nUnlocks the Container and return the unlock event.\n\n\n\n\n\n","category":"method"},{"location":"api/#Base.take!","page":"API","title":"Base.take!","text":"take!(::Store)\n\nAn alias for get(::Store) for easier interoperability with the Base.Channel interface. Blocks if the store is empty.\n\n\n\n\n\n","category":"function"},{"location":"guides/basics/#ConcurrentSim-basics","page":"Basics","title":"ConcurrentSim basics","text":"","category":"section"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"This guide describes the basic concepts of ConcurrentSim: How does it work? What are processes, events and the environment? What can I do with them?","category":"page"},{"location":"guides/basics/#How-ConcurrentSim-works","page":"Basics","title":"How ConcurrentSim works","text":"","category":"section"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"If you break ConcurrentSim down, it is just an asynchronous event dispatcher. You generate events and schedule them at a given simulation time. Events are sorted by priority, simulation time, and an increasing event id. An event also has a list of callbacks, which are executed when the event is triggered and processed by the event loop. Events may also have a return value.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The components involved in this are the Environment, events and the process functions that you write.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Process functions implement your simulation model, that is, they define the behavior of your simulation. They are @resumable functions that @yield instances of AbstractEvent.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The environment stores these events in its event list and keeps track of the current simulation time.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"If a process function yields an event, ConcurrentSim adds the process to the event’s callbacks and suspends the process until the event is triggered and processed. When a process waiting for an event is resumed, it will also receive the event’s value.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Here is a very simple example that illustrates all this:","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"using ResumableFunctions\nusing ConcurrentSim\n\n@resumable function example(env::Environment)\n event = timeout(env, 1, value=42)\n value = @yield event\n println(\"now=\", now(env), \", value=\", value)\nend\n\nsim = Simulation()\n@process example(sim)\nrun(sim)\n\n# output\n\nnow=1.0, value=42","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The example process function above first creates a timeout event. It passes the environment, a delay, and a value to it. The timeout schedules itself at now + delay (that’s why the environment is required); other event types usually schedule themselves at the current simulation time.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"The process function then yields the event and thus gets suspended. It is resumed, when ConcurrentSim processes the timeout event. The process function also receives the event’s value (42) – this is, however, optional, so @yield event would have been okay if the you were not interested in the value or if the event had no value at all.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Finally, the process function prints the current simulation time (that is accessible via the now function) and the timeout’s value.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"If all required process functions are defined, you can instantiate all objects for your simulation. In most cases, you start by creating an instance of Environment, e.g. a Simulation, because you’ll need to pass it around a lot when creating everything else.","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"Starting a process function involves two things:","category":"page"},{"location":"guides/basics/","page":"Basics","title":"Basics","text":"You have to call the macro @process with as argument a call to the process function. (This will not execute any code of that function yet.) This will schedule an initialisation event at the current simulation time which starts the execution of the process function. The process instance is also an event that is triggered when the process function returns.\nFinally, you can start ConcurrentSim’s event loop. By default, it will run as long as there are events in the event list, but you can also let it stop earlier by providing an until argument.","category":"page"},{"location":"guides/events/#Events","page":"Events","title":"Events","text":"","category":"section"},{"location":"guides/events/","page":"Events","title":"Events","text":"ConcurrentSim includes an extensive set of event types for various purposes. All of them are descendants of AbstractEvent. Here the following events are discussed:","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"Event\ntimeout\nOperator","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"The guide to resources describes the various resource events.","category":"page"},{"location":"guides/events/#Event-basics","page":"Events","title":"Event basics","text":"","category":"section"},{"location":"guides/events/","page":"Events","title":"Events","text":"ConcurrentSim events are very similar – if not identical — to deferreds, futures or promises. Instances of the type AbstractEvent are used to describe any kind of events. Events can be in one of the following states. An event:","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"might happen (idle),\nis going to happen (scheduled) or\nhas happened (processed).","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"They traverse these states exactly once in that order. Events are also tightly bound to time and time causes events to advance their state.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"Initially, events are idle and the function state returns ConcurrentSim.idle.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"If an event gets scheduled at a given time, it is inserted into ConcurrentSim’s event queue. The function state returns ConcurrentSim.scheduled.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"As long as the event is not processed, you can add callbacks to an event. Callbacks are function having an AbstractEvent as first parameter.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"An event becomes processed when ConcurrentSim pops it from the event queue and calls all of its callbacks. It is now no longer possible to add callbacks. The function state returns ConcurrentSim.processed.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"Events also have a value. The value can be set before or when the event is scheduled and can be retrieved via the function value or, within a process, by yielding the event (value = @yield event).","category":"page"},{"location":"guides/events/#Adding-callbacks-to-an-event","page":"Events","title":"Adding callbacks to an event","text":"","category":"section"},{"location":"guides/events/","page":"Events","title":"Events","text":"“What? Callbacks? I’ve never seen no callbacks!”, you might think if you have worked your way through the tutorial.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"That’s on purpose. The most common way to add a callback to an event is yielding it from your process function (@yield event). This will add the process’ resume function as a callback. That’s how your process gets resumed when it yielded an event.","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"However, you can add any function to the list of callbacks as long as it accepts AbstractEvent or a descendant as first parameter:","category":"page"},{"location":"guides/events/","page":"Events","title":"Events","text":"julia> using ConcurrentSim\n\njulia> function my_callback(ev::AbstractEvent)\n println(\"Called back from \", ev)\n end\nmy_callback (generic function with 1 method)\n\njulia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> ev = Event(sim)\nConcurrentSim.Event 1\n\njulia> @callback my_callback(ev)\n#1 (generic function with 1 method)\n\njulia> succeed(ev)\nConcurrentSim.Event 1\n\njulia> run(sim)\nCalled back from ConcurrentSim.Event 1","category":"page"},{"location":"examples/Latency/#Event-Latency","page":"Latency","title":"Event Latency","text":"","category":"section"},{"location":"examples/Latency/#Description","page":"Latency","title":"Description","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"In this example we show how to separate the time delay between processes from the processes themselves. We model a communications channel, called a Cable, where a sender sends messages regularly each SEND_PERIOD time units and a receiver listens each RECEIVE_PERIOD. The messages in the cable have a delay fo DELAY_DURATION until they reach the recevier.","category":"page"},{"location":"examples/Latency/#Load-Packages","page":"Latency","title":"Load Packages","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"using ConcurrentSim\nusing ResumableFunctions\nimport Base: put!, take!\n\n# output","category":"page"},{"location":"examples/Latency/#Define-Constants","page":"Latency","title":"Define Constants","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"const SIM_DURATION = 100.\nconst SEND_PERIOD = 5.0\nconst RECEIVE_PERIOD = 3.0;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/#Define-Cable-model","page":"Latency","title":"Define Cable model","text":"","category":"section"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The Cable contains reference to the simulation it is part of, the delay that messages experience, and a store that contains the sent messages","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"mutable struct Cable\n env::Simulation\n delay::Float64\n store::Store{String}\n \n function Cable(env::Simulation, delay::Float64)\n return new(env, delay, Store{String}(env))\n end\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The latency function is a generator which yields two events: first a timeout that represents the transmission delay, then a put event when the message gets stored in the store.","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"@resumable function latency(env::Simulation, cable::Cable, value::String)\n @yield timeout(cable.env, cable.delay)\n @yield put!(cable.store, value)\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The put! and take! functions allow interaction with the cable (note that these are not @resumable because they need to return the result of the operation and not the operation itself).","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"function put!(cable::Cable, value::String)\n @process latency(cable.env, cable, value) # results in the scheduling of all events generated by latency\nend\n\nfunction take!(cable::Cable); output = false\n take!(cable.store) # returns an element stored in the cable store\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"The sender and receiver generators yield events to the simulator.","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"@resumable function sender(env::Simulation, cable::Cable)\n while true\n @yield timeout(env, SEND_PERIOD)\n value = \"sender sent this at $(now(env))\"\n put!(cable, value)\n end\nend\n\n@resumable function receiver(env::Simulation, cable::Cable)\n while true\n @yield timeout(env, RECEIVE_PERIOD)\n msg = @yield take!(cable)\n println(\"Received this at $(now(env)) while $msg\")\n end\nend;\n\nnothing # hide\n\n# output","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"Create simulation, register events, and run!","category":"page"},{"location":"examples/Latency/","page":"Latency","title":"Latency","text":"env = Simulation()\ncable = Cable(env, 10.)\n@process sender(env, cable)\n@process receiver(env, cable)\n\nrun(env, SIM_DURATION)\n\n# output\n\nReceived this at 15.0 while sender sent this at 5.0\nReceived this at 20.0 while sender sent this at 10.0\nReceived this at 25.0 while sender sent this at 15.0\nReceived this at 30.0 while sender sent this at 20.0\nReceived this at 35.0 while sender sent this at 25.0\nReceived this at 40.0 while sender sent this at 30.0\nReceived this at 45.0 while sender sent this at 35.0\nReceived this at 50.0 while sender sent this at 40.0\nReceived this at 55.0 while sender sent this at 45.0\nReceived this at 60.0 while sender sent this at 50.0\nReceived this at 65.0 while sender sent this at 55.0\nReceived this at 70.0 while sender sent this at 60.0\nReceived this at 75.0 while sender sent this at 65.0\nReceived this at 80.0 while sender sent this at 70.0\nReceived this at 85.0 while sender sent this at 75.0\nReceived this at 90.0 while sender sent this at 80.0\nReceived this at 95.0 while sender sent this at 85.0","category":"page"},{"location":"examples/mmc/#Multi-server-Queue","page":"Multi-server Queue","title":"Multi-server Queue","text":"","category":"section"},{"location":"examples/mmc/#Description","page":"Multi-server Queue","title":"Description","text":"","category":"section"},{"location":"examples/mmc/","page":"Multi-server Queue","title":"Multi-server Queue","text":"An M/M/c queue is a basic queue with c identical servers, exponentially distributed interarrival times, and exponentially distributed service times for each server. The arrival rate is defined as λ such that the interarrival time distribution has mean 1/λ. Similarly, the service rate is defined as μ such that the service time distribution has mean 1/μ (for each server). The overall traffic intensity of the queue is ρ = λ / (c * μ). If the traffic intensity exceeds one, the queue is unstable and the queue length will grow indefinitely. ","category":"page"},{"location":"examples/mmc/#Code","page":"Multi-server Queue","title":"Code","text":"","category":"section"},{"location":"examples/mmc/","page":"Multi-server Queue","title":"Multi-server Queue","text":"#set simulation parameters\nRandom.seed!(8710) # set random number seed for reproducibility\nnum_customers = 10 # total number of customers generated\n\n# set queue parameters\nnum_servers = 2 # number of servers\nmu = 1.0 / 2 # service rate\nlam = 0.9 # arrival rate\narrival_dist = Exponential(1 / lam) # interarrival time distriubtion\nservice_dist = Exponential(1 / mu) # service time distribution\n\n# define customer behavior\n@resumable function customer(env::Environment, server::Resource, id::Integer, t_a::Float64, d_s::Distribution)\n @yield timeout(env, t_a) # customer arrives\n println(\"Customer $id arrived: \", now(env))\n @yield request(server) # customer starts service\n println(\"Customer $id entered service: \", now(env))\n @yield timeout(env, rand(d_s)) # server is busy\n @yield unlock(server) # customer exits service\n println(\"Customer $id exited service: \", now(env))\nend\n\n# setup and run simulation\nsim = Simulation() # initialize simulation environment\nserver = Resource(sim, num_servers) # initialize servers\narrival_time = 0.0\nfor i = 1:num_customers # initialize customers\n arrival_time += rand(arrival_dist)\n @process customer(sim, server, i, arrival_time, service_dist)\nend\nrun(sim) # run simulation\n\n## output\n#\n# Customer 1 arrived: 0.1229193244813443\n# Customer 1 entered service: 0.1229193244813443\n# Customer 2 arrived: 0.22607641035584877\n# Customer 2 entered service: 0.22607641035584877\n# Customer 3 arrived: 0.4570009029409502\n# Customer 2 exited service: 1.7657345101378559\n# Customer 3 entered service: 1.7657345101378559\n# Customer 1 exited service: 2.154824561031012\n# Customer 3 exited service: 2.2765287086137764\n# Customer 4 arrived: 2.3661687470062995\n# Customer 4 entered service: 2.3661687470062995\n# Customer 5 arrived: 2.6110816119637885\n# Customer 5 entered service: 2.6110816119637885\n# Customer 5 exited service: 2.8017888690417583\n# Customer 6 arrived: 3.019540357955037\n# Customer 6 entered service: 3.019540357955037\n# Customer 6 exited service: 3.351151832298383\n# Customer 7 arrived: 3.5254699872847612\n# Customer 7 entered service: 3.5254699872847612\n# Customer 7 exited service: 4.261422043181396\n# Customer 4 exited service: 4.602071952938201\n# Customer 8 arrived: 7.27536704811686\n# Customer 8 entered service: 7.27536704811686\n# Customer 9 arrived: 7.491176033637809\n# Customer 9 entered service: 7.491176033637809\n# Customer 10 arrived: 8.39098457094977\n# Customer 8 exited service: 8.683396356977969\n# Customer 10 entered service: 8.683396356977969\n# Customer 9 exited service: 8.7501656586875\n# Customer 10 exited service: 9.049670951561666","category":"page"},{"location":"#Overview","page":"Home","title":"Overview","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"ConcurrentSim is a discrete-event process-oriented simulation framework written in Julia inspired by the Python library SimPy. Its process dispatcher is based on semi-coroutines scheduling as implemented in ResumableFunctions or ResumableFunctions. A Process in ConcurrentSim is defined by a @resumable function yielding Events. ConcurrentSim provides three types of shared resources to model limited capacity congestion points: Resources, Containers and Stores. The API is modeled after the SimPy API but some specific Julia semantics are used.","category":"page"},{"location":"","page":"Home","title":"Home","text":"The documentation contains a tutorial, topical guides explaining key concepts, a number of examples and the API reference. The tutorial, the topical guides and some examples are borrowed from SimPy to allow a direct comparison and an easy migration path for users. The differences between ConcurrentSim and SimPy are clearly documented.","category":"page"},{"location":"","page":"Home","title":"Home","text":"ConcurrentSim.jl used to be known as SimJulia.jl until 2023. It is one of the longest-lived Julia packages.","category":"page"},{"location":"#Example","page":"Home","title":"Example","text":"","category":"section"},{"location":"","page":"Home","title":"Home","text":"A short example simulating two clocks ticking in different time intervals looks like this:","category":"page"},{"location":"","page":"Home","title":"Home","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function clock(sim::Simulation, name::String, tick::Float64)\n while true\n println(name, \" \", now(sim))\n @yield timeout(sim, tick)\n end\n end\nclock (generic function with 1 method)\n\njulia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process clock(sim, \"fast\", 0.5)\nConcurrentSim.Process 1\n\njulia> @process clock(sim, \"slow\", 1.0)\nConcurrentSim.Process 3\n\njulia> run(sim, 2)\nfast 0.0\nslow 0.0\nfast 0.5\nslow 1.0\nfast 1.0\nfast 1.5","category":"page"},{"location":"tutorial/#Tutorial","page":"Tutorial","title":"Tutorial","text":"","category":"section"},{"location":"tutorial/#Basic-Concepts","page":"Tutorial","title":"Basic Concepts","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Simjulia is a discrete-event simulation library. The behavior of active components (like vehicles, customers or messages) is modeled with processes. All processes live in an environment. They interact with the environment and with each other via events.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Processes are described by @resumable functions. You can call them process function. During their lifetime, they create events and @yield them in order to wait for them to be triggered.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"note: Note\nDetailed information about the @resumable and the @yield macros can be found in the documentation of ResumableFunctions.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"When a process yields an event, the process gets suspended. ConcurrentSim resumes the process, when the event occurs (we say that the event is triggered). Multiple processes can wait for the same event. ConcurrentSim resumes them in the same order in which they yielded that event.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"An important event type is the timeout. Events of this type are scheduled after a certain amount of (simulated) time has passed. They allow a process to sleep (or hold its state) for the given time. A timeout and all other events can be created by calling a constructor having the environment as first argument.","category":"page"},{"location":"tutorial/#Our-First-Process","page":"Tutorial","title":"Our First Process","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Our first example will be a car process. The car will alternately drive and park for a while. When it starts driving (or parking), it will print the current simulation time.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"So let’s start:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function car(env::Environment)\n while true\n println(\"Start parking at \", now(env))\n parking_duration = 5\n @yield timeout(env, parking_duration)\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(env, trip_duration)\n end\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Our car process requires a reference to an Environment in order to create new events. The car‘s behavior is described in an infinite loop. Remember, the car function is a @resumable function. Though it will never terminate, it will pass the control flow back to the simulation once a @yield statement is reached. Once the yielded event is triggered (“it occurs”), the simulation will resume the function at this statement.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"As said before, our car switches between the states parking and driving. It announces its new state by printing a message and the current simulation time (as returned by the function call now). It then calls the constructor timeout to create a timeout event. This event describes the point in time the car is done parking (or driving, respectively). By yielding the event, it signals the simulation that it wants to wait for the event to occur.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Now that the behavior of our car has been modeled, lets create an instance of it and see how it behaves:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment)\n while true\n println(\"Start parking at \", now(env))\n parking_duration = 5\n @yield timeout(env, parking_duration)\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(env, trip_duration)\n end\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process car(sim)\nConcurrentSim.Process 1\n\njulia> run(sim, 15)\nStart parking at 0.0\nStart driving at 5.0\nStart parking at 7.0\nStart driving at 12.0\nStart parking at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The first thing we need to do is to create an environment, e.g. an instance of Simulation. The macro @process having as argument a car process function call creates a process that is initialised and added to the environment automatically.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Note, that at this time, none of the code of our process function is being executed. Its execution is merely scheduled at the current simulation time.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The Process returned by the @process macro can be used for process interactions.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Finally, we start the simulation by calling run and passing an end time to it.","category":"page"},{"location":"tutorial/#Process-Interaction","page":"Tutorial","title":"Process Interaction","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The Process instance that is returned by @process macro can be utilized for process interactions. The two most common examples for this are to wait for another process to finish and to interrupt another process while it is waiting for an event.","category":"page"},{"location":"tutorial/#Waiting-for-a-Process","page":"Tutorial","title":"Waiting for a Process","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"As it happens, a ConcurrentSim Process can be used like an event. If you yield it, you are resumed once the process has finished. Imagine a car-wash simulation where cars enter the car-wash and wait for the washing process to finish, or an airport simulation where passengers have to wait until a security check finishes.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Lets assume that the car from our last example is an electric vehicle. Electric vehicles usually take a lot of time charging their batteries after a trip. They have to wait until their battery is charged before they can start driving again.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"We can model this with an additional charge process for our car. Therefore, we redefine our car process function and add a charge process function.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"A new charge process is started every time the vehicle starts parking. By yielding the Process instance that the @process macro returns, the run process starts waiting for it to finish:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\ncharge (generic function with 1 method)\n\njulia> @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n @yield charge_process\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n\n using ConcurrentSim\n\n @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\n\n @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n @yield charge_process\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Starting the simulation is straightforward again: We create a Simulation, one (or more) cars and finally call run.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> @process car(sim)\nConcurrentSim.Process 1\n\njulia> run(sim, 15)\nStart parking and charging at 0.0\nStart driving at 5.0\nStart parking and charging at 7.0\nStart driving at 12.0\nStart parking and charging at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/#Interrupting-Another-Process","page":"Tutorial","title":"Interrupting Another Process","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Imagine, you don’t want to wait until your electric vehicle is fully charged but want to interrupt the charging process and just start driving instead.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"ConcurrentSim allows you to interrupt a running process by calling the interrupt function:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function driver(env::Environment, car_process::Process)\n @yield timeout(env, 3)\n @yield interrupt(car_process)\n end\ndriver (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The driver process has a reference to the car process. After waiting for 3 time steps, it interrupts that process.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Interrupts are thrown into process functions as Interrupt exceptions that can (should) be handled by the interrupted process. The process can then decide what to do next (e.g., continuing to wait for the original event or yielding a new event):","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\ncharge (generic function with 1 method)\n\njulia> @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n try\n @yield charge_process\n catch\n println(\"Was interrupted. Hopefully, the battery is full enough ...\")\n end\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"When you compare the output of this simulation with the previous example, you’ll notice that the car now starts driving at time 3 instead of 5:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function driver(env::Environment, car_process::Process)\n @yield timeout(env, 3)\n @yield interrupt(car_process)\n end\n\n @resumable function charge(env::Environment, duration::Number)\n @yield timeout(env, duration)\n end\n\n @resumable function car(env::Environment)\n while true\n println(\"Start parking and charging at \", now(env))\n charge_duration = 5\n charge_process = @process charge(sim, charge_duration)\n try\n @yield charge_process\n catch\n println(\"Was interrupted. Hopefully, the battery is full enough ...\")\n end\n println(\"Start driving at \", now(env))\n trip_duration = 2\n @yield timeout(sim, trip_duration)\n end\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> car_process = @process car(sim)\nConcurrentSim.Process 1\n\njulia> @process driver(sim, car_process)\nConcurrentSim.Process 3\n\njulia> run(sim, 15)\nStart parking and charging at 0.0\nWas interrupted. Hopefully, the battery is full enough ...\nStart driving at 3.0\nStart parking and charging at 5.0\nStart driving at 10.0\nStart parking and charging at 12.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/#Shared-Resources","page":"Tutorial","title":"Shared Resources","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"ConcurrentSim offers three types of resources that help you modeling problems, where multiple processes want to use a resource of limited capacity (e.g., cars at a fuel station with a limited number of fuel pumps) or classical producer-consumer problems.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"In this section, we’ll briefly introduce ConcurrentSim’s Resource class.","category":"page"},{"location":"tutorial/#Basic-Resource-Usage","page":"Tutorial","title":"Basic Resource Usage","text":"","category":"section"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"We’ll slightly modify our electric vehicle process car that we introduced in the last sections.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The car will now drive to a battery charging station (BCS) and request (lock) one of its two charging spots. If both of these spots are currently in use, it waits until one of them becomes available again. It then starts charging its battery and leaves the station afterwards:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> using ResumableFunctions\n\njulia> using ConcurrentSim\n\njulia> @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\ncar (generic function with 1 method)","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"The resource’s request function generates an event that lets you wait until the resource becomes available again. If you are resumed, you “own” the resource until you release it with unlock.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"You are responsible to call unlock once you are done using the resource. When you unlock (release) a resource, the next waiting process is resumed and now “owns” one of the resource’s slots. The basic Resource sorts waiting processes in a FIFO (first in—first out) way.","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"A resource needs a reference to an Environment and a capacity when it is created:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> sim = Simulation()\nConcurrentSim.Simulation time: 0.0 active_process: nothing\n\njulia> bcs = Resource(sim, 2)\nConcurrentSim.Resource","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"We can now create the car processes and pass a reference to our resource as well as some additional parameters to them","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\n\n sim = Simulation()\n bcs = Resource(sim, 2)\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> for i in 1:4\n @process car(sim, i, bcs, 2i, 5)\n end\n\njulia> run(sim)\n1 arriving at 2.0\n1 starting to charge at 2.0\n2 arriving at 4.0\n2 starting to charge at 4.0\n3 arriving at 6.0\n1 leaving the bcs at 7.0\n3 starting to charge at 7.0\n4 arriving at 8.0\n2 leaving the bcs at 9.0\n4 starting to charge at 9.0\n3 leaving the bcs at 12.0\n4 leaving the bcs at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Finally, we can start the simulation. Since the car processes all terminate on their own in this simulation, we don’t need to specify an until time — the simulation will automatically stop when there are no more events left:","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = quote\n using ResumableFunctions\n using ConcurrentSim\n\n @resumable function car(env::Environment, name::Int, bcs::Resource, driving_time::Number, charge_duration::Number)\n @yield timeout(sim, driving_time)\n println(name, \" arriving at \", now(env))\n @yield request(bcs)\n println(name, \" starting to charge at \", now(env))\n @yield timeout(sim, charge_duration)\n println(name, \" leaving the bcs at \", now(env))\n @yield unlock(bcs)\n end\n\n sim = Simulation()\n bcs = Resource(sim, 2)\n for i in 1:4\n @process car(sim, i, bcs, 2i, 5)\n end\nend","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"julia> run(sim)\n1 arriving at 2.0\n1 starting to charge at 2.0\n2 arriving at 4.0\n2 starting to charge at 4.0\n3 arriving at 6.0\n1 leaving the bcs at 7.0\n3 starting to charge at 7.0\n4 arriving at 8.0\n2 leaving the bcs at 9.0\n4 starting to charge at 9.0\n3 leaving the bcs at 12.0\n4 leaving the bcs at 14.0","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"DocTestSetup = nothing","category":"page"},{"location":"tutorial/","page":"Tutorial","title":"Tutorial","text":"Note that the first two cars can start charging immediately after they arrive at the BCS, while cars 3 and 4 have to wait.","category":"page"},{"location":"examples/ross/#Ross,-Simulation-5th-edition:","page":"Ross","title":"Ross, Simulation 5th edition:","text":"","category":"section"},{"location":"examples/ross/#A-repair-problem","page":"Ross","title":"A repair problem","text":"","category":"section"},{"location":"examples/ross/#Source","page":"Ross","title":"Source","text":"","category":"section"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"Ross, Simulation 5th edition, Section 7.7, p. 124-126","category":"page"},{"location":"examples/ross/#Description","page":"Ross","title":"Description","text":"","category":"section"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"A system needs n working machines to be operational. To guard against machine breakdown, additional machines are kept available as spares. Whenever a machine breaks down it is immediately replaced by a spare and is itself sent to the repair facility, which consists of a single repairperson who repairs failed machines one at a time. Once a failed machine has been repaired it becomes available as a spare to be used when the need arises. All repair times are independent random variables having the common distribution function G. Each time a machine is put into use the amount of time it functions before breaking down is a random variable, independent of the past, having distribution function F.","category":"page"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"The system is said to “crash” when a machine fails and no spares are available. Assuming that there are initially n + s functional machines of which n are put in use and s are kept as spares, we are interested in simulating this system so as to approximate ET, where T is the time at which the system crashes.","category":"page"},{"location":"examples/ross/#Code","page":"Ross","title":"Code","text":"","category":"section"},{"location":"examples/ross/","page":"Ross","title":"Ross","text":"using ResumableFunctions\nusing ConcurrentSim\n\nusing Distributions\nusing Random\nusing StableRNGs\n\nconst RUNS = 5\nconst N = 10\nconst S = 3\nconst SEED = 150\nconst LAMBDA = 100\nconst MU = 1\n\nconst rng = StableRNG(42) # setting a random seed for reproducibility\nconst F = Exponential(LAMBDA)\nconst G = Exponential(MU)\n\n@resumable function machine(env::Environment, repair_facility::Resource, spares::Store{Process})\n while true\n try @yield timeout(env, Inf) catch end\n @yield timeout(env, rand(rng, F))\n get_spare = take!(spares)\n @yield get_spare | timeout(env)\n if state(get_spare) != ConcurrentSim.idle \n @yield interrupt(value(get_spare))\n else\n throw(StopSimulation(\"No more spares!\"))\n end\n @yield request(repair_facility)\n @yield timeout(env, rand(rng, G))\n @yield unlock(repair_facility)\n @yield put!(spares, active_process(env))\n end\nend\n\n@resumable function start_sim(env::Environment, repair_facility::Resource, spares::Store{Process})\n for i in 1:N\n proc = @process machine(env, repair_facility, spares)\n @yield interrupt(proc)\n end\n for i in 1:S\n proc = @process machine(env, repair_facility, spares)\n @yield put!(spares, proc) \n end\nend\n\nfunction sim_repair()\n sim = Simulation()\n repair_facility = Resource(sim)\n spares = Store{Process}(sim)\n @process start_sim(sim, repair_facility, spares)\n msg = run(sim)\n stop_time = now(sim)\n println(\"At time $stop_time: $msg\")\n stop_time\nend\n\nresults = Float64[]\nfor i in 1:RUNS push!(results, sim_repair()) end\nprintln(\"Average crash time: \", sum(results)/RUNS)\n\n# output\n\nAt time 12715.718224958666: No more spares!\nAt time 37335.53567595007: No more spares!\nAt time 30844.62667837361: No more spares!\nAt time 1601.2524911974856: No more spares!\nAt time 824.1048708405848: No more spares!\nAverage crash time: 16664.247588264083","category":"page"}] } diff --git a/dev/tutorial/index.html b/dev/tutorial/index.html index 35b48e4..3a40b3d 100644 --- a/dev/tutorial/index.html +++ b/dev/tutorial/index.html @@ -143,4 +143,4 @@ 2 leaving the bcs at 9.0 4 starting to charge at 9.0 3 leaving the bcs at 12.0 -4 leaving the bcs at 14.0

      Note that the first two cars can start charging immediately after they arrive at the BCS, while cars 3 and 4 have to wait.

      +4 leaving the bcs at 14.0

      Note that the first two cars can start charging immediately after they arrive at the BCS, while cars 3 and 4 have to wait.