Skip to content

Commit

Permalink
default clock sample rate 0.01
Browse files Browse the repository at this point in the history
  • Loading branch information
pbayer committed Jul 13, 2020
1 parent 3e189d1 commit c8f05ba
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 58 deletions.
12 changes: 3 additions & 9 deletions docs/src/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,6 @@ There is a default clock `𝐶`, which can be used for experimental work.
𝐶
```

`RTC` can be used to setup and control real time Clocks.

```@docs
RTC
```

You can create a clock with parallel active clocks on all available threads or fork existing clocks to other threads or collapse them if no longer needed. You can get direct access to parallel [`ActiveClock`](@ref)s and diagnose them.

```@docs
Expand Down Expand Up @@ -80,19 +74,19 @@ Functions and expressions can be given to events on their own or in tuples, even
function events()
event!(:(i += 1), after, 10) # one expression
event!(fun(f, 1, 2, 3, diff=pi), every, 1) # one fun
event!((:(i += 1), fun(g, j)), [:(tau() 50), fun(isready, input), :(a 10)]) # two funs under three conditions
event!((:(i += 1), fun(g, j)), (()->tau() 50, fun(isready, input), :(a 10))) # two funs under three conditions
end
```

Events are called or evaluated at a their scheduled times or when their
Events are called or evaluated at a their scheduled times or by sampling when their
preconditions become true.

!!! note
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

Functions or expressions can register for sampling and are then executed "continuously" at each clock increment Δt.
Functions or expressions can register for sampling and are then executed "continuously" at each clock increment Δt. The default clock sample rate Δt is 0.01 time units.

```@docs
sample_time!
Expand Down
37 changes: 20 additions & 17 deletions src/clock.jl
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ simulation at a time, you can use it for time keeping.
julia> using DiscreteEvents
julia> resetClock!(𝐶)
"clock reset to t₀=0.0, sampling rate Δt=0.0."
"clock reset to t₀=0.0, sampling rate Δt=0.01."
julia> 𝐶 # default clock
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=0.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
```
"""
Expand Down Expand Up @@ -49,7 +49,7 @@ julia> using DiscreteEvents, Unitful
julia> import Unitful: Time, s, minute, hr
julia> c = Clock(t0=60) # setup a new clock with t0=60
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=60.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=60.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
julia> tau(c) # current time is 60.0 NoUnits
Expand Down Expand Up @@ -116,7 +116,7 @@ Return the current simulation time.
julia> using DiscreteEvents
julia> resetClock!(𝐶)
"clock reset to t₀=0.0, sampling rate Δt=0.0."
"clock reset to t₀=0.0, sampling rate Δt=0.01."
julia> tau() # gives the central time
0.0
```
Expand Down Expand Up @@ -154,13 +154,13 @@ function sync!(clk::Clock, to::Clock=𝐶)
end

