From 3e2e26d2d5e4b674dc3dcdeb6366ad26883753c0 Mon Sep 17 00:00:00 2001 From: Aayush Sabharwal Date: Thu, 20 Jun 2024 23:48:36 +0530 Subject: [PATCH] refactor: rework discrete indexing behavior --- src/index_provider_interface.jl | 43 +++- src/parameter_indexing.jl | 167 ++++++++++------ src/state_indexing.jl | 83 ++------ src/symbol_cache.jl | 30 ++- src/value_provider_interface.jl | 60 +++--- test/parameter_indexing_test.jl | 344 +++++++++++++++++++------------- 6 files changed, 429 insertions(+), 298 deletions(-) diff --git a/src/index_provider_interface.jl b/src/index_provider_interface.jl index a114450a..a7d01851 100644 --- a/src/index_provider_interface.jl +++ b/src/index_provider_interface.jl @@ -120,16 +120,23 @@ struct ParameterObservedFunction{I, F <: Function} observed_fn::F end +function ParameterObservedFunction(ts_idx, f) + ParameterObservedFunction{typeof(ts_idx), typeof(f)}(ts_idx, f) +end +ParameterObservedFunction(f) = ParameterObservedFunction(nothing, f) + """ parameter_observed(indp, sym) Return the observed function of `sym` in `indp` as a [`ParameterObservedFunction`](@ref). -If `sym` only involves variables from a single parameter timeseries (optionally along -with non-timeseries parameters) the timeseries index of the parameter timeseries should -be provided in the [`ParameterObservedFunction`](@ref). In all other cases, just the -observed function should be returned as part of the `ParameterObservedFunction` object. +If `sym` only involves non-timeseries parameters, the timeseries index in the +`ParameterObservedFunction` should be `nothing`. If `sym` involves timeseries parameters, +the timeseries index should be specified in the `ParameterObservedFunction`. If `sym` +involves timeseries parameters from multiple different timeseries, all the relevant +timeseries indexes should be specified in the `ParameterObservedFunction` as a `Vector`. -By default, this function returns `nothing`. +By default, this function returns `nothing`, indicating that the index provider does not +support generating parameter observed functions. """ function parameter_observed(indp, sym) if hasmethod(symbolic_container, Tuple{typeof(indp)}) @@ -139,6 +146,32 @@ function parameter_observed(indp, sym) end end +""" + struct ContinuousTimeseries end + +A singleton struct corresponding to the timeseries index of the continuous timeseries. +""" +struct ContinuousTimeseries end + +""" + get_all_timeseries_indexes(indp, sym) + +Return a `Set` of all unique timeseries indexes of variables in symbolic expression +`sym`. `sym` may be a symbolic, or an array of symbolics. Continuous variables +correspond to the [`ContinuousTimeseries`](@ref) timeseries index. Non-timeseries +parameters do not have a timeseries index. Timeseries parameters have the same timeseries +index as that returned by [`timeseries_parameter_index`](@ref). + +By default, this function returns `Set([ContinuousTimeseries()])`. +""" +function get_all_timeseries_indexes(indp, sym) + if hasmethod(symbolic_container, Tuple{typeof(indp)}) + return get_all_timeseries_indexes(symbolic_container(indp), sym) + else + return Set([ContinuousTimeseries()]) + end +end + """ parameter_symbols(indp) diff --git a/src/parameter_indexing.jl b/src/parameter_indexing.jl index f89cde4c..1c82d151 100644 --- a/src/parameter_indexing.jl +++ b/src/parameter_indexing.jl @@ -48,7 +48,7 @@ end is_indexer_timeseries(::Type{GetParameterIndex{I}}) where {I} = IndexerNotTimeseries() function is_indexer_timeseries(::Type{GetParameterIndex{I}}) where {I <: ParameterTimeseriesIndex} - IndexerTimeseries() + IndexerOnlyTimeseries() end function indexer_timeseries_index(gpi::GetParameterIndex{<:ParameterTimeseriesIndex}) gpi.idx.timeseries_idx @@ -56,8 +56,11 @@ end function (gpi::GetParameterIndex)(::IsTimeseriesTrait, prob) parameter_values(prob, gpi.idx) end +function (gpi::GetParameterIndex)(::IsTimeseriesTrait, prob, arg) + parameter_values(prob, gpi.idx) +end function (gpi::GetParameterIndex)(::Timeseries, prob, args) - throw(ParameterTimeseriesValueIndexMismatchError{Timeseries}(prob, gpi, args)) + parameter_values(prob, gpi.idx) end function (gpi::GetParameterIndex{<:ParameterTimeseriesIndex})(::Timeseries, prob) get_parameter_timeseries_collection(prob)[gpi.idx] @@ -129,10 +132,12 @@ is_indexer_timeseries(::Type{G}) where {G <: GetParameterTimeseriesIndex} = Inde function indexer_timeseries_index(gpti::GetParameterTimeseriesIndex) indexer_timeseries_index(gpti.param_timeseries_idx) end -as_not_timeseries_indexer(::IndexerBoth, gpti::GetParameterTimeseriesIndex) = gpti.param_idx function as_timeseries_indexer(::IndexerBoth, gpti::GetParameterTimeseriesIndex) gpti.param_timeseries_idx end +function as_not_timeseries_indexer(::IndexerBoth, gpti::GetParameterTimeseriesIndex) + gpti.param_idx +end function (gpti::GetParameterTimeseriesIndex)(ts::Timeseries, prob, args...) gpti.param_timeseries_idx(ts, prob, args...) @@ -163,35 +168,51 @@ const SingleGetParameterObserved = GetParameterObserved{I, false} where {I} function is_indexer_timeseries(::Type{G}) where {G <: GetParameterObserved{Nothing}} IndexerNotTimeseries() end +function is_indexer_timeseries(::Type{G}) where {I <: Vector, G <: GetParameterObserved{I}} + IndexerMixedTimeseries() +end is_indexer_timeseries(::Type{G}) where {G <: GetParameterObserved} = IndexerBoth() indexer_timeseries_index(gpo::GetParameterObserved) = gpo.timeseries_idx -function as_not_timeseries_indexer( - ::IndexerBoth, gpo::GetParameterObserved{I, M}) where {I, M} - return GetParameterObserved{M}(nothing, gpo.obsfn) -end as_timeseries_indexer(::IndexerBoth, gpo::GetParameterObserved) = gpo +as_not_timeseries_indexer(::IndexerBoth, gpo::GetParameterObserved) = gpo +as_not_timeseries_indexer(::IndexerMixedTimeseries, gpo::GetParameterObserved) = gpo -function (gpo::GetParameterObserved{Nothing})(::Timeseries, prob) - gpo.obsfn(parameter_values(prob), current_time(prob)[end]) -end -for multiple in [true, false] - @eval function (gpo::GetParameterObserved{Nothing, $multiple})( - buffer::AbstractArray, ::Timeseries, prob) +for argType in [Union{Int, CartesianIndex}, Colon, AbstractArray{Bool}, Any] + @eval function (gpo::GetParameterObserved{Nothing})(::Timeseries, prob, args::$argType) + gpo.obsfn(parameter_values(prob), current_time(prob)[end]) + end + @eval function (gpo::GetParameterObserved{Nothing, true})( + buffer::AbstractArray, ::Timeseries, prob, args::$argType) gpo.obsfn(buffer, parameter_values(prob), current_time(prob)[end]) return buffer end -end -for argType in [Union{Int, CartesianIndex}, Colon, AbstractArray{Bool}, Any] - @eval function (gpo::GetParameterObserved{Nothing})(::Timeseries, prob, args::$argType) - throw(ParameterTimeseriesValueIndexMismatchError{Timeseries}(prob, gpo, args)) + @eval function (gpo::GetParameterObserved{<:Vector})(::Timeseries, prob, args::$argType) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(gpo))) end for multiple in [true, false] - @eval function (gpo::GetParameterObserved{Nothing, $multiple})( + @eval function (gpo::GetParameterObserved{<:Vector, $multiple})( ::AbstractArray, ::Timeseries, prob, args::$argType) - throw(ParameterTimeseriesValueIndexMismatchError{Timeseries}(prob, gpo, args)) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(gpo))) end end end + +function (gpo::GetParameterObserved{<:Vector})(::NotTimeseries, prob) + gpo.obsfn(parameter_values(prob), current_time(prob)) +end +function (gpo::GetParameterObserved{<:Vector, true})( + buffer::AbstractArray, ::NotTimeseries, prob) + gpo.obsfn(buffer, parameter_values(prob), current_time(prob)) +end +function (gpo::GetParameterObserved{<:Vector})(::Timeseries, prob) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(gpo))) +end +function (gpo::GetParameterObserved{<:Vector, true})(::AbstractArray, ::Timeseries, prob) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(gpo))) +end +function (gpo::GetParameterObserved{<:Vector, false})(::AbstractArray, ::Timeseries, prob) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(gpo))) +end function (gpo::GetParameterObserved)(::NotTimeseries, prob) gpo.obsfn(parameter_values(prob), current_time(prob)) end @@ -312,11 +333,7 @@ function _getp(sys, ::ScalarSymbolic, ::SymbolicTypeTrait, p) error("Invalid symbol $p for `getp`") end -struct MixedTimeseriesIndexes - indexes::Any -end - -struct MultipleParametersGetter{T <: IsIndexerTimeseries, G, I} <: +struct MultipleParametersGetter{T <: IndexerTimeseriesType, G, I} <: AbstractParameterGetIndexer getters::G timeseries_idx::I @@ -327,36 +344,40 @@ function MultipleParametersGetter(getters) return MultipleParametersGetter{IndexerNotTimeseries, typeof(getters), Nothing}( getters, nothing) end - has_timeseries_indexers = any(getters) do g - is_indexer_timeseries(g) == IndexerTimeseries() + has_only_timeseries_indexers = any(getters) do g + is_indexer_timeseries(g) == IndexerOnlyTimeseries() end has_non_timeseries_indexers = any(getters) do g is_indexer_timeseries(g) == IndexerNotTimeseries() end - if has_timeseries_indexers && has_non_timeseries_indexers - throw(ArgumentError("Cannot mix timeseries and non-timeseries indexers in `$MultipleParametersGetter`")) + all_non_timeseries_indexers = all(getters) do g + is_indexer_timeseries(g) == IndexerNotTimeseries() + end + if has_only_timeseries_indexers && has_non_timeseries_indexers + throw(ArgumentError("Cannot mix timeseries indexes with non-timeseries variables in `$MultipleParametersGetter`")) end - indexer_type = if has_timeseries_indexers + # If any getters are only timeseries, all of them should be + indexer_type = if has_only_timeseries_indexers getters = as_timeseries_indexer.(getters) - timeseries_idx = indexer_timeseries_index(first(getters)) - IndexerTimeseries - elseif has_non_timeseries_indexers - getters = as_not_timeseries_indexer.(getters) - timeseries_idx = nothing + IndexerOnlyTimeseries + # If all indexers are non-timeseries, so should their combination + elseif all_non_timeseries_indexers IndexerNotTimeseries else - timeseries_idx = indexer_timeseries_index(first(getters)) IndexerBoth end - if indexer_type != IndexerNotTimeseries && - !allequal(indexer_timeseries_index(g) for g in getters) - if indexer_type == IndexerTimeseries - throw(ArgumentError("All parameters must belong to the same timeseries")) - else - indexer_type = IndexerNotTimeseries - timeseries_idx = MixedTimeseriesIndexes(indexer_timeseries_index.(getters)) + timeseries_idx = if indexer_type == IndexerNotTimeseries + nothing + else + timeseries_idxs = Set(Iterators.flatten(indexer_timeseries_index(g) + for g in getters if is_indexer_timeseries(g) != IndexerNotTimeseries())) + if length(timeseries_idxs) > 1 + indexer_type = IndexerMixedTimeseries getters = as_not_timeseries_indexer.(getters) + collect(timeseries_idxs) + else + only(timeseries_idxs) end end @@ -364,26 +385,37 @@ function MultipleParametersGetter(getters) getters, timeseries_idx) end -const AtLeastTimeseriesMPG = Union{ - MultipleParametersGetter{IndexerTimeseries}, MultipleParametersGetter{IndexerBoth}} -const MixedTimeseriesIndexMPG = MultipleParametersGetter{ - IndexerNotTimeseries, G, MixedTimeseriesIndexes} where {G} +const OnlyTimeseriesMPG = MultipleParametersGetter{IndexerOnlyTimeseries} +const BothMPG = MultipleParametersGetter{IndexerBoth} +const NonTimeseriesMPG = MultipleParametersGetter{IndexerNotTimeseries} +const MixedTimeseriesMPG = MultipleParametersGetter{IndexerMixedTimeseries} +const AtLeastTimeseriesMPG = Union{OnlyTimeseriesMPG, BothMPG} is_indexer_timeseries(::Type{<:MultipleParametersGetter{T}}) where {T} = T() function indexer_timeseries_index(mpg::MultipleParametersGetter) mpg.timeseries_idx end +function as_timeseries_indexer(::IndexerBoth, mpg::MultipleParametersGetter) + MultipleParametersGetter(as_timeseries_indexer.(mpg.getters)) +end function as_not_timeseries_indexer(::IndexerBoth, mpg::MultipleParametersGetter) MultipleParametersGetter(as_not_timeseries_indexer.(mpg.getters)) end +function as_not_timeseries_indexer(::IndexerMixedTimeseries, mpg::MultipleParametersGetter) + return mpg +end -function as_timeseries_indexer(::IndexerBoth, mpg::MultipleParametersGetter) - MultipleParametersGetter(as_timeseries_indexer.(mpg.getters)) +function (mpg::MixedTimeseriesMPG)(::Timeseries, prob, args...) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(mpg))) +end +function (mpg::MixedTimeseriesMPG)(::AbstractArray, ::Timeseries, prob, args...) + throw(MixedParameterTimeseriesIndexError(prob, indexer_timeseries_index(mpg))) end for (indexerTimeseriesType, timeseriesType) in [ (IndexerNotTimeseries, IsTimeseriesTrait), - (IndexerBoth, NotTimeseries) + (IndexerBoth, NotTimeseries), + (IndexerMixedTimeseries, NotTimeseries) ] @eval function (mpg::MultipleParametersGetter{$indexerTimeseriesType})( ::$timeseriesType, prob) @@ -398,17 +430,16 @@ for (indexerTimeseriesType, timeseriesType) in [ end end -function (mpg::MixedTimeseriesIndexMPG)(::Timeseries, prob, args...) - throw(MixedParameterTimeseriesIndexError(prob, mpg.timeseries_idx.indexes)) -end - -function (mpg::MultipleParametersGetter{IndexerNotTimeseries})(::Timeseries, prob, args) - throw(ParameterTimeseriesValueIndexMismatchError{Timeseries}(prob, mpg, args)) +function (mpg::NonTimeseriesMPG)(::IsTimeseriesTrait, prob, arg) + return _call.(mpg.getters, (prob,), (arg,)) end -function (mpg::MultipleParametersGetter{IndexerNotTimeseries})( - ::AbstractArray, ::Timeseries, prob, args) - throw(ParameterTimeseriesValueIndexMismatchError{Timeseries}(prob, mpg, args)) +function (mpg::NonTimeseriesMPG)(buffer::AbstractArray, ::IsTimeseriesTrait, prob, arg) + for (buf_idx, getter) in zip(eachindex(buffer), mpg.getters) + buffer[buf_idx] = getter(prob) + end + return buffer end + function (mpg::AtLeastTimeseriesMPG)(ts::Timeseries, prob) map(eachindex(parameter_timeseries(prob, indexer_timeseries_index(mpg)))) do i mpg(ts, prob, i) @@ -457,10 +488,10 @@ function (mpg::AtLeastTimeseriesMPG)(buffer::AbstractArray, ts::Timeseries, prob end return buffer end -function (mpg::MultipleParametersGetter{IndexerTimeseries})(::NotTimeseries, prob) +function (mpg::OnlyTimeseriesMPG)(::NotTimeseries, prob) throw(ParameterTimeseriesValueIndexMismatchError{NotTimeseries}(prob, mpg)) end -function (mpg::MultipleParametersGetter{IndexerTimeseries})( +function (mpg::OnlyTimeseriesMPG)( ::AbstractArray, ::NotTimeseries, prob) throw(ParameterTimeseriesValueIndexMismatchError{NotTimeseries}(prob, mpg)) end @@ -490,6 +521,10 @@ wrap_tuple(::AsParameterTupleWrapper{N}, val) where {N} = ntuple(i -> val[i], Va function (atw::AsParameterTupleWrapper)(ts::IsTimeseriesTrait, prob, args...) atw(ts, is_indexer_timeseries(atw), prob, args...) end +function (atw::AsParameterTupleWrapper)( + ts::IsTimeseriesTrait, ::IndexerMixedTimeseries, prob, args...) + wrap_tuple(atw, atw.getter(ts, prob, args...)) +end function (atw::AsParameterTupleWrapper)(ts::Timeseries, ::AtLeastTimeseriesIndexer, prob) wrap_tuple.((atw,), atw.getter(ts, prob)) end @@ -519,6 +554,16 @@ is_observed_getter(::GetParameterObserved) = true is_observed_getter(::GetParameterObservedNoTime) = true is_observed_getter(mpg::MultipleParametersGetter) = any(is_observed_getter, mpg.getters) +function _supports_parameter_observed(indp, sym) + if hasmethod(parameter_observed, Tuple{typeof(indp), typeof(sym)}) + return true + elseif hasmethod(symbolic_container, Tuple{typeof(indp)}) + return _supports_parameter_observed(symbolic_container(indp), sym) + else + return false + end +end + for (t1, t2) in [ (ArraySymbolic, Any), (ScalarSymbolic, Any), @@ -531,7 +576,7 @@ for (t1, t2) in [ getters = getp.((sys,), p) num_observed = count(is_observed_getter, getters) - if num_observed == 0 + if num_observed == 0 || !_supports_parameter_observed(sys, p) return MultipleParametersGetter(getters) else pofn = parameter_observed(sys, p isa Tuple ? collect(p) : p) diff --git a/src/state_indexing.jl b/src/state_indexing.jl index b17e417e..41217168 100644 --- a/src/state_indexing.jl +++ b/src/state_indexing.jl @@ -51,58 +51,6 @@ function _getu(sys, ::NotSymbolic, ::NotSymbolic, sym) return GetStateIndex(sym) end -struct GetpAtStateTime{G} <: AbstractStateGetIndexer - getter::G -end - -function (g::GetpAtStateTime)(ts::Timeseries, prob) - g(ts, is_parameter_timeseries(prob), prob) -end -function (g::GetpAtStateTime)(ts::Timeseries, prob, i) - g(ts, is_parameter_timeseries(prob), prob, i) -end -function (g::GetpAtStateTime)(::Timeseries, ::NotTimeseries, prob, _...) - g.getter(prob) -end -function (g::GetpAtStateTime)(ts::Timeseries, p_ts::Timeseries, prob) - g(ts, p_ts, is_indexer_timeseries(g.getter), prob) -end -function (g::GetpAtStateTime)( - ::Timeseries, ::Timeseries, ::Union{IndexerTimeseries, IndexerBoth}, prob) - g.getter.((prob,), - parameter_timeseries_at_state_time(prob, indexer_timeseries_index(g.getter))) -end -function (g::GetpAtStateTime)(::Timeseries, ::Timeseries, ::IndexerNotTimeseries, prob) - g.getter(prob) -end -function (g::GetpAtStateTime)(ts::Timeseries, p_ts::Timeseries, prob, i) - g(ts, p_ts, is_indexer_timeseries(g.getter), prob, i) -end -function (g::GetpAtStateTime)( - ::Timeseries, ::Timeseries, ::Union{IndexerTimeseries, IndexerBoth}, prob, i) - g.getter(prob, - parameter_timeseries_at_state_time(prob, indexer_timeseries_index(g.getter), i)) -end -function (g::GetpAtStateTime)(::Timeseries, ::Timeseries, ::IndexerNotTimeseries, - prob, ::Union{Int, CartesianIndex}) - g.getter(prob) -end -function (g::GetpAtStateTime)( - ::Timeseries, ::Timeseries, ::IndexerNotTimeseries, prob, ::Colon) - map(_ -> g.getter(prob), current_time(prob)) -end -function (g::GetpAtStateTime)( - ::Timeseries, ::Timeseries, ::IndexerNotTimeseries, prob, i::AbstractArray{Bool}) - num_ones = sum(i) - map(_ -> g.getter(prob), 1:num_ones) -end -function (g::GetpAtStateTime)(::Timeseries, ::Timeseries, ::IndexerNotTimeseries, prob, i) - map(_ -> g.getter(prob), 1:length(i)) -end -function (g::GetpAtStateTime)(::NotTimeseries, prob) - g.getter(prob) -end - struct GetIndepvar <: AbstractStateGetIndexer end (::GetIndepvar)(::IsTimeseriesTrait, prob) = current_time(prob) @@ -168,15 +116,23 @@ function _getu(sys, ::ScalarSymbolic, ::SymbolicTypeTrait, sym) idx = variable_index(sys, sym) return getu(sys, idx) elseif is_parameter(sys, sym) - return GetpAtStateTime(getp(sys, sym)) + return getp(sys, sym) elseif is_independent_variable(sys, sym) return GetIndepvar() elseif is_observed(sys, sym) - fn = observed(sys, sym) - if is_time_dependent(sys) - return TimeDependentObservedFunction(fn) + ts_idxs = get_all_timeseries_indexes(sys, sym) + if ContinuousTimeseries() in ts_idxs + if length(ts_idxs) != 1 + throw(MixedContinuousParameterTimeseriesError(sym)) + end + fn = observed(sys, sym) + if is_time_dependent(sys) + return TimeDependentObservedFunction(fn) + else + return TimeIndependentObservedFunction(fn) + end else - return TimeIndependentObservedFunction(fn) + return getp(sys, sym) end end error("Invalid symbol $sym for `getu`") @@ -235,10 +191,15 @@ for (t1, t2) in [ ] @eval function _getu(sys, ::NotSymbolic, ::$t1, sym::$t2) num_observed = count(x -> is_observed(sys, x), sym) + num_params = count(x -> is_parameter(sys, x), sym) + num_timeseries_params = count( + x -> is_parameter(sys, x) && is_timeseries_parameter(sys, x), sym) + if num_timeseries_params > 0 && num_params < length(sym) # we have timeseries params and continuous variables + throw(MixedContinuousParameterTimeseriesError(sym)) + end if num_observed == 0 || num_observed == 1 && sym isa Tuple - if !isempty(sym) && all(Base.Fix1(is_parameter, sys), sym) && - all(!Base.Fix1(is_timeseries_parameter, sys), sym) - GetpAtStateTime(getp(sys, sym)) + if !isempty(sym) && num_timeseries_params > 0 + getp(sys, sym) else getters = getu.((sys,), sym) return MultipleGetters(getters) @@ -263,7 +224,7 @@ function _getu(sys, ::ArraySymbolic, ::SymbolicTypeTrait, sym) idx = variable_index(sys, sym) return getu(sys, idx) elseif is_parameter(sys, sym) - return GetpAtStateTime(getp(sys, sym)) + return getp(sys, sym) end return getu(sys, collect(sym)) end diff --git a/src/symbol_cache.jl b/src/symbol_cache.jl index 76262b52..4d3d8a2a 100644 --- a/src/symbol_cache.jl +++ b/src/symbol_cache.jl @@ -101,6 +101,24 @@ function timeseries_parameter_index(sc::SymbolCache, sym) sc.timeseries_parameters === nothing ? nothing : get(sc.timeseries_parameters, sym, nothing) end +function get_all_timeseries_indexes(sc::SymbolCache, sym::Symbol) + if is_variable(sc, sym) + return Set([ContinuousTimeseries()]) + elseif is_parameter(sc, sym) && is_timeseries_parameter(sc, sym) + return Set([sc.timeseries_parameters[sym].timeseries_idx]) + else + return Set() + end +end +function get_all_timeseries_indexes(sc::SymbolCache, sym::Expr) + exs = ExpressionSearcher() + exs(sc, sym) + return mapreduce( + Base.Fix1(get_all_timeseries_indexes, sc), union, exs.declared; init = Set()) +end +function get_all_timeseries_indexes(sc::SymbolCache, sym::AbstractArray) + return mapreduce(Base.Fix1(get_all_timeseries_indexes, sc), union, sym) +end function is_independent_variable(sc::SymbolCache, sym) sc.independent_variables === nothing && return false if symbolic_type(sc.independent_variables) == NotSymbolic() @@ -235,10 +253,12 @@ function parameter_observed(sc::SymbolCache, expr::Expr) f = let fn = observed(sc, expr) f1(p, t) = fn(nothing, p, t) end - if length(ts_idxs) == 1 + if isempty(ts_idxs) + return ParameterObservedFunction(f) + elseif length(ts_idxs) == 1 return ParameterObservedFunction(only(ts_idxs), f) else - return ParameterObservedFunction(nothing, f) + return ParameterObservedFunction(collect(ts_idxs), f) end else f = let fn = observed(sc, expr) @@ -267,10 +287,12 @@ function parameter_observed(sc::SymbolCache, exprs::Union{AbstractArray, Tuple}) f1(p, t) = oop(nothing, p, t) f1(buffer, p, t) = iip(buffer, nothing, p, t) end - if length(ts_idxs) == 1 + if isempty(ts_idxs) + return ParameterObservedFunction(f) + elseif length(ts_idxs) == 1 return ParameterObservedFunction(only(ts_idxs), f) else - return ParameterObservedFunction(nothing, f) + return ParameterObservedFunction(collect(ts_idxs), f) end else f = let oop = observed(sc, to_expr(exprs)), iip = inplace_observed(sc, exprs) diff --git a/src/value_provider_interface.jl b/src/value_provider_interface.jl index 9a9cb0e4..69394ca9 100644 --- a/src/value_provider_interface.jl +++ b/src/value_provider_interface.jl @@ -159,34 +159,31 @@ function (ai::AbstractParameterGetIndexer)(buffer::AbstractArray, prob, i) ai(buffer, is_parameter_timeseries(prob), prob, i) end -abstract type IsIndexerTimeseries end +abstract type IndexerTimeseriesType end -struct IndexerTimeseries <: IsIndexerTimeseries end -struct IndexerNotTimeseries <: IsIndexerTimeseries end -struct IndexerBoth <: IsIndexerTimeseries end - -const AtLeastTimeseriesIndexer = Union{IndexerTimeseries, IndexerBoth} -const AtLeastNotTimeseriesIndexer = Union{IndexerNotTimeseries, IndexerBoth} +# Can only index parameter timeseries +struct IndexerOnlyTimeseries <: IndexerTimeseriesType end +# Can only index non-timeseries objects +struct IndexerMixedTimeseries <: IndexerTimeseriesType end +# Can index timeseres and non-timeseries objects, has the same value at all times +struct IndexerNotTimeseries <: IndexerTimeseriesType end +# Value changes over time, can index timeseries and non-timeseries objects +struct IndexerBoth <: IndexerTimeseriesType end is_indexer_timeseries(x) = is_indexer_timeseries(typeof(x)) function indexer_timeseries_index end +const AtLeastTimeseriesIndexer = Union{IndexerOnlyTimeseries, IndexerBoth} +const AtLeastNotTimeseriesIndexer = Union{IndexerNotTimeseries, IndexerBoth} + +as_timeseries_indexer(x) = as_timeseries_indexer(is_indexer_timeseries(x), x) +as_timeseries_indexer(::IndexerOnlyTimeseries, x) = x +as_timeseries_indexer(::IndexerNotTimeseries, x) = x as_not_timeseries_indexer(x) = as_not_timeseries_indexer(is_indexer_timeseries(x), x) as_not_timeseries_indexer(::IndexerNotTimeseries, x) = x -function as_not_timeseries_indexer(::IndexerTimeseries, x) - error(""" - Tried to convert an `$IndexerTimeseries` to an `$IndexerNotTimeseries`. This \ - should never happen. Please file an issue with an MWE. - """) -end -as_timeseries_indexer(x) = as_timeseries_indexer(is_indexer_timeseries(x), x) -as_timeseries_indexer(::IndexerTimeseries, x) = x -function as_timeseries_indexer(::IndexerNotTimeseries, x) - error(""" - Tried to convert an `$IndexerNotTimeseries` to an `$IndexerTimeseries`. This \ - should never happen. Please file an issue with an MWE. - """) +struct MultipleTimeseriesIndexes{I} + idxs::Vector{I} end struct CallWith{A} @@ -219,12 +216,6 @@ struct ParameterTimeseriesValueIndexMismatchError{P <: IsTimeseriesTrait} <: Exc got $(valp). Open an issue in SymbolicIndexingInterface.jl with an MWE. """)) end - if is_indexer_timeseries(indexer) != IndexerNotTimeseries() - throw(ArgumentError(""" - This should never happen. Expected non-timeseries indexer, got \ - $(indexer). Open an issue in SymbolicIndexingInterface.jl with an MWE. - """)) - end return new{Timeseries}(valp, indexer, args) end function ParameterTimeseriesValueIndexMismatchError{NotTimeseries}(valp, indexer) @@ -235,7 +226,7 @@ struct ParameterTimeseriesValueIndexMismatchError{P <: IsTimeseriesTrait} <: Exc with an MWE. """)) end - if is_indexer_timeseries(indexer) != IndexerTimeseries() + if is_indexer_timeseries(indexer) != IndexerOnlyTimeseries() throw(ArgumentError(""" This should never happen. Expected timeseries indexer, got $(indexer). \ Open an issue in SymbolicIndexingInterface.jl with an MWE. @@ -263,14 +254,25 @@ function Base.showerror( end struct MixedParameterTimeseriesIndexError <: Exception - valp::Any + obj::Any ts_idxs::Any end function Base.showerror(io::IO, err::MixedParameterTimeseriesIndexError) print(io, """ - Invalid indexing operation: tried to access object of type $(typeof(err.valp)) \ + Invalid indexing operation: tried to access object of type $(typeof(err.obj)) \ (which is a parameter timeseries object) with variables having mixed timeseries \ indexes $(err.ts_idxs). """) end + +struct MixedContinuousParameterTimeseriesError <: Exception + syms::Any +end + +function Base.showerror(io::IO, err::MixedContinuousParameterTimeseriesError) + print(io, """ + Invalid indexing operation: tried to create indexer for mixture of continuous \ + and discrete variables $(err.syms). + """) +end diff --git a/test/parameter_indexing_test.jl b/test/parameter_indexing_test.jl index 47a75f5d..4b2fa61a 100644 --- a/test/parameter_indexing_test.jl +++ b/test/parameter_indexing_test.jl @@ -1,5 +1,6 @@ using SymbolicIndexingInterface -using SymbolicIndexingInterface: IndexerTimeseries, IndexerNotTimeseries, IndexerBoth, +using SymbolicIndexingInterface: IndexerOnlyTimeseries, IndexerNotTimeseries, IndexerBoth, + IndexerMixedTimeseries, is_indexer_timeseries, indexer_timeseries_index, ParameterTimeseriesValueIndexMismatchError, MixedParameterTimeseriesIndexError @@ -105,21 +106,27 @@ for sys in [ fi.counter[] = 0 end - for (sym, val) in [ - ([:a, :b, :c, :d], p), - ([:c, :a], p[[3, 1]]), - ((:b, :a), Tuple(p[[2, 1]])), - ((1, :c), Tuple(p[[1, 3]])), - (:(a + b + t), p[1] + p[2] + fi.t), - ([:(a + b + t), :c], [p[1] + p[2] + fi.t, p[3]]), - ((:(a + b + t), :c), (p[1] + p[2] + fi.t, p[3])) + for (sym, val, check_inference) in [ + ([:a, :b, :c, :d], p, true), + ([:c, :a], p[[3, 1]], !has_ts), + ((:b, :a), Tuple(p[[2, 1]]), true), + ((1, :c), Tuple(p[[1, 3]]), true), + (:(a + b + t), p[1] + p[2] + fi.t, true), + ([:(a + b + t), :c], [p[1] + p[2] + fi.t, p[3]], true), + ((:(a + b + t), :c), (p[1] + p[2] + fi.t, p[3]), true) ] get = getp(sys, sym) - @inferred get(fi) + if check_inference + @inferred get(fi) + end @test get(fi) == val if sym isa Union{Array, Tuple} buffer = zeros(length(sym)) - @inferred get(buffer, fi) + if check_inference + @inferred get(buffer, fi) + else + get(buffer, fi) + end @test buffer == collect(val) end end @@ -206,136 +213,199 @@ dval = fs.p[4] bidx = timeseries_parameter_index(sys, :b) cidx = timeseries_parameter_index(sys, :c) -for (sym, indexer_trait, timeseries_index, val, buffer, check_inference) in [ - (:a, IndexerNotTimeseries, 0, aval, nothing, true), - (1, IndexerNotTimeseries, 0, aval, nothing, true), - ([:a, :d], IndexerNotTimeseries, 0, [aval, dval], zeros(2), true), - ((:a, :d), IndexerNotTimeseries, 0, (aval, dval), zeros(2), true), - ([1, 4], IndexerNotTimeseries, 0, [aval, dval], zeros(2), true), - ((1, 4), IndexerNotTimeseries, 0, (aval, dval), zeros(2), true), - ([:a, 4], IndexerNotTimeseries, 0, [aval, dval], zeros(2), true), - ((:a, 4), IndexerNotTimeseries, 0, (aval, dval), zeros(2), true), - (:b, IndexerBoth, 1, bval, zeros(length(bval)), true), - (bidx, IndexerTimeseries, 1, bval, zeros(length(bval)), true), - ([:a, :b], IndexerNotTimeseries, 0, [aval, bval[end]], zeros(2), true), - ((:a, :b), IndexerNotTimeseries, 0, (aval, bval[end]), zeros(2), true), - ([1, :b], IndexerNotTimeseries, 0, [aval, bval[end]], zeros(2), true), - ((1, :b), IndexerNotTimeseries, 0, (aval, bval[end]), zeros(2), true), - ([:b, :b], IndexerBoth, 1, vcat.(bval, bval), map(_ -> zeros(2), bval), true), - ((:b, :b), IndexerBoth, 1, tuple.(bval, bval), map(_ -> zeros(2), bval), true), - ([bidx, :b], IndexerTimeseries, 1, vcat.(bval, bval), map(_ -> zeros(2), bval), true), - ((bidx, :b), IndexerTimeseries, 1, tuple.(bval, bval), map(_ -> zeros(2), bval), true), - ([bidx, bidx], IndexerTimeseries, 1, vcat.(bval, bval), map(_ -> zeros(2), bval), true), - ((bidx, bidx), IndexerTimeseries, 1, - tuple.(bval, bval), map(_ -> zeros(2), bval), true), - (:(a + b), IndexerBoth, 1, bval .+ aval, zeros(length(bval)), true), - ([:(a + b), :a], IndexerBoth, 1, vcat.(bval .+ aval, aval), - map(_ -> zeros(2), bval), true), - ((:(a + b), :a), IndexerBoth, 1, tuple.(bval .+ aval, aval), - map(_ -> zeros(2), bval), true), - ([:(a + b), :b], IndexerBoth, 1, vcat.(bval .+ aval, bval), - map(_ -> zeros(2), bval), true), - ((:(a + b), :b), IndexerBoth, 1, tuple.(bval .+ aval, bval), - map(_ -> zeros(2), bval), true), - ([:(a + b), :c], IndexerNotTimeseries, 0, - [aval + bval[end], cval[end]], zeros(2), true), - ((:(a + b), :c), IndexerNotTimeseries, 0, - (aval + bval[end], cval[end]), zeros(2), true) +# IndexerNotTimeseries +for (sym, val, buffer, check_inference) in [ + (:a, aval, nothing, true), + (1, aval, nothing, true), + ([:a, :d], [aval, dval], zeros(2), true), + ((:a, :d), (aval, dval), zeros(2), true), + ([1, 4], [aval, dval], zeros(2), true), + ((1, 4), (aval, dval), zeros(2), true), + ([:a, 4], [aval, dval], zeros(2), true), + ((:a, 4), (aval, dval), zeros(2), true) ] getter = getp(sys, sym) - @test is_indexer_timeseries(getter) isa indexer_trait - if indexer_trait <: Union{IndexerTimeseries, IndexerBoth} - @test indexer_timeseries_index(getter) == timeseries_index - end + @test is_indexer_timeseries(getter) isa IndexerNotTimeseries test_inplace = buffer !== nothing - test_non_timeseries = indexer_trait !== IndexerTimeseries - if test_inplace && test_non_timeseries - non_timeseries_val = indexer_trait == IndexerNotTimeseries ? val : val[end] - non_timeseries_buffer = indexer_trait == IndexerNotTimeseries ? deepcopy(buffer) : - deepcopy(buffer[end]) - test_non_timeseries_inplace = non_timeseries_buffer isa AbstractArray - end - isobs = sym isa Union{AbstractArray, Tuple} ? any(Base.Fix1(is_observed, sys), sym) : - is_observed(sys, sym) + if check_inference @inferred getter(fs) + @inferred getter(parameter_values(fs)) if test_inplace @inferred getter(deepcopy(buffer), fs) + @inferred getter(deepcopy(buffer), parameter_values(fs)) end - if test_non_timeseries && !isobs + end + @test getter(fs) == val + @test getter(parameter_values(fs)) == val + if test_inplace + target = collect(val) + for obj in (fs, parameter_values(fs)) + tmp = deepcopy(buffer) + getter(tmp, obj) + @test tmp == target + end + end + for subidx in [1, CartesianIndex(1), :, rand(Bool, 4), rand(1:4, 3), 1:2] + @test getter(fs, subidx) == val + if test_inplace + tmp = deepcopy(buffer) + getter(tmp, fs, subidx) + @test tmp == collect(val) + end + end +end + +# IndexerBoth +for (sym, timeseries_index, val, buffer, check_inference) in [ + (:b, 1, bval, zeros(length(bval)), true), + ([:a, :b], 1, vcat.(aval, bval), map(_ -> zeros(2), bval), false), + ((:a, :b), 1, tuple.(aval, bval), map(_ -> zeros(2), bval), true), + ([1, :b], 1, vcat.(aval, bval), map(_ -> zeros(2), bval), false), + ((1, :b), 1, tuple.(aval, bval), map(_ -> zeros(2), bval), true), + ([:b, :b], 1, vcat.(bval, bval), map(_ -> zeros(2), bval), true), + ((:b, :b), 1, tuple.(bval, bval), map(_ -> zeros(2), bval), true), + (:(a + b), 1, bval .+ aval, zeros(length(bval)), true), + ([:(a + b), :a], 1, vcat.(bval .+ aval, aval), map(_ -> zeros(2), bval), true), + ((:(a + b), :a), 1, tuple.(bval .+ aval, aval), map(_ -> zeros(2), bval), true), + ([:(a + b), :b], 1, vcat.(bval .+ aval, bval), map(_ -> zeros(2), bval), true), + ((:(a + b), :b), 1, tuple.(bval .+ aval, bval), map(_ -> zeros(2), bval), true) +] + getter = getp(sys, sym) + @test is_indexer_timeseries(getter) isa IndexerBoth + @test indexer_timeseries_index(getter) == timeseries_index + isobs = sym isa Union{AbstractArray, Tuple} ? any(Base.Fix1(is_observed, sys), sym) : + is_observed(sys, sym) + + if check_inference + @inferred getter(fs) + @inferred getter(deepcopy(buffer), fs) + if !isobs @inferred getter(parameter_values(fs)) - if test_inplace && test_non_timeseries_inplace && test_non_timeseries_inplace - @inferred getter(deepcopy(non_timeseries_buffer), parameter_values(fs)) + if !(eltype(val) <: Number) + @inferred getter(deepcopy(buffer[1]), parameter_values(fs)) end end end + @test getter(fs) == val - if test_inplace - tmp = deepcopy(buffer) - getter(tmp, fs) - if val isa Tuple - target = collect(val) - elseif eltype(val) <: Tuple - target = collect.(val) - else - target = val + if eltype(val) <: Number + target = val + else + target = collect.(val) + end + tmp = deepcopy(buffer) + getter(tmp, fs) + @test tmp == target + + if !isobs + @test getter(parameter_values(fs)) == val[end] + if !(eltype(val) <: Number) + target = collect(val[end]) + tmp = deepcopy(buffer)[end] + getter(tmp, parameter_values(fs)) + @test tmp == target end - @test tmp == target end - if test_non_timeseries && !isobs - non_timeseries_val = indexer_trait == IndexerNotTimeseries ? val : val[end] - @test getter(parameter_values(fs)) == non_timeseries_val - if test_inplace && test_non_timeseries && test_non_timeseries_inplace - getter(non_timeseries_buffer, parameter_values(fs)) - if non_timeseries_val isa Tuple - target = collect(non_timeseries_val) - else - target = non_timeseries_val + for subidx in [ + 1, CartesianIndex(1), :, rand(Bool, length(val)), rand(eachindex(val), 3), 1:2] + if check_inference + @inferred getter(fs, subidx) + if !isa(val[subidx], Number) + @inferred getter(deepcopy(buffer[subidx]), fs, subidx) end - @test non_timeseries_buffer == target end - elseif !isobs - @test_throws ParameterTimeseriesValueIndexMismatchError{NotTimeseries} getter(parameter_values(fs)) - if test_inplace - @test_throws ParameterTimeseriesValueIndexMismatchError{NotTimeseries} getter( - [], parameter_values(fs)) + @test getter(fs, subidx) == val[subidx] + tmp = deepcopy(buffer[subidx]) + if val[subidx] isa Number + continue end + target = val[subidx] + if eltype(target) <: Number + target = collect(target) + else + target = collect.(target) + end + getter(tmp, fs, subidx) + @test tmp == target end +end + +# IndexerOnlyTimeseries +for (sym, timeseries_index, val, buffer, check_inference) in [ + (bidx, 1, bval, zeros(length(bval)), true), + ([bidx, :b], 1, vcat.(bval, bval), map(_ -> zeros(2), bval), true), + ((bidx, :b), 1, tuple.(bval, bval), map(_ -> zeros(2), bval), true), + ([bidx, bidx], 1, vcat.(bval, bval), map(_ -> zeros(2), bval), true), + ((bidx, bidx), 1, tuple.(bval, bval), map(_ -> zeros(2), bval), true) +] + getter = getp(sys, sym) + @test is_indexer_timeseries(getter) isa IndexerOnlyTimeseries + @test indexer_timeseries_index(getter) == timeseries_index + + isscalar = eltype(val) <: Number + + if check_inference + @inferred getter(fs) + @inferred getter(deepcopy(buffer), fs) + end + + @test getter(fs) == val + target = if isscalar + val + else + collect.(val) + end + tmp = deepcopy(buffer) + getter(tmp, fs) + @test tmp == target + + @test_throws ParameterTimeseriesValueIndexMismatchError{NotTimeseries} getter(parameter_values(fs)) + @test_throws ParameterTimeseriesValueIndexMismatchError{NotTimeseries} getter( + [], parameter_values(fs)) + for subidx in [ 1, CartesianIndex(1), :, rand(Bool, length(val)), rand(eachindex(val), 3), 1:2] - if indexer_trait <: IndexerNotTimeseries - @test_throws ParameterTimeseriesValueIndexMismatchError{Timeseries} getter( - fs, subidx) - if test_inplace - @test_throws ParameterTimeseriesValueIndexMismatchError{Timeseries} getter( - [], fs, subidx) + if check_inference + @inferred getter(fs, subidx) + if !isa(val[subidx], Number) + @inferred getter(deepcopy(buffer[subidx]), fs, subidx) end + end + @test getter(fs, subidx) == val[subidx] + if val[subidx] isa Number + continue + end + tmp = deepcopy(buffer[subidx]) + target = val[subidx] + if eltype(target) <: Number + target = collect(target) else - if check_inference - @inferred getter(fs, subidx) - if test_inplace && buffer[subidx] isa AbstractArray - @inferred getter(deepcopy(buffer[subidx]), fs, subidx) - end - end - @test getter(fs, subidx) == val[subidx] - if test_inplace && buffer[subidx] isa AbstractArray - tmp = deepcopy(buffer[subidx]) - getter(tmp, fs, subidx) - if val[subidx] isa Tuple - target = collect(val[subidx]) - elseif eltype(val) <: Tuple - target = collect.(val[subidx]) - else - target = val[subidx] - end - @test tmp == target - end + target = collect.(target) end + getter(tmp, fs, subidx) + @test tmp == target end end -for sym in [[:a, bidx], (:a, bidx), [1, bidx], (1, bidx), - [bidx, :c], (bidx, :c), [bidx, cidx], (bidx, cidx)] +# IndexerMixedTimeseries +for sym in [ + [:a, :b, :c], + (:a, :b, :c), + :(b + c), + [:(a + b), :c], + (:(a + b), :c) +] + getter = getp(sys, sym) + @test_throws MixedParameterTimeseriesIndexError getter(fs) + @test_throws MixedParameterTimeseriesIndexError getter([], fs) + for subidx in [1, CartesianIndex(1), :, rand(Bool, 4), rand(1:4, 3), 1:2] + @test_throws MixedParameterTimeseriesIndexError getter(fs, subidx) + @test_throws MixedParameterTimeseriesIndexError getter([], fs, subidx) + end +end + +for sym in [[:a, bidx], (:a, bidx), [1, bidx], (1, bidx)] @test_throws ArgumentError getp(sys, sym) end @@ -344,35 +414,30 @@ for (sym, val) in [ ((:b, :c), (bval[end], cval[end])) ] getter = getp(sys, sym) - @test is_indexer_timeseries(getter) == IndexerNotTimeseries() + @test is_indexer_timeseries(getter) == IndexerMixedTimeseries() @test_throws MixedParameterTimeseriesIndexError getter(fs) @test getter(parameter_values(fs)) == val end -bval_state = [b_timeseries.u[searchsortedlast(b_timeseries.t, t)][] for t in fs.t] -cval_state = [c_timeseries.u[searchsortedlast(c_timeseries.t, t)][] for t in fs.t] -xval = getindex.(fs.u, 1) - for (sym, val_is_timeseries, val, check_inference) in [ (:a, false, aval, true), ([:a, :d], false, [aval, dval], true), ((:a, :d), false, (aval, dval), true), - (:b, true, bval_state, true), - ([:a, :b], true, vcat.(aval, bval_state), false), - ((:a, :b), true, tuple.(aval, bval_state), true), - ([:b, :c], true, vcat.(bval_state, cval_state), true), - ((:b, :c), true, tuple.(bval_state, cval_state), true), - ([:a, :b, :c], true, vcat.(aval, bval_state, cval_state), false), - ((:a, :b, :c), true, tuple.(aval, bval_state, cval_state), true), - ([:x, :b], true, vcat.(xval, bval_state), false), - ((:x, :b), true, tuple.(xval, bval_state), true), - ([:x, :b, :c], true, vcat.(xval, bval_state, cval_state), false), - ((:x, :b, :c), true, tuple.(xval, bval_state, cval_state), true), - ([:a, :b, :x], true, vcat.(aval, bval_state, xval), false), - ((:a, :b, :x), true, tuple.(aval, bval_state, xval), true), - (:(2b), true, 2 .* bval_state, true), - ([:x, :(2b), :(3c)], true, vcat.(xval, 2 .* bval_state, 3 .* cval_state), true), - ((:x, :(2b), :(3c)), true, tuple.(xval, 2 .* bval_state, 3 .* cval_state), true) + (:b, true, bval, true), + ([:a, :b], true, vcat.(aval, bval), false), + ((:a, :b), true, tuple.(aval, bval), true), + # ([:b, :c], true, vcat.(bval_state, cval_state), true), + # ((:b, :c), true, tuple.(bval_state, cval_state), true), + # ([:a, :b, :c], true, vcat.(aval, bval_state, cval_state), false), + # ((:a, :b, :c), true, tuple.(aval, bval_state, cval_state), true), + # ([:x, :b], true, vcat.(xval, bval_state), false), + # ((:x, :b), true, tuple.(xval, bval_state), true), + # ([:x, :b, :c], true, vcat.(xval, bval_state, cval_state), false), + # ((:x, :b, :c), true, tuple.(xval, bval_state, cval_state), true), + # ([:a, :b, :x], true, vcat.(aval, bval_state, xval), false), + # ((:a, :b, :x), true, tuple.(aval, bval_state, xval), true), + (:(2b), true, 2 .* bval, true), + ([:a, :(2b)], true, vcat.(aval, 2 .* bval), true) # ([:x, :(2b), :(3c)], true, vcat.(xval, 2 .* bval_state, 3 .* cval_state), true), # ((:x, :(2b), :(3c)), true, tuple.(xval, 2 .* bval_state, 3 .* cval_state), true) ] getter = getu(sys, sym) if check_inference @@ -381,15 +446,16 @@ for (sym, val_is_timeseries, val, check_inference) in [ @test getter(fs) == val for subidx in [ - 1, CartesianIndex(2), :, rand(Bool, length(fs.t)), rand(eachindex(fs.t), 3), 1:2] + 1, CartesianIndex(2), :, rand(Bool, length(bval)), rand(eachindex(bval), 3), 1:2] + @show sym subidx if check_inference @inferred getter(fs, subidx) end target = if val_is_timeseries val[subidx] else - if fs.t[subidx] isa AbstractArray - len = length(fs.t[subidx]) + if bval[subidx] isa AbstractArray + len = length(bval[subidx]) fill(val, len) else val @@ -399,6 +465,7 @@ for (sym, val_is_timeseries, val, check_inference) in [ end end +#= @test_throws ErrorException getp(sys, :not_a_param) let fs = fs, sys = sys @@ -444,3 +511,4 @@ for (sym, val, check_inference) in [ @test buffer == collect(val) end end +=#