Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
pbayer committed Aug 31, 2020
2 parents c09c0ac + f4933f8 commit eab59ac
Show file tree
Hide file tree
Showing 9 changed files with 50 additions and 49 deletions.
31 changes: 14 additions & 17 deletions docs/src/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
CurrentModule = DiscreteEvents
```

Events are computations ``\,α,β,γ,...\,`` at times ``\,t_1,t_2,t_3,...`` Those computations are called *actions*.
Events are computations ``\,α,β,γ,...\,`` at times ``\,t_1,t_2,t_3,...`` We call those computations *actions*.

## Actions

Expand All @@ -15,12 +15,6 @@ Action
fun
```

!!! warning "Evaluating expressions is slow!"

The use of expressions (`Expr`) and symbols (`Symbol`) in actions should be avoided. You will get a one time warning if you use them. They can be replaced easily by `fun`s or function closures.

They are evaluated at global scope in Module `Main` only. Other modules using `DiscreteEvents` cannot use `Expr` in events and have to use functions.

Actions can be combined into tuples:

```@repl events
Expand All @@ -33,12 +27,15 @@ isa(()->println(a), Action) # an anonymous function
(()->println(a), fun(println, a), :(println(a))) isa Action # a tuple of them
```

Actions can be scheduled for execution
### Expressions and Symbols

Expressions too are Actions. Also you can pass symbols to `fun` to delay evaluation of variables. `Expr`s and `Symbol`s are evaluated at global scope in Module `Main` only. This is a user convenience feature. Other modules using `DiscreteEvents` cannot use them in events and have to use functions.

!!! warning "Evaluating expressions is slow!"

1. at given clock times and
2. under specified conditions.
Usage of `Expr` or `Symbol` will generate a one time warning. You can replace them easily with `fun`s or function closures.

## Timed events
## Timed Events

Actions can be scheduled as events at given times:

Expand All @@ -47,7 +44,7 @@ Timing
event!(::CL,::A,::U) where {CL<:AbstractClock,A<:Action,U<:Number}
```

## Conditional events
## Conditional Events

Actions can be scheduled as events under given conditions:

Expand All @@ -59,7 +56,7 @@ event!(::T,::A,::C) where {T<:AbstractClock,A<:Action,C<:Action}

For conditions you should prefer inequalities like <, ≤, ≥, > to equality == in order to make sure that a condition can be detected, e.g. `tau() ≥ 100` is preferable to `tau() == 100`.

## Continuous sampling
## Continuous Sampling

Actions can be registered for sampling and are then executed "continuously" at each clock increment Δt. The default clock sample rate Δt is 0.01 time units.

Expand All @@ -68,7 +65,7 @@ sample_time!
periodic!
```

## Events and variables
## Events and Variables

Actions often depend on data or modify it. The data may change between the definition of an action and its later execution. If an action uses a *mutable variable* like an array or a mutable struct, it gets current data at event time and it is fast. If the action modifies the data, this is the best way to do it:

