Internals

The following types are handled internally by DiscreteEvents.jl, but maybe necessary for analyzing and debugging clocks and event schedules.

Events

DiscreteEvents.DiscreteEventType

`DiscreteEvent{T<:Action, X} <: AbstractEvent A discrete event is an Action to be executed at an event time.

Arguments, fields

  • ex::T: a function or an expression or a tuple of them,
  • t::Float64: event time,
  • Δt::X: repeat interval (Float64, Distribution or Nothing),
  • n::Int: number of repeats.
source
DiscreteEvents.DiscreteCondType
DiscreteCond{S<:Action, T<:Action} <: AbstractEvent

A condition to be evaluated repeatedly with expressions or functions to be executed if conditions are met.

Arguments, fields

  • cond::S: a conditional function or an expression or a tuple of them (conditions must evaluate to Bool),
  • ex::T: a function or an expression or a tuple of them to be executed if conditions are met,
source
DiscreteEvents.SampleType
Sample{T<:Action} <: AbstractEvent

Sampling actions are executed at sampling time.

Arguments, fields

  • ex<:Action: an Action to be executed at sample time.
source

Clocks

There is an abstract type for clocks and an active clock, used for controlling parallel clocks.

DiscreteEvents.ActiveClockType
ActiveClock{E <: ClockEvent} <: AbstractClock

An active clock is a wrapper around a local Clock on a parallel thread. It is operated by a thread local task. The master clock on thread 1 communicates with it through messages over its channels.

Fields

  • clock::Clock: the thread specific local clock,
  • master::Ref{Clock}: a pointer to the master clock (on thread 1),
  • forth::Channel{E}: the command channel from master,
  • back::Channel{E}: the response channel to master,
  • id::Int: the clocks id/thread number,
  • task::Task: the active clock`s task.
Don't setup an active clock explicitly!

It is done implicitly with PClock or by fork!ing a Clock to other available threads.

On a parallel thread tasks can access their local clock with pclock(clk). Then they can schedule 'thread local' events, delay! or wait! on it.

Don't share active clocks between threads!

In multithreading we communicate over channels and don't want to share variables between threads. A user can still access active clocks for diagnostic purposes.

source
DiscreteEvents.localClockFunction
localClock(c::Clock)

Create a thread local clock with an undefined reference to an active clock which inherits parameters of the master clock c.

source

An example on active clocks:

julia> using DiscreteEvents

julia> clk = Clock()         # create a clock
Clock 1: state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> fork!(clk)            # fork it to parallel threads

julia> clk                   # now you see parallel active clocks
Clock 1 (+1): state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> clk = PClock()        # create a parallel clock structure
Clock 1 (+1): state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> ac2 = pclock(clk, 2)  # get access to the active clock on thread 2
Active clock 2: state=:idle, t=0.0, Δt=0.01, prc:0
   scheduled ev:0, cev:0, sampl:0

julia> ac2.clock             # access the parallel clock 2
Clock 2: state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

Schedule and ClockChannel are two important Clock substructures:

DiscreteEvents.ScheduleType
Schedule()

A schedule contains events, conditional events and sampling functions to be executed or evaluated on the clock's time line.

Fields

  • events::PriorityQueue{DiscreteEvent,Float64}: scheduled events,
  • cevents::Array{DiscreteCond,1}: conditional events to evaluate at each tick,
  • samples::Array{Sample,1}: sampling expressions to evaluate at each tick,
source
DiscreteEvents.ClockChannelType
ClockChannel{T <: ClockEvent}

Provide a message channel to an active clock or a real time clock.

Fields

  • ref::Ref{Task}: a reference to an active clock task, useful for diagnosis,
  • forth::Channel{T}: a communication channel to an active clock,
  • back::Channel{T}: response channel from the active clock,
  • thread::Int: the thread id of the active clock,
  • done::Bool: flag indicating if the active clock has completed its cycle,
  • load::Int: internal flag.
source

Clock concurrency

If a task after activation by the clock gives control back to the Julia scheduler (e.g. by reading from a channel or by doing an IO-operation), it enqueues for its next schedule behind the clock. The clock may then increment time to $t_{i+1}$ before the task can finish its job at current event time $t_i$.

There are several ways to solve this problem:

  1. The clock does a 2ⁿᵈ yield() after invoking a task and enqueues again at the end of the scheduling queue. This is implemented for delay! and wait! of processes and should be enough for most those cases.
  2. Actors push! their message channel to the clock.channels vector and the clock will only proceed to the next event if all registered channels are empty [1].
  3. Tasks use now! to let the (master) clock do IO-operations for them. They can also print via the clock.

Error handling and diagnosis

  • 1In YAActL you can register! to a Vector{Channel}. To register actors is also useful for diagnosis.