Skip to content

Commit 5de5d1b

Browse files
feat: move clock interface here
1 parent 854e4fd commit 5de5d1b

File tree

3 files changed

+97
-0
lines changed

3 files changed

+97
-0
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
1212
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
1313
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
1414
EnumX = "4e289a0a-7415-4d19-859d-a7e5c4648b56"
15+
Expronicon = "6b7a57c9-7cc1-4fdf-b7f5-e857abae3636"
1516
FunctionWrappersWrappers = "77dc65aa-8811-40c2-897b-53d922fa7daf"
1617
IteratorInterfaceExtensions = "82899510-4779-5014-852e-03e436cf321d"
1718
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
@@ -62,6 +63,7 @@ DataFrames = "1.6"
6263
Distributed = "1.10"
6364
DocStringExtensions = "0.9"
6465
EnumX = "1"
66+
Expronicon = "0.8"
6567
ForwardDiff = "0.10.36"
6668
FunctionWrappersWrappers = "0.1.3"
6769
IteratorInterfaceExtensions = "^1"

src/SciMLBase.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import RuntimeGeneratedFunctions
2323
import EnumX
2424
import ADTypes: AbstractADType
2525
import Accessors: @set, @reset
26+
using Expronicon.ADT: @match
2627

2728
using Reexport
2829
using SciMLOperators
@@ -717,6 +718,7 @@ include("problems/problem_traits.jl")
717718
include("problems/problem_interface.jl")
718719
include("problems/optimization_problems.jl")
719720

721+
include("clock.jl")
720722
include("solutions/basic_solutions.jl")
721723
include("solutions/nonlinear_solutions.jl")
722724
include("solutions/ode_solutions.jl")
@@ -835,4 +837,6 @@ export step!, deleteat!, addat!, get_tmp_cache,
835837

836838
export ContinuousCallback, DiscreteCallback, CallbackSet, VectorContinuousCallback
837839

840+
export Clocks, TimeDomain, is_discrete_time_domain, isclock, issolverstepclock, iscontinuous
841+
838842
end

src/clock.jl

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
module Clocks
2+
3+
export TimeDomain
4+
5+
using Expronicon.ADT: @adt, @match
6+
7+
@adt TimeDomain begin
8+
Continuous
9+
struct PeriodicClock
10+
dt::Union{Nothing, Float64, Rational{Int}}
11+
end
12+
SolverStepClock
13+
end
14+
15+
Base.Broadcast.broadcastable(d::TimeDomain) = Ref(d)
16+
17+
end
18+
19+
using .Clocks
20+
21+
"""
22+
Clock(dt)
23+
Clock()
24+
25+
The default periodic clock with tick interval `dt`. If `dt` is left unspecified, it will
26+
be inferred (if possible).
27+
"""
28+
Clock(dt::Union{<:Rational, Float64}) = PeriodicClock(dt)
29+
Clock(dt) = PeriodicClock(convert(Float64, dt))
30+
Clock() = PeriodicClock(nothing)
31+
32+
@doc """
33+
SolverStepClock
34+
35+
A clock that ticks at each solver step (sometimes referred to as "continuous sample time").
36+
This clock **does generally not have equidistant tick intervals**, instead, the tick
37+
interval depends on the adaptive step-size selection of the continuous solver, as well as
38+
any continuous event handling. If adaptivity of the solver is turned off and there are no
39+
continuous events, the tick interval will be given by the fixed solver time step `dt`.
40+
41+
Due to possibly non-equidistant tick intervals, this clock should typically not be used with
42+
discrete-time systems that assume a fixed sample time, such as PID controllers and digital
43+
filters.
44+
""" SolverStepClock
45+
46+
isclock(c) = @match c begin
47+
PeriodicClock(_) => true
48+
_ => false
49+
end
50+
51+
issolverstepclock(c) = @match c begin
52+
&SolverStepClock => true
53+
_ => false
54+
end
55+
56+
iscontinuous(c) = @match c begin
57+
&Continuous => true
58+
_ => false
59+
end
60+
61+
is_discrete_time_domain(c) = !iscontinuous(c)
62+
63+
function first_clock_tick_time(c, t0)
64+
@match c begin
65+
PeriodicClock(dt) => ceil(t0 / dt) * dt
66+
&SolverStepClock => t0
67+
&Continuous => error("Continuous is not a discrete clock")
68+
end
69+
end
70+
71+
struct IndexedClock{I}
72+
clock::TimeDomain
73+
idx::I
74+
end
75+
76+
Base.getindex(c::TimeDomain, idx) = IndexedClock(c, idx)
77+
78+
function canonicalize_indexed_clock(ic::IndexedClock, sol::AbstractTimeseriesSolution)
79+
c = ic.clock
80+
81+
return @match c begin
82+
PeriodicClock(dt) => ceil(sol.prob.tspan[1] / dt) * dt .+ (ic.idx .- 1) .* dt
83+
&SolverStepClock => begin
84+
ssc_idx = findfirst(eachindex(sol.discretes)) do i
85+
!isa(sol.discretes[i].t, AbstractRange)
86+
end
87+
sol.discretes[ssc_idx].t[ic.idx]
88+
end
89+
&Continuous => sol.t[ic.idx]
90+
end
91+
end

0 commit comments

Comments
 (0)