Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d24fb96
Play with propagate_time! function options
lrennels Jan 27, 2021
d759e19
More work on first and last; stuck on changing dims
lrennels Jan 28, 2021
d20a720
Add tests
lrennels Jan 28, 2021
6285be8
Remove update_timesteps keyword argument
lrennels Jan 28, 2021
0e2c335
Fix documentation
lrennels Jan 28, 2021
f00df10
Add warning for update_timesteps keyword arg
lrennels Jan 29, 2021
f840656
Add testing
lrennels Jan 29, 2021
b8e730c
Fix typo in test
lrennels Jan 29, 2021
4a39aa9
Fix another test typo
lrennels Jan 29, 2021
232544f
Reenable testing and add a getindex method
lrennels Jan 31, 2021
0bf8568
Add more tests and a doc note
lrennels Jan 31, 2021
a6a530e
Edit some documentation
lrennels Jan 31, 2021
2e655c5
Cleanup and docs
lrennels Feb 3, 2021
8989198
Add get_shifted_ts functions
lrennels Feb 9, 2021
8b44981
Merge branch 'master' into time-dims
lrennels Feb 9, 2021
6939783
Fix up expected test values
lrennels Feb 9, 2021
1f0e5a1
Add testing
lrennels Feb 9, 2021
bf59ee8
Add performance improvements
lrennels Feb 9, 2021
b4b0f28
Fix off by one bug
lrennels Feb 9, 2021
1f28569
Merge branch 'master' into time-dims
lrennels Feb 9, 2021
2c8896a
Fix typo in test_all_models script
lrennels Feb 9, 2021
6f5db8d
Add tests for TimestepIndex and TimestepValue
lrennels Feb 17, 2021
e0755ca
Add some TODOs
davidanthoff Feb 19, 2021
542aa03
Add views and break everything
lrennels Feb 20, 2021
ab3ade7
Work on updating timesteparrays
lrennels Feb 20, 2021
2e63e0c
Revert tests back to proper array output expectation
lrennels Feb 20, 2021
2706d20
Fix connector problem
lrennels Feb 22, 2021
edb9a71
Fix bug in timesteparray test
lrennels Feb 22, 2021
cd91c6b
Fix _update_param! bug with explicit typing
lrennels Feb 23, 2021
af58cb5
Clean up
lrennels Feb 26, 2021
ac6eb55
Add performance improvements
lrennels Mar 2, 2021
c7f1b8e
sSimplify value replacement for TimestepArrays
lrennels Mar 2, 2021
36af9bb
Merge branch 'master' into time-dims
lrennels Mar 2, 2021
01ea91c
Add composites to datum_size and get_span methods
lrennels Mar 7, 2021
cbe3f35
Put offset back into connector components
lrennels Mar 7, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion benchmark/getindex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ end
function _get_timesteparray_type(years, num_dims, dtype=Float64)
if Mimi.isuniform(years)
first, stepsize = Mimi.first_and_step(years)
T = Mimi.TimestepArray{Mimi.FixedTimestep{first, stepsize}, Union{dtype, Missing}, num_dims}
last = years[last]
T = Mimi.TimestepArray{Mimi.FixedTimestep{first, stepsize, last}, Union{dtype, Missing}, num_dims}
else
T = Mimi.TimestepArray{Mimi.VariableTimestep{(years...,)}, Union{dtype, Missing}, num_dims}
end
Expand Down
6 changes: 6 additions & 0 deletions docs/src/howto/howto_1.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ add_comp!(m, ComponentA, :GDP)

The first argument to `add_comp!` is the model, the second is the name of the ComponentId defined by `@defcomp`. If an optional third symbol is provided (as in the second line above), this will be used as the name of the component in this model. This allows you to add multiple versions of the same component to a model, with different names.