"""
resetClock!(clk::Clock, Δt::T=0; t0::U=0; <keyword arguments>) where {T<:Number, U<:Number}
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`: time increment
- `Δ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
Expand All @@ -181,14 +181,14 @@ Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=60.0 s, Δt=1.0 s,
scheduled ev:0, cev:0, sampl:0
julia> resetClock!(c)
"clock reset to t₀=0.0, sampling rate Δt=0.0."
"clock reset to t₀=0.0, sampling rate Δt=0.01."
julia> c
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=0.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
```
"""
function resetClock!(clk::Clock, Δt::T=0;
function resetClock!(clk::Clock, Δt::T=0.01;
t0::U=0, hard::Bool=true, unit=NoUnits) where {T<:Number,U<:Number}
if (Δt == 0) && !isempty(clk.ac)
Δt = clk.Δt
Expand Down Expand Up @@ -269,13 +269,15 @@ _busy(clk::Clock) = clk.state == Busy()
# If sampling rate Δt==0, c.tn is set to 0
# If no events are present, c.tev is set to c.end_time
function _setTimes(clk::Clock)
if !isempty(clk.sc.events)
clk.tev = _nextevtime(clk)
clk.tn = clk.Δt > 0 ? clk.time + clk.Δt : 0.0
else
clk.tn = clk.Δt > 0 ? clk.time + clk.Δt : 0.0
clk.tev = clk.end_time
end
# if !isempty(clk.sc.events)
# clk.tev = _nextevtime(clk)
# clk.tn = clk.Δt > 0 ? clk.time + clk.Δt : 0.0
# else
# clk.tn = clk.Δt > 0 ? clk.time + clk.Δt : 0.0
# clk.tev = clk.end_time
# end
clk.tev = isempty(clk.sc.events) ? clk.end_time : _nextevtime(clk)
clk.tn = isempty(clk.sc.samples) ? 0.0 : clk.time + clk.Δt
end

# ----------------------------------------------------
Expand Down Expand Up @@ -303,7 +305,8 @@ function _run!(c::Clock, Δt::Float64)
end
end
!isempty(c.sc.events) && (c.tev == c.end_time) && _event!(c)
(c.Δt > 0) && (c.tn == c.end_time) && _tick!(c)
# (c.Δt > 0) && (c.tn == c.end_time) && _tick!(c)
!isempty(c.sc.samples) && (c.tn == c.end_time) && _tick!(c)
c.time = c.end_time
end

Expand Down
9 changes: 4 additions & 5 deletions src/events.jl
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ function _tick!(c::Clock)
c.scount +=1
end

_sampling(c::Clock) = !isempty(c.sc.samples) || !isempty(c.sc.cevents)

# ------------------------------------------------------
# step forward to next tick or scheduled event. At a tick evaluate
# 1) all sampling functions or expressions,
Expand All @@ -54,12 +56,9 @@ end
# -------------------------------------------------------
@inline function _step!(c::Clock)
c.state = Busy()
if (c.tev c.time) && (length(c.sc.events) > 0)
c.tev = _nextevtime(c)
end

if !isempty(c.sc.events)
if c.Δt > 0.0
c.tev c.time && (c.tev = _nextevtime(c))
if _sampling(c)
if c.tn <= c.tev # if t_next_tick ≤ t_next_event
_tick!(c)
if c.tn == c.tev # an event is scheduled at the same time
Expand Down
24 changes: 11 additions & 13 deletions src/schedule.jl
Original file line number Diff line number Diff line change
Expand Up @@ -95,22 +95,14 @@ Schedule ex as a conditional event, conditions cond get evaluated at each clock
julia> using DiscreteEvents
julia> c = Clock() # create a new clock
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
julia> event!(c, fun((x)->println(tau(x), ": now I'm triggered"), c), fun(>=, fun(tau, c), 5))
julia> c # a conditional event turns sampling on ⬇
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:1, sampl:0
julia> run!(c, 10) # sampling is not exact, so it takes 501 sample steps to fire the event
julia> run!(c, 10) # sampling is not exact, so it takes 502 sample steps to fire the event
5.009999999999938: now I'm triggered
"run! finished with 0 clock events, 501 sample steps, simulation time: 10.0"
julia> c # after the event sampling is again switched off ⬇
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=10.0 , Δt=0.0 , prc:0
scheduled ev:0, cev:0, sampl:0
"run! finished with 0 clock events, 502 sample steps, simulation time: 10.0"
```
"""
function event!(clk::T, ex::A, cond::C;
Expand Down Expand Up @@ -141,7 +133,10 @@ Register a function or expression for periodic execution at the clock`s sample r
"""
function periodic!(clk::Clock, ex::T, Δt::U=clk.Δt;
spawn=false) where {T<:Action,U<:Number}
clk.Δt = Δt == 0 ? _scale(clk.end_time - clk.time)/100 : Δt
# clk.Δt = Δt == 0 ? _scale(clk.end_time - clk.time)/100 : Δt
if Δt == 0 # pick a sample rate
clk.Δt = clk.evcount == 0 ? 0.01 : _scale(clk.time/clk.evcount)/100
end
_assign(clk, Sample(ex), spawn ? _spawnid(clk) : 0)
end
periodic!(ex::T, Δt::U=𝐶.Δt; kw...) where {T<:Action,U<:Number} = periodic!(𝐶, ex, Δt; kw...)
Expand Down Expand Up @@ -200,7 +195,10 @@ function _register!(c::Clock, ev::DiscreteEvent)
return
end
function _register!(c::Clock, cond::DiscreteCond)
(c.Δt == 0) && (c.Δt = _scale(c.end_time - c.time)/100)
# (c.Δt == 0) && (c.Δt = _scale(c.end_time - c.time)/100)
if c.Δt == 0 # pick a sample rate
c.Δt = c.evcount == 0 ? 0.01 : _scale(c.time/c.evcount)/100
end
push!(c.sc.cevents, cond)
return
end
Expand Down
17 changes: 8 additions & 9 deletions src/types.jl
Original file line number Diff line number Diff line change
Expand Up @@ -180,13 +180,12 @@ end

"""
```
Clock(Δt::T=0; t0::U=0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}
Clock(Δt::T=0.01; t0::U=0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}
```
Create a new simulation clock.
# Arguments
- `Δt::T=0`: time increment. If no Δt is given, the simulation doesn't tick,
but jumps from event to event. Δt can be set later with `sample_time!`.
- `Δ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
Expand Down Expand Up @@ -215,7 +214,7 @@ julia> using DiscreteEvents, Unitful
julia> import Unitful: s, minute, hr
julia> c = Clock() # create a unitless clock (standard)
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), 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]
Expand All @@ -227,7 +226,7 @@ Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 s, Δt=1.0 s,
scheduled ev:0, cev:0, sampl:0
julia> c3 = Clock(t0=60s) # another clock with implicit unit [s]
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=60.0 s, Δt=0.0 s, prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=60.0 s, Δt=0.01 s, prc:0
scheduled ev:0, cev:0, sampl:0
julia> c4 = Clock(1s, t0=1hr) # here Δt's unit [s] takes precedence
Expand All @@ -250,7 +249,7 @@ mutable struct Clock <: AbstractClock
evcount::Int
scount::Int

function Clock(Δt::T=0;
function Clock(Δt::T=0.01;
t0::U=0, unit::FreeUnits=NoUnits) where {T<:Number,U<:Number}
if 1unit isa Time
Δt = isa(Δt, Time) ? uconvert(unit, Δt).val : Δt
Expand Down Expand Up @@ -300,13 +299,13 @@ over the channel to the `ActiveClock` as it should be.
julia> using DiscreteEvents
julia> clk = Clock()
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
julia> fork!(clk)
julia> clk # ⬇ here you got 3 parallel active clocks
Clock 0, thrd 1 (+ 3 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.0 , prc:0
Clock 0, thrd 1 (+ 3 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
Expand All @@ -315,7 +314,7 @@ Clock 0, thrd 1 (+ 3 ac): state=DiscreteEvents.Undefined(), t=0.0 , Δt=0.01 , p
scheduled ev:0, cev:0, sampl:0
julia> ac1 = pclock(clk, 1) # get access to the 1st active clock (on thread 2)
Active clock 1 on thrd 2: state=DiscreteEvents.Idle(), t=0.0 , Δt=0.01 , prc:0
Active clock 1 on thread 2: state=DiscreteEvents.Idle(), t=0.0 , Δt=0.01 , prc:0
scheduled ev:0, cev:0, sampl:0
```
"""
Expand Down
16 changes: 11 additions & 5 deletions test/test_clock.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
#

println("... basic tests: printing ...")
str = "Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=0.0 , Δt=0.0 , prc:0\n scheduled ev:0, cev:0, sampl:0\n"
str = "Clock 0, thrd 1 (+ 0 ac): state=DiscreteEvents.Idle(), t=0.0 , Δt=0.01 , prc:0\n scheduled ev:0, cev:0, sampl:0\n"
resetClock!(𝐶)
if DiscreteEvents._show_default[1] == false
@test repr(𝐶) == str
DiscreteEvents._show_default[1] = true
str = "Clock(0, DiscreteEvents.Idle(), 0.0, , 0.0, DiscreteEvents.ClockChannel[], DiscreteEvents.Schedule(PriorityQueue{DiscreteEvents.DiscreteEvent,Float64,Base.Order.ForwardOrdering}(), DiscreteEvents.DiscreteCond[], DiscreteEvents.Sample[]), Dict{Any,Prc}(), 0.0, 0.0, 0.0, 0, 0)"
str = "Clock(0, DiscreteEvents.Idle(), 0.0, , 0.01, DiscreteEvents.ClockChannel[], DiscreteEvents.Schedule(PriorityQueue{DiscreteEvents.DiscreteEvent,Float64,Base.Order.ForwardOrdering}(), DiscreteEvents.DiscreteCond[], DiscreteEvents.Sample[]), Dict{Any,Prc}(), 0.0, 0.0, 0.0, 0, 0)"
@test repr(𝐶) == str
DiscreteEvents._show_default[1] = false
end
Expand Down Expand Up @@ -106,11 +106,18 @@ end
event!(sim, :(a += 1), every, 1)

# conditional events
@test sim.Δt == 0
sim.Δt = 0.0
event!(sim, :(a +=1), (:(tau(sim)>110), :(a>20)))

# mock sim.state = Busy
state = sim.state
sim.state = DiscreteEvents.Busy()
event!(sim, :(b +=1), (:(a==0), :(b==0))) # execute immediately
@test b == 1
sim.state = state

event!(sim, fun(event!, sim, :(b +=10), :(b==1)), 103) # execute immediately at 103
@test length(sim.sc.cevents) == 2
@test length(sim.sc.cevents) == 1
@test DiscreteEvents._evaluate(sim.sc.cevents[1].cond) == (false, false)
@test sim.Δt == 0.01

Expand All @@ -120,7 +127,6 @@ event!(sim, fun(event!, sim, :(b +=10), :(b==1)), 103) # execute immediately at
incr!(sim)
@test tau(sim) == 100
@test a == 1
@test b == 1
@test DiscreteEvents._nextevent(sim).t == 101

run!(sim, 5)
Expand Down

0 comments on commit c8f05ba

Please sign in to comment.