Expand All @@ -82,7 +79,7 @@ ff() # execute ff
a[1] # a has been modified correctly
```

If for some reason and against [better advise](https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables-1) you want to work with global variables, there are several ways to deal with them:
There are good [reasons to avoid global variables](https://docs.julialang.org/en/v1/manual/performance-tips/#Avoid-global-variables-1) but if you want to work with them, you can do it in several ways:

```@repl events
g(x; y=1) = x+y # define a function g
Expand All @@ -99,9 +96,9 @@ x += 1 # increment x
ii() # g gets an updated x
```

If you want to modify a global variable, you have to use the `global` keyword inside your function.
To modify a global variable, you have to use the `global` keyword inside your function.

## Time units
## Events with Time Units

Timed events can be scheduled with time units. Times are converted to the clock's time unit.

Expand Down
1 change: 0 additions & 1 deletion docs/src/internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ There are several ways to solve this problem:
## Error handling and diagnosis

```@docs
ClockException
prettyClock
```

Expand Down
18 changes: 9 additions & 9 deletions docs/src/intro.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ clock = Clock()

You created a [`Clock`](@ref) variable `clk` with a clock at thread 1 with pretty much everything set to 0, without yet any scheduled events (ev), conditional events (cev) or sampling events (sampl).

You can now schedule events to your clock. In order to demonstrate how it works we setup a small simulation.
You can now schedule events to your clock. We demonstrate how it works with a couple of small simulations.

## Inventory Control Problem

Expand Down Expand Up @@ -72,7 +72,7 @@ function customer(c::Clock, s::Station, X::Distribution)
s.q -= x # take x from tank
push!(s.t, c.time) # record time, amount, customer, sale
push!(s.qt, s.q)
s.cs += 1
s.cs += 1
s.qs += x
end

Expand Down Expand Up @@ -107,9 +107,9 @@ Now we setup our constants and variables, wrap the functions in [`fun`](@ref) an
Random.seed(123)
const λ = 0.5 # ~ every two minutes a customer
const ρ = 1/180 # ~ every 3 hours a replenishment truck
const μ = 30 # ~ mean demand per customer
const μ = 30 # ~ mean demand per customer
const σ = 10 # standard deviation
const a = 5 # minimum amount
const a = 5 # minimum amount
const Q = 6000.0 # replenishment amount
const M₁ = Exponential(1/λ) # customer arrival time distribution
const M₂ = Exponential(1/ρ) # replenishment time distribution
Expand Down Expand Up @@ -143,7 +143,7 @@ plot(s.t, s.qt, title="Filling Station", xlabel="time [min]", ylabel="inventory
savefig("invctrl.png")
```

![](img/invctrl.png)
![inventory](img/invctrl.png)

If we could manage to replenish immediately after the tank is empty, we would be much better off.