The `add_comp` function has two more optional keyword arguments, `first` and `last`, which can be used to indicate a fixed start and/or end time (year in this case) that the compnonent should run for (within the bounds of the model's time dimension). For example, the following indicates that `ComponentA` should only run from 1900 to 2000.

```julia
add_comp!(m, ComponentA; first = 1900, last = 2000)
```

The next step is to set the values for all the parameters in the components. Parameters can either have their values assigned from external data, or they can internally connect to the values from variables in other components of the model.

To make an external connection, the syntax is as follows:
Expand Down
14 changes: 6 additions & 8 deletions docs/src/howto/howto_4.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ An `AbstractTimestep` i.e. a `FixedTimestep` or a `VariableTimestep` is a type d
In the `run_timestep` functions which the user defines, it may be useful to use any of the following functions, where `t` is an `AbstractTimestep` object:

```julia
is_first(t) # returns true or false, true if t is the first timestep to be run
is_last(t) # returns true or false, true if t is the last timestep to be run
is_first(t) # returns true or false, true if t is the first timestep to be run for the respective component
is_last(t) # returns true or false, true if t is the last timestep to be run for the respective component
gettime(t) # returns the year represented by timestep t
```
There are also two helper types `TimestepValue` and `TimestepIndex` that can be used with comparison operators (`==`, `<`, and `>`) to check whether an `AbstractTimestep` `t` during the `run_timestep` function corresponds with a certain year or index number. For example:
Expand Down Expand Up @@ -52,8 +52,7 @@ Indexing into a variable or parameter's `time` dimension with an `Integer` is de

end
```
`TimestepIndex` has one field, `index`, which refers to the absolute index in the parameter or variable array's `time` dimension. Thus, constructing a `TimestepIndex` is done by simply writing `TimestepIndex(index::Int)`. Looking back at our original component example
one could modify the first line of `run_timestep` to always refer to the first timestep of `p.d` with the following. One may index into the `time` dimension with a single `TimestepIndex`, or an `Array` of them.
`TimestepIndex` has one field, `index`, which refers to the absolute index in the parameter or variable array's `time` dimension. Thus, constructing a `TimestepIndex` is done by simply writing `TimestepIndex(index::Int)`. Looking back at our original component example, one could modify the first line of `run_timestep` to always refer to the first timestep of `p.d` with the following. One may index into the `time` dimension with a single `TimestepIndex`, or an `Array` of them.
```julia
v.A[t] = p.c + p.d[TimestepIndex(1)]
```
Expand Down Expand Up @@ -101,14 +100,13 @@ In both of these cases, the parameter's values are stored of as an array (p1 is

## Updating an external parameter

When `set_param!` is called, it creates an external parameter by the name provided, and stores the provided scalar or array value. It is possible to later change the value associated with that parameter name using the functions described below. If the external parameter has a `:time` dimension, use the optional argument `update_timesteps=true` to indicate that the time keys (i.e., year labels) associated with the parameter should be updated in addition to updating the parameter values.
When `set_param!` is called, it creates an external parameter by the name provided, and stores the provided scalar or array value. It is possible to later change the value associated with that parameter name using the functions described below.

```julia
update_param!(m, :ParameterName, newvalues) # update values only
update_param!(m, :ParameterName, newvalues, update_timesteps=true) # also update time keys
update_param!(m, :ParameterName, newvalues)
```

Note: `newvalues` must be the same size and type (or be able to convert to the type) of the old values stored in that parameter.
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.

#### Setting parameters with a dictionary

Expand Down
4 changes: 2 additions & 2 deletions docs/src/howto/howto_5.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ In an effort to standardize the function naming protocol within Mimi, and to str
|`addcomponent!` |`add_comp!` |
|`connectparameter` |`connect_param!` |
|`setleftoverparameters` |`set_leftover_params!` |
|`setparameter` |`set_param!` |
|`setparameter` |`set_param!` |
|`adddimension` |`add_dimension!` |
|`setindex` |`set_dimension!` |

Expand Down Expand Up @@ -98,7 +98,7 @@ To update an external parameter, use the functions `update_param!` and `udpate_p

* `update_params!(md::ModelDef, parameters::Dict; update_timesteps = false)`

* `update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false)`
* `update_param!(md::ModelDef, name::Symbol, value; update_timesteps = false)`

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.

Expand Down
2 changes: 2 additions & 0 deletions docs/src/howto/howto_6.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ replace!(m, old => new)

*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:*
Expand Down
2 changes: 2 additions & 0 deletions docs/src/ref/ref_structures_definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ 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).
Expand Down
14 changes: 5 additions & 9 deletions docs/src/tutorials/tutorial_3.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,13 @@ Possible modifications range in complexity, from simply altering parameter value

Several types of changes to models revolve around the parameters themselves, and may include updating the values of parameters and changing parameter connections without altering the elements of the components themselves or changing the general component structure of the model. The most useful functions of the common API in these cases are likely **[`update_param!`](@ref)/[`update_params!`](@ref), [`disconnect_param!`](@ref), and [`connect_param!`](@ref)**. For detail on these functions see the API reference guide, Reference Guide: The Mimi API.

When the original model calls [`set_param!`](@ref), Mimi creates an external parameter by the name provided, and stores the provided scalar or array value. The functions [`update_param!`](@ref) and [`update_params!`](@ref) allow you to change the value associated with this external parameter. Note that if the external parameter has a `:time` dimension, use the optional argument `update_timesteps=true` to indicate that the time keys (i.e., year labels) associated with the parameter should be updated in addition to updating the parameter values.
When the original model calls [`set_param!`](@ref), Mimi creates an external parameter by the name provided, and stores the provided scalar or array value. The functions [`update_param!`](@ref) and [`update_params!`](@ref) allow you to change the value associated with this external parameter.

```julia
update_param!(mymodel, :parametername, newvalues) # update values only

update_param!(mymodel, :parametername, newvalues, update_timesteps=true) # also update time keys. Only necessary if the time dimension of the model has been changed.
update_param!(mymodel, :parametername, newvalues)
```

In the code above, `newvalues` must be the same size and type (or be able to convert to the type) of the old values stored in that parameter.
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).

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.

