From ec328e7fc62314e0cb20ae0122558b668f8500fa Mon Sep 17 00:00:00 2001 From: lrennels Date: Mon, 12 Apr 2021 18:36:54 -0700 Subject: [PATCH 01/21] Begin changing first and last behavior --- docs/src/ref/ref_structures_definitions.md | 2 - src/core/build.jl | 2 +- src/core/defs.jl | 118 +++++++++------------ src/core/dimensions.jl | 26 ++--- src/core/model.jl | 4 - src/core/types/defs.jl | 3 - test/test_model_structure.jl | 6 +- test/test_parametertypes.jl | 51 +++++---- 8 files changed, 96 insertions(+), 116 deletions(-) diff --git a/docs/src/ref/ref_structures_definitions.md b/docs/src/ref/ref_structures_definitions.md index c2ed860c4..a8cfec2d4 100644 --- a/docs/src/ref/ref_structures_definitions.md +++ b/docs/src/ref/ref_structures_definitions.md @@ -23,8 +23,6 @@ dim_dict::OrderedDict{Symbol, Union{Nothing, Dimension}} namespace::OrderedDict{Symbol, Any} first::Union{Nothing, Int} last::Union{Nothing, Int} -first_free::Bool -last_free::Bool is_uniform::Bool ``` The namespace of a leaf component can hold `ParameterDef`s and `VariableDef`s, both which are subclasses of `DatumDef` (see below for more details on these types). diff --git a/src/core/build.jl b/src/core/build.jl index 8e61599c6..88a8dbae9 100644 --- a/src/core/build.jl +++ b/src/core/build.jl @@ -359,7 +359,7 @@ function _build(md::ModelDef) t = dimension(md, :time) time_bounds = (firstindex(t), lastindex(t)) - propagate_time!(md, t = t) # this might not be needed, but is a final propagation to double check everything + _propagate_time_dim!(md, t) # this might not be needed, but is a final propagation to double check everything ci = _build(md, vdict, pdict, time_bounds) mi = ModelInstance(ci, md) diff --git a/src/core/defs.jl b/src/core/defs.jl index 9efb3267d..5e52c6a2d 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -765,58 +765,17 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom end """ - propagate_time!(obj::AbstractComponentDef, t::Dimension; first::NothingInt=nothing, last::NothingInt=nothing) + _propagate_first_last(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) -Propagate a time dimension down through the comp def tree. This consists of two -primary functions which first push first and last through the comp def tree, and -then push the time dimension through, which sets any leftover first and last -attributes as well. +Propagate first and/or last through a component def tree. """ -function propagate_time!(obj::AbstractComponentDef; t::Union{Dimension, Nothing}=nothing, first::NothingInt=nothing, last::NothingInt=nothing) - - # the first step is pushing through fist and last, if they are set explicitly - if first !== nothing || last!== nothing - _propagate_firstlast!(obj, first=first, last=last) - end - - if t !== nothing - # propagate the actual time dimension through the components, and fill in - # any missing first and lasts as defaulting to the first and last elements of the - # time Dimension - _propagate_time_dim!(obj, t) - - # run over the object and check that first and last are within the time - # dimension of the parent - _check_times(obj, [keys(t)...]) - end - -end - -""" - _propagate_firstlast(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) - -Propagate first and last through a component def treeIf first and last keyword -arguments are included as integers, then the object's first_free and/or last_free -flags are set to false respectively, these first and last are propagated through -and are immutable for the future (they will not vary freely with the model's -time dimension). -""" -function _propagate_firstlast!(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) +function _propagate_first_last!(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) - # set first - if !isnothing(first) && obj.first_free - obj.first_free = false - obj.first = first - end - - # set first - if !isnothing(last) && obj.last_free - obj.last_free = false - obj.last = last - end + !isnothing(first) ? obj.first = first : nothing + !isnothing(last) ? obj.last = last : nothing for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes - _propagate_firstlast!(c, first=first, last=last) + _propagate_first_last!(c, first=first, last=last) end end @@ -825,20 +784,50 @@ end Propagate a time dimension down through the comp def tree. If first and last keyword arguments are not set in a given comp def they will be set to match the -time dimension, but first_free and /or last_free flags are left as true so these -can vary with the model's time dimension in the future. +time dimension. """ function _propagate_time_dim!(obj::AbstractComponentDef, t::Dimension) set_dimension!(obj, :time, t) - obj.first_free ? obj.first = firstindex(t) : nothing - obj.last_free ? obj.last = lastindex(t) : nothing + t_first = firstindex(t) + t_last = lastindex(t) + + curr_first = obj.first + curr_last = obj.last + + # Warnings and Errors + if isa(obj, Mimi.ModelDef) + !isnothing(curr_first) && t_first > curr_first && error("Cannot redefine the time dimension to start at $t_first because it is after the model's current start $curr_first.") + !isnothing(curr_first) && curr_first > t_last && error("Cannot redefine the time dimension to end at $t_last because it is before the model's current start $curr_first") + !isnothing(curr_last) && t_last < curr_last && @warn("Redefining the time dimension to end at $t_last, which is before the model's previous end $curr_last.") + else + !isnothing(curr_first) && t_first > curr_first && error("Cannot redefine the time dimension to start at $t_first, because it is after component $(nameof(obj))'s start $curr_first.") + !isnothing(curr_first) && curr_first > t_last && error("Cannot redefine the time dimension to end at $t_last because it is before the component $(nameof(obj))'s current start $curr_first") + !isnothing(curr_last) && t_last < curr_last && @warn("Redefining the time dimension to end at $t_last, which is before component $(nameof(obj))'s end $curr_last.") + end + + # Handle First + if isnothing(curr_first) || isa(obj, Mimi.ModelDef) # if we are working with an unset attribute or a ModelDef we always want to set first + obj.first = t_first + elseif t_first > curr_first # working with a component so we only want to move it if we're moving first forward (currently warns but maybe should be an error) + obj.first = t_first + end + + # Handle Last + if isnothing(curr_last) || isa(obj, Mimi.ModelDef) + obj.last = t_last + elseif t_last < curr_last # working with a component so we only want to move it if we're moving last back (currently also warns but maybe should be an error) + obj.last = t_last + end for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes _propagate_time_dim!(c, t) end + # run over the object and check that first and last are within the time + # dimension of the parent + _check_times(obj, [keys(t)...]) end """ @@ -850,10 +839,10 @@ function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) first_index = findfirst(isequal(obj.first), parent_time_keys) - isnothing(first_index) ? error("The first index ($(obj.first)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys.") : nothing + isnothing(first_index) ? error("The first index ($(obj.first)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys. Try explicitly updating component's first and last with `set_first_last!` before redefinint time dimension.") : nothing last_index = findfirst(isequal(obj.last), parent_time_keys) - isnothing(last_index) ? error("The last index ($(obj.last)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys.") : nothing + isnothing(last_index) ? error("The last index ($(obj.last)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys. Try explicitly updating component's first and last with `set_first_last!` before redefinint time dimension.") : nothing for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes _check_times(c, parent_time_keys[first_index:last_index]) @@ -867,8 +856,6 @@ end comp_name::Symbol=comp_def.comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - first_free::Bool=true, - last_free::Bool=true, before::NothingSymbol=nothing, after::NothingSymbol=nothing, rename::NothingPairList=nothing @@ -887,8 +874,6 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_name::Symbol=comp_def.comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - first_free::Bool=true, - last_free::Bool=true, before::NothingSymbol=nothing, after::NothingSymbol=nothing, rename::NothingPairList=nothing) # TBD: rename is not yet implemented @@ -905,13 +890,18 @@ function add_comp!(obj::AbstractCompositeComponentDef, comp_def.name = comp_name parent!(comp_def, obj) - # Handle time dimension for the copy, leave the time unset for the original - # component template - note that if the obj does not yet have a :time dimension - # set we can still set first and last, which is useful for calls to Component within - # the @defcomposite macro producing add_comp! calls - has_dim(obj, :time) ? t = dimension(obj, :time) : t = nothing - propagate_time!(comp_def, t = t, first=first, last=last) + # Handle time dimension for the component and leaving the time unset for the + # original component template + # (1) Propagate the give first and last through the component + # (2) If the obj has a time dimension propgagate this through the component + _propagate_first_last!(comp_def; first = first, last = last) + isa(obj, ModelDef) && !has_dim(obj, :time) && error("Cannot add a component to a Model without first setting the :time dimension") + if has_dim(obj, :time) + t = dimension(obj, :time) + _propagate_time_dim!(comp_def, t) + end + # Add dims and insert comp _add_anonymous_dims!(obj, comp_def) _insert_comp!(obj, comp_def, before=before, after=after) @@ -926,8 +916,6 @@ end comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - first_free::Bool=true, - last_free::Bool=true, before::NothingSymbol=nothing, after::NothingSymbol=nothing, rename::NothingPairList=nothing diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index ca75c2494..421be3e5e 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -86,30 +86,22 @@ Set the values of `ccd` dimension `name` to integers 1 through `count`, if `keys an integer; or to the values in the vector or range if `keys` is either of those types. """ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys::Union{Int, Vector, Tuple, AbstractRange}) + redefined = has_dim(ccd, name) - # if redefined - # @warn "Redefining dimension :$name" - # end - dim = Dimension(keys) if name == :time - # check to make sure if we are setting time dimension for the Model that - # it doesn't start after, or end before, any of the components. Note that - # here we can dependent on the invariant that all subcomponents of a composite - # component have the same first and last bounds - ccd_first = [keys...][1] - ccd_last = [keys...][end] - for subcomp in compdefs(ccd) - subcomp.first_free || ccd_first > subcomp.first && error("Top time dimension must end after or at same time as all it's subcomponents, but $(ccd_first) is after $(subcomp.first).") - subcomp.last_free || ccd_last < subcomp.last && error("Top time dimension must start before or at same time as all it's subcomponents, but $(ccd_last) is before $(subcomp.last).") - end - - propagate_time!(ccd, t = dim) + # propagate the time dimension through all sub-components + _propagate_time_dim!(ccd, dim) set_uniform!(ccd, isuniform(keys)) - end + + if redefined + + # TODO: pad all parameters with a time dimension + end + end return set_dimension!(ccd, name, dim) end diff --git a/src/core/model.jl b/src/core/model.jl index 21ccbd34d..d71b178c5 100644 --- a/src/core/model.jl +++ b/src/core/model.jl @@ -127,8 +127,6 @@ is deprecated, but temporarily remains as a dummy argument to allow warning dete m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - first_free::Bool=true, - last_free::Bool=true, before::NothingSymbol=nothing, after::NothingSymbol=nothing, rename::NothingPairList=nothing @@ -152,8 +150,6 @@ end m::Model, comp_def::AbstractComponentDef, comp_name::Symbol=comp_id.comp_name; first::NothingInt=nothing, last::NothingInt=nothing, - first_free::Bool=true, - last_free::Bool=true, before::NothingSymbol=nothing, after::NothingSymbol=nothing, rename::NothingPairList=nothing diff --git a/src/core/types/defs.jl b/src/core/types/defs.jl index feb80c072..efb029e2b 100644 --- a/src/core/types/defs.jl +++ b/src/core/types/defs.jl @@ -42,8 +42,6 @@ end namespace::OrderedDict{Symbol, Any} first::Union{Nothing, Int} last::Union{Nothing, Int} - first_free::Bool - last_free::Bool is_uniform::Bool # Store a reference to the AbstractCompositeComponent that contains this comp def. @@ -75,7 +73,6 @@ end self.dim_dict = OrderedDict{Symbol, Union{Nothing, Dimension}}() self.namespace = OrderedDict{Symbol, Any}() self.first = self.last = nothing - self.first_free = self.last_free = true self.is_uniform = true self.parent = nothing return self diff --git a/test/test_model_structure.jl b/test/test_model_structure.jl index db68b56eb..1c26c7ea0 100644 --- a/test/test_model_structure.jl +++ b/test/test_model_structure.jl @@ -42,9 +42,9 @@ end m = Model() -# TBD: This is not necessarily an error with composites. -# make sure you can't add a component before setting time dimension -# @test_throws ErrorException add_comp!(m, A) +# make sure you can't add a component before setting time dimension (only true for +# adding a component to a model, not adding to a composite component) +@test_throws ErrorException add_comp!(m, A) set_dimension!(m, :time, 2015:5:2100) diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index cd5f04021..5611cc508 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -136,26 +136,32 @@ set_dimension!(m, :time, 2000:2004) add_comp!(m, MyComp2, first=2001, last=2003) set_param!(m, :MyComp2, :x, [1, 2, 3, 4, 5]) -@test_throws ErrorException set_dimension!(m, :time, 2002:2004) # model starts after component -@test_throws ErrorException set_dimension!(m, :time, 2000:2002) # model ends before component +# Year x Model MyComp2 +# 2000 1 first +# 2001 2 first +# 2002 3 +# 2003 4 last +# 2004 5 last -# these tests specifically look for the case of updating a TimestepArray's data -# without time labels, thus simply calling param.values.data = new_data ... the addition -# of SubArray to data's type options made it necessary to convert types explicitly -# which may be causing a performance hit update_param!(m, :x, [2.,3.,4.,5.,6.]) update_param!(m, :x, zeros(5)) update_param!(m, :x, [1,2,3,4,5]) -set_dimension!(m, :time, 2001:2003) +set_dimension!(m, :time, 1999:2001) + +# Year x Model MyComp2 +# 1999 2 first +# 2000 3 first +# 2001 4 last last + update_param!(m, :x, [2, 3, 4]) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{2001, 1, 2003}, Union{Missing,Float64}, 1} +@test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999, 1, 2001}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] run(m) -@test m[:MyComp2, :y][1] == 2 # 2001 -@test m[:MyComp2, :y][2] == 3 # 2002 - +@test ismissing(m[:MyComp2, :y][1]) # 1999 +@test ismissing(m[:MyComp2, :y][2]) # 2000 +@test m[:MyComp2, :y][3] == 4 # 2001 # 2. Test with Variable Timesteps @@ -164,16 +170,16 @@ set_dimension!(m, :time, [2000, 2005, 2020]) add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) -set_dimension!(m, :time, [2005, 2020, 2050]) +set_dimension!(m, :time, [2000, 2020, 2050]) update_param!(m, :x, [2, 3, 4]) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Union{Missing,Float64}, 1} +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2020, 2050)}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] run(m) -@test m[:MyComp2, :y][1] == 2 # 2005 +@test m[:MyComp2, :y][1] == 2 # 2000 @test m[:MyComp2, :y][2] == 3 # 2020 - +@test ismissing(m[:MyComp2, :y][3]) # 2050 - past last attribute for component # 3. Test updating from a dictionary @@ -182,16 +188,17 @@ set_dimension!(m, :time, [2000, 2005, 2020]) add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) -set_dimension!(m, :time, [2005, 2020, 2050]) +set_dimension!(m, :time, [2000, 2020, 2050]) update_params!(m, Dict(:x=>[2, 3, 4])) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2005, 2020, 2050)}, Union{Missing,Float64}, 1} +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2020, 2050)}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] run(m) + @test m[:MyComp2, :y][1] == 2 # 2005 @test m[:MyComp2, :y][2] == 3 # 2020 -@test m[:MyComp2, :y][3] == 4 # 2050 +@test ismissing(m[:MyComp2, :y][3]) # 2050 # 4. Test updating the time index to a different length @@ -209,7 +216,9 @@ x = external_param(m.md, :x) @test x.values.data == [2., 3., 4., 5., 6.] run(m) -@test m[:MyComp2, :y] == [2., 3., 4., 5., 6.] +@test ismissing(m[:MyComp2, :y][1]) +@test m[:MyComp2, :y][2:4] == [3., 4., 5.] +@test ismissing(m[:MyComp2, :y][5]) # 5. Test all the warning and error cases @@ -235,11 +244,11 @@ update_param!(m, :z, 1) @test external_param(m.md, :z).value == 1 # Reset the time dimensions -set_dimension!(m, :time, 2005:2007) +set_dimension!(m, :time, 1999:2001) update_params!(m, Dict(:x=>[3,4,5], :y=>[10,20], :z=>0)) # Won't error when updating from a dictionary -@test external_param(m.md, :x).values isa Mimi.TimestepArray{Mimi.FixedTimestep{2005,1, 2007},Union{Missing,Float64},1} +@test external_param(m.md, :x).values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999,1, 2001},Union{Missing,Float64},1} @test external_param(m.md, :x).values.data == [3.,4.,5.] @test external_param(m.md, :y).values == [10.,20.] @test external_param(m.md, :z).value == 0 From 303a6ca97851a2914e41586a5dd7a08bde7af130 Mon Sep 17 00:00:00 2001 From: lrennels Date: Mon, 12 Apr 2021 18:41:57 -0700 Subject: [PATCH 02/21] Add notes on failing tests --- test/runtests.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/runtests.jl b/test/runtests.jl index 00f10e7d0..e9ec0c3ff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -78,7 +78,7 @@ Electron.prep_test_env() @time include("test_getindex_variabletimestep.jl") @info("test_components.jl") - @time include("test_components.jl") + @time include("test_components.jl") # TODO broken @info("test_variables_model_instance.jl") @time include("test_variables_model_instance.jl") @@ -92,13 +92,13 @@ Electron.prep_test_env() @info("test_clock.jl") @time include("test_clock.jl") - @info("test_timesteps.jl") + @info("test_timesteps.jl") # TODO broken @time include("test_timesteps.jl") @info("test_timesteparrays.jl") @time include("test_timesteparrays.jl") - @info("test_dimensions.jl") + @info("test_dimensions.jl") # TODO broken @time include("test_dimensions.jl") @info("test_datum_storage.jl") @@ -107,7 +107,7 @@ Electron.prep_test_env() @info("test_connectorcomp.jl") @time include("test_connectorcomp.jl") - @info("test_firstlast.jl") + @info("test_firstlast.jl") # TODO broken @time include("test_firstlast.jl") @info("test_explorer_model.jl") From 5d1c7d472bf10fb3cda7d015ee0ab7acbe9155a5 Mon Sep 17 00:00:00 2001 From: lrennels Date: Mon, 12 Apr 2021 22:34:37 -0700 Subject: [PATCH 03/21] Add tests --- src/core/defs.jl | 39 +++++++++++++++++-- test/runtests.jl | 2 +- test/test_components.jl | 64 +++++++++++++++--------------- test/test_dimensions.jl | 6 ++- test/test_firstlast.jl | 47 +++++++++++++++++----- test/test_parametertypes.jl | 78 +++++++++++++++++++++++++++++++------ 6 files changed, 177 insertions(+), 59 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index 5e52c6a2d..6587a7d9c 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -839,16 +839,41 @@ function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) first_index = findfirst(isequal(obj.first), parent_time_keys) - isnothing(first_index) ? error("The first index ($(obj.first)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys. Try explicitly updating component's first and last with `set_first_last!` before redefinint time dimension.") : nothing + isnothing(first_index) ? error("The first index ($(obj.first)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys.") : nothing last_index = findfirst(isequal(obj.last), parent_time_keys) - isnothing(last_index) ? error("The last index ($(obj.last)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys. Try explicitly updating component's first and last with `set_first_last!` before redefinint time dimension.") : nothing + isnothing(last_index) ? error("The last index ($(obj.last)) of component $(nameof(obj)) must exist within its model's time dimension $parent_time_keys.") : nothing for c in compdefs(obj) # N.B. compdefs returns empty list for leaf nodes _check_times(c, parent_time_keys[first_index:last_index]) end end + +function _check_first_last(obj::Union{Model, ModelDef}; first::NothingInt = nothing, last::NothingInt = nothing) + times = time_labels(obj) + !isnothing(first) && isnothing(findfirst(isequal(first), times)) && error("The first index ($first) must exist within the model's time dimension $times.") + !isnothing(last) && isnothing(findfirst(isequal(last), times)) && error("The last index ($last) must exist within the model's time dimension $times") +end +""" + function set_first_last!(obj::AbstractCompositeComponentDef, comp_name::Symbol; first::NothingInt=nothing, last::NothingInt=nothing) + + Set the `first` and/or `last` attributes of model `obj`'s component `comp_name`, + after it has been added to the model. This will propagate the `first` and `last` + through any subcomponents of `comp_name` as well. Note that this will override + any previous `first` and `last` settings. + """ + function set_first_last!(obj::Model, comp_name::Symbol; first::NothingInt=nothing, last::NothingInt=nothing) + !has_comp(obj, comp_name) && error("Model does not contain a component named $comp_name") + + _check_first_last(obj, first = first, last = last) + + comp_def = compdef(obj, comp_name) + _propagate_first_last!(comp_def, first=first, last=last) + + dirty!(comp_def) +end + """ add_comp!( obj::AbstractCompositeComponentDef, @@ -892,9 +917,15 @@ function add_comp!(obj::AbstractCompositeComponentDef, # Handle time dimension for the component and leaving the time unset for the # original component template - # (1) Propagate the give first and last through the component - # (2) If the obj has a time dimension propgagate this through the component + + # (1) Propagate the first and last from the add_comp! call through the component (default to nothing) + if has_dim(obj, :time) + _check_first_last(obj, first = first, last = last) + end _propagate_first_last!(comp_def; first = first, last = last) + + # (2) If the obj has a time dimension propgagate this through the component, + # which also sets remaining first and last to match the time dimension. isa(obj, ModelDef) && !has_dim(obj, :time) && error("Cannot add a component to a Model without first setting the :time dimension") if has_dim(obj, :time) t = dimension(obj, :time) diff --git a/test/runtests.jl b/test/runtests.jl index e9ec0c3ff..1a2cfbd67 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -78,7 +78,7 @@ Electron.prep_test_env() @time include("test_getindex_variabletimestep.jl") @info("test_components.jl") - @time include("test_components.jl") # TODO broken + @time include("test_components.jl") @info("test_variables_model_instance.jl") @time include("test_variables_model_instance.jl") diff --git a/test/test_components.jl b/test/test_components.jl index e9951bf63..7f2df64af 100644 --- a/test/test_components.jl +++ b/test/test_components.jl @@ -5,7 +5,8 @@ using Test import Mimi: compdefs, compdef, compkeys, has_comp, first_period, - last_period, compmodule, compname, compinstance, dim_keys, dim_values + last_period, compmodule, compname, compinstance, dim_keys, dim_values, + set_first_last! my_model = Model() @@ -46,6 +47,9 @@ end end end +# Testing that you cannot add a component without a time dimension +@test_throws ErrorException add_comp!(my_model, testcomp1) + # Start building up the model set_dimension!(my_model, :time, 2015:5:2110) add_comp!(my_model, testcomp1) @@ -104,34 +108,34 @@ end # 1. Test resetting the time dimension without explicit first/last values -cd = testcomp1 -@test cd.first === nothing # original component definition's first and last values are unset -@test cd.last === nothing +comp_def = testcomp1 +@test comp_def.first === nothing # original component definition's first and last values are unset +@test comp_def.last === nothing m = Model() set_dimension!(m, :time, 2001:2005) add_comp!(m, testcomp1, :C) # Don't set the first and last values here -cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first === 2001 -@test cd.last === 2005 +comp_def = compdef(m.md, :C) # Get the component definition in the model +@test comp_def.first === 2001 +@test comp_def.last === 2005 set_param!(m, :C, :par1, zeros(5)) -Mimi.build!(m) # Build the model -ci = compinstance(m, :C) # Get the component instance -@test ci.first == 2001 # The component instance's first and last values should match the model's index -@test ci.last == 2005 +Mimi.build!(m) # Build the model +ci = compinstance(m, :C) # Get the component instance +@test ci.first == 2001 && ci.last == 2005 # no change -set_dimension!(m, :time, 2005:2020) # Reset the time dimension -cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first === 2005 -@test cd.last === 2020 +set_dimension!(m, :time, 2000:2020) # Reset the time dimension +comp_def = compdef(m.md, :C) # Get the component definition in the model +@test comp_def.first === 2001 && comp_def.last === 2005 # no change -update_param!(m, :par1, zeros(16)) +update_param!(m, :par1, zeros(21)) Mimi.build!(m) # Build the model -ci = compinstance(m, :C) # Get the component instance -@test ci.first == 2005 # The component instance's first and last values should match the model's index -@test ci.last == 2020 +ci = compinstance(m, :C) # Get the component instance +@test ci.first == 2001 && ci.last == 2005 # no change +set_first_last!(m, :C, first = 2000, last = 2020) +comp_def = compdef(m.md, :C) # Get the component definition in the model +@test comp_def.first == 2000 && comp_def.last == 2020 # change! # 2. Test resetting the time dimension with explicit first/last values @@ -139,24 +143,22 @@ m = Model() set_dimension!(m, :time, 2000:2100) add_comp!(m, testcomp1, :C; first = 2010, last = 2090) -cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first == 2010 # First and last values are defined in the comp def because they were explicitly given -@test cd.last == 2090 +comp_def = compdef(m.md, :C) # Get the component definition in the model +@test comp_def.first == 2010 && comp_def.last == 2090 -set_dimension!(m, :time, 2010:2090) -set_param!(m, :C, :par1, zeros(81)) +set_dimension!(m, :time, 1950:2090) +set_param!(m, :C, :par1, zeros(141)) Mimi.build!(m) # Build the model ci = compinstance(m, :C) # Get the component instance -@test ci.first == 2010 # The component instance's first and last values are the same as in the comp def -@test ci.last == 2090 +@test ci.first == 2010 && ci.last == 2090 # The component instance's first and last values are the same as in the comp def -set_dimension!(m, :time, 2000:2200) # Reset the time dimension -update_param!(m, :par1, zeros(201)) # Have to reset the parameter to have the same width as the model time dimension +set_dimension!(m, :time, 1940:2200) # Reset the time dimension +update_param!(m, :par1, zeros(261)) # Have to reset the parameter to have the same width as the model time dimension -cd = compdef(m.md, :C) # Get the component definition in the model -@test cd.first == 2010 # First and last values should still be the same -@test cd.last == 2090 +comp_def = compdef(m.md, :C) # Get the component definition in the model +@test comp_def.first == 2010 # First and last values should still be the same +@test comp_def.last == 2090 Mimi.build!(m) # Build the model ci = compinstance(m, :C) # Get the component instance diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index b98f97d48..fb6228f9c 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -99,9 +99,9 @@ my_foo2 = compdef(foo2_ref) @test_throws ErrorException set_param!(m, :foo2, :x, 2005:2095) # too short set_param!(m, :foo2, :x, 2000:2100) #Shouldn't error -set_dimension!(m, :time, 2010:2050) +set_dimension!(m, :time, 1990:2050) -@test first_period(m.md) == 2010 +@test first_period(m.md) == 1990 @test last_period(m.md) == 2050 @@ -128,7 +128,9 @@ add_comp!(m, bar) run(m) @test m[:bar, :v1] == fixed_years +m = Model() set_dimension!(m, :time, variable_years) +add_comp!(m, bar) run(m) @test m[:bar, :v1] == variable_years diff --git a/test/test_firstlast.jl b/test/test_firstlast.jl index ae5d3bbc5..d9cfbac30 100644 --- a/test/test_firstlast.jl +++ b/test/test_firstlast.jl @@ -3,7 +3,7 @@ module TestFirstLast using Mimi using Test -import Mimi: time_labels +import Mimi: time_labels, set_first_last! # # Define some Components @@ -84,10 +84,10 @@ set_dimension!(m, :time, collect(2015:5:2115)) # check that the first, last, and time have been updated properly for both the # ModelDef and ComponentDef(s) @test collect(2015:5:2115) == time_labels(m.md) == [keys(m.md.namespace[:emissions].dim_dict[:time])...] == [keys(m.md.namespace[:grosseconomy].dim_dict[:time])...] -@test m.md.first == m.md.namespace[:grosseconomy].first # grosseconomy first and last vary with model limits -@test m.md.last == m.md.namespace[:grosseconomy].last # grosseconomy first and last vary with model limits -@test m.md.namespace[:emissions].first == 2020 # emissions first and last are fixed -@test m.md.namespace[:emissions].last == 2105 # emissions first and last are fixed +@test m.md.namespace[:grosseconomy].first == 2015 # same as original model dim +@test m.md.namespace[:grosseconomy].last == 2110 # same as original model dim +@test m.md.namespace[:emissions].first == 2020 # explicitly set +@test m.md.namespace[:emissions].last == 2105 # explicitly set # reset any parameters that have a time dimension update_param!(m, :l, [(1. + 0.015)^t *6404 for t in 1:21]) @@ -100,8 +100,8 @@ run(m) # test that there are missing values in :emissions variables outside of the component's # run period, and no missing values in the :grosseconomy variables @test ismissing(m[:emissions, :E][1]) - @test sum(ismissing.(m[:emissions, :E][20:21])) == 2 - @test sum(ismissing.(m[:emissions, :E][2:19])) == 0 +@test sum(ismissing.(m[:emissions, :E][20:21])) == 2 +@test sum(ismissing.(m[:emissions, :E][2:19])) == 0 @test sum(ismissing.(m[:grosseconomy, :l])) == 0 # @@ -416,10 +416,39 @@ set_param!(m, :par_1_1, collect(1:length(time_labels(m)))) run(m) # check that first and last moved through properly -@test m.md.first == m.md.namespace[:top].first == m.md.namespace[:top].namespace[:A].first == m.md.namespace[:top].namespace[:A].namespace[:Comp1].first== 2005 -@test m.md.last == m.md.namespace[:top].last == m.md.namespace[:top].namespace[:A].last ==m.md.namespace[:top].namespace[:A].namespace[:Comp1].last == 2020 +@test m.md.namespace[:top].first == m.md.namespace[:top].namespace[:A].first == m.md.namespace[:top].namespace[:A].namespace[:Comp1].first== 2005 +@test m.md.namespace[:top].last == m.md.namespace[:top].namespace[:A].last ==m.md.namespace[:top].namespace[:A].namespace[:Comp1].last == 2020 @test m.md.namespace[:top].namespace[:B].first == m.md.namespace[:top].namespace[:B].namespace[:Comp3].first== 2010 @test m.md.namespace[:top].namespace[:B].last ==m.md.namespace[:top].namespace[:B].namespace[:Comp3].last == 2015 + + # + # Test set_first_last! function + # + + m = Model() + set_dimension!(m, :time, collect(2015:5:2110)) # 20 timesteps + add_comp!(m, grosseconomy) + add_comp!(m, emissions) + + # check that the attributes of the ModelDef and ComponentDef(s) have been set + # as expected + @test collect(2015:5:2110) == time_labels(m.md) == [keys(m.md.namespace[:emissions].dim_dict[:time])...] == [keys(m.md.namespace[:grosseconomy].dim_dict[:time])...] + @test m.md.first == m.md.namespace[:grosseconomy].first == m.md.namespace[:emissions].first + @test m.md.last == m.md.namespace[:grosseconomy].last == m.md.namespace[:emissions].last + + # now set the emissions first and last and check + set_first_last!(m, :emissions, first = 2020, last = 2105) + + @test collect(2015:5:2110) == time_labels(m.md) == [keys(m.md.namespace[:emissions].dim_dict[:time])...] == [keys(m.md.namespace[:grosseconomy].dim_dict[:time])...] + @test m.md.first == m.md.namespace[:grosseconomy].first + @test m.md.last == m.md.namespace[:grosseconomy].last + @test m.md.namespace[:emissions].first == 2020 + @test m.md.namespace[:emissions].last == 2105 + + # check warnings + @test_throws ErrorException set_first_last!(m, :grosseconomy, first = 2000) # too early + @test_throws ErrorException set_first_last!(m, :grosseconomy, last = 3000) + end #module diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index 5611cc508..aacc31336 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -5,7 +5,8 @@ using Test import Mimi: external_params, external_param, TimestepMatrix, TimestepVector, - ArrayModelParameter, ScalarModelParameter, FixedTimestep, import_params! + ArrayModelParameter, ScalarModelParameter, FixedTimestep, import_params!, + set_first_last! # # Test that parameter type mismatches are caught @@ -104,7 +105,7 @@ set_param!(m, :a, Array{Int,2}(zeros(101, 3))) # should be able to convert from update_param!(m, :d, 5) # should work, will convert to float new_extpars = external_params(m) # Since there are changes since the last build, need to access the updated dictionary in the model definition @test extpars[:d].value == 0.5 # The original dictionary still has the old value -@test new_extpars[:d].value == 5. # The new dicitonary has the updated value +@test new_extpars[:d].value == 5. # The new dictionary has the updated value @test_throws ErrorException update_param!(m, :e, 5) # wrong type; should be array @test_throws ErrorException update_param!(m, :e, ones(10)) # wrong size update_param!(m, :e, [4,5,6,7]) @@ -129,13 +130,12 @@ update_param!(m, :e, [4,5,6,7]) end end -# 1. Test with Fixed Timesteps +# 1. update_param! with Fixed Timesteps m = Model() set_dimension!(m, :time, 2000:2004) add_comp!(m, MyComp2, first=2001, last=2003) set_param!(m, :MyComp2, :x, [1, 2, 3, 4, 5]) - # Year x Model MyComp2 # 2000 1 first # 2001 2 first @@ -148,13 +148,12 @@ update_param!(m, :x, zeros(5)) update_param!(m, :x, [1,2,3,4,5]) set_dimension!(m, :time, 1999:2001) - +update_param!(m, :x, [2, 3, 4]) # updates the time labels # Year x Model MyComp2 # 1999 2 first -# 2000 3 first -# 2001 4 last last +# 2000 3 +# 2001 4 last first, last -update_param!(m, :x, [2, 3, 4]) x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999, 1, 2001}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] @@ -163,16 +162,33 @@ run(m) @test ismissing(m[:MyComp2, :y][2]) # 2000 @test m[:MyComp2, :y][3] == 4 # 2001 +set_first_last!(m, :MyComp2, first = 1999, last = 2001) +# Year x Model MyComp2 +# 1999 2 first first +# 2000 3 +# 2001 4 last last + +run(m) +@test m[:MyComp2, :y] == [2, 3, 4] + # 2. Test with Variable Timesteps m = Model() set_dimension!(m, :time, [2000, 2005, 2020]) add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) +# Year x Model MyComp2 +# 2000 1 first first +# 2005 2 +# 2010 3 last last set_dimension!(m, :time, [2000, 2020, 2050]) - update_param!(m, :x, [2, 3, 4]) +# Year x Model MyComp2 +# 2000 1 first first +# 2020 2 last +# 2050 3 last + x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2020, 2050)}, Union{Missing,Float64}, 1} @test x.values.data == [2., 3., 4.] @@ -181,6 +197,15 @@ run(m) @test m[:MyComp2, :y][2] == 3 # 2020 @test ismissing(m[:MyComp2, :y][3]) # 2050 - past last attribute for component +set_first_last!(m, :MyComp2, first = 2000, last = 2050) +# Year x Model MyComp2 +# 2000 1 first first +# 2020 2 +# 2050 3 last last + +run(m) +@test m[:MyComp2, :y] == [2., 3., 4.] + # 3. Test updating from a dictionary m = Model() @@ -200,17 +225,26 @@ run(m) @test m[:MyComp2, :y][2] == 3 # 2020 @test ismissing(m[:MyComp2, :y][3]) # 2050 - # 4. Test updating the time index to a different length m = Model() set_dimension!(m, :time, 2000:2002) # length 3 add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) +# Year x Model MyComp2 +# 2000 1 first first +# 2001 2 +# 2002 3 last last set_dimension!(m, :time, 1999:2003) # length 5 - update_param!(m, :x, [2, 3, 4, 5, 6]) +# Year x Model MyComp2 +# 1999 2 first +# 2000 3 first +# 2001 4 +# 2002 5 last +# 2003 6 last + x = external_param(m.md, :x) @test x.values isa Mimi.TimestepArray{Mimi.FixedTimestep{1999, 1, 2003}, Union{Missing, Float64}, 1, 1} @test x.values.data == [2., 3., 4., 5., 6.] @@ -220,6 +254,19 @@ run(m) @test m[:MyComp2, :y][2:4] == [3., 4., 5.] @test ismissing(m[:MyComp2, :y][5]) +set_first_last!(m, :MyComp2, first = 1999, last = 2001) +# Year x Model MyComp2 +# 1999 2 first first +# 2000 3 +# 2001 4 last +# 2002 5 +# 2003 6 last + +run(m) +@test ismissing(m[:MyComp2, :y][4]) +@test ismissing(m[:MyComp2, :y][5]) +@test m[:MyComp2, :y][1:3] == [2., 3., 4.] + # 5. Test all the warning and error cases @defcomp MyComp3 begin @@ -253,12 +300,19 @@ update_params!(m, Dict(:x=>[3,4,5], :y=>[10,20], :z=>0)) # Won't error when upda @test external_param(m.md, :y).values == [10.,20.] @test external_param(m.md, :z).value == 0 +# time dimension errors +m = Model() +set_dimension!(m, :time, 2000:2005) +add_comp!(m, MyComp2) +@test_throws ErrorException set_dimension!(m, :time, 2001:2005) # move model start forward +@test_throws ErrorException set_dimension!(m, :time, 1800:1810) # model last before component first +@test_throws ErrorException set_first_last!(m, :MyComp2, first = 1999) # first not in time dim keys +@test_throws ErrorException set_first_last!(m, :MyComp2, last = 2006) # last not in time dim keys #------------------------------------------------------------------------------ # Test the three different set_param! methods for a Symbol type parameter #------------------------------------------------------------------------------ - @defcomp A begin p1 = Parameter{Symbol}() end From d64e1d922fba3398ec9478b103dd76df0824da87 Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 13 Apr 2021 17:51:53 -0700 Subject: [PATCH 04/21] Cleanup --- src/core/defs.jl | 20 ++++++++++++++------ test/runtests.jl | 6 +++--- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/core/defs.jl b/src/core/defs.jl index 6587a7d9c..d6c262a39 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -765,9 +765,10 @@ function _insert_comp!(obj::AbstractCompositeComponentDef, comp_def::AbstractCom end """ - _propagate_first_last(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) + _propagate_first_last!(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) -Propagate first and/or last through a component def tree. +Propagate first and/or last through a component def tree. This function will override +any first and last that have been previously set. """ function _propagate_first_last!(obj::AbstractComponentDef; first::NothingInt=nothing, last::NothingInt=nothing) @@ -810,14 +811,14 @@ function _propagate_time_dim!(obj::AbstractComponentDef, t::Dimension) # Handle First if isnothing(curr_first) || isa(obj, Mimi.ModelDef) # if we are working with an unset attribute or a ModelDef we always want to set first obj.first = t_first - elseif t_first > curr_first # working with a component so we only want to move it if we're moving first forward (currently warns but maybe should be an error) + elseif t_first > curr_first # working with a component so we only want to move it if we're moving first forward (currently unreachable b/c error caught above) obj.first = t_first end # Handle Last if isnothing(curr_last) || isa(obj, Mimi.ModelDef) obj.last = t_last - elseif t_last < curr_last # working with a component so we only want to move it if we're moving last back (currently also warns but maybe should be an error) + elseif t_last < curr_last # working with a component so we only want to move it if we're moving last back (currently unreachable b/c caught error above) obj.last = t_last end @@ -833,7 +834,7 @@ end """ function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) - Check that all first and last times are properly contained within a comp_def + Check that all first and last times exist within contained within a comp_def `obj`'s parent time keys `parent_time_keys`. """ function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) @@ -850,11 +851,18 @@ function _check_times(obj::AbstractComponentDef, parent_time_keys::Array) end +""" +function _check_first_last(obj::Union{Model, ModelDef}; first::NothingInt = nothing, last::NothingInt = nothing) + + Check that all first and last times are properly contained within a comp_def + `obj`'s time labels. +""" function _check_first_last(obj::Union{Model, ModelDef}; first::NothingInt = nothing, last::NothingInt = nothing) times = time_labels(obj) !isnothing(first) && isnothing(findfirst(isequal(first), times)) && error("The first index ($first) must exist within the model's time dimension $times.") !isnothing(last) && isnothing(findfirst(isequal(last), times)) && error("The last index ($last) must exist within the model's time dimension $times") end + """ function set_first_last!(obj::AbstractCompositeComponentDef, comp_name::Symbol; first::NothingInt=nothing, last::NothingInt=nothing) @@ -920,7 +928,7 @@ function add_comp!(obj::AbstractCompositeComponentDef, # (1) Propagate the first and last from the add_comp! call through the component (default to nothing) if has_dim(obj, :time) - _check_first_last(obj, first = first, last = last) + _check_first_last(obj, first = first, last = last) # check that the first and last fall in the obj's time labels end _propagate_first_last!(comp_def; first = first, last = last) diff --git a/test/runtests.jl b/test/runtests.jl index 1a2cfbd67..00f10e7d0 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -92,13 +92,13 @@ Electron.prep_test_env() @info("test_clock.jl") @time include("test_clock.jl") - @info("test_timesteps.jl") # TODO broken + @info("test_timesteps.jl") @time include("test_timesteps.jl") @info("test_timesteparrays.jl") @time include("test_timesteparrays.jl") - @info("test_dimensions.jl") # TODO broken + @info("test_dimensions.jl") @time include("test_dimensions.jl") @info("test_datum_storage.jl") @@ -107,7 +107,7 @@ Electron.prep_test_env() @info("test_connectorcomp.jl") @time include("test_connectorcomp.jl") - @info("test_firstlast.jl") # TODO broken + @info("test_firstlast.jl") @time include("test_firstlast.jl") @info("test_explorer_model.jl") From afb4e43d23b9fbf76df895f75b9663b5b324d70a Mon Sep 17 00:00:00 2001 From: lrennels Date: Fri, 16 Apr 2021 23:34:04 -0700 Subject: [PATCH 05/21] Pad parameters when time dimension is redefined --- src/core/connections.jl | 67 ++++++++++++++++++++++++++++++++++ src/core/defs.jl | 19 ++-------- src/core/dimensions.jl | 60 +++++++++++++++++++++++++++--- test/runtests.jl | 2 +- test/test_parametertypes.jl | 73 ++++++++++++++++++++++--------------- 5 files changed, 171 insertions(+), 50 deletions(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index f4d840f67..2f04cea61 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -650,3 +650,70 @@ function add_connector_comps!(obj::AbstractCompositeComponentDef) return nothing end + +function _pad_parameters!(obj::ModelDef) + + model_times = time_labels(obj) + + for (name, param) in obj.external_params + if (param isa ArrayModelParameter) && (:time in param.dim_names) + + param_times = _get_param_times(param) + padded_data = _get_padded_data(param, param_times, model_times) + update_param!(obj, name, padded_data) + + end + end +end + +function _get_padded_data(param::ArrayModelParameter, param_times::Vector{Int64}, model_times::Vector{Int64}) + + data = param.values.data + ti = get_time_index_position(param) + + # first handle the back end + model_last = last(model_times) + param_last = last(param_times) + + if model_last < param_last # trim down the data + + trim_idx = findfirst(isequal(last(model_times)), param_times) + idxs = repeat(Any[:], ndims(data)) + idxs[ti] = 1:trim_idx + data = data[idxs...] + + elseif model_last > param_last # pad the data + + pad_length = length(model_times[findfirst(isequal(param_last), model_times)+1:end]) + dims = [size(data)...] + dims[ti] = pad_length + end_padding_rows = Array{Union{Missing, Number}}(missing, dims...) + data = vcat(data, end_padding_rows) + + end + + # now handle the front end + model_first = first(model_times) + param_first = first(param_times) + + # note we do not allow for any trimming off the front end + if model_first < param_first + + pad_length = length(model_times[1:findfirst(isequal(param_first), model_times)-1]) + dims = [size(data)...] + dims[ti] = pad_length + begin_padding_rows = Array{Union{Missing, Number}}(missing, dims...) + data = vcat(begin_padding_rows, data) + + end + + return data +end + +function _get_param_times(param::ArrayModelParameter{TimestepArray{FixedTimestep{FIRST, STEP, LAST}, T, N, ti, S}}) where {FIRST, STEP, LAST, T, N, ti, S} + return collect(FIRST:STEP:LAST) +end + +function _get_param_times(param::ArrayModelParameter{TimestepArray{VariableTimestep{TIMES}, T, N, ti, S}}) where {TIMES, T, N, ti, S} + return [TIMES...] +end diff --git a/src/core/defs.jl b/src/core/defs.jl index d6c262a39..14d1d14f1 100644 --- a/src/core/defs.jl +++ b/src/core/defs.jl @@ -439,8 +439,8 @@ function set_param!(md::ModelDef, comp_def::AbstractComponentDef, param_name::Sy if has_parameter(md, ext_param_name) error("Cannot set parameter :$ext_param_name, the model already has an external parameter with this name.", - " Use `update_param(m, param_name, value)` to change the value, or use ", - "`set_param(m, comp_name, param_name, unique_param_name, value)` to set a value for only this component.") + " Use `update_param!(m, param_name, value)` to change the value, or use ", + "`set_param!(m, comp_name, param_name, unique_param_name, value)` to set a value for only this component.") end set_param!(md, param_name, value, dims = dims, comps = [comp_def], ext_param_name = ext_param_name) @@ -797,17 +797,6 @@ function _propagate_time_dim!(obj::AbstractComponentDef, t::Dimension) curr_first = obj.first curr_last = obj.last - # Warnings and Errors - if isa(obj, Mimi.ModelDef) - !isnothing(curr_first) && t_first > curr_first && error("Cannot redefine the time dimension to start at $t_first because it is after the model's current start $curr_first.") - !isnothing(curr_first) && curr_first > t_last && error("Cannot redefine the time dimension to end at $t_last because it is before the model's current start $curr_first") - !isnothing(curr_last) && t_last < curr_last && @warn("Redefining the time dimension to end at $t_last, which is before the model's previous end $curr_last.") - else - !isnothing(curr_first) && t_first > curr_first && error("Cannot redefine the time dimension to start at $t_first, because it is after component $(nameof(obj))'s start $curr_first.") - !isnothing(curr_first) && curr_first > t_last && error("Cannot redefine the time dimension to end at $t_last because it is before the component $(nameof(obj))'s current start $curr_first") - !isnothing(curr_last) && t_last < curr_last && @warn("Redefining the time dimension to end at $t_last, which is before component $(nameof(obj))'s end $curr_last.") - end - # Handle First if isnothing(curr_first) || isa(obj, Mimi.ModelDef) # if we are working with an unset attribute or a ModelDef we always want to set first obj.first = t_first @@ -859,8 +848,8 @@ function _check_first_last(obj::Union{Model, ModelDef}; first::NothingInt = noth """ function _check_first_last(obj::Union{Model, ModelDef}; first::NothingInt = nothing, last::NothingInt = nothing) times = time_labels(obj) - !isnothing(first) && isnothing(findfirst(isequal(first), times)) && error("The first index ($first) must exist within the model's time dimension $times.") - !isnothing(last) && isnothing(findfirst(isequal(last), times)) && error("The last index ($last) must exist within the model's time dimension $times") + !isnothing(first) && !(first in times) && error("The first index ($first) must exist within the model's time dimension $times.") + !isnothing(last) && !(last in times) && error("The last index ($last) must exist within the model's time dimension $times") end """ diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index 421be3e5e..4c953e2ab 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -92,16 +92,20 @@ function set_dimension!(ccd::AbstractCompositeComponentDef, name::Symbol, keys:: if name == :time + # if we are redefining the time dimension of a model, the timestep length + # must match the timestep length of the old time dimension + redefined && _check_time_redefinition(ccd, keys) + # propagate the time dimension through all sub-components _propagate_time_dim!(ccd, dim) set_uniform!(ccd, isuniform(keys)) - if redefined - - # TODO: pad all parameters with a time dimension + # if we are redefining the time dimension for a Model Definition + # pad the time arrays with missings and update their time labels + redefined && (ccd isa ModelDef) && _pad_parameters!(ccd) - end end + return set_dimension!(ccd, name, dim) end @@ -136,7 +140,53 @@ function dim_names(ccd::AbstractCompositeComponentDef) return collect(dims) end +function _check_time_redefinition(obj::AbstractCompositeComponentDef, keys::Union{Int, Vector, Tuple, AbstractRange}) where T + + # get useful variables + curr_keys = time_labels(obj) + curr_first = obj.first + curr_last = obj.last + + new_keys = [keys...] + new_first = first(new_keys) + new_last = last(new_keys) + + # (1) check that the shift is legal + if isa(obj, ModelDef) + new_first > curr_first && error("Cannot redefine the time dimension to start at $t_first because it is after the model's current start $curr_first.") + curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the model's current start $curr_first") + # t_last < curr_last && @warn "Redefining the time dimension to end at $new_last, which is before the model's previous end $curr_last." + else + new_first > curr_first && error("Cannot redefine the time dimension to start at $new_first, because it is after component $(nameof(obj))'s start $curr_first.") + curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the component $(nameof(obj))'s current start $curr_first") + # t_last < curr_last && @warn "Redefining the time dimension to end at $new_last, which is before component $(nameof(obj))'s end $curr_last." + end + + # (2) check first and last + !(curr_first in new_keys) && error("The current first index ($curr_first) must exist within the model's new time dimension $new_keys.") # can be assumed since we cannot move the time forward + curr_last >= new_last && !(new_last in curr_keys) && error("The new last index ($new_last) must exist within the model's current time dimension $curr_keys, since the time redefinition contracts to an earlier year.") + curr_last < new_last && !(curr_last in new_keys) && error("The current last index ($curr_last) must exist within the model's redefined time dimension $new_keys, since the time redefinition expands to a later year.") + + # (3) check that the overlap region between the current keys and new keys holds same keys + if length(curr_keys) > 1 && length(new_keys) > 1 + if isuniform(curr_keys) # fixed timesteps + step_size(curr_keys) != step_size(new_keys) && error("Cannot redefine the time dimension to have a timestep size of $(step_size(new_keys)), must match the timestep size of current time dimension, $(step_size(curr_keys))") + + else # variable timesteps + start_idx = 1 # can be assumed since we cannot move the time forward + new_last < curr_last ? end_idx = findfirst(isequal(curr_last), new_keys) : end_idx = length(curr_keys) + expected_overlap = curr_keys[start_idx:end_idx] + + start_idx = findfirst(isequal(curr_first), new_keys) + end_idx = start_idx + length(expected_overlap) - 1 + observed_overlap = new_keys[start_idx:end_idx] + + expected_overlap != observed_overlap && error("Cannot redefine the time dimension, the overlapping portion of the current and new times must be identical.") + end + end + +end + dim_names(comp_def::AbstractComponentDef, datum_name::Symbol) = dim_names(datumdef(comp_def, datum_name)) dim_count(def::AbstractDatumDef) = length(dim_names(def)) - diff --git a/test/runtests.jl b/test/runtests.jl index 00f10e7d0..b75e5dc5c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -110,7 +110,7 @@ Electron.prep_test_env() @info("test_firstlast.jl") @time include("test_firstlast.jl") - @info("test_explorer_model.jl") + @info("test_explorer_model.jl") # BROKEN @time include("test_explorer_model.jl") @info("test_explorer_sim.jl") diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index aacc31336..d1bb1ecf3 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -148,7 +148,17 @@ update_param!(m, :x, zeros(5)) update_param!(m, :x, [1,2,3,4,5]) set_dimension!(m, :time, 1999:2001) -update_param!(m, :x, [2, 3, 4]) # updates the time labels +# Year x Model MyComp2 +# 1999 missing first +# 2000 1 +# 2001 2 last first, last + +x = external_param(m.md, :x) +@test ismissing(x.values.data[1]) +@test x.values.data[2:3] == [1.0, 2.0] +run(m) # should be runnable + +update_param!(m, :x, [2, 3, 4]) # change x to match # Year x Model MyComp2 # 1999 2 first # 2000 3 @@ -182,29 +192,42 @@ set_param!(m, :MyComp2, :x, [1, 2, 3]) # 2005 2 # 2010 3 last last -set_dimension!(m, :time, [2000, 2020, 2050]) -update_param!(m, :x, [2, 3, 4]) +set_dimension!(m, :time, [2000, 2005, 2020, 2100]) # Year x Model MyComp2 # 2000 1 first first -# 2020 2 last -# 2050 3 last +# 2005 2 +# 2020 3 last +# 2100 missing last + +x = external_param(m.md, :x) +@test ismissing(x.values.data[4]) +@test x.values.data[1:3] == [1.0, 2.0, 3.0] + +update_param!(m, :x, [2, 3, 4, 5]) # change x to match +# Year x Model MyComp2 +# 2000 2 first first +# 2005 3 +# 2020 4 last +# 2100 5 last x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2020, 2050)}, Union{Missing,Float64}, 1} -@test x.values.data == [2., 3., 4.] +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2005, 2020, 2100)}, Union{Missing,Float64}, 1} +@test x.values.data == [2., 3., 4., 5.] run(m) @test m[:MyComp2, :y][1] == 2 # 2000 -@test m[:MyComp2, :y][2] == 3 # 2020 -@test ismissing(m[:MyComp2, :y][3]) # 2050 - past last attribute for component +@test m[:MyComp2, :y][2] == 3 # 2005 +@test m[:MyComp2, :y][3] == 4 # 2020 +@test ismissing(m[:MyComp2, :y][4]) # 2100 - past last attribute for component -set_first_last!(m, :MyComp2, first = 2000, last = 2050) +set_first_last!(m, :MyComp2, first = 2000, last = 2020) # Year x Model MyComp2 # 2000 1 first first -# 2020 2 -# 2050 3 last last +# 2005 2 +# 2020 3 last last run(m) -@test m[:MyComp2, :y] == [2., 3., 4.] +@test m[:MyComp2, :y][1:3] == [2., 3., 4.] +@test ismissing(m[:MyComp2, :y][4]) # 3. Test updating from a dictionary @@ -213,17 +236,18 @@ set_dimension!(m, :time, [2000, 2005, 2020]) add_comp!(m, MyComp2) set_param!(m, :MyComp2, :x, [1, 2, 3]) -set_dimension!(m, :time, [2000, 2020, 2050]) +set_dimension!(m, :time, [2000, 2005, 2020, 2100]) -update_params!(m, Dict(:x=>[2, 3, 4])) +update_params!(m, Dict(:x=>[2, 3, 4, 5])) x = external_param(m.md, :x) -@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2020, 2050)}, Union{Missing,Float64}, 1} -@test x.values.data == [2., 3., 4.] +@test x.values isa Mimi.TimestepArray{Mimi.VariableTimestep{(2000, 2005, 2020, 2100)}, Union{Missing,Float64}, 1} +@test x.values.data == [2., 3., 4., 5.] run(m) -@test m[:MyComp2, :y][1] == 2 # 2005 -@test m[:MyComp2, :y][2] == 3 # 2020 -@test ismissing(m[:MyComp2, :y][3]) # 2050 +@test m[:MyComp2, :y][1] == 2 # 2000 +@test m[:MyComp2, :y][2] == 3 # 2005 +@test m[:MyComp2, :y][3] == 4 # 2020 +@test ismissing(m[:MyComp2, :y][4]) # 2100 # 4. Test updating the time index to a different length @@ -300,15 +324,6 @@ update_params!(m, Dict(:x=>[3,4,5], :y=>[10,20], :z=>0)) # Won't error when upda @test external_param(m.md, :y).values == [10.,20.] @test external_param(m.md, :z).value == 0 -# time dimension errors -m = Model() -set_dimension!(m, :time, 2000:2005) -add_comp!(m, MyComp2) -@test_throws ErrorException set_dimension!(m, :time, 2001:2005) # move model start forward -@test_throws ErrorException set_dimension!(m, :time, 1800:1810) # model last before component first -@test_throws ErrorException set_first_last!(m, :MyComp2, first = 1999) # first not in time dim keys -@test_throws ErrorException set_first_last!(m, :MyComp2, last = 2006) # last not in time dim keys - #------------------------------------------------------------------------------ # Test the three different set_param! methods for a Symbol type parameter #------------------------------------------------------------------------------ From 833de3d23c223769d6a44fc587d2cd326c04c2e4 Mon Sep 17 00:00:00 2001 From: lrennels Date: Sat, 17 Apr 2021 00:01:19 -0700 Subject: [PATCH 06/21] Loosen parameterization to fix bug --- src/core/connections.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/connections.jl b/src/core/connections.jl index 2f04cea61..6d42904e2 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -666,7 +666,7 @@ function _pad_parameters!(obj::ModelDef) end end -function _get_padded_data(param::ArrayModelParameter, param_times::Vector{Int64}, model_times::Vector{Int64}) +function _get_padded_data(param::ArrayModelParameter, param_times::Vector, model_times::Vector) data = param.values.data ti = get_time_index_position(param) From 5f7dc12f743251bfb7122a1bb78a72c55eadcadb Mon Sep 17 00:00:00 2001 From: lrennels Date: Mon, 19 Apr 2021 12:25:41 -0700 Subject: [PATCH 07/21] Add testing and fix a bug --- src/core/dimensions.jl | 4 +- test/test_dimensions.jl | 104 ++++++++++++++++++++++++------------ test/test_firstlast.jl | 2 +- test/test_parametertypes.jl | 4 +- 4 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index 4c953e2ab..f320e68f2 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -153,13 +153,11 @@ function _check_time_redefinition(obj::AbstractCompositeComponentDef, keys::Unio # (1) check that the shift is legal if isa(obj, ModelDef) - new_first > curr_first && error("Cannot redefine the time dimension to start at $t_first because it is after the model's current start $curr_first.") + new_first > curr_first && error("Cannot redefine the time dimension to start at $new_first because it is after the model's current start $curr_first.") curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the model's current start $curr_first") - # t_last < curr_last && @warn "Redefining the time dimension to end at $new_last, which is before the model's previous end $curr_last." else new_first > curr_first && error("Cannot redefine the time dimension to start at $new_first, because it is after component $(nameof(obj))'s start $curr_first.") curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the component $(nameof(obj))'s current start $curr_first") - # t_last < curr_last && @warn "Redefining the time dimension to end at $new_last, which is before component $(nameof(obj))'s end $curr_last." end # (2) check first and last diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index fb6228f9c..883dcbcfb 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -5,7 +5,11 @@ using Test import Mimi: compdef, AbstractDimension, RangeDimension, Dimension, key_type, first_period, last_period, - ComponentReference, ComponentPath + ComponentReference, ComponentPath, ComponentDef, time_labels + +## +## Constants +## dim_varargs = Dimension(:foo, :bar, :baz) # varargs dim_vec = Dimension([:foo, :bar, :baz]) # Vector @@ -13,6 +17,10 @@ dim_range = Dimension(2010:2100) # AbstractRange rangedim = RangeDimension(2010:2100) # RangeDimension type dim_vals = Dimension(4) # Same as 1:4 +## +## Test a Bunch of Small Functionalities and Helpers +## + @test key_type(dim_varargs) == Symbol @test key_type(dim_vec) == Symbol @test key_type(dim_range) == Int @@ -33,7 +41,6 @@ dim_vals = Dimension(4) # Same as 1:4 @test lastindex(dim_range) == 2100 @test lastindex(dim_vals) == 4 - @test Base.keys(rangedim) == [2010:2100...] @test Base.values(rangedim) == [1:91...] @@ -45,7 +52,6 @@ end @test dim_varargs[:] == [1,2,3] # @test rangedim[2011] == 2 # TODO: this errors.. - @test get(dim_varargs, :bar, 999) == 2 @test get(dim_varargs, :new, 4) == 4 #adds a key/value pair @test get(rangedim, 2010, 1) == 1 @@ -76,37 +82,7 @@ end @test getindex(dim_varargs, :bar) == 2 @test getindex(dim_varargs, :) == [1,2,3] - -# Test resetting the time dimension - -@defcomp foo2 begin - x = Parameter(index=[time]) - y = Variable(index=[4]) -end - -m = Model() -set_dimension!(m, :time, 2000:2100) - -@test_throws ErrorException add_comp!(m, foo2; first = 2005, last = 2105) # Can't add a component longer than a model - -foo2_ref = add_comp!(m, foo2) - -foo2_ref = ComponentReference(m, :foo2) -my_foo2 = compdef(foo2_ref) - -# Test parameter connections -@test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long -@test_throws ErrorException set_param!(m, :foo2, :x, 2005:2095) # too short -set_param!(m, :foo2, :x, 2000:2100) #Shouldn't error - -set_dimension!(m, :time, 1990:2050) - -@test first_period(m.md) == 1990 -@test last_period(m.md) == 2050 - - # Test that d.time returns AbstracTimesteps that can be used as indexes - @defcomp bar begin v1 = Variable(index = [time]) @@ -141,4 +117,66 @@ dims = [:time] @test variable_dimensions(m, :bar, :v1) == dims @test variable_dimensions(m, (:bar,), :v1) == dims +## +## Test time dimension (and resetting it!) +## + +@defcomp foo2 begin + x = Parameter(index=[time]) + y = Variable(index=[4]) +end + +# build model, set dims, and add components +m = Model() +@test_throws ErrorException add_comp!(m, foo2) # cannot add a component before time dimension is set +set_dimension!(m, :time, 2000:2100) + +@test_throws ErrorException add_comp!(m, foo2; first = 2000, last = 2105) # 2105 cannot be found in the model's time dimension +@test_throws ErrorException add_comp!(m, foo2; first = 1950, last = 2100) # 1950 cannot be found in the model's time dimension + +foo2_ref1 = add_comp!(m, foo2) +foo2_ref2 = ComponentReference(m, :foo2) +my_foo2 = compdef(foo2_ref) +@test foo2_ref1 === foo2_ref2 +@test typeof(foo2_ref1) == typeof(foo2_ref2) == ComponentReference +@test typeof(my_foo2) == ComponentDef + +@test first_period(m.md) == first_period(m.md.namespace[:foo2]) == 2000 +@test last_period(m.md) == last_period(m.md.namespace[:foo2]) == 2100 + +# Set Parameters +original_x_vals = collect(2000:2100) +@test_throws ErrorException set_param!(m, :foo2, :x, 1990:2200) # too long +@test_throws ErrorException set_param!(m, :foo2, :x, 2005:2095) # too short +set_param!(m, :foo2, :x, original_x_vals) + +run(m) + +# Reset Dimension +@test_throws ErrorException set_dimension!(m, :time, 2050:2100) # can't move time forward +@test_throws ErrorException set_dimension!(m, :time, 2105:2200) # can't move new first past old last +set_dimension!(m, :time, 1990:2050) + +@test first_period(m.md) == 1990 +@test last_period(m.md) == 2050 +@test first_period(m.md.namespace[:foo2]) == 2000 # no change +@test last_period(m.md.namespace[:foo2]) == 2050 # trimmed with model + +# check that parameters were padded properly +new_x_vals = m.md.external_params[:x].values.data +@test length(new_x_vals) == length(time_labels(m)) +@test new_x_vals[11:end] == original_x_vals[1:51] +@test all(ismissing, new_x_vals[1:10]) + +run(m) # should still run because parameters were adjusted under the hood + +# reset again with late end +set_dimension!(m, :time, 1990:2200) +new_x_vals = m.md.external_params[:x].values.data +@test length(new_x_vals) == length(time_labels(m)) +@test all(ismissing, new_x_vals[1:10]) +@test new_x_vals[11:61] == original_x_vals[1:51] +@test all(ismissing, new_x_vals[62:end]) + +run(m) end #module diff --git a/test/test_firstlast.jl b/test/test_firstlast.jl index d9cfbac30..2491e61a9 100644 --- a/test/test_firstlast.jl +++ b/test/test_firstlast.jl @@ -100,7 +100,7 @@ run(m) # test that there are missing values in :emissions variables outside of the component's # run period, and no missing values in the :grosseconomy variables @test ismissing(m[:emissions, :E][1]) -@test sum(ismissing.(m[:emissions, :E][20:21])) == 2 +@test all(ismissing, m[:emissions, :E][20:21]) @test sum(ismissing.(m[:emissions, :E][2:19])) == 0 @test sum(ismissing.(m[:grosseconomy, :l])) == 0 diff --git a/test/test_parametertypes.jl b/test/test_parametertypes.jl index d1bb1ecf3..2156b4950 100644 --- a/test/test_parametertypes.jl +++ b/test/test_parametertypes.jl @@ -6,7 +6,7 @@ using Test import Mimi: external_params, external_param, TimestepMatrix, TimestepVector, ArrayModelParameter, ScalarModelParameter, FixedTimestep, import_params!, - set_first_last! + set_first_last!, _get_param_times # # Test that parameter type mismatches are caught @@ -79,6 +79,7 @@ extpars = external_params(m.mi.md) @test isa(extpars[:a], ArrayModelParameter) @test isa(extpars[:b], ArrayModelParameter) +@test _get_param_times(extpars[:a]) == _get_param_times(extpars[:b]) == 2000:2100 @test isa(extpars[:c], ArrayModelParameter) @test isa(extpars[:d], ScalarModelParameter) @@ -156,6 +157,7 @@ set_dimension!(m, :time, 1999:2001) x = external_param(m.md, :x) @test ismissing(x.values.data[1]) @test x.values.data[2:3] == [1.0, 2.0] +@test _get_param_times(x) == 1999:2001 run(m) # should be runnable update_param!(m, :x, [2, 3, 4]) # change x to match From 7a42f78b05ccce8de449e23da8b8fea4841d78af Mon Sep 17 00:00:00 2001 From: lrennels Date: Mon, 19 Apr 2021 13:11:04 -0700 Subject: [PATCH 08/21] fix test --- test/test_dimensions.jl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/test_dimensions.jl b/test/test_dimensions.jl index 883dcbcfb..90b9314fe 100644 --- a/test/test_dimensions.jl +++ b/test/test_dimensions.jl @@ -136,10 +136,8 @@ set_dimension!(m, :time, 2000:2100) foo2_ref1 = add_comp!(m, foo2) foo2_ref2 = ComponentReference(m, :foo2) -my_foo2 = compdef(foo2_ref) @test foo2_ref1 === foo2_ref2 -@test typeof(foo2_ref1) == typeof(foo2_ref2) == ComponentReference -@test typeof(my_foo2) == ComponentDef +my_foo2 = compdef(foo2_ref1) @test first_period(m.md) == first_period(m.md.namespace[:foo2]) == 2000 @test last_period(m.md) == last_period(m.md.namespace[:foo2]) == 2100 From 153665427688db98d27ccf6e01bd009591386b56 Mon Sep 17 00:00:00 2001 From: lrennels Date: Mon, 19 Apr 2021 14:13:30 -0700 Subject: [PATCH 09/21] Add documentation and doc strings --- docs/make.jl | 5 +- docs/src/howto/howto_4.md | 2 +- docs/src/howto/howto_5.md | 126 ++++----------- docs/src/howto/howto_6.md | 254 ++++++++----------------------- docs/src/howto/howto_7.md | 233 ++++++++++++++++++++++++++++ docs/src/howto/howto_main.md | 7 +- docs/src/tutorials/tutorial_3.md | 10 +- src/core/connections.jl | 29 ++++ src/core/dimensions.jl | 6 + 9 files changed, 376 insertions(+), 296 deletions(-) create mode 100644 docs/src/howto/howto_7.md diff --git a/docs/make.jl b/docs/make.jl index 00c252651..a90aa11fa 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -21,8 +21,9 @@ makedocs( "2 Explore Results" => "howto/howto_2.md", "3 Monte Carlo + SA" => "howto/howto_3.md", "4 Timesteps, Params, and Vars" => "howto/howto_4.md", - "5 Port to v0.5.0" => "howto/howto_5.md", - "6 Port to v1.0.0" => "howto/howto_6.md" + "5 Dimensions" => "howto/howto_5.md,", + "6 Port to v0.5.0" => "howto/howto_6.md", + "7 Port to v1.0.0" => "howto/howto_7.md" ], "Advanced How-to Guides" => Any[ "Advanced How-to Guides Intro" => "howto_advanced/howto_adv_main.md", diff --git a/docs/src/howto/howto_4.md b/docs/src/howto/howto_4.md index 88c4ddcde..2733c6af4 100644 --- a/docs/src/howto/howto_4.md +++ b/docs/src/howto/howto_4.md @@ -106,7 +106,7 @@ When `set_param!` is called, it creates an external parameter by the name provid update_param!(m, :ParameterName, newvalues) ``` -Note here that `newvalues` must be the same type (or be able to convert to the type) of the old values stored in that parameter, and the same size as the model dimensions indicate. Also note that it if you have updated the time dimension of the model with `set_dimension!(m, :time, values)` you will need to update all parameters with a `:time` dimension, **even if the values have not changed**, so that the model can update the underlying time labels attached to the parameters. +Note here that `newvalues` must be the same type (or be able to convert to the type) of the old values stored in that parameter, and the same size as the model dimensions indicate. #### Setting parameters with a dictionary diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index 7e61ca7c2..8c9aeb043 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -1,107 +1,41 @@ -# How-to Guide 5: Port to Mimi v0.5.0 +# How-to Guide 5: Work with Dimensions -The release of Mimi v0.5.0 is a breaking release, necessitating the adaptation of existing models' syntax and structure in order for those models to run on this new version. This guide provides an overview of the steps required to get most models using the v0.4.0 API working with v0.5.0. It is **not** a comprehensive review of all changes and new functionalities, but a guide to the minimum steps required to port old models between versions. For complete information on the new version and its functionalities, see the full documentation. +## Updating the Time Dimension -This guide is organized into six main sections, each descripting an independent set of changes that can be undertaken in any order desired. +This section provides some specific details on updating an already set `time` dimension of a model. -1) Defining components -2) Constructing a model -3) Running the model -4) Accessing results -5) Plotting -6) Advanced topics - -**A Note on Function Naming**: There has been a general overhaul on function names, especially those in the explicity user-facing API, to be consistent with Julia conventions and the conventions of this Package. These can be briefly summarized as follows: - -- use `_` for readability -- append all functions with side-effects, i.e., non-pure functions that return a value but leave all else unchanged with a `!` -- the commonly used terms `component`, `variable`, and `parameter` are shortened to `comp`, `var`, and `param` -- functions that act upon a `component`, `variable`, or `parameter` are often written in the form `[action]_[comp/var/param]` - -## Defining Components - -The `run_timestep` function is now contained by the `@defcomp` macro, and takes the parameters `p, v, d, t`, referring to Parameters, Variables, and Dimensions of the component you defined. The fourth argument is an `AbstractTimestep`, i.e., either a `FixedTimestep` or a `VariableTimestep`. Similarly, the optional `init` function is also contained by `@defcomp`, and takes the parameters `p, v, d`. Thus, as described in the user guide, defining a single component is now done as follows: - -In this version, the fourth argument (`t` below) can no longer always be used simply as an `Int`. Indexing with `t` is still permitted, but special care must be taken when comparing `t` with conditionals or using it in arithmatic expressions. The full API as described later in this document in **Advanced Topics: Timesteps and available functions**. Since differential equations are commonly used as the basis for these models' equations, the most commonly needed change will be changing `if t == 1` to `if is_first(t)` - -```julia -@defcomp component1 begin - - # First define the state this component will hold - savingsrate = Parameter() - - # Second, define the (optional) init function for the component - function init(p, v, d) - end - - # Third, define the run_timestep function for the component - function run_timestep(p, v, d, t) - end - -end +A runnable model necessarily has a `time` dimension, originally set with the following call, but in some cases it may be desireable to alter this dimension. ``` +set_dimension!(m, :time, time_keys) +``` +As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two. Since the default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time and FUND only kicking in in 1950 and running to 2500. -## Constructing a Model - -In an effort to standardize the function naming protocol within Mimi, and to streamline it with the Julia convention, several function names have been changed. The table below lists a **subset** of these changes, focused on the exported API functions most commonly used in model construction. - -| Old Syntax | New Syntax | -| ------------------------ |:-------------------------:| -|`addcomponent!` |`add_comp!` | -|`connectparameter` |`connect_param!` | -|`setleftoverparameters` |`set_leftover_params!` | -|`setparameter` |`set_param!` | -|`adddimension` |`add_dimension!` | -|`setindex` |`set_dimension!` | - -Changes to various optional keyword arguments: - -- `add_comp!`: Through Mimi v0.9.4, the optional keyword arguments `first` and `last` could be used to specify times for components that do not run for the full length of the model, like this: `add_comp!(mymodel, ComponentC; first=2010, last=2100)`. This functionality is currently disabled, and all components must run for the full length of the model's time dimension. This functionality may be re-implemented in a later version of Mimi. - -## Running a Model - -## Accessing Results - -## Plotting and the Explorer UI - -This release of Mimi does not include the plotting functionality previously offered by Mimi. While the previous files are still included, the functions are not exported as efforts are made to simplify and improve the plotting associated with Mimi. - -The new version does, however, include a new UI tool that can be used to visualize model results. This `explore` function is described in the User Guide under **Advanced Topics**. - -## Advanced Topics - -#### Timesteps and available functions - -As previously mentioned, some relevant function names have changed. These changes were made to eliminate ambiguity. For example, the new naming clarifies that `is_last` returns whether the timestep is on the last valid period to be run, not whether it has run through that period already. This check can still be achieved with `is_finished`, which retains its name and function. Below is a subset of such changes related to timesteps and available functions. - -| Old Syntax | New Syntax | -| ------------------------ |:-------------------------:| -|`isstart` |`is_first` | -|`isstop` |`is_last` | - -As mentioned in earlier in this document, the fourth argument in `run_timestep` is an `AbstractTimestep` i.e. a `FixedTimestep` or a `VariableTimestep` and is a type defined within Mimi in "src/time.jl". In this version, the fourth argument (`t` below) can no longer always be used simply as an `Int`. Defining the `AbstractTimestep` object as `t`, indexing with `t` is still permitted, but special care must be taken when comparing `t` with conditionals or using it in arithmatic expressions. Since differential equations are commonly used as the basis for these models' equations, the most commonly needed change will be changing `if t == 1` to `if is_first(t)`. - -The full API: - -- you may index into a variable or parameter with `[t]` or `[t +/- x]` as usual -- to access the time value of `t` (currently a year) as a `Number`, use `gettime(t)` -- useful functions for commonly used conditionals are `is_first(t)` and `is_last(t)` -- 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. - -#### Parameter connections between different length components - -#### More on parameter indices - -#### Updating an external parameter +We start with FUND +``` +using Mimi +using MimiFUND +m = MimiFUND.get_model() +``` +where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. -To update an external parameter, use the functions `update_param!` and `udpate_params!` (previously known as `update_external_parameter` and `update_external_parameters`, respectively.) Their calling signatures are: +Now we want to change the `time` dimension to be 1765:3000. Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect resuts caused by our under-the-hood assumptions: -* `update_params!(md::ModelDef, parameters::Dict; update_timesteps = false)` +- The new time dimension start later than the original time dimension. The complexities and importance of initialization values and steps make it impossible to safely updating the time dimension in this way. +- The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension +- The timesteps must match. +- It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. -* `update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false)` +We now go ahead and change the `time` dimension to be 1765:2500. +``` +set_dimension!(m, :tmie, 1765:2500) +``` +At this point the model `m` can be run, and will run from it's `first` year of 1765 to it's `last` year of 2500. That said, the components are all associated with FUND, which started in 1950 and ended in 3000, so they will only run in the subset of years 1950 to 2500. These components have a `first` of 1950 and a `last` of 2500, and all associated external parameters have been padded with `missing` values from 1765 to 1949, so that the value previously associated with 1950 **is still associated with that year**. To summarize, after the time dimension is reset -For external parameters with a `:time` dimension, passing `update_timesteps=true` indicates that the time _keys_ (i.e., year labels) should also be updated in addition to updating the parameter values. +- The model's `time` dimension labels are updated, as is the `first` and `last` run years for the model. +- The component's in the model maintain the `first` and `last` years they held before the `time` dimension is reset, and will still run in those years. +- All external parameters are trimmed if the new time dimension ends earlier than the original one, and padded with `missing`s at the front if the new time dimension starts earlier than the original, and at the end if the new time dimension ends later than the original. -#### Setting parameters with a dictionary +In most cases, the above will create the expected, correct behavior, however the following options are available for further modifcations: -The function `set_leftover_params!` replaces the function `setleftoverparameters`. +- If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. +- You can update external parameters to, for example, have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function diff --git a/docs/src/howto/howto_6.md b/docs/src/howto/howto_6.md index 12ef7db53..edef2aab8 100644 --- a/docs/src/howto/howto_6.md +++ b/docs/src/howto/howto_6.md @@ -1,233 +1,107 @@ -# How-to Guide 6: Port from (>=) Mimi v0.5.0 to Mimi v1.0.0 +# How-to Guide 6: Port to Mimi v0.5.0 -The release of Mimi v1.0.0 is a breaking release, necessitating the adaptation of existing models' syntax and structure in order for those models to run on this new version. We have worked hard to keep these changes clear and as minimal as possible. +The release of Mimi v0.5.0 is a breaking release, necessitating the adaptation of existing models' syntax and structure in order for those models to run on this new version. This guide provides an overview of the steps required to get most models using the v0.4.0 API working with v0.5.0. It is **not** a comprehensive review of all changes and new functionalities, but a guide to the minimum steps required to port old models between versions. For complete information on the new version and its functionalities, see the full documentation. -This guide provides an overview of the steps required to get most models using the v0.9.5 API working with v1.0.0. It is **not** a comprehensive review of all changes and new functionalities, but a guide to the minimum steps required to port old models between versions. For complete information on the new version and its additional functionalities, see the full documentation. +This guide is organized into six main sections, each descripting an independent set of changes that can be undertaken in any order desired. -## Workflow Advice +1) Defining components +2) Constructing a model +3) Running the model +4) Accessing results +5) Plotting +6) Advanced topics -To port your model, we recommend you update to **Mimi v0.10.0**, which is identical to Mimi v1.0.0 **except** that it includes deprecation warnings for most breaking changes, instead of errors. This means that models written using Mimi v0.9.5 will, in most cases, run successfully under Mimi v0.10.0 and things that will cause errors in v1.0.0 will throw deprecation warnings. These can guide your changes, and thus a good workflow would be: +**A Note on Function Naming**: There has been a general overhaul on function names, especially those in the explicity user-facing API, to be consistent with Julia conventions and the conventions of this Package. These can be briefly summarized as follows: -1) Update your environment to use Mimi v0.10.0 with - ```julia - pkg> add Mimi#v0.10.0 - ``` -2) Read through this guide to get a sense for what has changed -3) Run your code and incrementally update it, using the deprecation warnings as guides for what to change and the instructions in this guide as explanations, until no warnings are thrown and you have changed anything relevant to your code that is explained in this gude. -4) Update to Mimi v1.0.0 with the following code, which will update Mimi to it's latest version, v1.0.0 - ```julia - pkg> free Mimi - ``` -5) Run your model! Things should run smoothly now. If not double check the guide, and feel free to reach out on the forum with any questions. Also, if you are curious about the reasons behind a change, just ask! +- use `_` for readability +- append all functions with side-effects, i.e., non-pure functions that return a value but leave all else unchanged with a `!` +- the commonly used terms `component`, `variable`, and `parameter` are shortened to `comp`, `var`, and `param` +- functions that act upon a `component`, `variable`, or `parameter` are often written in the form `[action]_[comp/var/param]` -This guide is organized into a few main sections, each descripting an independent set of changes that can be undertaken in any order desired. +## Defining Components -- Syntax Within the @defcomp Macro -- The set_param! Function -- The replace_comp! Function -- Different-length Components -- Marginal Models -- Simulation Syntax -- Composite Components (optional) +The `run_timestep` function is now contained by the `@defcomp` macro, and takes the parameters `p, v, d, t`, referring to Parameters, Variables, and Dimensions of the component you defined. The fourth argument is an `AbstractTimestep`, i.e., either a `FixedTimestep` or a `VariableTimestep`. Similarly, the optional `init` function is also contained by `@defcomp`, and takes the parameters `p, v, d`. Thus, as described in the user guide, defining a single component is now done as follows: -## Syntax Within the @defcomp Macro +In this version, the fourth argument (`t` below) can no longer always be used simply as an `Int`. Indexing with `t` is still permitted, but special care must be taken when comparing `t` with conditionals or using it in arithmatic expressions. The full API as described later in this document in **Advanced Topics: Timesteps and available functions**. Since differential equations are commonly used as the basis for these models' equations, the most commonly needed change will be changing `if t == 1` to `if is_first(t)` -#### Type-parameterization for Parameters - -*The Mimi Change:* - -To be consistent with julia syntax, Mimi now uses bracketing syntax to type-parameterize `Parameter`s inside the `@defcomp` macro instead of double-colon syntax. h +```julia +@defcomp component1 begin -*The User Change:* + # First define the state this component will hold + savingsrate = Parameter() -Where you previously indicated that the parameter `a` should be an `Int` with -```julia -@defcomp my_comp begin - a::Int = Parameter() - function run_timestep(p, v, d, t) + # Second, define the (optional) init function for the component + function init(p, v, d) end -end -``` -you should now use -```julia -@defcomp my_comp begin - a = Parameter{Int}() + + # Third, define the run_timestep function for the component function run_timestep(p, v, d, t) end -end -``` - -#### Integer Indexing - -*The Mimi Change:* -For safety, Mimi no longer allows indexing into `Parameter`s or `Varaible`s with the `run_timestep` function of the `@defcomp` macro with integers. Instead, this functionality is supported with two new types: `TimestepIndex` and `TimestepValue`. Complete details on indexing options can be found in How-to Guide 4: Work with Timesteps, Parameters, and Variables, but below we will describe the minimum steps to get your models working. - -*The User Change:* - -Where you previously used integers to index into a `Parameter` or `Variable`, you should now use the `TimestepIndex` type. For example, the code -```julia -function run_timestep(p, v, d, t) - v.my_var[t] = p.my_param[10] end ``` -should now read -```julia -function run_timestep(p, v, d, t) - v.my_var[t] = p.my_param[TimestepIndex(10)] -end -``` -Also, if you previously used logic to determine which integer index pertained to a specific year, and then used that integer for indexing, you should now use the `TimestepValue` type. For example, if you previously knew that the index 2 referred to the year 2012, and added that value to a parameter with -```julia -function run_timestep(p, v, d, t) - v.my_var[t] = p.my_param[t] + p.my_other_param[2] -end -``` -you should now use -```julia -function run_timestep(p, v, d, t) - v.my_var[t] = p.my_param[t] + p.my_other_param[TimestepValue(2012)] -end -``` - -#### `is_timestep` and `is_time` - -*The Mimi Change:* - -For simplicity and consistency with the change above, Mimi no longer supports the `is_timestep` or `is_time` functions and has replaced this functionality with comparison operators combined with the afformentioned `TimestepValue` and `TimestepIndex` types. - -*The User Change:* - -Any instance of the `is_timestep` function should be replaced with simple comparison with a `TimestepIndex` object ie. replace the logic `if is_timestep(t, 10) ...` with `if t == TimestepIndex(10) ...`. - -Any instance of the `is_time` function should be repalced with simple comparison with a `TimestepValue` object ie. replace the logic `if is_time(t, 2010) ...` with `if t == TimestepValue(2010) ...`. - -## The set_param! Function - -*The Mimi Change:* - -The `set_param!` method for setting a parameter value in a component now has the following signature: -``` -set_param!(m::Model, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol, val::Any) -``` -This function creates an external parameter called `ext_param_name` with value `val` in the model `m`'s list of external parameters, and connects the parameter `param_name` in component `comp_name` to this newly created external parameter. If there is already a parameter called `ext_param_name` in the model's list of external parameters, it errors. -There are two available shortcuts: -``` -# Shortcut 1 -set_param!(m::Model, param_name::Symbol, val::Any) -``` -This method creates an external parameter in the model called `param_name`, sets its value to `val`, looks at all the components in the model `m`, finds all the unbound parameters named `param_name`, and creates connections from all the unbound parameters that are named `param_name` to the newly created external parameter. If there is already a parameter called `param_name` in the external parameter list, it errors. +## Constructing a Model -``` -# Shortcut 2 -set_param!(m::Model, comp_name::Symbol, param_name::Symbol, val::Any) -``` -This method creates a new external parameter called `param_name` in the model `m` (if that already exists, it errors), sets its value to `val`, and then connects the parameter `param_name` in component `comp_name` to this newly created external parameter. +In an effort to standardize the function naming protocol within Mimi, and to streamline it with the Julia convention, several function names have been changed. The table below lists a **subset** of these changes, focused on the exported API functions most commonly used in model construction. -*The User Change:* +| Old Syntax | New Syntax | +| ------------------------ |:-------------------------:| +|`addcomponent!` |`add_comp!` | +|`connectparameter` |`connect_param!` | +|`setleftoverparameters` |`set_leftover_params!` | +|`setparameter` |`set_param!` | +|`adddimension` |`add_dimension!` | +|`setindex` |`set_dimension!` | -Any old code that uses the `set_param!` method with only 4 arguments (shortcut #2 shown above) will still work for setting parameters **if they are found in only one component** ... but if you have multiple components that have parameters with the same name, using the old 4-argument version of `set_param!` multiple times will cause an error. Instead, you need to determine what behavior you want across multiple components with parameters of the same name: -- If you want parameters with the same name that are found in multiple components to have the _same_ value, use the 3-argument method: `set_param!(m, :param_name, val)`. You only have to call this once and it will set the same value for all components with an unconnected parameter called `param_name`. -- If you want different components that have parameters with the same name to have _different_ values, then you need to call the 5-argument version of `set_param!` individually for each parameter value, such as: -``` -set_param!(m, :comp1, :foo, :foo1, 25) # creates an external parameter called :foo1 with value 25, and connects just comp1/foo to that value -set_param!(m, :comp2, :foo, :foo2, 30) # creates an external parameter called :foo2 with value 30, and connects just comp2/foo to that value -``` +Changes to various optional keyword arguments: -Also, you can no longer call `set_param!` to change the value of a parameter that has already been set in the model. If the parameter has already been set, you must use the following to change it: -``` -update_param!(m, ext_param_name, new_val) -``` -This updates the value of the external parameter called `ext_param_name` in the model `m`'s list of external parameters. Any component that have parameters connected to this external parameter will now be connected to this new value. - -## The replace_comp! Function - -*The Mimi Change:* +- `add_comp!`: Through Mimi v0.9.4, the optional keyword arguments `first` and `last` could be used to specify times for components that do not run for the full length of the model, like this: `add_comp!(mymodel, ComponentC; first=2010, last=2100)`. This functionality is currently disabled, and all components must run for the full length of the model's time dimension. This functionality may be re-implemented in a later version of Mimi. -For simplicity, the `replace_comp!` function has been replaced with a method augmenting the julia Base `replace!` function. +## Running a Model -*The User Change:* +## Accessing Results -Where you previously used -```julia -replace_comp!(m, new, old) -``` -to replace the `old` component with `new`, they should now use -```julia -replace!(m, old => new) -``` +## Plotting and the Explorer UI -## Different-length Components +This release of Mimi does not include the plotting functionality previously offered by Mimi. While the previous files are still included, the functions are not exported as efforts are made to simplify and improve the plotting associated with Mimi. -*The Mimi Change:* +The new version does, however, include a new UI tool that can be used to visualize model results. This `explore` function is described in the User Guide under **Advanced Topics**. -**Update: This Functionality has been reenabled, please feel free to use it again, your old code should now be valid again.** +## Advanced Topics -Through Mimi v0.9.4, the optional keyword arguments `first` and `last` could be used to specify times for components that do not run for the full length of the model, like this: `add_comp!(mymodel, ComponentC; first=2010, last=2100)`. This functionality is still disabled, as it was starting in v0.9.5, and all components must run for the full length of the model's time dimension. This functionality may be re-implemented in a later version of Mimi. +#### Timesteps and available functions -*The User Change:* +As previously mentioned, some relevant function names have changed. These changes were made to eliminate ambiguity. For example, the new naming clarifies that `is_last` returns whether the timestep is on the last valid period to be run, not whether it has run through that period already. This check can still be achieved with `is_finished`, which retains its name and function. Below is a subset of such changes related to timesteps and available functions. -Refactor your model so that all components are the same length. You may use the `run_timestep` function within each component to dictate it's behavior in different timesteps, including doing no calculations for a portion of the full model runtime. +| Old Syntax | New Syntax | +| ------------------------ |:-------------------------:| +|`isstart` |`is_first` | +|`isstop` |`is_last` | -## Marginal Models +As mentioned in earlier in this document, the fourth argument in `run_timestep` is an `AbstractTimestep` i.e. a `FixedTimestep` or a `VariableTimestep` and is a type defined within Mimi in "src/time.jl". In this version, the fourth argument (`t` below) can no longer always be used simply as an `Int`. Defining the `AbstractTimestep` object as `t`, indexing with `t` is still permitted, but special care must be taken when comparing `t` with conditionals or using it in arithmatic expressions. Since differential equations are commonly used as the basis for these models' equations, the most commonly needed change will be changing `if t == 1` to `if is_first(t)`. -*The Mimi Change:* - -For clarity, the previously named `marginal` attribute of a Mimi `MarginalModel` has been renamed to `modified`. Hence a `MarginalModel` is now described as a Mimi `Model` whose results are obtained by subtracting results of one `base` Model from those of another `marginal` Model that has a difference of `delta` with the signature: - -*The User Change:* - -Any previous access to the `marginal` attribute of a `MarginalModel`, `mm` below, should be changed from -```julia -model = mm.marginal -``` -to -```julia -model = mm.modified -``` -## Simulation Syntax - -#### Results Access - -*The Mimi Change:* - -For clarity of return types, Mimi no longer supports use of square brackets (a shortcut for julia Base `getindex`) to access the results of a Monte Carlo analysis, which are stored in the `SimulationInstance`. Instead, access to resulst is supported with the `getdataframe` function, which will return the results in the same type and format as the square bracket method used to return. - -*The User Change:* - -Results previously obtained with -```julia -results = si[:grosseconomy, :K] -``` -should now be obtained with -```julia -results = getdataframe(si, :grosseconomy, :K) -``` -#### Simulation Definition Modification Functions +The full API: -*The Mimi Change:* +- you may index into a variable or parameter with `[t]` or `[t +/- x]` as usual +- to access the time value of `t` (currently a year) as a `Number`, use `gettime(t)` +- useful functions for commonly used conditionals are `is_first(t)` and `is_last(t)` +- 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. -For consistency with julia syntax rules, the small set of unexported functions available to modify an existing `SimulationDefinition` have been renamed, moving from a camel case format to an underscore-based format as follows. +#### Parameter connections between different length components -*The User Change:* +#### More on parameter indices -Replace your functions as follows. +#### Updating an external parameter -- `deleteRV!` --> `delete_RV!` -- `addRV!` --> `add_RV!` -- `replaceRV!` --> `replace_RV!` -- `deleteTransform!` --> `delete_transform!` -- `addTransform!` --> `add_transform!` -- `deleteSave!` --> `delete_save!` -- `addSave!` --> `add_save!` +To update an external parameter, use the functions `update_param!` and `udpate_params!` (previously known as `update_external_parameter` and `update_external_parameters`, respectively.) Their calling signatures are: -## Composite Components (optional) +* `update_params!(md::ModelDef, parameters::Dict; update_timesteps = false)` -*The Mimi Change:* +* `update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false)` -The biggest functionality **addition** of Mimi v1.0.0 is the inclusion of composite components. Prior versions of Mimi supported only "flat" models, i.e., with one level of components. This new version supports mulitple layers of components, with some components being "final" or leaf components, and others being "composite" components which themselves contain other leaf or composite components. This approach allows for a cleaner organization of complex models, and allows the construction of building blocks that can be re-used in multiple models. +For external parameters with a `:time` dimension, passing `update_timesteps=true` indicates that the time _keys_ (i.e., year labels) should also be updated in addition to updating the parameter values. -*The User Change:* +#### Setting parameters with a dictionary -All previous models are considered "flat" models, i.e. they have only one level of components, and do **not** need to be converted into multiple layer models to run. Thus this addition does not mean users need to alter their models, but we encourage you to check out the other documentation on composite components to learn how you can enhance your current models and built better onces in the future! +The function `set_leftover_params!` replaces the function `setleftoverparameters`. diff --git a/docs/src/howto/howto_7.md b/docs/src/howto/howto_7.md new file mode 100644 index 000000000..c9a7b1149 --- /dev/null +++ b/docs/src/howto/howto_7.md @@ -0,0 +1,233 @@ +# How-to Guide 7: Port from (>=) Mimi v0.5.0 to Mimi v1.0.0 + +The release of Mimi v1.0.0 is a breaking release, necessitating the adaptation of existing models' syntax and structure in order for those models to run on this new version. We have worked hard to keep these changes clear and as minimal as possible. + +This guide provides an overview of the steps required to get most models using the v0.9.5 API working with v1.0.0. It is **not** a comprehensive review of all changes and new functionalities, but a guide to the minimum steps required to port old models between versions. For complete information on the new version and its additional functionalities, see the full documentation. + +## Workflow Advice + +To port your model, we recommend you update to **Mimi v0.10.0**, which is identical to Mimi v1.0.0 **except** that it includes deprecation warnings for most breaking changes, instead of errors. This means that models written using Mimi v0.9.5 will, in most cases, run successfully under Mimi v0.10.0 and things that will cause errors in v1.0.0 will throw deprecation warnings. These can guide your changes, and thus a good workflow would be: + +1) Update your environment to use Mimi v0.10.0 with + ```julia + pkg> add Mimi#v0.10.0 + ``` +2) Read through this guide to get a sense for what has changed +3) Run your code and incrementally update it, using the deprecation warnings as guides for what to change and the instructions in this guide as explanations, until no warnings are thrown and you have changed anything relevant to your code that is explained in this gude. +4) Update to Mimi v1.0.0 with the following code, which will update Mimi to it's latest version, v1.0.0 + ```julia + pkg> free Mimi + ``` +5) Run your model! Things should run smoothly now. If not double check the guide, and feel free to reach out on the forum with any questions. Also, if you are curious about the reasons behind a change, just ask! + +This guide is organized into a few main sections, each descripting an independent set of changes that can be undertaken in any order desired. + +- Syntax Within the @defcomp Macro +- The set_param! Function +- The replace_comp! Function +- Different-length Components +- Marginal Models +- Simulation Syntax +- Composite Components (optional) + +## Syntax Within the @defcomp Macro + +#### Type-parameterization for Parameters + +*The Mimi Change:* + +To be consistent with julia syntax, Mimi now uses bracketing syntax to type-parameterize `Parameter`s inside the `@defcomp` macro instead of double-colon syntax. h + +*The User Change:* + +Where you previously indicated that the parameter `a` should be an `Int` with +```julia +@defcomp my_comp begin + a::Int = Parameter() + function run_timestep(p, v, d, t) + end +end +``` +you should now use +```julia +@defcomp my_comp begin + a = Parameter{Int}() + function run_timestep(p, v, d, t) + end +end +``` + +#### Integer Indexing + +*The Mimi Change:* + +For safety, Mimi no longer allows indexing into `Parameter`s or `Varaible`s with the `run_timestep` function of the `@defcomp` macro with integers. Instead, this functionality is supported with two new types: `TimestepIndex` and `TimestepValue`. Complete details on indexing options can be found in How-to Guide 4: Work with Timesteps, Parameters, and Variables, but below we will describe the minimum steps to get your models working. + +*The User Change:* + +Where you previously used integers to index into a `Parameter` or `Variable`, you should now use the `TimestepIndex` type. For example, the code +```julia +function run_timestep(p, v, d, t) + v.my_var[t] = p.my_param[10] +end +``` +should now read +```julia +function run_timestep(p, v, d, t) + v.my_var[t] = p.my_param[TimestepIndex(10)] +end +``` +Also, if you previously used logic to determine which integer index pertained to a specific year, and then used that integer for indexing, you should now use the `TimestepValue` type. For example, if you previously knew that the index 2 referred to the year 2012, and added that value to a parameter with +```julia +function run_timestep(p, v, d, t) + v.my_var[t] = p.my_param[t] + p.my_other_param[2] +end +``` +you should now use +```julia +function run_timestep(p, v, d, t) + v.my_var[t] = p.my_param[t] + p.my_other_param[TimestepValue(2012)] +end +``` + +#### `is_timestep` and `is_time` + +*The Mimi Change:* + +For simplicity and consistency with the change above, Mimi no longer supports the `is_timestep` or `is_time` functions and has replaced this functionality with comparison operators combined with the afformentioned `TimestepValue` and `TimestepIndex` types. + +*The User Change:* + +Any instance of the `is_timestep` function should be replaced with simple comparison with a `TimestepIndex` object ie. replace the logic `if is_timestep(t, 10) ...` with `if t == TimestepIndex(10) ...`. + +Any instance of the `is_time` function should be repalced with simple comparison with a `TimestepValue` object ie. replace the logic `if is_time(t, 2010) ...` with `if t == TimestepValue(2010) ...`. + +## The set_param! Function + +*The Mimi Change:* + +The `set_param!` method for setting a parameter value in a component now has the following signature: +``` +set_param!(m::Model, comp_name::Symbol, param_name::Symbol, ext_param_name::Symbol, val::Any) +``` +This function creates an external parameter called `ext_param_name` with value `val` in the model `m`'s list of external parameters, and connects the parameter `param_name` in component `comp_name` to this newly created external parameter. If there is already a parameter called `ext_param_name` in the model's list of external parameters, it errors. + +There are two available shortcuts: +``` +# Shortcut 1 +set_param!(m::Model, param_name::Symbol, val::Any) +``` +This method creates an external parameter in the model called `param_name`, sets its value to `val`, looks at all the components in the model `m`, finds all the unbound parameters named `param_name`, and creates connections from all the unbound parameters that are named `param_name` to the newly created external parameter. If there is already a parameter called `param_name` in the external parameter list, it errors. + +``` +# Shortcut 2 +set_param!(m::Model, comp_name::Symbol, param_name::Symbol, val::Any) +``` +This method creates a new external parameter called `param_name` in the model `m` (if that already exists, it errors), sets its value to `val`, and then connects the parameter `param_name` in component `comp_name` to this newly created external parameter. + +*The User Change:* + +Any old code that uses the `set_param!` method with only 4 arguments (shortcut #2 shown above) will still work for setting parameters **if they are found in only one component** ... but if you have multiple components that have parameters with the same name, using the old 4-argument version of `set_param!` multiple times will cause an error. Instead, you need to determine what behavior you want across multiple components with parameters of the same name: +- If you want parameters with the same name that are found in multiple components to have the _same_ value, use the 3-argument method: `set_param!(m, :param_name, val)`. You only have to call this once and it will set the same value for all components with an unconnected parameter called `param_name`. +- If you want different components that have parameters with the same name to have _different_ values, then you need to call the 5-argument version of `set_param!` individually for each parameter value, such as: +``` +set_param!(m, :comp1, :foo, :foo1, 25) # creates an external parameter called :foo1 with value 25, and connects just comp1/foo to that value +set_param!(m, :comp2, :foo, :foo2, 30) # creates an external parameter called :foo2 with value 30, and connects just comp2/foo to that value +``` + +Also, you can no longer call `set_param!` to change the value of a parameter that has already been set in the model. If the parameter has already been set, you must use the following to change it: +``` +update_param!(m, ext_param_name, new_val) +``` +This updates the value of the external parameter called `ext_param_name` in the model `m`'s list of external parameters. Any component that have parameters connected to this external parameter will now be connected to this new value. + +## The replace_comp! Function + +*The Mimi Change:* + +For simplicity, the `replace_comp!` function has been replaced with a method augmenting the julia Base `replace!` function. + +*The User Change:* + +Where you previously used +```julia +replace_comp!(m, new, old) +``` +to replace the `old` component with `new`, they should now use +```julia +replace!(m, old => new) +``` + +## Different-length Components + +*The Mimi Change:* + +**Update: This Functionality has been reenabled, please feel free to use it again, your old code should now be valid again.** + +Through Mimi v0.9.4, the optional keyword arguments `first` and `last` could be used to specify times for components that do not run for the full length of the model, like this: `add_comp!(mymodel, ComponentC; first=2010, last=2100)`. This functionality is still disabled, as it was starting in v0.9.5, and all components must run for the full length of the model's time dimension. This functionality may be re-implemented in a later version of Mimi. + +*The User Change:* + +Refactor your model so that all components are the same length. You may use the `run_timestep` function within each component to dictate it's behavior in different timesteps, including doing no calculations for a portion of the full model runtime. + +## Marginal Models + +*The Mimi Change:* + +For clarity, the previously named `marginal` attribute of a Mimi `MarginalModel` has been renamed to `modified`. Hence a `MarginalModel` is now described as a Mimi `Model` whose results are obtained by subtracting results of one `base` Model from those of another `marginal` Model that has a difference of `delta` with the signature: + +*The User Change:* + +Any previous access to the `marginal` attribute of a `MarginalModel`, `mm` below, should be changed from +```julia +model = mm.marginal +``` +to +```julia +model = mm.modified +``` +## Simulation Syntax + +#### Results Access + +*The Mimi Change:* + +For clarity of return types, Mimi no longer supports use of square brackets (a shortcut for julia Base `getindex`) to access the results of a Monte Carlo analysis, which are stored in the `SimulationInstance`. Instead, access to resulst is supported with the `getdataframe` function, which will return the results in the same type and format as the square bracket method used to return. + +*The User Change:* + +Results previously obtained with +```julia +results = si[:grosseconomy, :K] +``` +should now be obtained with +```julia +results = getdataframe(si, :grosseconomy, :K) +``` +#### Simulation Definition Modification Functions + +*The Mimi Change:* + +For consistency with julia syntax rules, the small set of unexported functions available to modify an existing `SimulationDefinition` have been renamed, moving from a camel case format to an underscore-based format as follows. + +*The User Change:* + +Replace your functions as follows. + +- `deleteRV!` --> `delete_RV!` +- `addRV!` --> `add_RV!` +- `replaceRV!` --> `replace_RV!` +- `deleteTransform!` --> `delete_transform!` +- `addTransform!` --> `add_transform!` +- `deleteSave!` --> `delete_save!` +- `addSave!` --> `add_save!` + +## Composite Components (optional) + +*The Mimi Change:* + +The biggest functionality **addition** of Mimi v1.0.0 is the inclusion of composite components. Prior versions of Mimi supported only "flat" models, i.e., with one level of components. This new version supports mulitple layers of components, with some components being "final" or leaf components, and others being "composite" components which themselves contain other leaf or composite components. This approach allows for a cleaner organization of complex models, and allows the construction of building blocks that can be re-used in multiple models. + +*The User Change:* + +All previous models are considered "flat" models, i.e. they have only one level of components, and do **not** need to be converted into multiple layer models to run. Thus this addition does not mean users need to alter their models, but we encourage you to check out the other documentation on composite components to learn how you can enhance your current models and built better onces in the future! diff --git a/docs/src/howto/howto_main.md b/docs/src/howto/howto_main.md index 1fcc08155..1a48c350f 100644 --- a/docs/src/howto/howto_main.md +++ b/docs/src/howto/howto_main.md @@ -18,7 +18,10 @@ If you find a bug in these guides, or have a clarifying question or suggestion, [How-to Guide 4: Work with Timesteps, Parameters, and Variables](@ref) -[How-to Guide 5: Port to Mimi v0.5.0](@ref) +[How-to Guide 5: Work with Dimensions](@ref) -[How-to Guide 6: Port from (>=) Mimi v0.5.0 to Mimi v1.0.0](@ref) +[How-to Guide 6: Port to Mimi v0.5.0](@ref) + + +[How-to Guide 7: Port from (>=) Mimi v0.5.0 to Mimi v1.0.0](@ref) diff --git a/docs/src/tutorials/tutorial_3.md b/docs/src/tutorials/tutorial_3.md index e08567559..c48381793 100644 --- a/docs/src/tutorials/tutorial_3.md +++ b/docs/src/tutorials/tutorial_3.md @@ -28,7 +28,7 @@ When the original model calls [`set_param!`](@ref), Mimi creates an external par update_param!(mymodel, :parametername, newvalues) ``` -Note here that `newvalues` must be the same type (or be able to convert to the type) of the old values stored in that parameter, and the same size as the model dimensions indicate. Also note that it if you have updated the time dimension of the model with `set_dimension!(m, :time, values)` you will need to update all parameters with a `:time` dimension, **even if the values have not changed**, so that the model can update the underlying time labels (ie. year labels) to match your new model time labels (ie. year labels). +Note here that `newvalues` must be the same type (or be able to convert to the type) of the old values stored in that parameter, and the same size as the model dimensions indicate. If you wish to alter connections within an existing model, [`disconnect_param!`](@ref) and [`connect_param!`](@ref) can be used in conjunction with each other to update the connections within the model, although this is more likely to be done as part of larger changes involving components themslves, as discussed in the next subsection. @@ -83,20 +83,20 @@ update_param!(m, :fco22x, 3.000) run(m) ``` -A more complex example may be a situation where you want to update several parameters, including some with a `:time` dimension, in conjunction with altering the time index of the model itself. DICE uses a default time horizon of 2005 to 2595 with 10 year increment timesteps. If you wish to change this, say, to 2000 to 2500 by 10 year increment timesteps and use parameters that match this time, you could use the following code: +A more complex example may be a situation where you want to update several parameters, including some with a `:time` dimension, in conjunction with altering the time index of the model itself. DICE uses a default time horizon of 2005 to 2595 with 10 year increment timesteps. If you wish to change this, say, to 1995 to 2505 by 10 year increment timesteps and use parameters that match this time, you could use the following code: First you upate the `time` dimension of the model as follows: ```julia const ts = 10 -const years = collect(2000:ts:2500) +const years = collect(1995:ts:2505) nyears = length(years) set_dimension!(m, :time, years) ``` -Now you must update at least all parameters with a `:time` dimension, even if the length and values remain the same, so that the underlying time labels (ie. year labels) update to match your new model time labels (ie. year labels). +At this point all parameters with a `:time` dimension have been slightly modified under the hood, but the original values are still tied to their original years. In this case, for example, the external parameter has been shorted by 9 values (end from 2595 --> 2505) and padded at the front with a value of `missing` (start from 2005 --> 1995). Since some values, especially initializing values, are not time-agnostic, we maintain the relationship between values and time labels. If you wish to attach new values, you can use `update_param!` as below. In this case this is probably necessary, since having a `missing` in the first spot of a parameter with a `:time` dimension will likely cause an error when this value is accessed. -Create a dictionary `params` with one entry `(k, v)` per external parameter by name `k` to value `v`. Each key `k` must be a symbol or convert to a symbol matching the name of an external parameter that already exists in the model definition. Part of this dictionary may look like: +Create a dictionary `params` with one entry `(k, v)` per external parameter you want to update by name `k` to value `v`. Each key `k` must be a symbol or convert to a symbol matching the name of an external parameter that already exists in the model definition. Part of this dictionary may look like: ```julia params = Dict{Any, Any}() diff --git a/src/core/connections.jl b/src/core/connections.jl index 6d42904e2..3aab02ab6 100644 --- a/src/core/connections.jl +++ b/src/core/connections.jl @@ -651,6 +651,15 @@ function add_connector_comps!(obj::AbstractCompositeComponentDef) return nothing end + +""" + _pad_parameters!(obj::ModelDef) + +Take each external parameter of the Model Definition `obj` and `update_param!` +with new data values that are altered to match a new time dimension by (1) trimming +the values down if the time dimension has been shortened and (2) padding with missings +as necessary. +""" function _pad_parameters!(obj::ModelDef) model_times = time_labels(obj) @@ -666,6 +675,14 @@ function _pad_parameters!(obj::ModelDef) end end +""" + _get_padded_data(param::ArrayModelParameter, param_times::Vector, model_times::Vector) + +Obtain the new data values for the Array Model Paramter `param` with current +time labels `param_times` such that they are altered to match a new time dimension +with keys `model_times` by (1) trimming the values down if the time dimension has +been shortened and (2) padding with missings as necessary. +""" function _get_padded_data(param::ArrayModelParameter, param_times::Vector, model_times::Vector) data = param.values.data @@ -710,10 +727,22 @@ function _get_padded_data(param::ArrayModelParameter, param_times::Vector, model return data end +""" + _get_param_times(param::ArrayModelParameter{TimestepArray{FixedTimestep{FIRST, STEP, LAST}, T, N, ti, S}}) + +Return the time labels that parameterize the `TimestepValue` which in turn parameterizes +the ArrayModelParameter `param`. +""" function _get_param_times(param::ArrayModelParameter{TimestepArray{FixedTimestep{FIRST, STEP, LAST}, T, N, ti, S}}) where {FIRST, STEP, LAST, T, N, ti, S} return collect(FIRST:STEP:LAST) end +""" + _get_param_times(param::ArrayModelParameter{TimestepArray{VariableTimestep{TIMES}, T, N, ti, S}}) + +Return the time labels that parameterize the `TimestepValue` which in turn parameterizes +the ArrayModelParameter `param`. +""" function _get_param_times(param::ArrayModelParameter{TimestepArray{VariableTimestep{TIMES}, T, N, ti, S}}) where {TIMES, T, N, ti, S} return [TIMES...] end diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index f320e68f2..46ecf8bb9 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -140,6 +140,12 @@ function dim_names(ccd::AbstractCompositeComponentDef) return collect(dims) end +""" + _check_time_redefinition(obj::AbstractCompositeComponentDef, keys::Union{Int, Vector, Tuple, AbstractRange}) + +Run through all necesssary safety checks for redefining `obj`'s time dimenson to +a new dimension with keys `keys`. +""" function _check_time_redefinition(obj::AbstractCompositeComponentDef, keys::Union{Int, Vector, Tuple, AbstractRange}) where T # get useful variables From 112c61fce91633ae8b4393a697a2ede89e1628ec Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 00:24:02 -0700 Subject: [PATCH 10/21] Update documentation --- docs/src/howto/howto_5.md | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index 8c9aeb043..f1f21c618 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -8,7 +8,7 @@ A runnable model necessarily has a `time` dimension, originally set with the fol ``` set_dimension!(m, :time, time_keys) ``` -As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two. Since the default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time and FUND only kicking in in 1950 and running to 2500. +As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two. We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. We start with FUND ``` @@ -18,22 +18,23 @@ m = MimiFUND.get_model() ``` where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. -Now we want to change the `time` dimension to be 1765:3000. Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect resuts caused by our under-the-hood assumptions: +Now we want to change the `time` dimension to be 1765 to 3000. Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. -- The new time dimension start later than the original time dimension. The complexities and importance of initialization values and steps make it impossible to safely updating the time dimension in this way. +- The new time dimension cannot start later than the original time dimension. - The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension -- The timesteps must match. -- It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. +- The new time dimension must use the same timestep lengths as the original dimension. -We now go ahead and change the `time` dimension to be 1765:2500. +It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. + +We now go ahead and change the `time` dimension to be 1765 to 2500. ``` -set_dimension!(m, :tmie, 1765:2500) +set_dimension!(m, :time, 1765:2500) ``` -At this point the model `m` can be run, and will run from it's `first` year of 1765 to it's `last` year of 2500. That said, the components are all associated with FUND, which started in 1950 and ended in 3000, so they will only run in the subset of years 1950 to 2500. These components have a `first` of 1950 and a `last` of 2500, and all associated external parameters have been padded with `missing` values from 1765 to 1949, so that the value previously associated with 1950 **is still associated with that year**. To summarize, after the time dimension is reset +At this point the model `m` can be run, and will run from 1765 to 2500. That said, the components will only run in the subset of years 1950 to 2500. All associated external parameters with a `:time` dimension have been padded with `missing` values from 1765 to 1949, so that the value previously associated with 1950 is still associated with that year. To add a bit of detail, after the time dimension is reset the following are true: -- The model's `time` dimension labels are updated, as is the `first` and `last` run years for the model. -- The component's in the model maintain the `first` and `last` years they held before the `time` dimension is reset, and will still run in those years. -- All external parameters are trimmed if the new time dimension ends earlier than the original one, and padded with `missing`s at the front if the new time dimension starts earlier than the original, and at the end if the new time dimension ends later than the original. +- The model's `time` dimension values are updated, and it will run for each year in that dimension. +- The components in the model maintain the `first` and `last` years they held before the `time` dimension was reset, and will maintain that run period. +- All external parameters are trimmed and padded as needed so the model can still run, **but the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one then the `last` is trimmed back. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back. In most cases, the above will create the expected, correct behavior, however the following options are available for further modifcations: From 0a176b219fe8475c7bebcb62ed645ddf29f767c7 Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:03:21 -0700 Subject: [PATCH 11/21] Add documentation --- docs/src/howto/howto_5.md | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index f1f21c618..94f69b7b7 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -30,12 +30,31 @@ We now go ahead and change the `time` dimension to be 1765 to 2500. ``` set_dimension!(m, :time, 1765:2500) ``` -At this point the model `m` can be run, and will run from 1765 to 2500. That said, the components will only run in the subset of years 1950 to 2500. All associated external parameters with a `:time` dimension have been padded with `missing` values from 1765 to 1949, so that the value previously associated with 1950 is still associated with that year. To add a bit of detail, after the time dimension is reset the following are true: +At this point the model `m` can be run, and will run from 1765 to 2500. That said, the components will only run in the subset of years 1950 to 2500. All associated external parameters with a `time` dimension have been padded with `missing` values from 1765 to 1949, so that the values previously associated with 1950 to 2500 are still associated with those years. To add a bit of detail, after the time dimension is reset the following holds: - The model's `time` dimension values are updated, and it will run for each year in that dimension. -- The components in the model maintain the `first` and `last` years they held before the `time` dimension was reset, and will maintain that run period. -- All external parameters are trimmed and padded as needed so the model can still run, **but the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one then the `last` is trimmed back. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back. - + ``` + julia> Mimi.time_labels(m) + 736-element Vector{Int64}: [1765, 1766, 1767, … 2498, 2499, 2500] + ``` +- The components `time` dimension values are updated, but (1) the components maintain the `first` year as set by the original `time` dimension so the run period start year does not change and (2) they maintain their `last` year as set by the original `time` dimension, unless that year is now later than the model's last year, in which case it is trimmed back to the `time` dimensions last year. Thus, the components will run for the same run period, or a shorter one if the new time dimension ends before the component used to. + ``` + julia> component = m.md.namespace[:emissions] # get component def, ignore the messy syntax + julia> component.first + 1950 + julia> component.last + 2500 + julia> component.dim_dict[:time] + [1765, 1766, 1767, … 2498, 2499, 2500] +``` +- All external parameters are trimmed and padded as needed so the model can still run, **and the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one than the parameter values are trimmed back. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back. + ``` + julia> parameter_values = Mimi.external_params(m)[:currtaxn2o].values.data + julia> size(parameter_values) + (736, 16) + julia> parameter_values[1:(1950-1765),:] # all missing + julia> parameter_values[(1950-1764),:] # hold set values + ``` In most cases, the above will create the expected, correct behavior, however the following options are available for further modifcations: - If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. From c4270dc46bba6ce3e10d26ebf7daea8aac87ddad Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:07:09 -0700 Subject: [PATCH 12/21] Add formatting --- docs/src/howto/howto_5.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index 94f69b7b7..d3397a565 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -2,13 +2,11 @@ ## Updating the Time Dimension -This section provides some specific details on updating an already set `time` dimension of a model. - -A runnable model necessarily has a `time` dimension, originally set with the following call, but in some cases it may be desireable to alter this dimension. +A runnable model necessarily has a `time` dimension, originally set with the following call, but in some cases it may be desireable to alter this dimension by calling the following on a model which already has a time dimension set. ``` set_dimension!(m, :time, time_keys) ``` -As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two. We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. +**As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two.** We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. We start with FUND ``` @@ -18,7 +16,7 @@ m = MimiFUND.get_model() ``` where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. -Now we want to change the `time` dimension to be 1765 to 3000. Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. +**Now we want to change the `time` dimension to be 1765 to 3000.** Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. - The new time dimension cannot start later than the original time dimension. - The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension @@ -26,7 +24,7 @@ Now we want to change the `time` dimension to be 1765 to 3000. Before we do so, It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. -We now go ahead and change the `time` dimension to be 1765 to 2500. +**We now go ahead and change the `time` dimension to be 1765 to 2500.** ``` set_dimension!(m, :time, 1765:2500) ``` @@ -46,7 +44,7 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai 2500 julia> component.dim_dict[:time] [1765, 1766, 1767, … 2498, 2499, 2500] -``` + ``` - All external parameters are trimmed and padded as needed so the model can still run, **and the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one than the parameter values are trimmed back. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back. ``` julia> parameter_values = Mimi.external_params(m)[:currtaxn2o].values.data @@ -55,7 +53,7 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` -In most cases, the above will create the expected, correct behavior, however the following options are available for further modifcations: +**The following options are available for further modifcations if the current state is not desireable.** - If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. - You can update external parameters to, for example, have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function From afd9e94fcc29a703a525781c095075b30b456e2b Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:10:16 -0700 Subject: [PATCH 13/21] Formatting --- docs/src/howto/howto_5.md | 16 +++++++++------- docs/src/howto/howto_main.md | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index d3397a565..c179daf48 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -1,12 +1,12 @@ -# How-to Guide 5: Work with Dimensions - -## Updating the Time Dimension +# How-to Guide 5: Update the Time Dimension A runnable model necessarily has a `time` dimension, originally set with the following call, but in some cases it may be desireable to alter this dimension by calling the following on a model which already has a time dimension set. ``` set_dimension!(m, :time, time_keys) ``` -**As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two.** We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. +#### As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two: + +We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. We start with FUND ``` @@ -16,7 +16,9 @@ m = MimiFUND.get_model() ``` where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. -**Now we want to change the `time` dimension to be 1765 to 3000.** Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. +#### Now we want to change the `time` dimension to be 1765 to 3000: + +Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. - The new time dimension cannot start later than the original time dimension. - The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension @@ -24,7 +26,7 @@ where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000) It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. -**We now go ahead and change the `time` dimension to be 1765 to 2500.** +#### We now go ahead and change the `time` dimension to be 1765 to 2500: ``` set_dimension!(m, :time, 1765:2500) ``` @@ -53,7 +55,7 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` -**The following options are available for further modifcations if the current state is not desireable.** +#### The following options are now available for further modifcations if this end state is not desireable: - If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. - You can update external parameters to, for example, have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function diff --git a/docs/src/howto/howto_main.md b/docs/src/howto/howto_main.md index 1a48c350f..314acd117 100644 --- a/docs/src/howto/howto_main.md +++ b/docs/src/howto/howto_main.md @@ -18,7 +18,7 @@ If you find a bug in these guides, or have a clarifying question or suggestion, [How-to Guide 4: Work with Timesteps, Parameters, and Variables](@ref) -[How-to Guide 5: Work with Dimensions](@ref) +[How-to Guide 5: Update the Time Dimension](@ref) [How-to Guide 6: Port to Mimi v0.5.0](@ref) From a91647d26402b3b64f1277651e2b2e10f680272f Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:10:55 -0700 Subject: [PATCH 14/21] Add headers --- docs/src/howto/howto_5.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index c179daf48..0f7a5bd95 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -16,7 +16,7 @@ m = MimiFUND.get_model() ``` where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. -#### Now we want to change the `time` dimension to be 1765 to 3000: +### Now we want to change the `time` dimension to be 1765 to 3000: Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. @@ -26,7 +26,7 @@ Before we do so, note some important rules and precautions. These are in place t It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. -#### We now go ahead and change the `time` dimension to be 1765 to 2500: +### We now go ahead and change the `time` dimension to be 1765 to 2500: ``` set_dimension!(m, :time, 1765:2500) ``` @@ -55,7 +55,7 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` -#### The following options are now available for further modifcations if this end state is not desireable: +### The following options are now available for further modifcations if this end state is not desireable: - If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. - You can update external parameters to, for example, have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function From 64b242cce422e8e65110cc1c699e6116bb0cb9d6 Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:11:49 -0700 Subject: [PATCH 15/21] Add horizontal lines --- docs/src/howto/howto_5.md | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index 0f7a5bd95..05941093b 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -4,6 +4,7 @@ A runnable model necessarily has a `time` dimension, originally set with the fol ``` set_dimension!(m, :time, time_keys) ``` +---- #### As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two: We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. @@ -15,8 +16,8 @@ using MimiFUND m = MimiFUND.get_model() ``` where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. - -### Now we want to change the `time` dimension to be 1765 to 3000: +---- +#### Now we want to change the `time` dimension to be 1765 to 3000: Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. @@ -25,8 +26,8 @@ Before we do so, note some important rules and precautions. These are in place t - The new time dimension must use the same timestep lengths as the original dimension. It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. - -### We now go ahead and change the `time` dimension to be 1765 to 2500: +---- +#### We now go ahead and change the `time` dimension to be 1765 to 2500: ``` set_dimension!(m, :time, 1765:2500) ``` @@ -55,7 +56,8 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` -### The following options are now available for further modifcations if this end state is not desireable: +---- +#### The following options are now available for further modifcations if this end state is not desireable: - If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. - You can update external parameters to, for example, have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function From 58e9476722e8cc4717205315a05cee5e2782a6fe Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:13:13 -0700 Subject: [PATCH 16/21] More formattign improvements --- docs/src/howto/howto_5.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index 05941093b..c70bcc6d5 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -4,6 +4,7 @@ A runnable model necessarily has a `time` dimension, originally set with the fol ``` set_dimension!(m, :time, time_keys) ``` + ---- #### As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two: @@ -16,6 +17,7 @@ using MimiFUND m = MimiFUND.get_model() ``` where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. + ---- #### Now we want to change the `time` dimension to be 1765 to 3000: @@ -26,6 +28,7 @@ Before we do so, note some important rules and precautions. These are in place t - The new time dimension must use the same timestep lengths as the original dimension. It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. + ---- #### We now go ahead and change the `time` dimension to be 1765 to 2500: ``` @@ -56,6 +59,7 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` + ---- #### The following options are now available for further modifcations if this end state is not desireable: From b6378a38b02c3d750ac5c05e41d25f728cb0ec92 Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:21:16 -0700 Subject: [PATCH 17/21] Fix documentation typos --- docs/src/howto/howto_5.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index c70bcc6d5..ccc7dcb3c 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -6,9 +6,9 @@ set_dimension!(m, :time, time_keys) ``` ---- -#### As a concrete example, one may wish to replace the FUND model's climate module with a different one, say FAIR, and make new conections between the two: +#### For example, one may wish to replace the FUND model's climate module with a different one, such as FAIR: -We will soon publish a notebook explaining all the steps for this coupling, but for now we focus on this as an example of resetting the time dimension. The default FUND runs from 1950 to 3000 with 1 year timesteps, and FAIR runs from 1765 to 2500 with 1 year timesteps. Thus, our new model will run from 1765 to 2500 with 1 year timesteps, with FAIR running the whole time (acessing backup parameter values when FUND is not running) and then with FUND kicking in in 1950 and running to 2500, connected appropriately to FAIR. +For the purposes of this guide we focus on the first step of such modification. Since FUND runs yearly from 1950 to 3000 and FAIR yearly from 1765 to 2500, our modified model will need to run yearly from 1950 to 1765. We start with FUND ``` @@ -19,22 +19,20 @@ m = MimiFUND.get_model() where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. ---- -#### Now we want to change the `time` dimension to be 1765 to 3000: +#### Now we need to change the `time` dimension to be 1765 to 3000: Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. - The new time dimension cannot start later than the original time dimension. -- The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension +- The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension. - The new time dimension must use the same timestep lengths as the original dimension. -It is possible that an existing model has special behavior that is explicitly tied to a year value. If that is true, the user will need to account for that. - ---- #### We now go ahead and change the `time` dimension to be 1765 to 2500: ``` set_dimension!(m, :time, 1765:2500) ``` -At this point the model `m` can be run, and will run from 1765 to 2500. That said, the components will only run in the subset of years 1950 to 2500. All associated external parameters with a `time` dimension have been padded with `missing` values from 1765 to 1949, so that the values previously associated with 1950 to 2500 are still associated with those years. To add a bit of detail, after the time dimension is reset the following holds: +At this point the model `m` can be run, and will run from 1765 to 2500. That said, the components will only run in the subset of years 1950 to 2500. All associated external parameters with a `time` dimension have been padded with `missing` values from 1765 to 1949, so that the values previously associated with 1950 to 2500 are still associated with those years. To add detail, after the time dimension is reset the following holds: - The model's `time` dimension values are updated, and it will run for each year in that dimension. ``` @@ -44,14 +42,14 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai - The components `time` dimension values are updated, but (1) the components maintain the `first` year as set by the original `time` dimension so the run period start year does not change and (2) they maintain their `last` year as set by the original `time` dimension, unless that year is now later than the model's last year, in which case it is trimmed back to the `time` dimensions last year. Thus, the components will run for the same run period, or a shorter one if the new time dimension ends before the component used to. ``` julia> component = m.md.namespace[:emissions] # get component def, ignore the messy syntax + julia> component.dim_dict[:time] + [1765, 1766, 1767, … 2498, 2499, 2500] julia> component.first 1950 julia> component.last 2500 - julia> component.dim_dict[:time] - [1765, 1766, 1767, … 2498, 2499, 2500] ``` -- All external parameters are trimmed and padded as needed so the model can still run, **and the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one than the parameter values are trimmed back. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back. +- All external parameters are trimmed and padded as needed so the model can still run, **and the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one than the parameter value vector/matrix is trimmed at the end. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back respectively. ``` julia> parameter_values = Mimi.external_params(m)[:currtaxn2o].values.data julia> size(parameter_values) @@ -59,7 +57,6 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` - ---- #### The following options are now available for further modifcations if this end state is not desireable: From bb3d980f1ad33018505a9d5824dadb7308897230 Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:23:29 -0700 Subject: [PATCH 18/21] Final spelling fixes --- docs/src/howto/howto_5.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index ccc7dcb3c..1be9038b0 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -19,9 +19,9 @@ m = MimiFUND.get_model() where `MimiFUND.get_model` includes the call `set_dimension!(m, time, 1950:3000)`. ---- -#### Now we need to change the `time` dimension to be 1765 to 3000: +#### Now we need to change the `time` dimension to be 1765 to 2500: -Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where they are a problem please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. +Before we do so, note some important rules and precautions. These are in place to avoid unexpected behavior, complications, or incorrect results caused by our under-the-hood assumptions, but if a use case arises where these are prohibitive please get in touch on the [forum](https://forum.mimiframework.org) and we can help you out. - The new time dimension cannot start later than the original time dimension. - The new time dimension cannot end before the start of the original time dimension ie. it cannot completely exclude all times in the original time dimension. From c57c8ae4df30a57e078332d1afe1bce2c6860c3e Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:33:07 -0700 Subject: [PATCH 19/21] Cleanup --- docs/src/howto/howto_5.md | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index 1be9038b0..a3115d5ce 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -8,7 +8,7 @@ set_dimension!(m, :time, time_keys) ---- #### For example, one may wish to replace the FUND model's climate module with a different one, such as FAIR: -For the purposes of this guide we focus on the first step of such modification. Since FUND runs yearly from 1950 to 3000 and FAIR yearly from 1765 to 2500, our modified model will need to run yearly from 1950 to 1765. +For the purposes of this guide we focus on the first step of such modification. Since FUND runs yearly from 1950 to 3000 and FAIR yearly from 1765 to 2500, our modified model will need to run yearly from 1765 to 1950. We start with FUND ``` @@ -32,14 +32,18 @@ Before we do so, note some important rules and precautions. These are in place t ``` set_dimension!(m, :time, 1765:2500) ``` -At this point the model `m` can be run, and will run from 1765 to 2500. That said, the components will only run in the subset of years 1950 to 2500. All associated external parameters with a `time` dimension have been padded with `missing` values from 1765 to 1949, so that the values previously associated with 1950 to 2500 are still associated with those years. To add detail, after the time dimension is reset the following holds: +At this point the model `m` can be run, and will run from 1765 to 2500. In fact, we could start adding FAIR components to the model, which would automatically take on the entire model time dimension, ie. +``` +add_comp(m, FAIR_component) # will run from 1765 to 1950 +``` +**However**, the FUND components will only run in the subset of years 1950 to 2500, using the same parameter values each year was previously associated with, and containing placeholder `missing` values in the parameter value spots from 1765 to 1949. More specifically: -- The model's `time` dimension values are updated, and it will run for each year in that dimension. +- The model's `time` dimension values are updated, and it will run for each year in the new 1765:1950 dimension. ``` julia> Mimi.time_labels(m) 736-element Vector{Int64}: [1765, 1766, 1767, … 2498, 2499, 2500] ``` -- The components `time` dimension values are updated, but (1) the components maintain the `first` year as set by the original `time` dimension so the run period start year does not change and (2) they maintain their `last` year as set by the original `time` dimension, unless that year is now later than the model's last year, in which case it is trimmed back to the `time` dimensions last year. Thus, the components will run for the same run period, or a shorter one if the new time dimension ends before the component used to. +- The components `time` dimension values are updated, but (1) the components maintain the `first` year as set by the original `time` dimension (1950) so the run period start year does not change and (2) they maintain their `last` year as set by the original `time` dimension, unless that year is now later than the model's last year, in which case it is trimmed back to the `time` dimensions last year (2500). Thus, the components will run for the same run period, or a shorter one if the new time dimension ends before the component used to (in this case 1950:2500). ``` julia> component = m.md.namespace[:emissions] # get component def, ignore the messy syntax julia> component.dim_dict[:time] @@ -57,8 +61,9 @@ At this point the model `m` can be run, and will run from 1765 to 2500. That sai julia> parameter_values[1:(1950-1765),:] # all missing julia> parameter_values[(1950-1764),:] # hold set values ``` + ---- #### The following options are now available for further modifcations if this end state is not desireable: -- If you want to update a component's run period, you may use the (nonexported) function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specific when you want the component to run. -- You can update external parameters to, for example, have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function +- If you want to update a component's run period, you may use the function `Mimi.set_first_last!(m, :ComponentName, first = new_first, last = new_last)` to specify when you want the component to run. +- You can update external parameters to have values in place of the assumed `missing`s using the `update_param!(m, :ParameterName, values)` function From d5d0c536e64e74e60ade7245dc777d69d4f5ea33 Mon Sep 17 00:00:00 2001 From: lrennels Date: Tue, 20 Apr 2021 01:42:03 -0700 Subject: [PATCH 20/21] Fix more typos --- docs/src/howto/howto_5.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/src/howto/howto_5.md b/docs/src/howto/howto_5.md index a3115d5ce..590e937a6 100644 --- a/docs/src/howto/howto_5.md +++ b/docs/src/howto/howto_5.md @@ -32,9 +32,9 @@ Before we do so, note some important rules and precautions. These are in place t ``` set_dimension!(m, :time, 1765:2500) ``` -At this point the model `m` can be run, and will run from 1765 to 2500. In fact, we could start adding FAIR components to the model, which would automatically take on the entire model time dimension, ie. +At this point the model `m` can be run, and will run from 1765 to 2500 (Try running it and looking at `explore(m)` for parameters and variables with a `time` dimension!). In fact, we could start adding FAIR components to the model, which would automatically take on the entire model time dimension, ie. ``` -add_comp(m, FAIR_component) # will run from 1765 to 1950 +add_comp!(m, FAIR_component) # will run from 1765 to 1950 ``` **However**, the FUND components will only run in the subset of years 1950 to 2500, using the same parameter values each year was previously associated with, and containing placeholder `missing` values in the parameter value spots from 1765 to 1949. More specifically: @@ -43,9 +43,9 @@ add_comp(m, FAIR_component) # will run from 1765 to 1950 julia> Mimi.time_labels(m) 736-element Vector{Int64}: [1765, 1766, 1767, … 2498, 2499, 2500] ``` -- The components `time` dimension values are updated, but (1) the components maintain the `first` year as set by the original `time` dimension (1950) so the run period start year does not change and (2) they maintain their `last` year as set by the original `time` dimension, unless that year is now later than the model's last year, in which case it is trimmed back to the `time` dimensions last year (2500). Thus, the components will run for the same run period, or a shorter one if the new time dimension ends before the component used to (in this case 1950:2500). +- The components `time` dimension values are updated, but (1) the components maintain the `first` year as set implicitly by the original `time` dimension (1950) so the run period start year does not change and (2) they maintain their `last` year as set implicitly by the original `time` dimension, unless that year is now later than the model's last year, in which case it is trimmed back to the `time` dimensions last year (2500). Thus, the components will run for the same run period, or a shorter one if the new time dimension ends before the component used to (in this case 1950:2500). ``` - julia> component = m.md.namespace[:emissions] # get component def, ignore the messy syntax + julia> component = m.md.namespace[:emissions] # get component def(ignore messy internals syntax) julia> component.dim_dict[:time] [1765, 1766, 1767, … 2498, 2499, 2500] julia> component.first @@ -55,7 +55,7 @@ add_comp(m, FAIR_component) # will run from 1765 to 1950 ``` - All external parameters are trimmed and padded as needed so the model can still run, **and the values are still linked to their original years**. More specifically, if the new time dimension ends earlier than the original one than the parameter value vector/matrix is trimmed at the end. If the new time dimension starts earlier than the original, or ends later, the parameter values are padded with `missing`s at the front and/or back respectively. ``` - julia> parameter_values = Mimi.external_params(m)[:currtaxn2o].values.data + julia> parameter_values = Mimi.external_params(m)[:currtaxn2o].values.data # get param values for use in next run (ignore messy internals syntax) julia> size(parameter_values) (736, 16) julia> parameter_values[1:(1950-1765),:] # all missing From eb1aeaebf0dc30d1efb730f333168e3ee5cfdd70 Mon Sep 17 00:00:00 2001 From: lrennels Date: Wed, 21 Apr 2021 09:51:46 -0700 Subject: [PATCH 21/21] Simplify syntax and fix bug --- src/core/dimensions.jl | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/core/dimensions.jl b/src/core/dimensions.jl index 46ecf8bb9..cf3512f74 100644 --- a/src/core/dimensions.jl +++ b/src/core/dimensions.jl @@ -158,14 +158,10 @@ function _check_time_redefinition(obj::AbstractCompositeComponentDef, keys::Unio new_last = last(new_keys) # (1) check that the shift is legal - if isa(obj, ModelDef) - new_first > curr_first && error("Cannot redefine the time dimension to start at $new_first because it is after the model's current start $curr_first.") - curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the model's current start $curr_first") - else - new_first > curr_first && error("Cannot redefine the time dimension to start at $new_first, because it is after component $(nameof(obj))'s start $curr_first.") - curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the component $(nameof(obj))'s current start $curr_first") - end - + isa(obj, ModelDef) ? obj_name = "model" : obj_name = "component $(nameof(obj))" + new_first > curr_first && error("Cannot redefine the time dimension to start at $new_first because it is after the $obj_name's current start $curr_first.") + curr_first > new_last && error("Cannot redefine the time dimension to end at $new_last because it is before the $obj_name's current start $curr_first") + # (2) check first and last !(curr_first in new_keys) && error("The current first index ($curr_first) must exist within the model's new time dimension $new_keys.") # can be assumed since we cannot move the time forward curr_last >= new_last && !(new_last in curr_keys) && error("The new last index ($new_last) must exist within the model's current time dimension $curr_keys, since the time redefinition contracts to an earlier year.") @@ -178,7 +174,7 @@ function _check_time_redefinition(obj::AbstractCompositeComponentDef, keys::Unio else # variable timesteps start_idx = 1 # can be assumed since we cannot move the time forward - new_last < curr_last ? end_idx = findfirst(isequal(curr_last), new_keys) : end_idx = length(curr_keys) + new_last < curr_last ? end_idx = findfirst(isequal(new_last), curr_keys) : end_idx = length(curr_keys) expected_overlap = curr_keys[start_idx:end_idx] start_idx = findfirst(isequal(curr_first), new_keys)