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.Clock
— TypeClock{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: aVector{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 clockSchedule
(events, cond events and sampling),processes::Dict{Any, Prc}
: registeredPrc
es,channels::Vector{Channel}
: registered (Actor) channels,tn::Float64
: next timestep,tev::Float64
: next event time,evcount::Int
: event counter,scount::Int
: sample counter
DiscreteEvents.Clock
— MethodClock(Δ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 withsample_time!
.t0::U=0
: start time for simulationunit::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).
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
You can query the current clock time:
DiscreteEvents.tau
— Functiontau(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
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 ActiveClock
s 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.PClock
— FunctionPClock(Δ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.
Processes on multiple threads are possible in Julia ≥ 1.3 and with JULIA_NUM_THREADS > 1
.
DiscreteEvents.pclock
— Functionpclock(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 orac::ActiveClock
: an active clock,id::Int=threadid()
: thread id, defaults to the caller's current thread.
Returns
- the master
Clock
if id==1, - a parallel
ActiveClock
else
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.fork!
— Functionfork!(master::Clock)
Establish copies of a master clock (thread 1) on all parallel threads.
DiscreteEvents.collapse!
— Functioncollapse!(master::Clock)
Transfer the schedules of the parallel clocks to master and them stop them.
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.
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
DiscreteEvents.diagnose
— Functiondiagnose(master::Clock, id::Int)
Return the stacktrace from parallel clock id.
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.
RTClock
s schedule and execute actions on a real (system) time line.
DiscreteEvents.RTClock
— TypeRTClock{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 workcmd::Channel{T}
: command channel to asynchronous clockback::Channel{T}
: back channel from async clockid::Int
: arbitrary id numberthread::Int
: thread the async clock is living intime::Float64
: clock time since start in secondst0::Float64
: system time at clock start in secondsT::Float64
: clock period in seconds
DiscreteEvents.createRTClock
— FunctioncreateRTClock(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.001id::Int
: clock identification number other than 0:nthreads()thrd::Int=nthreads()
: thread, the clock task should run inch_size::Int=256
: clock communication channel size
DiscreteEvents.stopRTClock
— FunctionstopRTClock(rtc::RTClock)
Stop a real time clock.
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.run!
— Functionrun!(clk::Clock, duration::N) where {N<:Number}
Run a simulation for a given duration.
DiscreteEvents.@run!
— Macro@run! clk t
Run a clock clk
for a duration t
.
DiscreteEvents.incr!
— Functionincr!(clk::Clock)
Take one simulation step, execute the next tick or event.
DiscreteEvents.resetClock!
— FunctionresetClock!(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 ratet0::Float64=0
ort0::Time
: start timehard::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
resetClock!(rtc::RTClock)
Reset a real time clock. Set its time to zero and delete all scheduled and sampling events.
DiscreteEvents.stop!
— Functionstop!(clk::Clock)
Stop a running simulation.
Stop a Prc
DiscreteEvents.resume!
— Functionresume!(clk::Clock)
Resume a halted simulation.
DiscreteEvents.sync!
— Functionsync!(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.
Time Units
You can set time units of a virtual clock:
DiscreteEvents.setUnit!
— FunctionsetUnit!(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 ofms
,s
,minute
orhr
or another UnitfulTime
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
This is not yet implemented for parallel clocks!