From 994c66091e4d005b05b37e03bf5e119a54e0b7f2 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Apr 2025 22:50:33 +0530 Subject: [PATCH 1/7] refactor: allow new clocks to be defined in downstream packages --- src/clock.jl | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/clock.jl b/src/clock.jl index a417ebea4..a7357644b 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -1,4 +1,6 @@ -@data Clocks begin +abstract type AbstractClock end + +@data Clocks<:AbstractClock begin ContinuousClock struct PeriodicClock dt::Union{Nothing, Float64, Rational{Int}} @@ -63,6 +65,7 @@ issolverstepclock(::Any) = false iscontinuous(::Any) = false is_discrete_time_domain(::Any) = false +# public function first_clock_tick_time(c, t0) @match c begin PeriodicClock(dt) => ceil(t0 / dt) * dt @@ -71,13 +74,51 @@ function first_clock_tick_time(c, t0) end end -struct IndexedClock{I} - clock::TimeDomain +# public +""" + $(TYPEDEF) + +A struct representing the operation of indexing a clock to obtain a subset of the time +points at which it ticked. The actual list of time points depends on the time interval +for which the clock was running, and can be obtained via `canonicalize_indexed_clock` +by providing a timeseries solution object. + +For example, `IndexedClock(PeriodicClock(0.1), 3)` refers to the third time that +`PeriodicClock(0.1)` ticked. If the simulation started at `t = 0`, then this would be +`t = 0.2`. Similarly, `IndexedClock(PeriodicClock(0.1), [1, 5])` refers to `t = 0.0` +and `t = 0.4` in this context. + +# Fields + +$(TYPEDFIELDS) +""" +struct IndexedClock{C <: AbstractClock, I} + """ + The clock being indexed. A subtype of `SciMLBase.AbstractClock` + """ + clock::C + """ + The subset of indexes being referred to. This can be an integer, an array of integers, + a range or `Colon()` to refer to all the points that the clock ticked. + """ idx::I end -Base.getindex(c::TimeDomain, idx) = IndexedClock(c, idx) +# public +""" + $(TYPEDSIGNATURES) + +Return a `SciMLBase.IndexedClock` representing the subset of the time points that the clock +ticked indicated by `idx`. +""" +Base.getindex(c::AbstractClock, idx) = IndexedClock(c, idx) +# public +""" + $(TYPEDSIGNATURES) + +Return the time points in the interval +""" function canonicalize_indexed_clock(ic::IndexedClock, sol::AbstractTimeseriesSolution) c = ic.clock From 66d688dd62afef40fa8889d51f59b4285a4b305c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 17 Apr 2025 11:02:59 +0530 Subject: [PATCH 2/7] Update src/clock.jl Co-authored-by: Fredrik Bagge Carlson --- src/clock.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clock.jl b/src/clock.jl index a7357644b..1f4da6e78 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -79,8 +79,8 @@ end $(TYPEDEF) A struct representing the operation of indexing a clock to obtain a subset of the time -points at which it ticked. The actual list of time points depends on the time interval -for which the clock was running, and can be obtained via `canonicalize_indexed_clock` +points at which it ticked. The actual list of time points depends on the tick instances +on which the clock was ticking, and can be obtained via `canonicalize_indexed_clock` by providing a timeseries solution object. For example, `IndexedClock(PeriodicClock(0.1), 3)` refers to the third time that From f6ad4259d0dce21296425e5ac2198eb783178609 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Fri, 20 Jun 2025 14:12:01 +0530 Subject: [PATCH 3/7] feat: add EventClock --- src/clock.jl | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/clock.jl b/src/clock.jl index 1f4da6e78..acdaf72cf 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -7,11 +7,14 @@ abstract type AbstractClock end phase::Float64 = 0.0 end SolverStepClock + struct EventClock + id::Symbol + end end # for backwards compatibility const TimeDomain = Clocks.Type -using .Clocks: ContinuousClock, PeriodicClock, SolverStepClock +using .Clocks: ContinuousClock, PeriodicClock, SolverStepClock, EventClock const Continuous = ContinuousClock() (clock::TimeDomain)() = clock @@ -57,12 +60,18 @@ iscontinuous(c::TimeDomain) = @match c begin _ => false end +iseventclock(c::TimeDomain) = @match c begin + EventClock() => true + _ => false +end + is_discrete_time_domain(c::TimeDomain) = !iscontinuous(c) # workaround for https://github.com/Roger-luo/Moshi.jl/issues/43 isclock(::Any) = false issolverstepclock(::Any) = false iscontinuous(::Any) = false +iseventclock(::Any) = false is_discrete_time_domain(::Any) = false # public @@ -71,6 +80,8 @@ function first_clock_tick_time(c, t0) PeriodicClock(dt) => ceil(t0 / dt) * dt SolverStepClock() => t0 ContinuousClock() => error("ContinuousClock() is not a discrete clock") + EventClock() => error("Event clocks do not have a defined first tick time.") + _ => error("Unimplemented for clock $c") end end From ee649f170f6d970fba1943a4e6c27c921f4c604c Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 15 Jul 2025 10:39:34 +0530 Subject: [PATCH 4/7] feat: add `show` method for `Clocks.Type` --- src/SciMLBase.jl | 1 + src/clock.jl | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/SciMLBase.jl b/src/SciMLBase.jl index 49d9e246d..780e68e80 100644 --- a/src/SciMLBase.jl +++ b/src/SciMLBase.jl @@ -25,6 +25,7 @@ import ADTypes: ADTypes, AbstractADType import Accessors: @set, @reset, @delete, @insert using Moshi.Data: @data using Moshi.Match: @match +import Moshi.Derive: @derive import StaticArraysCore import Adapt: adapt_structure, adapt diff --git a/src/clock.jl b/src/clock.jl index acdaf72cf..cd02e713f 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -12,6 +12,8 @@ abstract type AbstractClock end end end +@derive Clocks[Show] + # for backwards compatibility const TimeDomain = Clocks.Type using .Clocks: ContinuousClock, PeriodicClock, SolverStepClock, EventClock From 8076f59fdb103b0a97539a05eafc214856be1c51 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Jul 2025 11:42:25 +0530 Subject: [PATCH 5/7] feat: also derive `Hash` and `Eq` for clock --- src/clock.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clock.jl b/src/clock.jl index cd02e713f..479448c63 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -12,7 +12,7 @@ abstract type AbstractClock end end end -@derive Clocks[Show] +@derive Clocks[Show, Hash, Eq] # for backwards compatibility const TimeDomain = Clocks.Type From 4d2af81b1af509587045c3b1259203c32aef8d20 Mon Sep 17 00:00:00 2001 From: Benjamin Chung Date: Thu, 10 Jul 2025 17:08:47 -0700 Subject: [PATCH 6/7] Change TimeDomain to incude user-provided clocks --- src/clock.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/clock.jl b/src/clock.jl index 479448c63..897eab48f 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -15,7 +15,7 @@ end @derive Clocks[Show, Hash, Eq] # for backwards compatibility -const TimeDomain = Clocks.Type +const TimeDomain = AbstractClock using .Clocks: ContinuousClock, PeriodicClock, SolverStepClock, EventClock const Continuous = ContinuousClock() (clock::TimeDomain)() = clock From b82ca64b7dd694c666810938b704c3269b3cc748 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Wed, 16 Jul 2025 11:50:18 +0530 Subject: [PATCH 7/7] refactor: change `::TimeDomain` methods to `::Clocks.Type`, add fallbacks --- src/clock.jl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/clock.jl b/src/clock.jl index 897eab48f..c80a32463 100644 --- a/src/clock.jl +++ b/src/clock.jl @@ -47,25 +47,29 @@ discrete-time systems that assume a fixed sample time, such as PID controllers a filters. """ SolverStepClock -isclock(c::TimeDomain) = @match c begin +isclock(c::Clocks.Type) = @match c begin PeriodicClock() => true _ => false end +isclock(::TimeDomain) = false -issolverstepclock(c::TimeDomain) = @match c begin +issolverstepclock(c::Clocks.Type) = @match c begin SolverStepClock() => true _ => false end +issolverstepclock(::TimeDomain) = false -iscontinuous(c::TimeDomain) = @match c begin +iscontinuous(c::Clocks.Type) = @match c begin ContinuousClock() => true _ => false end +iscontinuous(::TimeDomain) = false -iseventclock(c::TimeDomain) = @match c begin +iseventclock(c::Clocks.Type) = @match c begin EventClock() => true _ => false end +iseventclock(::TimeDomain) = false is_discrete_time_domain(c::TimeDomain) = !iscontinuous(c) @@ -77,7 +81,7 @@ iseventclock(::Any) = false is_discrete_time_domain(::Any) = false # public -function first_clock_tick_time(c, t0) +function first_clock_tick_time(c::Clocks.Type, t0) @match c begin PeriodicClock(dt) => ceil(t0 / dt) * dt SolverStepClock() => t0 @@ -87,6 +91,10 @@ function first_clock_tick_time(c, t0) end end +function first_clock_tick_time(c::TimeDomain, _) + error("Unimplemented for clock $c") +end + # public """ $(TYPEDEF)