Expand Down Expand Up @@ -96,7 +94,7 @@ nyears = length(years)
set_dimension!(m, :time, years)
```

Now that you have changed the time dimension, you have a mismatch between the time labels attached to your parameters and the time labels used by the model. Thus, **you must update at least all parameters with a `:time`** dimension and use the explicit `update_timesteps=true` flag to get the time labels on the parameters to match those in the model. This is required even in cases where you do not want to change the parameter values themselves (see the forum question [here](https://forum.mimiframework.org/t/update-time-index/134/5)) for an in-depth explanation of this case. You may of course also update parameters without a `:time` dimension as desired.
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).

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:

Expand All @@ -112,12 +110,10 @@ params[:S] = repeat([0.23], nyears)
Now you simply update the parameters listen in `params` and re-run the model with

```julia
update_params!(m, params, update_timesteps=true)
update_params!(m, params)
run(m)
```

Note again that here we use the `update_timesteps` flag and set it to `true`, because since we have changed the time index we want the time labels on the parameters to change, not simply their values.

## Component and Structural Modifications: The API

Most model modifications will include not only parametric updates, but also structural changes and component modification, addition, replacement, and deletion along with the required re-wiring of parameters etc. The most useful functions of the common API, in these cases are likely **[`replace!`](@ref), [`add_comp!`](@ref)** along with **`delete!`** and the requisite functions for parameter setting and connecting. For detail on the public API functions look at the API reference.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/tutorials/tutorial_4.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ end
We can now use Mimi to construct a model that binds the `grosseconomy` and `emissions` components together in order to solve for the emissions level of the global economy over time. In this example, we will run the model for twenty periods with a timestep of five years between each period.

* Once the model is defined, [`set_dimension!`](@ref) is used to set the length and interval of the time step.
* We then use [`add_comp!`](@ref) to incorporate each component that we previously created into the model. It is important to note that the order in which the components are listed here matters. The model will run through each equation of the first component before moving onto the second component.
* We then use [`add_comp!`](@ref) to incorporate each component that we previously created into the model. It is important to note that the order in which the components are listed here matters. The model will run through each equation of the first component before moving onto the second component. One can also use the optional `first` and `last` keyword arguments to indicate a subset of the model's time dimension when the component should start and end.
* Next, [`set_param!`](@ref) is used to assign values to each parameter in the model, with parameters being uniquely tied to each component. If _population_ was a parameter for two different components, it must be assigned to each one using [`set_param!`](@ref) two different times. The syntax is `set_param!(model_name, :component_name, :parameter_name, value)`
* If any variables of one component are parameters for another, [`connect_param!`](@ref) is used to couple the two components together. In this example, _YGROSS_ is a variable in the `grosseconomy` component and a parameter in the `emissions` component. The syntax is `connect_param!(model_name, :component_name_parameter, :parameter_name, :component_name_variable, :variable_name)`, where `:component_name_variable` refers to the component where your parameter was initially calculated as a variable.
* Finally, the model can be run using the command `run(model_name)`.
Expand Down
24 changes: 12 additions & 12 deletions src/components/connector.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ using Mimi
first = Parameter() # first year to use the shorter data
last = Parameter() # last year to use the shorter data

function run_timestep(p, v, d, ts)
if gettime(ts) >= p.first && gettime(ts) <= p.last
function run_timestep(p, v, d, t)
if gettime(t) >= p.first && gettime(t) <= p.last
input = p.input1
else
input = p.input2
end

v.output[ts] = @allow_missing(input[ts])
v.output[t] = @allow_missing(input[t])

end
end
Expand All @@ -30,16 +30,16 @@ end
first = Parameter() # first year to use the shorter data
last = Parameter() # last year to use the shorter data

function run_timestep(p, v, d, ts)
function run_timestep(p, v, d, t)

if gettime(ts) >= p.first && gettime(ts) <= p.last
if gettime(t) >= p.first && gettime(t) <= p.last
input = p.input1
else
input = p.input2
end

for r in d.regions
v.output[ts, r] = @allow_missing(input[ts, r])
v.output[t, r] = @allow_missing(input[t, r])
end
end
end
Expand All @@ -55,14 +55,14 @@ end
# output = Variable(index = [time, ...])

# # Allow copying of vars/params with arbitrary dimensions
# function run_timestep(p, v, d, ts)
# function run_timestep(p, v, d, t)
# colons = repeat([:], inner=ndims(v.output) - 1)
# if hasvalue(p.input1, ts)
# v.output[ts, colons...] = p.input1[ts, colons...]
# elseif hasvalue(p.input2, ts)
# v.output[ts, colons...] = p.input2[ts, colons...]
# if hasvalue(p.input1, t)
# v.output[t, colons...] = p.input1[t, colons...]
# elseif hasvalue(p.input2, t)
# v.output[t, colons...] = p.input2[t, colons...]
# else
# error("Neither of the inputs to ConnectorComp have data for the current timestep: $(gettime(ts)).")
# error("Neither of the inputs to ConnectorComp have data for the current timestep: $(gettime(t)).")
# end
# end
# end
Loading