Clocks

Clocks schedule and execute actions, computations that happen as events at specified times (or under specified conditions).

Virtual Clocks

A Clock is not bound to physical time and executes an event sequence as fast as possible by jumping from event to event.

DiscreteEvents.ClockType
Clock{AC} <: AbstractClock

A virtual clock structure, used for global and thread local clocks.

Fields

  • id::Int: clock ident number 1: master clock, > 1: parallel clock,
  • ac::AC: if id == 1: a Vector{ClockChannel} else: Ref{ActiveClock},
  • state::ClockState: clock state,
  • time::Float64: clock time,
  • unit::FreeUnits: time unit,
  • end_time::Float64: end time for simulation,
  • Δt::Float64: sampling time, timestep between ticks,
  • sc::Schedule: the clock Schedule (events, cond events and sampling),
  • processes::Dict{Any, Prc}: registered Prces,
  • channels::Vector{Channel}: registered (Actor) channels,
  • tn::Float64: next timestep,
  • tev::Float64: next event time,
  • evcount::Int: event counter,
  • scount::Int: sample counter
source
DiscreteEvents.ClockMethod
Clock(Δt::T=0.01; t0::U=0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}

Create a new virtual clock.

Arguments

  • Δt::T=0.01: time increment for sampling. Δt can be set later with sample_time!.
  • t0::U=0: start time for simulation
  • unit::FreeUnits=NoUnits: clock time unit. Units can be set explicitely by setting e.g. unit=minute or implicitly by giving Δt as a time or else setting t0 to a time, e.g. t0=60s.

The created clock has id==1 (master).

source

You can create clocks easily:

julia> using DiscreteEvents, Unitful, .Threads

julia> import Unitful: s, minute, hr

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

julia> c1 = Clock(1s, unit=minute)  # create a clock with unit [minute]
Clock 1: state=:idle, t=0.0minute, Δt=0.01667minute, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> c2 = Clock(1s)               # create a clock with implicit unit [s]
Clock 1: state=:idle, t=0.0s, Δt=1.0s, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> c3 = Clock(t0=60s)           # another clock with implicit unit [s]
Clock 1: state=:idle, t=60.0s, Δt=0.01s, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> c4 = Clock(1s, t0=1hr)       # here Δt's unit [s] takes precedence
Clock 1: state=:idle, t=3600.0s, Δt=1.0s, prc:0
  scheduled ev:0, cev:0, sampl:0

There is a default clock 𝐶 for experimental work:

DiscreteEvents.𝐶Constant
𝐶

𝐶 (𝐶 = \itC+tab) is a default clock, ready to use.

Examples

julia> using DiscreteEvents

julia> resetClock!(𝐶)
"clock reset to t₀=0.0, sampling rate Δt=0.01."

julia> 𝐶  # default clock
Clock 1: state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0
source

You can query the current clock time:

DiscreteEvents.tauFunction
tau(clk::Clock=𝐶)

Return the current simulation time.

Examples

julia> using DiscreteEvents

julia> resetClock!(𝐶)   # reset the default clock
"clock reset to t₀=0.0, sampling rate Δt=0.01."
julia> tau()            # gives the default clock's time
0.0
source

Parallel Clocks (Experimental)

Parallel clocks are a new feature in v0.3 and cannot yet considered to be stable. Please develop your applications first single-threaded before going parallel. Please report any failures.

Parallel clocks are virtual clocks with local clocks on parallel threads to support multi-threaded simulations.

A parallel clock structure consists of a master (global) clock on thread 1 and ActiveClocks on all available threads > 1. An active clock is a task running a thread local clock. The thread local clock can schedule and execute events locally.

The master clock communicates with its parallel active clocks via message channels. It synchronizes time with the local clocks. Tasks (processes and actors) have access to their thread local clock from it and then work only with the local clock.

DiscreteEvents.PClockFunction
PClock(Δt::T=0.01; t0::U=0.0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}

Setup a clock with parallel clocks on all available threads.

When running the master clock on thread 1 will synchronize time between parallel clocks every time increment Δt.

