diff --git a/docs/src/reference.md b/docs/src/reference.md index 3b9c1388d..59583a9c5 100644 --- a/docs/src/reference.md +++ b/docs/src/reference.md @@ -29,6 +29,8 @@ replace_comp! set_dimension! set_leftover_params! set_param! +TimestepIndex +TimestepValue variable_dimensions variable_names update_param! diff --git a/docs/src/userguide.md b/docs/src/userguide.md index 552f9ad42..ac6a73cb8 100644 --- a/docs/src/userguide.md +++ b/docs/src/userguide.md @@ -149,7 +149,7 @@ In order to invoke the explorer UI and explore all of the variables and paramete ```julia run(mymodel) - explore(mymodel, title = "run1 results") +explore(mymodel, title = "run1 results") ``` ![Explorer Model Example](figs/explorer_model_example.png) @@ -183,8 +183,8 @@ In the `run_timestep` functions which the user defines, it may be useful to use is_first(t) # returns true or false, true if t is the first timestep to be run is_last(t) # returns true or false, true if t is the last timestep to be run gettime(t) # returns the year represented by timestep t -is_time(t, s) # Return true or false, true if the current time (year) for t is y -is_timestep(t, y) # rReturn true or false, true if t timestep is step s. +is_time(t, y) # Return true or false, true if the current time (year) for t is y +is_timestep(t, s) # Return true or false, true if t timestep is step s. ``` The API details for AbstractTimestep object `t` are as follows: @@ -194,6 +194,46 @@ The API details for AbstractTimestep object `t` are as follows: - useful functions for commonly used conditionals are `is_first(t)`,`is_last(t)`, `is_time(t, s)`, and `is_timestep(t, y)` as listed above - to access the index value of `t` as a `Number` representing the position in the time array, use `t.t`. Users are encouraged to avoid this access, and instead use the options listed above or a separate counter variable. each time the function gets called. +Indexing into a variable or parameter's `time` dimension with an `Integer` is deprecated and will soon error. Instead, users should take advantage of the `TimestepIndex` and `TimestepValue` types. For examples we will refer back to our component definition above, and repeated below. +```julia +@defcomp MyComponentName begin + regions = Index() + + A = Variable(index = [time]) + B = Variable(index = [time, regions]) + + c = Parameter() + d = Parameter(index = [time]) + e = Parameter(index = [time, regions]) + f = Parameter(index = [regions]) + + function run_timestep(p, v, d, t) + v.A[t] = p.c + p.d[t] + for r in d.regions + v.B[t, r] = p.f[r] * p.e[t, r] + end + end + +end +``` +`TimestepIndex` has one field, `index`, which refers to the absolute index in the parameter or variable array's `time` dimension. Thus, constructing a `TimestepIndex` is done by simply writing `TimestepIndex(index::Int)`. Looking back at our original component example +one could modify the first line of `run_timestep` to always refer to the first timestep of `p.d` with the following. One may index into the `time` dimension with a single `TimestepIndex`, or an `Array` of them. +```julia +v.A[t] = p.c + p.d[TimestepIndex(1)] +``` +`TimestepValue` has two fields, `value` and `offset`, referring to the value within the `time` dimension and an optional `offset` from that `value`. Thus, constructing a `TimestepValue` is done either by writing `TimestepValue(value)`, with an implied offset of 0, or `TimestepValue(value, offset = i::Int)`, with an explicit offset of i. One may index into the `time` dimension with a single `TimestepValue`, or an `Array` of them. For example, you can use a `TimestepValue` to keep track of a baseline year. +```julia +v.A[t] = p.c + p.d[TimestepValue(2000)] +``` +You may also use shorthand to create arrays of `TimestepIndex` using Colon syntax. +```julia +TimestepIndex(1):TimestepIndex(10) # implicit step size of 1 +TimestepIndex(1):2:TimestepIndex(10) # explicit step of type Int +``` +Both `TimestepIndex` and `TimestepArray` have methods to support addition and subtraction of integers. Note that the addition or subtraction is relative to the definition of the `time` dimension, so while `TimestepIndex(1) + 1 == TimestepIndex(2)`, `TimestepValue(2000) + 1` could be equivalent to `TimestepValue(2001)` **if** 2001 is the next year in the time dimension, or `TimestepValue(2005)` if the array has a step size of 5. Hence adding or subtracting is relative to the definition of the `time` dimension. + + + ### Parameter connections between different length components As mentioned earlier, it is possible for some components to start later or end sooner than the full length of the model. This presents potential complications for connecting their parameters. If you are setting the parameters to external values, then the provided values just need to be the right size for that component's parameter. If you are making an internal connection, this can happen in one of two ways: diff --git a/src/Mimi.jl b/src/Mimi.jl index d60ef6251..55645a0a7 100644 --- a/src/Mimi.jl +++ b/src/Mimi.jl @@ -43,6 +43,8 @@ export set_dimension!, set_leftover_params!, set_param!, + TimestepIndex, + TimestepValue, update_param!, update_params!, # variables, diff --git a/src/core/time.jl b/src/core/time.jl index 7f9b32075..8086391c6 100644 --- a/src/core/time.jl +++ b/src/core/time.jl @@ -106,14 +106,21 @@ function Base.:-(ts::VariableTimestep{TIMES}, val::Int) where {TIMES} return VariableTimestep{TIMES}(ts.t - val) end +function Base.:-(ts::TimestepValue, val::Int) + return TimestepValue(ts.value; offset = ts.offset - val) +end + +function Base.:-(ts::TimestepIndex, val::Int) + return TimestepIndex(ts.index - val) +end + function Base.:+(ts::FixedTimestep{FIRST, STEP, LAST}, val::Int) where {FIRST, STEP, LAST} if finished(ts) error("Cannot get next timestep, this is last timestep.") elseif gettime(ts) + val > LAST + 1 error("Cannot get requested timestep, exceeds last timestep.") end - new_ts = FixedTimestep{FIRST, STEP, LAST}(ts.t + val) - + return FixedTimestep{FIRST, STEP, LAST}(ts.t + val) end function Base.:+(ts::VariableTimestep{TIMES}, val::Int) where {TIMES} @@ -125,6 +132,24 @@ function Base.:+(ts::VariableTimestep{TIMES}, val::Int) where {TIMES} new_ts = VariableTimestep{TIMES}(ts.t + val) end +function Base.:+(ts::TimestepValue, val::Int) + return TimestepValue(ts.value; offset = ts.offset + val) +end + +function Base.:+(ts::TimestepIndex, val::Int) + return TimestepIndex(ts.index + val) +end + +# Colon support +function Base.:(:)(start::T, step::Int, stop::T) where {T<:TimestepIndex} + indices = [start.index:step:stop.index...] + return TimestepIndex.(indices) +end + +function Base.:(:)(start::T, stop::T) where {T<:TimestepIndex} + return Base.:(:)(start, 1, stop) +end + # # CLOCK # diff --git a/src/core/time_arrays.jl b/src/core/time_arrays.jl index eb09425bf..9b91c10af 100644 --- a/src/core/time_arrays.jl +++ b/src/core/time_arrays.jl @@ -27,6 +27,7 @@ function get_time_index_position(obj::AbstractCompositeComponentDef, comp_name:: end const AnyIndex = Union{Int, Vector{Int}, Tuple, Colon, OrdinalRange} +const AnyIndex_NonColon = Union{Int, Vector{Int}, Tuple, OrdinalRange} # Helper function for getindex; throws a MissingException if data is missing, otherwise returns data function _missing_data_check(data, t) @@ -37,6 +38,36 @@ function _missing_data_check(data, t) end end +# Helper function for getindex; throws an error if the TimestepIndex index is out of range of the TimestepArray +function _index_bounds_check(data, dim, t) + if size(data, dim) < t + error("TimestepIndex index $t extends beyond bounds of TimestepArray dimension $dim") + end +end + +# Helper function for getindex; throws an error if you index into a N-dimensional TimestepArray with only one index +# if N > 1; Note that Julia does allow this and returns the column-major value, but this could produce unexpected +# behavior for users in this case so we do not allow it for now +function _single_index_check(data, idxs) + num_idxs = length(idxs) + num_dims = length(size(data)) + if num_idxs < num_dims + error("Not enough indices provided to index into TimestepArray, $num_idxs provided, $num_dims required") + end +end + +# Helper functionfor getindex; throws an error if one indexes into a TimestepArray with an integer +function _throw_int_getindex_depwarning() + msg = "Indexing with getindex into a TimestepArray with Integer(s) is deprecated, please index with a TimestepIndex(index::Int) instead ie. instead of t[2] use t[TimestepIndex(2)]" + Base.depwarn("$msg, $(stacktrace())", :getindex) +end + +# Helper function for setindex; throws an deprecation warning if one indexes into a TimestepArray with an integer +function _throw_int_setindex_depwarning() + msg = "Indexing with setindex into a TimestepArray with Integer(s) is deprecated, please index with a TimestepIndex(index::Int) instead ie. instead of t[2] use t[TimestepIndec(2)]" + Base.depwarn("$msg, $(stacktrace())", :setindex) +end + # Helper macro used by connector macro allow_missing(expr) let e = gensym("e") @@ -55,6 +86,30 @@ macro allow_missing(expr) end end + +# Helper functions for TimestepValue type +function _get_time_value_position(times::Union{Tuple, Array}, ts::TimestepValue{T}) where T + t = findfirst(isequal.(ts.value, times)) + if t === nothing + error("cannot use TimestepValue with value $(ts.value), value is not in the TimestepArray") + end + + t_offset = t + ts.offset + if t_offset > length(times) + error("cannot get TimestepValue offset of $(ts.offset) from value $(ts.value), offset is after the end of the TimestepArray") + end + return t_offset +end + +# Helper function to get the array of indices from an Array{TimestepIndex,1} +function _get_ts_indices(ts_array::Array{TimestepIndex, 1}) + return [ts.index for ts in ts_array] +end + +function _get_ts_indices(ts_array::Array{TimestepValue{T}, 1}, times::Union{Tuple, Array}) where T + return [_get_time_value_position(times, ts) for ts in ts_array] +end + # # b. TimestepVector # @@ -81,15 +136,24 @@ function Base.getindex(v::TimestepVector{VariableTimestep{D_TIMES}, T}, ts::Vari _missing_data_check(data, t) end -# int indexing version supports old-style components and internal functions, not -# part of the public API +function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T_data}, ts::TimestepValue{T_time}) where {T_data, FIRST, STEP, T_time} + LAST = FIRST + ((length(v.data)-1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + data = v.data[t] + _missing_data_check(data, t) +end -function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, i::AnyIndex) where {T, FIRST, STEP} - return v.data[i] +function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T_data}, ts::TimestepValue{T_time}) where {T_data, TIMES, T_time} + t = _get_time_value_position(TIMES, ts) + data = v.data[t] + _missing_data_check(data, t) end -function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, i::AnyIndex) where {T, TIMES} - return v.data[i] +function Base.getindex(v::TimestepVector, ts::TimestepIndex) + t = ts.index + _index_bounds_check(v.data, 1, t) + data = v.data[t] + _missing_data_check(data, t) end function Base.setindex!(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, val, ts::FixedTimestep{FIRST, STEP, LAST}) where {T, FIRST, STEP, LAST} @@ -110,14 +174,41 @@ function Base.setindex!(v::TimestepVector{VariableTimestep{D_TIMES}, T}, val, ts setindex!(v.data, val, t) end +function Base.setindex!(v::TimestepVector{FixedTimestep{FIRST, STEP}, T_data}, val, ts::TimestepValue{T_time}) where {T_data, FIRST, STEP, T_time} + LAST = FIRST + ((length(v.data)-1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + setindex!(v.data, val, t) +end + +function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T_data}, val, ts::TimestepValue{T_time}) where {T_data, TIMES, T_time} + t = _get_time_value_position(TIMES, ts) + setindex!(v.data, val, t) +end + +function Base.setindex!(v::TimestepVector, val, ts::TimestepIndex) + setindex!(v.data, val, ts.index) +end + # int indexing version supports old-style components and internal functions, not # part of the public API -function Base.setindex!(v::TimestepVector{FixedTimestep{Start, STEP}, T}, val, i::AnyIndex) where {T, Start, STEP} +function Base.getindex(v::TimestepVector{FixedTimestep{FIRST, STEP}, T}, i::AnyIndex_NonColon) where {T, FIRST, STEP} + _throw_int_getindex_depwarning() + return v.data[i] +end + +function Base.getindex(v::TimestepVector{VariableTimestep{TIMES}, T}, i::AnyIndex_NonColon) where {T, TIMES} + _throw_int_getindex_depwarning() + return v.data[i] +end + +function Base.setindex!(v::TimestepVector{FixedTimestep{Start, STEP}, T}, val, i::AnyIndex_NonColon) where {T, Start, STEP} + _throw_int_setindex_depwarning() setindex!(v.data, val, i) end -function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T}, val, i::AnyIndex) where {T, TIMES} +function Base.setindex!(v::TimestepVector{VariableTimestep{TIMES}, T}, val, i::AnyIndex_NonColon) where {T, TIMES} + _throw_int_setindex_depwarning() setindex!(v.data, val, i) end @@ -176,6 +267,45 @@ function Base.getindex(mat::TimestepMatrix{VariableTimestep{D_TIMES}, T, 2}, idx _missing_data_check(data, t) end +function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T_data, 1}, ts::TimestepValue{T_time}, idx::AnyIndex) where {T_data, FIRST, STEP, T_time} + LAST = FIRST + ((size(mat.data, 1) - 1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + data = mat.data[t, idx] + _missing_data_check(data, t) +end + +function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T_data, 1}, ts::TimestepValue{T_time}, idx::AnyIndex) where {T_data, TIMES, T_time} + t = _get_time_value_position(TIMES, ts) + data = mat.data[t, idx] + _missing_data_check(data, t) +end + +function Base.getindex(mat::TimestepMatrix, ts::TimestepIndex, idx::AnyIndex) + t = ts.index + _index_bounds_check(mat.data, 1, t) + data = mat.data[t, idx] + _missing_data_check(data, t) +end + +function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T_data, 2}, idx::AnyIndex, ts::TimestepValue{T_time}) where {T_data, FIRST, STEP, T_time} + LAST = FIRST + ((size(mat.data, 2) - 1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + data = mat.data[idx, t] + _missing_data_check(data, t) +end + +function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T_data, 2}, idx::AnyIndex, ts::TimestepValue{T_time}) where {T_data, TIMES, T_time} + t = _get_time_value_position(TIMES, ts) + data = mat.data[idx, t] + _missing_data_check(data, t) +end + +function Base.getindex(mat::TimestepMatrix, idx::AnyIndex, ts::TimestepIndex) + t = ts.index + _index_bounds_check(mat.data, 2, t) + data = mat.data[idx, t] + _missing_data_check(data, t) +end function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, 1}, val, ts::FixedTimestep{FIRST, STEP, LAST}, idx::AnyIndex) where {T, FIRST, STEP, LAST} setindex!(mat.data, val, ts.t, idx) @@ -213,30 +343,66 @@ function Base.setindex!(mat::TimestepMatrix{VariableTimestep{D_TIMES}, T, 2}, va setindex!(mat.data, val, idx, t) end +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T_data, 1}, val, ts::TimestepValue{T_time}, idx::AnyIndex) where {T_data, FIRST, STEP, T_time} + LAST = FIRST + ((size(mat.data, 1) - 1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + setindex!(mat.data, val, t, idx) +end + +function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T_data, 1}, val, ts::TimestepValue{T_time}, idx::AnyIndex) where {T_data, TIMES, T_time} + t = _get_time_value_position(TIMES, ts) + setindex!(mat.data, val, t, idx) +end + +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T_data, 2}, val, idx::AnyIndex, ts::TimestepValue{T_time}) where {T_data, FIRST, STEP, T_time} + LAST = FIRST + ((size(mat.data, 1) - 1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + setindex!(mat.data, val, idx, t) +end + +function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T_data, 2}, val, idx::AnyIndex, ts::TimestepValue{T_time}) where {T_data, TIMES, T_time} + t = _get_time_value_position(TIMES, ts) + setindex!(mat.data, val, idx, t) +end + +function Base.setindex!(mat::TimestepMatrix, val, idx::AnyIndex, ts::TimestepIndex) + setindex!(mat.data, val, idx, ts.index) +end + +function Base.setindex!(mat::TimestepMatrix, val, ts::TimestepIndex, idx::AnyIndex) + setindex!(mat.data, val, ts.index, idx) +end + # int indexing version supports old-style components and internal functions, not # part of the public API -function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, ti}, idx1::AnyIndex, idx2::AnyIndex) where {T, FIRST, STEP, ti} +function Base.getindex(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, ti}, idx1::AnyIndex_NonColon, idx2::AnyIndex_NonColon) where {T, FIRST, STEP, ti} + _throw_int_getindex_depwarning() return mat.data[idx1, idx2] end -function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T, ti}, idx1::AnyIndex, idx2::AnyIndex) where {T, TIMES, ti} +function Base.getindex(mat::TimestepMatrix{VariableTimestep{TIMES}, T, ti}, idx1::AnyIndex_NonColon, idx2::AnyIndex_NonColon) where {T, TIMES, ti} + _throw_int_getindex_depwarning() return mat.data[idx1, idx2] end function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, ti}, val, idx1::Int, idx2::Int) where {T, FIRST, STEP, ti} + _throw_int_setindex_depwarning() setindex!(mat.data, val, idx1, idx2) end -function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, ti}, val, idx1::AnyIndex, idx2::AnyIndex) where {T, FIRST, STEP, ti} +function Base.setindex!(mat::TimestepMatrix{FixedTimestep{FIRST, STEP}, T, ti}, val, idx1::AnyIndex_NonColon, idx2::AnyIndex_NonColon) where {T, FIRST, STEP, ti} + _throw_int_setindex_depwarning() mat.data[idx1, idx2] .= val end function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T, ti}, val, idx1::Int, idx2::Int) where {T, TIMES, ti} + _throw_int_setindex_depwarning() setindex!(mat.data, val, idx1, idx2) end -function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T, ti}, val, idx1::AnyIndex, idx2::AnyIndex) where {T, TIMES, ti} +function Base.setindex!(mat::TimestepMatrix{VariableTimestep{TIMES}, T, ti}, val, idx1::AnyIndex_NonColon, idx2::AnyIndex_NonColon) where {T, TIMES, ti} + _throw_int_setindex_depwarning() mat.data[idx1, idx2] .= val end @@ -292,6 +458,37 @@ function Base.getindex(arr::TimestepArray{VariableTimestep{D_TIMES}, T, N, ti}, return arr.data[idxs1..., t, idxs2...] end +function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T_data, N, ti}, idxs::Union{TimestepValue{T_time}, AnyIndex}...) where {T_data, N, ti, FIRST, STEP, T_time} + _single_index_check(arr.data, idxs) + idxs1, ts, idxs2 = split_indices(idxs, ti) + LAST = FIRST + ((size(arr.data, ti) - 1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + return arr.data[idxs1..., t, idxs2...] +end + +function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T_data, N, ti}, idxs::Union{TimestepValue{T_time}, AnyIndex}...) where {T_data, N, ti, TIMES, T_time} + _single_index_check(arr.data, idxs) + idxs1, ts, idxs2 = split_indices(idxs, ti) + t = _get_time_value_position(TIMES, ts) + return arr.data[idxs1..., t, idxs2...] +end + +function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, idxs::Union{TimestepIndex, AnyIndex}...) where {T, N, ti, FIRST, STEP} + _single_index_check(arr.data, idxs) + idxs1, ts, idxs2 = split_indices(idxs, ti) + t = ts.index + _index_bounds_check(arr.data, ti, t) + return arr.data[idxs1..., t, idxs2...] +end + +function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, idxs::Union{TimestepIndex, AnyIndex}...) where {T, N, ti, TIMES} + _single_index_check(arr.data, idxs) + idxs1, ts, idxs2 = split_indices(idxs, ti) + t = ts.index + _index_bounds_check(arr.data, ti, t) + return arr.data[idxs1..., t, idxs2...] +end + function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, val, idxs::Union{FixedTimestep{FIRST, STEP, LAST}, AnyIndex}...) where {T, N, ti, FIRST, STEP, LAST} idxs1, ts, idxs2 = split_indices(idxs, ti) setindex!(arr.data, val, idxs1..., ts.t, idxs2...) @@ -314,23 +511,93 @@ function Base.setindex!(arr::TimestepArray{VariableTimestep{D_TIMES}, T, N, ti}, setindex!(arr.data, val, idxs1..., t, idxs2...) end -# int indexing version supports old-style components and internal functions, not -# part of the public API; first index is Int or Range, rather than a Timestep +function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T_data, N, ti}, val, idxs::Union{TimestepValue{T_time}, AnyIndex}...) where {T_data, N, ti, FIRST, STEP, T_time} + _single_index_check(arr.data, idxs) + idxs1, ts, idxs2 = split_indices(idxs, ti) + LAST = FIRST + ((size(arr.data, ti) - 1) * STEP) + t = _get_time_value_position([FIRST:STEP:LAST...], ts) + setindex!(arr.data, val, idxs1..., t, idxs2...) +end + +function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T_data, N, ti}, val, idxs::Union{TimestepValue{T_time}, AnyIndex}...) where {T_data, N, ti, TIMES, T_time} + _single_index_check(arr.data, idxs) + idxs1, ts, idxs2 = split_indices(idxs, ti) + t = _get_time_value_position(TIMES, ts) + setindex!(arr.data, val, idxs1..., t, idxs2...) +end + +function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, val, idxs::Union{TimestepIndex, AnyIndex}...) where {T, N, ti, FIRST, STEP} + idxs1, ts, idxs2 = split_indices(idxs, ti) + t = ts.index + setindex!(arr.data, val, idxs1..., t, idxs2...) +end + +function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, val, idxs::Union{TimestepIndex, AnyIndex}...) where {T, N, ti, TIMES} + idxs1, ts, idxs2 = split_indices(idxs, ti) + t = ts.index + setindex!(arr.data, val, idxs1..., t, idxs2...) +end + +# Colon support - this allows the time dimension to be indexed with a colon, and +# the deprecation warning will become an error when integer indexing is fully deprecated + +function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, idxs::AnyIndex...) where {FIRST, STEP, T, N, ti} + isa(idxs[ti], AnyIndex_NonColon) ? _throw_int_getindex_depwarning() : nothing + return arr.data[idxs...] +end + +function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, idxs::AnyIndex...) where {TIMES, T, N, ti} + isa(idxs[ti], AnyIndex_NonColon) ? _throw_int_getindex_depwarning() : nothing + return arr.data[idxs...] +end + +function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, val, idxs::AnyIndex...) where {FIRST, STEP, T, N, ti} + isa(idxs[ti], AnyIndex_NonColon) ? _throw_int_getindex_depwarning() : nothing + setindex!(arr.data, val, idxs...) +end + +function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, val, idxs::AnyIndex...) where {TIMES, T, N, ti} + isa(idxs[ti], AnyIndex_NonColon) ? _throw_int_getindex_depwarning() : nothing + setindex!(arr.data, val, idxs...) +end + +# Indexing with arrays of TimestepIndexes or TimestepValues +function Base.getindex(arr::TimestepArray{TS, T, N, ti}, idxs::Union{Array{TimestepIndex,1}, AnyIndex}...) where {TS, T, N, ti} + idxs1, ts_array, idxs2 = split_indices(idxs, ti) + ts_idxs = _get_ts_indices(ts_array) + return arr.data[idxs1..., ts_idxs, idxs2...] +end + +function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T_data, N, ti}, idxs::Union{Array{TimestepValue{T_time},1}, AnyIndex}...) where {T_data, N, ti, FIRST, STEP, T_time} + idxs1, ts_array, idxs2 = split_indices(idxs, ti) + LAST = FIRST + ((length(arr.data)-1) * STEP) + ts_idxs = _get_ts_indices(ts_array, [FIRST:STEP:LAST...]) + return arr.data[idxs1..., ts_idxs, idxs2...] +end -function Base.getindex(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, ti, FIRST, STEP} - return arr.data[idx1, idx2, idxs...] +function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T_data, N, ti}, idxs::Union{Array{TimestepValue{T_times},1}, AnyIndex}...) where {T_data, N, ti, TIMES, T_times} + idxs1, ts_array, idxs2 = split_indices(idxs, ti) + ts_idxs = _get_ts_indices(ts_array, TIMES) + return arr.data[idxs1..., ts_idxs, idxs2...] end -function Base.getindex(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, ti, TIMES} - return arr.data[idx1, idx2, idxs...] +function Base.setindex!(arr::TimestepArray{TS, T, N, ti}, vals, idxs::Union{Array{TimestepIndex,1}, AnyIndex}...) where {TS, T, N, ti} + idxs1, ts_array, idxs2 = split_indices(idxs, ti) + ts_idxs = _get_ts_indices(ts_array) + setindex!(arr.data, vals, idxs1..., ts_idxs, idxs2...) end -function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T, N, ti}, val, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, ti, FIRST, STEP} - setindex!(arr.data, val, idx1, idx2, idxs...) +function Base.setindex!(arr::TimestepArray{FixedTimestep{FIRST, STEP}, T_data, N, ti}, vals, idxs::Union{Array{TimestepValue{T_times},1}, AnyIndex}...) where {T_data, N, ti, FIRST, STEP, T_times} + idxs1, ts_array, idxs2 = split_indices(idxs, ti) + LAST = FIRST + ((length(arr.data)-1) * STEP) + ts_idxs = _get_ts_indices(ts_array, [FIRST:STEP:LAST...]) + setindex!(arr.data, vals, idxs1..., ts_idxs, idxs2...) end -function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T, N, ti}, val, idx1::AnyIndex, idx2::AnyIndex, idxs::AnyIndex...) where {T, N, ti, TIMES} - setindex!(arr.data, val, idx1, idx2, idxs...) +function Base.setindex!(arr::TimestepArray{VariableTimestep{TIMES}, T_data, N, ti}, vals, idxs::Union{Array{TimestepValue{T_times},1}, AnyIndex}...) where {T_data, N, ti, TIMES, T_times} + idxs1, ts_array, idxs2 = split_indices(idxs, ti) + ts_idxs = _get_ts_indices(ts_array, TIMES) + setindex!(arr.data, vals, idxs1..., ts_idxs, idxs2...) end """ diff --git a/src/core/types/time.jl b/src/core/types/time.jl index 7c1adcdbe..b03378bf4 100644 --- a/src/core/types/time.jl +++ b/src/core/types/time.jl @@ -22,6 +22,19 @@ struct VariableTimestep{TIMES} <: AbstractTimestep end end +struct TimestepValue{T} + value::T + offset::Int + + function TimestepValue(v::T; offset::Int = 0) where T + return new{T}(v, offset) + end +end + +struct TimestepIndex + index::Int +end + mutable struct Clock{T <: AbstractTimestep} <: MimiStruct ts::T diff --git a/src/mcs/montecarlo.jl b/src/mcs/montecarlo.jl index 56dedd269..0b9793b70 100644 --- a/src/mcs/montecarlo.jl +++ b/src/mcs/montecarlo.jl @@ -304,10 +304,11 @@ function _param_indices(param::ArrayModelParameter{T}, md::ModelDef, trans::Tran indices = Vector() for (dim_name, dim_values) in zip(pdims, tdims) dim = dimension(md, dim_name) - push!(indices, dim[dim_values]) + dim_indices = dim[dim_values] + dim_name == :time ? dim_indices = TimestepIndex.(dim_indices) : nothing + push!(indices, dim_indices) end - # println("indices: $indices") return indices end diff --git a/test/dependencies/run_dependency_tests.jl b/test/dependencies/run_dependency_tests.jl index 79c6e2877..dd7bb8a66 100644 --- a/test/dependencies/run_dependency_tests.jl +++ b/test/dependencies/run_dependency_tests.jl @@ -12,7 +12,7 @@ for (pkg_url, pkg_rev, pkg_name) in packages_to_test Pkg.develop(PackageSpec(path=joinpath(@__DIR__, "..", ".."))) Pkg.add(PackageSpec(url=pkg_url, rev=pkg_rev)) - + Pkg.test(pkg_name) end end diff --git a/test/test_timesteparrays.jl b/test/test_timesteparrays.jl index 15748a314..2f3f437f2 100644 --- a/test/test_timesteparrays.jl +++ b/test/test_timesteparrays.jl @@ -4,10 +4,20 @@ using Mimi using Test import Mimi: - FixedTimestep, VariableTimestep, TimestepVector, TimestepMatrix, next_timestep, hasvalue, + FixedTimestep, VariableTimestep, TimestepVector, TimestepMatrix, TimestepArray, next_timestep, hasvalue, isuniform, first_period, last_period, first_and_step -a = collect(reshape(1:16,4,4)) +function reset_time_val(arr, vals::Array{T, 1}) where T + arr[:] = vals +end + +function reset_time_val(arr, vals::Array{T, 2}) where T + arr[:,:] = vals +end + +function reset_time_val(arr, vals::Array{T, 3}) where T + arr[:,:,:] = vals +end ## quick check of isuniform @test isuniform([]) == false @@ -22,18 +32,47 @@ a = collect(reshape(1:16,4,4)) #------------------------------------------------------------------------------ # 1. Test TimestepVector - Fixed Timestep #------------------------------------------------------------------------------ -years = collect(2000:1:2003) #1a. test constructor, lastindex, and length (with both # matching years and mismatched years) -x = TimestepVector{FixedTimestep{2000, 1}, Int}(a[:,3]) +x = TimestepVector{FixedTimestep{2000, 1}, Int}([9, 10, 11, 12]) @test length(x) == 4 @test lastindex(x) == 4 +time_dim_val = [9, 10, 11, 12] +temp_dim_val = [100, 101, 102, 103] + #1b. test hasvalue, getindex, and setindex! (with both matching years and # mismatched years) +# Using a colon in the time dimension +@test x[:] == time_dim_val +x[:] = temp_dim_val +@test x[:] == temp_dim_val +reset_time_val(x, time_dim_val) + +# TimestepValue and TimestepIndex Indexing +@test x[TimestepIndex(1)] == time_dim_val[1] +@test x[TimestepIndex(1) + 1] == time_dim_val[2] +@test x[TimestepIndex(4)] == time_dim_val[4] +@test_throws ErrorException x[TimestepIndex(5)] + +x[TimestepIndex(1)] = temp_dim_val[1] +@test x[TimestepIndex(1)] == temp_dim_val[1] +reset_time_val(x, time_dim_val) + +@test x[TimestepValue(2000)] == time_dim_val[1] +@test x[TimestepValue(2000; offset = 1)] == time_dim_val[2] +@test x[TimestepValue(2000) + 1] == time_dim_val[2] +@test_throws ErrorException x[TimestepValue(2005)] +@test_throws ErrorException x[TimestepValue(2004)+1] + +x[TimestepValue(2000)] = temp_dim_val[1] +@test x[TimestepValue(2000)] == temp_dim_val[1] +reset_time_val(x, time_dim_val) + +# AbstractTimestep Indexing t = FixedTimestep{2001, 1, 3000}(1) @test hasvalue(x, t) @@ -41,179 +80,516 @@ t = FixedTimestep{2001, 1, 3000}(1) @test x[t] == 10 t2 = next_timestep(t) - -@test x[t2] == 11 - -x[t2] = 99 -@test x[t2] == 99 +@test x[t2] == time_dim_val[3] +x[t2] = temp_dim_val[3] +@test x[t2] == temp_dim_val[3] +reset_time_val(x, time_dim_val) t3 = FixedTimestep{2000, 1, 2003}(1) -@test x[t3] == 9 - -x[t3] = 100 -@test x[t3] == 100 - -x[1] = 101 -@test x[1] == 101 +@test x[t3] == time_dim_val[1] +x[t3] = temp_dim_val[1] +@test x[t3] == temp_dim_val[1] +reset_time_val(x, time_dim_val) +# Deprecated int indexing should still run +@test x[3] == time_dim_val[3] +x[3] = temp_dim_val[3] +@test x[3] == temp_dim_val[3] +reset_time_val(x, time_dim_val) #------------------------------------------------------------------------------ # 2. Test TimestepVector - Variable Timestep #------------------------------------------------------------------------------ years = (2000, 2005, 2015, 2025) -x = TimestepVector{VariableTimestep{years}, Int}(a[:,3]) +x = TimestepVector{VariableTimestep{years}, Int}([9, 10, 11, 12]) + +time_dim_val = [9, 10, 11, 12] +temp_dim_val = [100, 101, 102, 103] #2a. test hasvalue, getindex, and setindex! (with both matching years and # mismatched years) +# Using a colon in the time dimension +@test x[:] == time_dim_val +x[:] = temp_dim_val +@test x[:] == temp_dim_val +reset_time_val(x, time_dim_val) + +# TimestepValue and TimestepIndex Indexing +@test x[TimestepIndex(1)] == time_dim_val[1] +@test x[TimestepIndex(1) + 1] == time_dim_val[2] +@test x[TimestepIndex(4)] == time_dim_val[4] +@test_throws ErrorException x[TimestepIndex(5)] + +x[TimestepIndex(1)] = temp_dim_val[1] +@test x[TimestepIndex(1)] == temp_dim_val[1] +reset_time_val(x, time_dim_val) + +@test x[TimestepValue(2000)] == time_dim_val[1] +@test x[TimestepValue(2000; offset = 1)] == time_dim_val[2] +@test x[TimestepValue(2015)] == time_dim_val[3] +@test x[TimestepValue(2015; offset = 1)] == time_dim_val[4] +@test x[TimestepValue(2000) + 1] == time_dim_val[2] +@test_throws ErrorException x[TimestepValue(2014)] +@test_throws ErrorException x[TimestepValue(2025)+1] + +x[TimestepValue(2015)] = temp_dim_val[3] +@test x[TimestepValue(2015)] == temp_dim_val[3] +reset_time_val(x, time_dim_val) + +# AbstractTimestep Indexing y2 = Tuple([2005:5:2010; 2015:10:3000]) t = VariableTimestep{y2}() @test hasvalue(x, t) -@test !hasvalue(x, VariableTimestep{years}(10)) -@test x[t] == 10 +@test !hasvalue(x, VariableTimestep{years}(time_dim_val[2])) +@test x[t] == time_dim_val[2] t2 = next_timestep(t) - -@test x[t2] == 11 -#@test indices(x) == (2000:2003,) #may remove this function - -x[t2] = 99 -@test x[t2] == 99 +@test x[t2] == time_dim_val[3] +x[t2] = temp_dim_val[3] +@test x[t2] == temp_dim_val[3] +reset_time_val(x, time_dim_val) t3 = VariableTimestep{years}() -@test x[t3] == 9 - -x[t3] = 100 -@test x[t3] == 100 - -x[1] = 101 -@test x[1] == 101 +@test x[t3] == time_dim_val[1] +x[t3] = temp_dim_val[1] +@test x[t3] == temp_dim_val[1] +reset_time_val(x, time_dim_val) +# Deprecated int indexing should still run +@test x[3] == time_dim_val[3] +x[3] = temp_dim_val[3] +@test x[3] == temp_dim_val[3] +reset_time_val(x, time_dim_val) #------------------------------------------------------------------------------ # 3. Test TimestepMatrix - Fixed Timestep #------------------------------------------------------------------------------ -years = Tuple(2000:1:2003) - -#3a. test constructor (with both matching years -# and mismatched years) - -y = TimestepMatrix{FixedTimestep{2000, 1}, Int, 1}(a[:,1:2]) - -#3b. test hasvalue, getindex, and setindex! (with both matching years and -# mismatched years) - -t = FixedTimestep{2001, 1, 3000}(1) - -@test hasvalue(y, t, 1) -@test !hasvalue(y, FixedTimestep{2000, 1, 3000}(10), 1) -@test y[t,1] == 2 -@test y[t,2] == 6 - -t2 = next_timestep(t) -@test y[t2,1] == 3 -@test y[t2,2] == 7 - -y[t2, 1] = 5 -@test y[t2, 1] == 5 +for ti = 1:2 -t3 = FixedTimestep{2000, 1, 2005}(1) + #3a. test constructor (with both matching years + # and mismatched years) -@test y[t3, 1] == 1 -@test y[t3, 2] == 5 + y = TimestepMatrix{FixedTimestep{2000, 1}, Int, ti}(collect(reshape(1:8, 4, 2))) + z = TimestepMatrix{FixedTimestep{2000, 2}, Int, ti}(collect(reshape(1:8, 4, 2))) -y[t3, 1] = 10 -@test y[t3,1] == 10 + time_dim_val = collect(reshape(1:8, 4, 2)) + temp_dim_val = collect(reshape(100:107, 4, 2)) -y[:,:] = 11 -@test all([y[i,1] == 11 for i in 1:4]) -@test all([y[1,j] == 11 for j in 1:2]) + #3b. test hasvalue, getindex, and setindex! (with both matching years and + # mismatched years) -#3c. interval wider than 1 -z = TimestepMatrix{FixedTimestep{2000, 2}, Int, 1}(a[:,3:4]) -t = FixedTimestep{1980, 2, 3000}(11) + # Using a colon in the time dimension + y[:,:] = temp_dim_val + @test y[:,:] == temp_dim_val + y[:,:] = time_dim_val # reset + if ti == 1 + @test y[:,1] == time_dim_val[:,1] + y[:, 1] = temp_dim_val[:,1] + @test y[:, 1] == temp_dim_val[:,1] + else + @test y[1,:] == time_dim_val[1,:] + y[1,:] = temp_dim_val[1,:] + @test y[1,:] == temp_dim_val[1,:] + end + reset_time_val(y, time_dim_val) + + # TimestepValue and TimestepIndex Indexing + if ti == 1 + @test y[TimestepIndex(1), 1] == time_dim_val[1,1] + @test y[TimestepIndex(1), 2] == time_dim_val[1,2] + @test y[TimestepIndex(1) + 1, 1] == time_dim_val[2,1] + @test y[TimestepIndex(4), 2] == time_dim_val[4,2] + @test_throws ErrorException y[TimestepIndex(5), 2] + + y[TimestepIndex(1), 1] = temp_dim_val[1] + @test y[TimestepIndex(1), 1] == temp_dim_val[1] + reset_time_val(y, time_dim_val) + + @test y[TimestepValue(2000), 1] == time_dim_val[1, 1] + @test y[TimestepValue(2000), 2] == time_dim_val[1, 2] + @test y[TimestepValue(2001), :] == time_dim_val[2, :] + @test y[TimestepValue(2000; offset = 1), 1] == time_dim_val[2,1] + @test y[TimestepValue(2000) + 1, 1] == time_dim_val[2,1] + @test_throws ErrorException y[TimestepValue(2005), 1] + @test_throws ErrorException y[TimestepValue(2004)+1, 1] + + y[TimestepValue(2000), 1] = temp_dim_val[1] + @test y[TimestepValue(2000), 1] == temp_dim_val[1] + reset_time_val(y, time_dim_val) + else + @test y[1, TimestepIndex(1)] == time_dim_val[1,1] + @test y[2, TimestepIndex(1)] == time_dim_val[2, 1] + @test y[1, TimestepIndex(1) + 1] == time_dim_val[1, 2] + @test y[2, TimestepIndex(2)] == time_dim_val[2,2] + @test_throws ErrorException y[2, TimestepIndex(3)] + + y[1, TimestepIndex(1)] = temp_dim_val[1] + @test y[1, TimestepIndex(1)] == temp_dim_val[1] + reset_time_val(y, time_dim_val) + + @test y[1, TimestepValue(2000)] == time_dim_val[1,1] + @test y[2, TimestepValue(2000)] == time_dim_val[2,1] + @test y[:, TimestepValue(2001)] == time_dim_val[:,2] + @test y[1, TimestepValue(2000; offset = 1)] == time_dim_val[1,2] + @test y[1, TimestepValue(2000) + 1] == time_dim_val[1,2] + @test_throws ErrorException y[1, TimestepValue(2003)] + @test_throws ErrorException y[1, TimestepValue(2002)+1] + + y[1, TimestepValue(2000)] = temp_dim_val[1] + @test y[1, TimestepValue(2000)] == temp_dim_val[1] + reset_time_val(y, time_dim_val) + end -@test z[t,1] == 9 -@test z[t,2] == 13 + # AbstractTimestep Indexing + t = FixedTimestep{2001, 1, 3000}(1) + @test hasvalue(y, t, 1) + @test !hasvalue(y, FixedTimestep{2000, 1, 3000}(10), 1) + + if ti == 1 + @test y[t,1] == time_dim_val[2,1] + @test y[t,2] == time_dim_val[2,2] + + t2 = next_timestep(t) + @test y[t2,1] == time_dim_val[3,1] + @test y[t2,2] == time_dim_val[3,2] + y[t2, 1] = temp_dim_val[3,1] + @test y[t2, 1] == temp_dim_val[3,1] + reset_time_val(y, time_dim_val) + + t3 = FixedTimestep{2000, 1, 2005}(1) + @test y[t3, 1] == time_dim_val[1,1] + @test y[t3, 2] == time_dim_val[1,2] + y[t3, 1] = temp_dim_val[1,1] + @test y[t3,1] == temp_dim_val[1,1] + reset_time_val(y, time_dim_val) + + #3c. interval wider than 1 using z from above + t = FixedTimestep{1980, 2, 3000}(11) + + @test z[t,1] == time_dim_val[1,1] + @test z[t,2] == time_dim_val[1,2] + + t2 = next_timestep(t) + @test z[t2,1] == time_dim_val[2,1] + @test z[t2,2] == time_dim_val[2,2] + + else + @test y[1, t] == time_dim_val[1,2] + @test y[2, t] == time_dim_val[2,2] + + t2 = FixedTimestep{2000, 1, 2005}(1) + @test y[1, t2] == time_dim_val[1,1] + @test y[2, t2] == time_dim_val[2,1] + y[1, t2] = temp_dim_val[1,1] + @test y[1, t2] == temp_dim_val[1,1] + reset_time_val(y, time_dim_val) + + #3c. interval wider than 1 using z from above + t = FixedTimestep{1980, 2, 3000}(11) + + @test z[1, t] == time_dim_val[1,1] + @test z[2, t] == time_dim_val[2,1] + + t2 = next_timestep(t) + @test z[1, t2] == time_dim_val[1,2] + @test z[2, t2] == time_dim_val[2,2] + end -t2 = next_timestep(t) -@test z[t2,1] == 10 -@test z[t2,2] == 14 + # Deprecated int indexing should still run + @test y[1,2] == time_dim_val[1,2] + @test z[1,2] == time_dim_val[1,2] + y[1,2] = temp_dim_val[1] + z[1,2] = temp_dim_val[1] + @test y[1,2] == z[1,2] == temp_dim_val[1] + reset_time_val(y, time_dim_val) + reset_time_val(z, time_dim_val) +end #------------------------------------------------------------------------------ # 4. Test TimestepMatrix - Variable Timestep #------------------------------------------------------------------------------ -years = Tuple([2000:5:2005; 2015:10:2025]) -y = TimestepMatrix{VariableTimestep{years}, Int, 1}(a[:,1:2]) +for ti = 1:2 -#4a. test hasvalue, getindex, setindex!, and lastindex (with both matching years and -# mismatched years) - -t = VariableTimestep{Tuple([2005:5:2010; 2015:10:3000])}() - -@test hasvalue(y, t, 1) -@test !hasvalue(y, VariableTimestep{years}(10)) -@test y[t,1] == 2 -@test y[t,2] == 6 + #4a. test constructor (with both matching years + # and mismatched years) -t2 = next_timestep(t) + if ti == 1 + years = Tuple([2000:5:2005; 2015:10:2025]) + else + years = (2000, 2005) + end + y = TimestepMatrix{VariableTimestep{years}, Int, ti}(collect(reshape(1:8, 4, 2))) + y = TimestepMatrix{VariableTimestep{years}, Int, ti}(collect(reshape(1:8, 4, 2))) + + time_dim_val = collect(reshape(1:8, 4, 2)) + temp_dim_val = collect(reshape(100:107, 4, 2)) + + #4b. test hasvalue, getindex, setindex!, and lastindex (with both matching years and + # mismatched years) + + # Using a colon in the time dimension + y[:,:] = temp_dim_val + @test y[:,:] == temp_dim_val + y[:,:] = time_dim_val # reset + if ti == 1 + @test y[:,1] == time_dim_val[:,1] + y[:, 1] = temp_dim_val[:,1] + @test y[:, 1] == temp_dim_val[:,1] + else + @test y[1,:] == time_dim_val[1,:] + y[1,:] = temp_dim_val[1,:] + @test y[1,:] == temp_dim_val[1,:] + end + reset_time_val(y, time_dim_val) + + # TimestepValue and TimestepIndex Indexing + if ti == 1 + @test y[TimestepIndex(1), 1] == time_dim_val[1,1] + @test y[TimestepIndex(1), 2] == time_dim_val[1,2] + @test y[TimestepIndex(1) + 1, 1] == time_dim_val[2,1] + @test y[TimestepIndex(4), 2] == time_dim_val[4,2] + @test_throws ErrorException y[TimestepIndex(5),2] + + y[TimestepIndex(1), 1] = temp_dim_val[1,1] + @test y[TimestepIndex(1), 1] == temp_dim_val[1,1] + reset_time_val(y, time_dim_val) + + @test y[TimestepValue(2000), 1] == time_dim_val[1,1] + @test y[TimestepValue(2000), 2] == time_dim_val[1,2] + @test y[TimestepValue(2000; offset = 1), 1] == time_dim_val[2,1] + @test y[TimestepValue(2000) + 1, 1] == time_dim_val[2,1] + @test y[TimestepValue(2015), 1] == time_dim_val[3,1] + @test y[TimestepValue(2015) + 1, 2] == time_dim_val[4,2] + @test_throws ErrorException y[TimestepValue(2006), 1] + @test_throws ErrorException y[TimestepValue(2025)+1, 1] + + y[TimestepValue(2015), 1] = temp_dim_val[3,1] + @test y[TimestepValue(2015), 1] == temp_dim_val[3,1] + reset_time_val(y, time_dim_val) + else + @test y[1, TimestepIndex(1)] == time_dim_val[1,1] + @test y[2, TimestepIndex(1)] == time_dim_val[2,1] + @test y[1, TimestepIndex(1) + 1] == time_dim_val[1,2] + @test y[2, TimestepIndex(2)] == time_dim_val[2,2] + @test_throws ErrorException y[2, TimestepIndex(3)] + + y[1, TimestepIndex(1)] = temp_dim_val[1,1] + @test y[1, TimestepIndex(1)] == temp_dim_val[1,1] + reset_time_val(y, time_dim_val) + + @test y[1, TimestepValue(2000)] == time_dim_val[1,1] + @test y[2, TimestepValue(2000)] == time_dim_val[2,1] + @test y[1, TimestepValue(2000; offset = 1)] == time_dim_val[1,2] + @test y[1, TimestepValue(2000) + 1] == time_dim_val[1,2] + @test y[1, TimestepValue(2005)] == time_dim_val[1,2] + @test_throws ErrorException y[1, TimestepValue(2006)] + @test_throws ErrorException y[1, TimestepValue(2005)+1] + + y[1, TimestepValue(2005)] = temp_dim_val[1,2] + @test y[1, TimestepValue(2005)] == temp_dim_val[1,2] + reset_time_val(y, time_dim_val) + end -@test y[t2,1] == 3 -@test y[t2,2] == 7 - -y[t2, 1] = 5 -@test y[t2, 1] == 5 + # AbstractTimestep Indexing + t = VariableTimestep{Tuple([2005:5:2010; 2015:10:3000])}() + if ti == 1 + @test hasvalue(y, t, time_dim_val[1]) + @test !hasvalue(y, VariableTimestep{years}(10)) + @test y[t,1] == time_dim_val[2,1] + @test y[t,2] == time_dim_val[2,2] + + t2 = next_timestep(t) + @test y[t2,1] == time_dim_val[3,1] + @test y[t2,2] == time_dim_val[3,2] + y[t2, 1] = temp_dim_val[3,1] + @test y[t2, 1] == temp_dim_val[3,1] + reset_time_val(y, time_dim_val) + + t3 = VariableTimestep{years}() + @test y[t3, 1] == time_dim_val[1,1] + @test y[t3, 2] == time_dim_val[1,2] + y[t3, 1] = temp_dim_val[1,1] + @test y[t3,1] == temp_dim_val[1,1] + reset_time_val(y, time_dim_val) + + else + @test hasvalue(y, t, time_dim_val[1]) + @test !hasvalue(y, VariableTimestep{years}(10)) + @test y[1,t] == time_dim_val[1,2] + @test y[2,t] == time_dim_val[2,2] + + t3 = VariableTimestep{years}() + @test y[1, t3] == time_dim_val[1,1] + @test y[2, t3] == time_dim_val[2,1] + y[1,t3] = temp_dim_val[1,1] + @test y[1,t3] == temp_dim_val[1,1] + reset_time_val(y, time_dim_val) + end -t3 = VariableTimestep{years}() + # Deprecated int indexing should still run + @test y[1,2] == time_dim_val[1,2] + y[1,2] = temp_dim_val[1,2] + @test y[1,2] == temp_dim_val[1,2] + reset_time_val(y, time_dim_val) +end -@test y[t3, 1] == 1 -@test y[t3, 2] == 5 +#------------------------------------------------------------------------------ +# 5. Test TimestepArray methods (3 dimensional) +#------------------------------------------------------------------------------ -y[t3, 1] = 10 -@test y[t3,1] == 10 +for ti = 1:2 + + years = Tuple([2000:5:2005; 2015:10:2025]) + arr_fixed = TimestepArray{FixedTimestep{2000, 5}, Int, 3, ti}(collect(reshape(1:64, 4, 4, 4))) + arr_variable = TimestepArray{VariableTimestep{years}, Int, 3, ti}(collect(reshape(1:64, 4, 4, 4))) + + time_dim_val = collect(reshape(1:64, 4, 4, 4)) + temp_dim_val = collect(reshape(100:163, 4, 4, 4)) + + # Using a colon in the time dimension + @test arr_fixed[:,1,1] == arr_variable[:,1,1] == time_dim_val[:,1,1] + @test arr_fixed[:,2,3] == arr_variable[:,2,3] == time_dim_val[:,2,3] + arr_fixed[:,1,1] = temp_dim_val[:,1,1] + arr_variable[:,1,1] = temp_dim_val[:,1,1] + @test arr_fixed[:,1,1] == arr_variable[:,1,1] == temp_dim_val[:,1,1] + arr_fixed[:,:,2] = temp_dim_val[:,:,2] + arr_variable[:,:,2] = temp_dim_val[:,:,2] + @test arr_fixed[:,:,2] == arr_variable[:,:,2] == temp_dim_val[:,:,2] + arr_fixed[:,:,:] = temp_dim_val + arr_variable[:,:,:] = temp_dim_val + @test arr_fixed[:,:,:] == arr_variable[:,:,:] == temp_dim_val[:,:,:] + + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + + @test_throws ErrorException arr_fixed[TimestepValue(2000)] + @test_throws ErrorException arr_variable[TimestepValue(2000)] + + # Indexing with single TimestepIndex + if ti == 1 + @test arr_fixed[TimestepIndex(1), 1, 1] == arr_variable[TimestepIndex(1), 1, 1] == time_dim_val[1,1,1] + @test arr_fixed[TimestepIndex(3), 3, 3] == arr_variable[TimestepIndex(3), 3, 3] == time_dim_val[3,3,3] + + arr_fixed[TimestepIndex(1), 1, 1] = temp_dim_val[1,1,1] + arr_variable[TimestepIndex(1), 1, 1] = temp_dim_val[1,1,1] + @test arr_fixed[TimestepIndex(1), 1, 1] == arr_variable[TimestepIndex(1), 1, 1] == temp_dim_val[1,1,1] + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + + @test_throws ErrorException arr_fixed[TimestepIndex(1)] + @test_throws ErrorException arr_variable[TimestepIndex(1)] + + # Indexing with Array{TimestepIndex, N} + @test arr_fixed[TimestepIndex.([1,3]), 1, 1] == time_dim_val[[1,3], 1, 1] + @test arr_variable[TimestepIndex.([2,4]), 1, 1] == time_dim_val[[2,4], 1, 1] + + # Indexing with Array{TimestepIndex, N} created by Colon syntax + @test arr_fixed[TimestepIndex(1):TimestepIndex(3), 1, 1] == time_dim_val[[1:3...], 1, 1] + @test arr_fixed[TimestepIndex(1):2:TimestepIndex(3), 1, 1] == time_dim_val[[1:2:3...], 1, 1] + + # Indexing with single TimestepValue + @test arr_fixed[TimestepValue(2000), 1, 1] == arr_variable[TimestepValue(2000), 1, 1] == time_dim_val[1,1,1] + @test arr_fixed[TimestepValue(2010), 3, 3] == arr_variable[TimestepValue(2015), 3, 3] == time_dim_val[3,3,3] + + arr_fixed[TimestepValue(2000), 1, 1] = time_dim_val[1,1,1] + arr_variable[TimestepValue(2000), 1, 1] = time_dim_val[1,1,1] + @test arr_fixed[TimestepValue(2000), 1, 1] == arr_variable[TimestepValue(2000), 1, 1] == time_dim_val[1,1,1] + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + + # Indexing with Array{TimestepValue, N} + @test arr_fixed[TimestepValue.([2000, 2010]), 1, 1] == time_dim_val[[1,3],1,1] + @test arr_variable[TimestepValue.([2000, 2005, 2025]), 1, 1] == time_dim_val[[1,2,4],1,1] + + arr_fixed[TimestepValue.([2000, 2010]), 1, 1] = temp_dim_val[[1,3],1,1] + arr_variable[TimestepValue.([2000, 2005, 2025]), 1, 1] = temp_dim_val[[1,2,4],1,1] + @test arr_fixed[TimestepValue.([2000, 2010]), 1, 1] == temp_dim_val[[1,3],1,1] + @test arr_variable[TimestepValue.([2000, 2005, 2025]), 1, 1] == temp_dim_val[[1,2,4],1,1] + + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + + else + + @test arr_fixed[1, TimestepIndex(1), 1] == arr_variable[1, TimestepIndex(1), 1] == time_dim_val[1,1,1] + @test arr_fixed[3, TimestepIndex(3), 3] == arr_variable[3, TimestepIndex(3), 3] == time_dim_val[3,3,3] + + arr_fixed[1, TimestepIndex(1), 1] = temp_dim_val[1,1,1] + arr_variable[1, TimestepIndex(1), 1] = temp_dim_val[1,1,1] + @test arr_fixed[1, TimestepIndex(1), 1] == arr_variable[1, TimestepIndex(1), 1] == temp_dim_val[1,1,1] + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + + # Indexing with Array{TimestepIndex, N} + @test arr_fixed[1, TimestepIndex.([1,3]), 1] == time_dim_val[1, [1,3], 1] + @test arr_variable[1, TimestepIndex.([2,4]), 1] == time_dim_val[1, [2,4], 1] + + # Indexing with Array{TimestepIndex, N} created by Colon syntax + @test arr_fixed[1, TimestepIndex(1):TimestepIndex(3), 1] == time_dim_val[1, [1:3...], 1] + @test arr_fixed[1, TimestepIndex(1):2:TimestepIndex(3), 1] == time_dim_val[1, [1:2:3...], 1] + + # Indexing with single TimestepValue + @test arr_fixed[1, TimestepValue(2000), 1] == arr_variable[1, TimestepValue(2000), 1] == time_dim_val[1,1,1] + @test arr_fixed[3, TimestepValue(2010), 3] == arr_variable[3, TimestepValue(2015), 3] == time_dim_val[3,3,3] + + arr_fixed[1, TimestepValue(2000), 1] = temp_dim_val[1,1,1] + arr_variable[1, TimestepValue(2000), 1] = temp_dim_val[1,1,1] + @test arr_fixed[1, TimestepValue(2000), 1] == arr_variable[1, TimestepValue(2000), 1] == temp_dim_val[1,1,1] + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + + # Indexing with Array{TimestepValue, N} + @test arr_fixed[1, TimestepValue.([2000, 2010]), 1] == time_dim_val[1, [1,3],1] + @test arr_variable[1, TimestepValue.([2000, 2005, 2025]), 1] == time_dim_val[1,[1,2,4],1] + arr_fixed[1, TimestepValue.([2000, 2010]), 1] = temp_dim_val[1, [1,3],1] + arr_variable[1, TimestepValue.([2000, 2005, 2025]), 1] = temp_dim_val[1,[1,2,4],1] + @test arr_fixed[1, TimestepValue.([2000, 2010]), 1] == temp_dim_val[1, [1,3],1] + @test arr_variable[1, TimestepValue.([2000, 2005, 2025]), 1] == temp_dim_val[1,[1,2,4],1] + + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) + end -y[:,:] = 11 -@test all([y[i,1] == 11 for i in 1:4]) -@test all([y[1,j] == 11 for j in 1:2]) + # Deprecated int indexing should still run + @test arr_fixed[1,2,3] == time_dim_val[1,2,3] + @test arr_variable[1,2,3] == time_dim_val[1,2,3] + arr_fixed[1,2,3] = temp_dim_val[1,2,3] + arr_variable[1,2,3] = temp_dim_val[1,2,3] + @test arr_fixed[1,2,3] == arr_variable[1,2,3] == temp_dim_val[1,2,3] + reset_time_val(arr_fixed, time_dim_val) + reset_time_val(arr_variable, time_dim_val) +end -#------------------------------------------------------------------------------ -# 5. Test TimestepArray methods -#------------------------------------------------------------------------------ +# other methods +time_dim_val = collect(reshape(1:64, 4, 4, 4)) x_years = Tuple(2000:5:2015) #fixed y_years = Tuple([2000:5:2005; 2015:10:2025]) #variable -x_vec = TimestepVector{FixedTimestep{2000, 5}, Int}(a[:,3]) -x_mat = TimestepMatrix{FixedTimestep{2000, 5}, Int, 1}(a[:,1:2]) -y_vec = TimestepVector{VariableTimestep{y_years}, Int}(a[:,3]) -y_mat = TimestepMatrix{VariableTimestep{y_years}, Int, 1}(a[:,1:2]) +x_vec = TimestepVector{FixedTimestep{2000, 5}, Int}(time_dim_val[:,1,1]) +x_mat = TimestepMatrix{FixedTimestep{2000, 5}, Int, 1}(time_dim_val[:,:,1]) +y_vec = TimestepVector{VariableTimestep{y_years}, Int}(time_dim_val[:,2,2]) +y_mat = TimestepMatrix{VariableTimestep{y_years}, Int, 1}(time_dim_val[:,:,2]) @test first_period(x_vec) == first_period(x_mat) == x_years[1] @test first_period(y_vec) == first_period(y_mat) == y_years[1] @test last_period(x_vec) == last_period(x_mat) == x_years[end] @test last_period(y_vec) == last_period(y_mat) == y_years[end] -@test size(x) == size(a[:,3]) -@test size(y) == size(a[:,1:2]) -@test size(y,2) == size(a[:,1:2],2) - -@test ndims(x) == 1 -@test ndims(y) == 2 - -@test eltype(x) == eltype(a) -@test eltype(y) == eltype(a) +@test size(x_vec) == size(y_vec) == (4,) +@test size(x_mat) == size(y_mat) == (4,4) -fill!(x, 2) -fill!(y, 2) -@test x.data == fill(2, (4)) -@test y.data == fill(2, (4, 2)) +@test ndims(x_vec) == ndims(y_vec) == 1 +@test ndims(x_mat) == ndims(y_mat) == 2 +@test eltype(x_vec) == eltype(y_vec) == eltype(y_vec) == eltype(y_mat) == eltype(time_dim_val) #------------------------------------------------------------------------------ # 6. Test that getindex for TimestepArrays doesn't allow access to `missing` @@ -300,4 +676,4 @@ close(w) @test all(!ismissing, m[:gdp, :pop]) @test all(!ismissing, m[:gdp, :mat2]) -end #module \ No newline at end of file +end #module diff --git a/test/test_timesteps.jl b/test/test_timesteps.jl index 81cc8bd25..5d56b5a2f 100644 --- a/test/test_timesteps.jl +++ b/test/test_timesteps.jl @@ -73,6 +73,25 @@ t4 = next_timestep(t3) @test_throws ErrorException t_next = t4 + 1 @test_throws ErrorException next_timestep(t4) +#------------------------------------------------------------------------------ +# Test some basic functions for TimestepIndex and TimestepValue +#------------------------------------------------------------------------------ +start = 1 +stop = 10 +step = 2 + +# TimestepValue +@test TimestepValue(2000, offset = 1) + 1 == TimestepValue(2000, offset = 2) +@test TimestepValue(2000) + 1 == TimestepValue(2000, offset = 1) +@test TimestepValue(2000, offset = 1) - 1 == TimestepValue(2000) + +# TimestepIndex +@test TimestepIndex(start):TimestepIndex(stop) == TimestepIndex.([start:stop...]) +@test TimestepIndex(start):TimestepIndex(stop) == TimestepIndex(start):1:TimestepIndex(stop) +@test TimestepIndex(start):step:TimestepIndex(stop) == TimestepIndex.([start:step:stop...]) + +@test TimestepIndex(1) + 1 == TimestepIndex(2) +@test TimestepIndex(2) - 1 == TimestepIndex(1) #------------------------------------------------------------------------------ # Test a model with components with different offsets