Expand Down Expand Up @@ -179,7 +179,7 @@ function serve(c::Clock, s::Server, input::Channel, output::Vector{Caller}, limi
call = take!(input) # take a call
call.t₂ = c.time # record the beginning of service time
delay!(c, s.S) # delay for service time
call.t₃ = c.time # record the end of service time
call.t₃ = c.time # record the end of service time
s.tbusy += call.t₃ - call.t₂ # log service time
push!(output, call) # hang up
call.id limit && stop!(c)
Expand Down Expand Up @@ -236,18 +236,18 @@ plot(wt, title="A-B-Call center ", xlabel="callers", ylabel="waiting time [min]"
savefig("ccenter.png")
```

![](img/ccenter.png)
![call center](img/ccenter.png)

Given the average values, something unexpected emerges: The responsiveness of our call center is not good and waiting times often get way too long. If we want to shorten them, we must improve our service times or add more servers.

## Evaluation

It is easy to simulate discrete event systems such as stochastic processes or queueing systems with `DiscreteEvents`. It integrates well with Julia.
It is easy to simulate discrete event systems such as continuous-time stochastic processes or queueing systems with `DiscreteEvents`. It integrates well with Julia.

## Further examples

You can find more examples at [`DiscreteEventsCompanion`](https://pbayer.github.io/DiscreteEventsCompanion.jl/dev/examples/examples/).

[^1]: This is a modified version of example 4.1.1 in Tijms: A First Course in Stochastic Models, Wiley 2003, p 143ff
[^2]: This is a simplified version of the Able-Baker Call Center Problem in Banks, Carson, Nelson, Nicol: Discrete-Event System Simulation, Pearson 2005, p 35 ff
[^3]: The different approaches to modeling: event-based and process-based can be combined.
[^3]: The different approaches to modeling: event-based and process-based can be combined.
4 changes: 2 additions & 2 deletions docs/src/news.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ multithreading, resource handling and a streamlined documentation.
- you can now create a real time clock [`RTClock`](@ref) and schedule events to it (experimental),
- actors can register their message channels to the `clock.channels` vector and the clock will not proceed before they are empty,
- processes and actors (asynchronous tasks) can transfer IO-operations to the clock with [`now!`](@ref) or print directly via the clock,
- `event!` and `delay!` now also accept stochastic time variables (`Distribution`).
- there is a `n` keyword parameter for repeat `event!`s.
- `event!` and `delay!` now also accept stochastic time variables (a `Distribution`).
- there is a `n` keyword parameter for the number of repeat `event!`s.

### Multithreading (experimental)

Expand Down
17 changes: 11 additions & 6 deletions docs/src/processes.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
# Processes

Processes are typical event sequences implemented in a function. They
are run by asynchronous tasks in a loop and get registered to a clock.
Processes are *typical event sequences*. They get implemented in a function, run as asynchronous tasks in a loop and get registered to a clock.

```@docs
Prc
process!
interrupt!
```

!!! warning "Processes must yield!"

A process has to `yield` to Julia by e.g. doing a `take!(input)` or by calling [`delay!`](@ref) or [`wait!`](@ref). Otherwise it will starve everything else!
A process has to give control back to Julia by e.g. doing a `take!(input)` or by calling [`delay!`](@ref) or [`wait!`](@ref). Otherwise it will starve everything else!

## Delay and wait …

Expand All @@ -22,9 +20,16 @@ delay!
wait!
```

!!! warning "Processes use blocking calls and are not responsive!"
## Exceptions

If other events (customers reneging, failures) interrupt the typical event sequence of a process, it is blocked and not ready to respond. Processes therefore must use exception handling to handle unusual events.

```@docs
PrcException
interrupt!
```

If the typical event sequence of a process is interrupted by other events (e.g. a customer reneging, a failure) the process is blocked and has to use exception handling to tackle it.
An example at DiscreteEventsCompanion illustrates how to handle exceptions to a process. Things can get messy quickly if there are several unusual events which have to be handled in a process.

## Now

Expand Down
3 changes: 2 additions & 1 deletion src/DiscreteEvents.jl
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ export Clock, PClock, RTClock, setUnit!, 𝐶,
incr!, run!, stop!, resume!, sync!, resetClock!,
Prc, process!, interrupt!, delay!, wait!, now!,
fork!, collapse!, pclock, diagnose, onthread,
createRTClock, stopRTClock,
createRTClock, stopRTClock,
PrcException,
Resource


Expand Down
6 changes: 3 additions & 3 deletions src/process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -155,12 +155,12 @@ function wait!(clk::Clock, cond::A) where {A<:Action}
end

"""
interrupt!(p::Prc, ev::ClockEvent, value=nothing)
interrupt!(p::Prc, ev, value)
Interrupt a `Prc` by throwing a `ClockException` to it.
"""
function interrupt!(p::Prc, ev::EV, value=nothing) where {EV<:ClockEvent}
schedule(p.task, ClockException(ev, value), error=true)
function interrupt!(p::Prc, ev, value)
schedule(p.task, PrcException(ev, value), error=true)
yield()
end

Expand Down
15 changes: 7 additions & 8 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -88,18 +88,17 @@ struct Sample{T<:Action} <: AbstractEvent
end

"""
ClockException(ev::ClockEvent, value=nothing)
PrcException(ev, value)
A ClockException to be thrown at processes.
An exception to be thrown at processes.
# Arguments, fields
- `ev::ClockEvent`: delivers an event to the interrupted task
- `value=nothing`: deliver some other value
- `event`: deliver an event to the interrupted task
- `value`: deliver some other value
"""
struct ClockException <: Exception
ev::ClockEvent
value::Any
ClockException(ev::ClockEvent, value=nothing) = new(ev, value)
struct PrcException{U,V} <: Exception
event::U
value::V
end

"""
Expand Down
4 changes: 2 additions & 2 deletions test/test_process.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
using DiscreteEvents, Random, Distributions

println("... basic tests: processes ...")
simex = DiscreteEvents.ClockException(DiscreteEvents.Stop())
@test simex.ev == DiscreteEvents.Stop()
simex = DiscreteEvents.PrcException(DiscreteEvents.Stop(), nothing)
@test simex.event == DiscreteEvents.Stop()
@test simex.value === nothing

# ===== test process registration
Expand Down

0 comments on commit eab59ac

Please sign in to comment.