Arguments

  • Δt::T=0.01: time increment > 0. If given Δt ≤ 0, it gets set to 0.01.
  • t0::U=0.0: start time for simulation,
  • unit::FreeUnits=NoUnits: clock time unit for explicitl unit setting.
Note

Processes on multiple threads are possible in Julia ≥ 1.3 and with JULIA_NUM_THREADS > 1.

source
DiscreteEvents.pclockFunction
pclock(clk::Clock, id::Int ) :: C where {C<:AbstractClock}
pclock(ac::ActiveClock, id::Int ) :: C where {C<:AbstractClock}

Get a parallel clock to a given clock.

Arguments

  • master::Clock: a master clock or
  • ac::ActiveClock: an active clock,
  • id::Int=threadid(): thread id, defaults to the caller's current thread.

Returns

source

Parallel clocks can be identified by their thread number: the master clock works on thread 1, local clocks on parallel threads ≥ 2. They can be setup and accessed easily:

julia> @show x=nthreads()-1;
x = nthreads() - 1 = 1

julia> clk = PClock()       # now the clock has (+x) active parallel clocks
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) # access 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            # the thread local clock
Clock 2: state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> ac2.clock.ac[]       # local clocks can access their active clock
Active clock 2: state=:idle, t=0.0, Δt=0.01, prc:0
   scheduled ev:0, cev:0, sampl:0

Tasks on parallel threads have access to the thread local clock by pclock(clk). Then they can schedule events, delay! or wait! on it as usual. The thread local clock is passed to a process! automatically if you set it up on a parallel thread.

You can fork explicitly existing clocks to other threads or collapse them if no longer needed. You can get direct access to parallel active clocks and diagnose them.

DiscreteEvents.collapse!Function
collapse!(master::Clock)

Transfer the schedules of the parallel clocks to master and them stop them.

Note

If there are processes on other threads registered to parallel clocks, make sure that they aren't needed anymore before calling collapse. They are not transferred to and cannot be controlled by master.

source
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

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

julia> collapse!(clk)     # collapse it

julia> clk                # it now has no parallel clocks
Clock 1: state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

Real Time Clocks (Experimental)

Real time clocks are a new feature in v0.3 and thus cannot yet be considered as stable. Please try and report problems.

RTClocks schedule and execute actions on a real (system) time line.

DiscreteEvents.RTClockType
RTClock{E <: ClockEvent} <: AbstractClock

A real time clock checks every given period for scheduled events and executes them. It has a time in seconds since its start or last reset and uses system time for updating.

Real time clocks are controlled over channels. Multiple real time clocks can be setup with arbitrary periods (≥ 1 ms). Real time clocks should not be created directly but rather with CreateRTClock.

Fields

  • Timer::Timer: clock period in seconds, minimum is 0.001 (1 ms)
  • clock::Clock: clock work
  • cmd::Channel{T}: command channel to asynchronous clock
  • back::Channel{T}: back channel from async clock
  • id::Int: arbitrary id number
  • thread::Int: thread the async clock is living in
  • time::Float64: clock time since start in seconds
  • t0::Float64: system time at clock start in seconds
  • T::Float64: clock period in seconds
source
DiscreteEvents.createRTClockFunction
createRTClock(T::Float64, id::Int, thrd::Int=nthreads(); ch_size::Int=256)

Create, start and return a real time Clock.

The clock takes the current system time and starts to count in seconds with the given period T. Events or sampling functions can then be scheduled to it.

Arguments

  • T::Float64: period (clock resolution) in seconds, T ≥ 0.001
  • id::Int: clock identification number other than 0:nthreads()
  • thrd::Int=nthreads(): thread, the clock task should run in
  • ch_size::Int=256: clock communication channel size
source

You can work with real time clocks easily:

julia> rtc = createRTClock(0.01, 99)     # create a real time clock
RTClock 99 on thread 2: state=:idle, t=0.1696 s, T=0.01 s, prc:0
   scheduled ev:0, cev:0, sampl:0

julia> sleep(1)

