From f08f203d4cfb550911c9a7aefa77ccd43d5b96e7 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Tue, 30 Apr 2024 16:07:00 +0530 Subject: [PATCH] refactor: return functors from getu and getp --- src/SymbolicIndexingInterface.jl | 9 +- src/parameter_indexing.jl | 239 +++++++++------------------- src/state_indexing.jl | 264 +++++++++++-------------------- src/value_provider_interface.jl | 143 +++++++++++++++++ 4 files changed, 319 insertions(+), 336 deletions(-) create mode 100644 src/value_provider_interface.jl diff --git a/src/SymbolicIndexingInterface.jl b/src/SymbolicIndexingInterface.jl index 492ae77..cb8300c 100644 --- a/src/SymbolicIndexingInterface.jl +++ b/src/SymbolicIndexingInterface.jl @@ -24,11 +24,14 @@ export SymbolCache include("symbol_cache.jl") export parameter_values, set_parameter!, finalize_parameters_hook!, - parameter_values_at_time, parameter_values_at_state_time, parameter_timeseries, getp, - setp + parameter_values_at_time, parameter_values_at_state_time, parameter_timeseries, + state_values, set_state!, current_time +include("value_provider_interface.jl") + +export getp, setp include("parameter_indexing.jl") -export state_values, set_state!, current_time, getu, setu +export getu, setu include("state_indexing.jl") export BatchedInterface, associated_systems diff --git a/src/parameter_indexing.jl b/src/parameter_indexing.jl index d7b73cf..b52686d 100644 --- a/src/parameter_indexing.jl +++ b/src/parameter_indexing.jl @@ -1,101 +1,21 @@ -""" - parameter_values(p) - parameter_values(p, i) - -Return an indexable collection containing the value of each parameter in `p`. The two- -argument version of this function returns the parameter value at index `i`. The -two-argument version of this function will default to returning -`parameter_values(p)[i]`. - -If this function is called with an `AbstractArray` or `Tuple`, it will return the same -array/tuple. -""" -function parameter_values end - parameter_values(arr::AbstractArray) = arr parameter_values(arr::Tuple) = arr parameter_values(arr::AbstractArray, i) = arr[i] parameter_values(arr::Tuple, i) = arr[i] parameter_values(prob, i) = parameter_values(parameter_values(prob), i) -""" - parameter_values_at_time(p, i) - -Return an indexable collection containing the value of all parameters in `p` at time index -`i`. This is useful when parameter values change during the simulation -(such as through callbacks) and their values are saved. `i` is the time index in the -timeseries formed by these changing parameter values, obtained using -[`parameter_timeseries`](@ref). - -By default, this function returns `parameter_values(p)` regardless of `i`, and only needs -to be specialized for timeseries objects where parameter values are not constant at all -times. The resultant object should be indexable using [`parameter_values`](@ref). - -If this function is implemented, [`parameter_values_at_state_time`](@ref) must be -implemented for [`getu`](@ref) to work correctly. -""" -function parameter_values_at_time end parameter_values_at_time(p, i) = parameter_values(p) -""" - parameter_values_at_state_time(p, i) - -Return an indexable collection containing the value of all parameters in `p` at time -index `i`. This is useful when parameter values change during the simulation (such as -through callbacks) and their values are saved. `i` is the time index in the timeseries -formed by dependent variables (as opposed to the timeseries of the parameters, as in -[`parameter_values_at_time`](@ref)). - -By default, this function returns `parameter_values(p)` regardless of `i`, and only needs -to be specialized for timeseries objects where parameter values are not constant at -all times. The resultant object should be indexable using [`parameter_values`](@ref). - -If this function is implemented, [`parameter_values_at_time`](@ref) must be implemented for -[`getp`](@ref) to work correctly. -""" -function parameter_values_at_state_time end parameter_values_at_state_time(p, i) = parameter_values(p) -""" - parameter_timeseries(p) - -Return an iterable of time steps at which the parameter values are saved. This is only -required for objects where `is_timeseries(p) === Timeseries()` and the parameter values -change during the simulation (such as through callbacks). By default, this returns `[0]`. - -See also: [`parameter_values_at_time`](@ref). -""" -function parameter_timeseries end parameter_timeseries(_) = [0] -""" - set_parameter!(sys, val, idx) - -Set the parameter at index `idx` to `val` for system `sys`. This defaults to modifying -`parameter_values(sys)`. If any additional bookkeeping needs to be performed or the -default implementation does not work for a particular type, this method needs to be -defined to enable the proper functioning of [`setp`](@ref). - -See: [`parameter_values`](@ref) -""" -function set_parameter! end - # Tuple only included for the error message function set_parameter!(sys::Union{AbstractArray, Tuple}, val, idx) sys[idx] = val end set_parameter!(sys, val, idx) = set_parameter!(parameter_values(sys), val, idx) -""" - finalize_parameters_hook!(prob, p) - -This is a callback run one for each call to the function returned by [`setp`](@ref) -which can be used to update internal data structures when parameters are modified. -This is in contrast to [`set_parameter!`](@ref) which is run once for each parameter -that is updated. -""" -finalize_parameters_hook!(prob, p) = nothing - """ getp(sys, p) @@ -125,43 +45,87 @@ function getp(sys, p) _getp(sys, symtype, elsymtype, p) end +struct GetParameterIndex{I} <: AbstractIndexer + idx::I +end + +function (gpi::GetParameterIndex)(::IsTimeseriesTrait, prob) + parameter_values(prob, gpi.idx) +end +function (gpi::GetParameterIndex)(::Timeseries, prob, i::Union{Int, CartesianIndex}) + parameter_values( + parameter_values_at_time( + prob, only(to_indices(parameter_timeseries(prob), (i,)))), + gpi.idx) +end +function (gpi::GetParameterIndex)(::Timeseries, prob, i::Union{AbstractArray{Bool}, Colon}) + parameter_values.( + parameter_values_at_time.((prob,), + (j for j in only(to_indices(parameter_timeseries(prob), (i,))))), + (gpi.idx,)) +end +function (gpi::GetParameterIndex)(::Timeseries, prob, i) + parameter_values.(parameter_values_at_time.((prob,), i), (gpi.idx,)) +end + function _getp(sys, ::NotSymbolic, ::NotSymbolic, p) - return let p = p - function _getter(::NotTimeseries, prob) - parameter_values(prob, p) - end - function _getter(::Timeseries, prob) - parameter_values(prob, p) - end - function _getter(::Timeseries, prob, i::Union{Int, CartesianIndex}) - parameter_values( - parameter_values_at_time( - prob, only(to_indices(parameter_timeseries(prob), (i,)))), - p) - end - function _getter(::Timeseries, prob, i::Union{AbstractArray{Bool}, Colon}) - parameter_values.( - parameter_values_at_time.((prob,), - (j for j in only(to_indices(parameter_timeseries(prob), (i,))))), - (p,)) - end - function _getter(::Timeseries, prob, i) - parameter_values.(parameter_values_at_time.((prob,), i), (p,)) - end - getter = let _getter = _getter - function getter(prob, args...) - return _getter(is_timeseries(prob), prob, args...) - end - end - getter - end + return GetParameterIndex(p) end function _getp(sys, ::ScalarSymbolic, ::SymbolicTypeTrait, p) idx = parameter_index(sys, p) return invoke(_getp, Tuple{Any, NotSymbolic, NotSymbolic, Any}, sys, NotSymbolic(), NotSymbolic(), idx) - return _getp(sys, NotSymbolic(), NotSymbolic(), idx) +end + +struct MultipleParameterGetters{G} + getters::G +end + +function (mpg::MultipleParameterGetters)(::IsTimeseriesTrait, prob) + map(g -> g(prob), mpg.getters) +end +function (mpg::MultipleParameterGetters)(::Timeseries, prob, i::Union{Int, CartesianIndex}) + map(g -> g(prob, i), mpg.getters) +end +function (mpg::MultipleParameterGetters)(::Timeseries, prob, i) + [map(g -> g(prob, j), mpg.getters) + for j in only(to_indices(parameter_timeseries(prob), (i,)))] +end +function (mpg::MultipleParameterGetters)(buffer::AbstractArray, ::Timeseries, prob) + for (g, bufi) in zip(mpg.getters, eachindex(buffer)) + buffer[bufi] = g(prob) + end + buffer +end +function (mpg::MultipleParameterGetters)( + buffer::AbstractArray, ::Timeseries, prob, i::Union{Int, CartesianIndex}) + for (g, bufi) in zip(mpg.getters, eachindex(buffer)) + buffer[bufi] = g(prob, i) + end + buffer +end +function (mpg::MultipleParameterGetters)(buffer::AbstractArray, ::Timeseries, prob, i) + for (bufi, tsi) in zip( + eachindex(buffer), only(to_indices(parameter_timeseries(prob), (i,)))) + for (g, bufj) in zip(mpg.getters, eachindex(buffer[bufi])) + buffer[bufi][bufj] = g(prob, tsi) + end + end + buffer +end +function (mpg::MultipleParameterGetters)(buffer::AbstractArray, ::NotTimeseries, prob) + for (g, bufi) in zip(mpg.getters, eachindex(buffer)) + buffer[bufi] = g(prob) + end + buffer +end + +function (mpg::MultipleParameterGetters)(buffer::AbstractArray, prob, i...) + mpg(buffer, is_timeseries(prob), prob, i...) +end +function (mpg::MultipleParameterGetters)(prob, i...) + mpg(is_timeseries(prob), prob, i...) end for (t1, t2) in [ @@ -171,60 +135,7 @@ for (t1, t2) in [ ] @eval function _getp(sys, ::NotSymbolic, ::$t1, p::$t2) getters = getp.((sys,), p) - - return let getters = getters - function _getter(::NotTimeseries, prob) - map(g -> g(prob), getters) - end - function _getter(::Timeseries, prob) - map(g -> g(prob), getters) - end - function _getter(::Timeseries, prob, i::Union{Int, CartesianIndex}) - map(g -> g(prob, i), getters) - end - function _getter(::Timeseries, prob, i) - [map(g -> g(prob, j), getters) - for j in only(to_indices(parameter_timeseries(prob), (i,)))] - end - function _getter!(buffer, ::NotTimeseries, prob) - for (g, bufi) in zip(getters, eachindex(buffer)) - buffer[bufi] = g(prob) - end - buffer - end - function _getter!(buffer, ::Timeseries, prob) - for (g, bufi) in zip(getters, eachindex(buffer)) - buffer[bufi] = g(prob) - end - buffer - end - function _getter!(buffer, ::Timeseries, prob, i::Union{Int, CartesianIndex}) - for (g, bufi) in zip(getters, eachindex(buffer)) - buffer[bufi] = g(prob, i) - end - buffer - end - function _getter!(buffer, ::Timeseries, prob, i) - for (bufi, tsi) in zip( - eachindex(buffer), only(to_indices(parameter_timeseries(prob), (i,)))) - for (g, bufj) in zip(getters, eachindex(buffer[bufi])) - buffer[bufi][bufj] = g(prob, tsi) - end - end - buffer - end - _getter, _getter! - getter = let _getter = _getter, _getter! = _getter! - function getter(prob, i...) - return _getter(is_timeseries(prob), prob, i...) - end - function getter(buffer::AbstractArray, prob, i...) - return _getter!(buffer, is_timeseries(prob), prob, i...) - end - getter - end - getter - end + return MultipleParameterGetters(getters) end end diff --git a/src/state_indexing.jl b/src/state_indexing.jl index 782fc7b..8d51fd5 100644 --- a/src/state_indexing.jl +++ b/src/state_indexing.jl @@ -1,51 +1,10 @@ -""" - state_values(p) - state_values(p, i) - -Return an indexable collection containing the values of all states in the integrator or -problem `p`. If `is_timeseries(p)` is [`Timeseries`](@ref), return a vector of arrays, -each of which contain the state values at the corresponding timestep. In this case, the -two-argument version of the function can also be implemented to efficiently return -the state values at timestep `i`. By default, the two-argument method calls -`state_values(p)[i]` - -If this function is called with an `AbstractArray`, it will return the same array. - -See: [`is_timeseries`](@ref) -""" -function state_values end state_values(arr::AbstractArray) = arr state_values(arr, i) = state_values(arr)[i] -""" - set_state!(sys, val, idx) - -Set the state at index `idx` to `val` for system `sys`. This defaults to modifying -`state_values(sys)`. If any additional bookkeeping needs to be performed or the -default implementation does not work for a particular type, this method needs to be -defined to enable the proper functioning of [`setu`](@ref). - -See: [`state_values`](@ref) -""" function set_state!(sys, val, idx) state_values(sys)[idx] = val end -""" - current_time(p) - current_time(p, i) - -Return the current time in the integrator or problem `p`. If -`is_timeseries(p)` is [`Timeseries`](@ref), return the vector of timesteps at which -the state value is saved. In this case, the two-argument version of the function can -also be implemented to efficiently return the time at timestep `i`. By default, the two- -argument method calls `current_time(p)[i]` - - -See: [`is_timeseries`](@ref) -""" -function current_time end - current_time(p, i) = current_time(p)[i] """ @@ -74,19 +33,68 @@ function getu(sys, sym) _getu(sys, symtype, elsymtype, sym) end +struct GetStateIndex{I} <: AbstractIndexer + idx::I +end +function (gsi::GetStateIndex)(::Timeseries, prob) + getindex.(state_values(prob), (gsi.idx,)) +end +function (gsi::GetStateIndex)(::Timeseries, prob, i) + getindex(state_values(prob, i), gsi.idx) +end +function (gsi::GetStateIndex)(::NotTimeseries, prob) + state_values(prob, gsi.idx) +end + function _getu(sys, ::NotSymbolic, ::NotSymbolic, sym) - _getter(::Timeseries, prob) = getindex.(state_values(prob), (sym,)) - _getter(::Timeseries, prob, i) = getindex(state_values(prob, i), sym) - _getter(::NotTimeseries, prob) = state_values(prob, sym) - return let _getter = _getter - function getter(prob) - return _getter(is_timeseries(prob), prob) - end - function getter(prob, i) - return _getter(is_timeseries(prob), prob, i) - end - getter - end + return GetStateIndex(sym) +end + +struct GetpAtStateTime{G} <: AbstractIndexer + getter::G +end + +function (g::GetpAtStateTime)(::Timeseries, prob) + [g.getter(parameter_values_at_state_time(prob, i)) + for i in eachindex(current_time(prob))] +end +function (g::GetpAtStateTime)(::Timeseries, prob, i) + g.getter(parameter_values_at_state_time(prob, i)) +end +function (g::GetpAtStateTime)(::NotTimeseries, prob) + g.getter(prob) +end + +struct GetIndepvar <: AbstractIndexer end + +(::GetIndepvar)(::IsTimeseriesTrait, prob) = current_time(prob) +(::GetIndepvar)(::Timeseries, prob, i) = current_time(prob, i) + +struct TimeDependentObservedFunction{F} <: AbstractIndexer + obsfn::F +end + +function (o::TimeDependentObservedFunction)(::Timeseries, prob) + curtime = current_time(prob) + return o.obsfn.(state_values(prob), + (parameter_values_at_state_time(prob, i) for i in eachindex(curtime)), + curtime) +end +function (o::TimeDependentObservedFunction)(::Timeseries, prob, i) + return o.obsfn(state_values(prob, i), + parameter_values_at_state_time(prob, i), + current_time(prob, i)) +end +function (o::TimeDependentObservedFunction)(::NotTimeseries, prob) + return o.obsfn(state_values(prob), parameter_values(prob), current_time(prob)) +end + +struct TimeIndependentObservedFunction{F} <: AbstractIndexer + obsfn::F +end + +function (o::TimeIndependentObservedFunction)(::IsTimeseriesTrait, prob) + return o.obsfn(state_values(prob), parameter_values(prob)) end function _getu(sys, ::ScalarSymbolic, ::SymbolicTypeTrait, sym) @@ -94,65 +102,43 @@ function _getu(sys, ::ScalarSymbolic, ::SymbolicTypeTrait, sym) idx = variable_index(sys, sym) return getu(sys, idx) elseif is_parameter(sys, sym) - return let fn = getp(sys, sym) - _getter_p(::NotTimeseries, prob) = fn(prob) - function _getter_p(::Timeseries, prob) - [fn(parameter_values_at_state_time(prob, i)) - for i in eachindex(current_time(prob))] - end - _getter_p(::Timeseries, prob, i) = fn(parameter_values_at_state_time(prob, i)) - let _getter = _getter_p - getter(prob, args...) = _getter(is_timeseries(prob), prob, args...) - getter - end - end + return GetpAtStateTime(getp(sys, sym)) elseif is_independent_variable(sys, sym) - _getter(::IsTimeseriesTrait, prob) = current_time(prob) - _getter(::Timeseries, prob, i) = current_time(prob, i) - return let _getter = _getter - getter(prob) = _getter(is_timeseries(prob), prob) - getter(prob, i) = _getter(is_timeseries(prob), prob, i) - getter - end + return GetIndepvar() elseif is_observed(sys, sym) fn = observed(sys, sym) if is_time_dependent(sys) - function _getter2(::Timeseries, prob) - curtime = current_time(prob) - return fn.(state_values(prob), - (parameter_values_at_state_time(prob, i) for i in eachindex(curtime)), - curtime) - end - function _getter2(::Timeseries, prob, i) - return fn(state_values(prob, i), - parameter_values_at_state_time(prob, i), - current_time(prob, i)) - end - function _getter2(::NotTimeseries, prob) - return fn(state_values(prob), parameter_values(prob), current_time(prob)) - end - - return let _getter2 = _getter2 - function getter2(prob) - return _getter2(is_timeseries(prob), prob) - end - function getter2(prob, i) - return _getter2(is_timeseries(prob), prob, i) - end - getter2 - end + return TimeDependentObservedFunction(fn) else - # if there is no time, there is no timeseries - return let fn = fn - function getter3(prob) - return fn(state_values(prob), parameter_values(prob)) - end - end + return TimeIndependentObservedFunction(fn) end end error("Invalid symbol $sym for `getu`") end +struct MultipleGetters{G} <: AbstractIndexer + getters::G +end + +function (mg::MultipleGetters)(::Timeseries, prob) + return broadcast(i -> map(g -> g(prob, i), mg.getters), + eachindex(state_values(prob))) +end +function (mg::MultipleGetters)(::Timeseries, prob, i) + return map(g -> g(prob, i), mg.getters) +end +function (mg::MultipleGetters)(::NotTimeseries, prob) + return map(g -> g(prob), mg.getters) +end + +struct AsTupleWrapper{G} <: AbstractIndexer + getter::G +end + +function (atw::AsTupleWrapper)(::IsTimeseriesTrait, args...) + return Tuple(atw.getter(args...)) +end + for (t1, t2) in [ (ScalarSymbolic, Any), (ArraySymbolic, Any), @@ -162,78 +148,18 @@ for (t1, t2) in [ num_observed = count(x -> is_observed(sys, x), sym) if num_observed <= 1 getters = getu.((sys,), sym) - _call(getter, args...) = getter(args...) - return let getters = getters, _call = _call - _getter(::NotTimeseries, prob) = map(g -> g(prob), getters) - function _getter(::Timeseries, prob) - broadcast(i -> map(g -> _call(g, prob, i), getters), - eachindex(state_values(prob))) - end - function _getter(::Timeseries, prob, i) - return map(g -> _call(g, prob, i), getters) - end - - # Need another scope for this to not box `_getter` - let _getter = _getter - function getter(prob) - return _getter(is_timeseries(prob), prob) - end - function getter(prob, i) - return _getter(is_timeseries(prob), prob, i) - end - getter - end - end + return MultipleGetters(getters) else obs = observed(sys, sym isa Tuple ? collect(sym) : sym) - _getter2 = if is_time_dependent(sys) - let obs = obs, is_tuple = sym isa Tuple - function _getter2a(::NotTimeseries, prob) - obs(state_values(prob), parameter_values(prob), current_time(prob)) - end - function _getter2a(::Timeseries, prob) - curtime = current_time(prob) - obs.(state_values(prob), - (parameter_values_at_state_time(prob, i) - for i in eachindex(curtime)), - curtime) - end - function _getter2a(::Timeseries, prob, i) - obs(state_values(prob, i), - parameter_values_at_state_time(prob, i), - current_time(prob, i)) - end - _getter2a - end + getter = if is_timeseries(sys) + TimeDependentObservedFunction(obs) else - let obs = obs - function _getter2b(::NotTimeseries, prob) - obs(state_values(prob), parameter_values(prob)) - end - _getter2b - end + TimeIndependentObservedFunction(obs) end - return if sym isa Tuple - let _getter2 = _getter2 - function getter2(prob) - Tuple(_getter2(is_timeseries(prob), prob)) - end - function getter2(prob, i) - Tuple(_getter2(is_timeseries(prob), prob, i)) - end - getter2 - end - else - let _getter2 = _getter2 - function getter3(prob) - _getter2(is_timeseries(prob), prob) - end - function getter3(prob, i) - _getter2(is_timeseries(prob), prob, i) - end - getter3 - end + if sym isa Tuple + getter = AsTupleWrapper(getter) end + return getter end end end diff --git a/src/value_provider_interface.jl b/src/value_provider_interface.jl new file mode 100644 index 0000000..5214841 --- /dev/null +++ b/src/value_provider_interface.jl @@ -0,0 +1,143 @@ +########### +# Parameter Indexing +########### + +""" + parameter_values(p) + parameter_values(p, i) + +Return an indexable collection containing the value of each parameter in `p`. The two- +argument version of this function returns the parameter value at index `i`. The +two-argument version of this function will default to returning +`parameter_values(p)[i]`. + +If this function is called with an `AbstractArray` or `Tuple`, it will return the same +array/tuple. +""" +function parameter_values end + +""" + parameter_values_at_time(p, i) + +Return an indexable collection containing the value of all parameters in `p` at time index +`i`. This is useful when parameter values change during the simulation +(such as through callbacks) and their values are saved. `i` is the time index in the +timeseries formed by these changing parameter values, obtained using +[`parameter_timeseries`](@ref). + +By default, this function returns `parameter_values(p)` regardless of `i`, and only needs +to be specialized for timeseries objects where parameter values are not constant at all +times. The resultant object should be indexable using [`parameter_values`](@ref). + +If this function is implemented, [`parameter_values_at_state_time`](@ref) must be +implemented for [`getu`](@ref) to work correctly. +""" +function parameter_values_at_time end + +""" + parameter_values_at_state_time(p, i) + +Return an indexable collection containing the value of all parameters in `p` at time +index `i`. This is useful when parameter values change during the simulation (such as +through callbacks) and their values are saved. `i` is the time index in the timeseries +formed by dependent variables (as opposed to the timeseries of the parameters, as in +[`parameter_values_at_time`](@ref)). + +By default, this function returns `parameter_values(p)` regardless of `i`, and only needs +to be specialized for timeseries objects where parameter values are not constant at +all times. The resultant object should be indexable using [`parameter_values`](@ref). + +If this function is implemented, [`parameter_values_at_time`](@ref) must be implemented for +[`getp`](@ref) to work correctly. +""" +function parameter_values_at_state_time end + +""" + parameter_timeseries(p) + +Return an iterable of time steps at which the parameter values are saved. This is only +required for objects where `is_timeseries(p) === Timeseries()` and the parameter values +change during the simulation (such as through callbacks). By default, this returns `[0]`. + +See also: [`parameter_values_at_time`](@ref). +""" +function parameter_timeseries end + +""" + set_parameter!(sys, val, idx) + +Set the parameter at index `idx` to `val` for system `sys`. This defaults to modifying +`parameter_values(sys)`. If any additional bookkeeping needs to be performed or the +default implementation does not work for a particular type, this method needs to be +defined to enable the proper functioning of [`setp`](@ref). + +See: [`parameter_values`](@ref) +""" +function set_parameter! end + +""" + finalize_parameters_hook!(prob, p) + +This is a callback run one for each call to the function returned by [`setp`](@ref) +which can be used to update internal data structures when parameters are modified. +This is in contrast to [`set_parameter!`](@ref) which is run once for each parameter +that is updated. +""" +finalize_parameters_hook!(prob, p) = nothing + +########### +# State Indexing +########### + +""" + state_values(p) + state_values(p, i) + +Return an indexable collection containing the values of all states in the integrator or +problem `p`. If `is_timeseries(p)` is [`Timeseries`](@ref), return a vector of arrays, +each of which contain the state values at the corresponding timestep. In this case, the +two-argument version of the function can also be implemented to efficiently return +the state values at timestep `i`. By default, the two-argument method calls +`state_values(p)[i]` + +If this function is called with an `AbstractArray`, it will return the same array. + +See: [`is_timeseries`](@ref) +""" +function state_values end + +""" + set_state!(sys, val, idx) + +Set the state at index `idx` to `val` for system `sys`. This defaults to modifying +`state_values(sys)`. If any additional bookkeeping needs to be performed or the +default implementation does not work for a particular type, this method needs to be +defined to enable the proper functioning of [`setu`](@ref). + +See: [`state_values`](@ref) +""" +function set_state! end + +""" + current_time(p) + current_time(p, i) + +Return the current time in the integrator or problem `p`. If +`is_timeseries(p)` is [`Timeseries`](@ref), return the vector of timesteps at which +the state value is saved. In this case, the two-argument version of the function can +also be implemented to efficiently return the time at timestep `i`. By default, the two- +argument method calls `current_time(p)[i]` + + +See: [`is_timeseries`](@ref) +""" +function current_time end + +########### +# Utilities +########### + +abstract type AbstractIndexer end + +(ai::AbstractIndexer)(prob) = ai(is_timeseries(prob), prob) +(ai::AbstractIndexer)(prob, i) = ai(is_timeseries(prob), prob, i)