julia> tau(rtc)                          # query its time after a sleep
1.2061004910000008

julia> a = [1]                           # create a mutable variable
1-element Array{Int64,1}:
 1

julia> f(x) = x[1] += 1                  # an incrementing function
f (generic function with 1 method)

julia> event!(rtc, fun(f, a), every, 1)  # increment now and then every second
DiscreteEvents.Register{DiscreteEvents.DiscreteEvent{DiscreteEvents.var"#9#10"{typeof(Main.ex-clocks.f)},Float64}}(DiscreteEvents.DiscreteEvent{DiscreteEvents.var"#9#10"{typeof(Main.ex-clocks.f)},Float64}(DiscreteEvents.var"#9#10"{typeof(Main.ex-clocks.f)}(Core.Box(nothing), Main.ex-clocks.f, Core.Box(([2],))), 1.2061004910000008, 1.0, 9223372036854775807))

julia> sleep(3)                          # sleep 3 seconds

julia> a[1]                              # query a
5

julia> stopRTClock(rtc)                  # stop the clock
DiscreteEvents.Stop()

Clock Operation

Virtual clocks can be run, stopped or stepped through and thereby used to simulate chains of events.

DiscreteEvents.resetClock!Function
resetClock!(clk::Clock, Δt::T=0.01; t0::U=0; <keyword arguments>) where {T<:Number, U<:Number}

Reset a clock.

Arguments

  • clk::Clock
  • Δt::T=0.01: sample rate
  • t0::Float64=0 or t0::Time: start time
  • hard::Bool=true: time is reset, all scheduled events and sampling are deleted. If hard=false, then only time is reset, event and sampling times are adjusted accordingly.
  • unit=NoUnits: the Time unit for the clock after reset. If a Δt::Time is given, its Time unit goes into the clock Time unit. If only t0::Time is given, its Time unit goes into the clock time unit.

Examples

julia> using DiscreteEvents, Unitful

julia> import Unitful: s

julia> c = Clock(1s, t0=60s)
Clock 1: state=:idle, t=60.0s, Δt=1.0s, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> resetClock!(c)
"clock reset to t₀=0.0, sampling rate Δt=0.01."

julia> c
Clock 1: state=:idle, t=0.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0
source
resetClock!(rtc::RTClock)

Reset a real time clock. Set its time to zero and delete all scheduled and sampling events.

source
DiscreteEvents.sync!Function
sync!(clk::Clock, to::Clock=𝐶)

Force a synchronization of two clocks. Change all registered times of clk accordingly. Convert or force clk.unit to to.unit.

source

Time Units

You can set time units of a virtual clock:

DiscreteEvents.setUnit!Function
setUnit!(clk::Clock, new::FreeUnits)

set a clock to a new time unit in Unitful. If necessary convert current clock times to the new unit.

Arguments

  • clk::Clock
  • new::FreeUnits: new is one of ms, s, minute or hr or another Unitful Time unit.

Examples

julia> using DiscreteEvents, Unitful

julia> import Unitful: Time, s, minute, hr

julia> c = Clock(t0=60)     # setup a new clock with t0=60
Clock 1: state=:idle, t=60.0, Δt=0.01, prc:0
  scheduled ev:0, cev:0, sampl:0

julia> tau(c)               # current time is 60.0 NoUnits
60.0

julia> setUnit!(c, s)       # set clock unit to Unitful.s
60.0 s

julia> tau(c)               # current time is now 60.0 s
60.0 s

julia> setUnit!(c, minute)  # set clock unit to Unitful.minute
1.0 minute

julia> tau(c)               # current time is now 1.0 minute
1.0 minute

julia> isa(tau(c), Time)
true

julia> uconvert(s, tau(c))  # ... which can be converted to other time units
60.0 s

julia> tau(c).val           # it has a value of 1.0
1.0

julia> c.time               # internal clock time is set to 1.0 (a Float64)
1.0

julia> c.unit               # internal clock unit is set to Unitful.minute
minute
source
Note

This is not yet implemented for parallel clocks!