Replacing a component in FUND after loading as a

Hi Lisa,

Moving here from our e-mail discussion. I am attempting to configure FUND to use percentage of coast protected from sea level as a lever for use in optimization. In order to do this, I need to do two things:

  1. Create a new sea level rise component that replaces the calculation of protection level with a simple parameter, which I have done.

  2. Disconnect the original SLR component and replace it with the newly created component from within the Jupyter Notebook wrapper function we are using.

The tutorial on building models describes an add component, but doesn’t seem to describe how to replace a component once I’ve loaded FUND as a package.

Do you know how that can be done, or do I just need to work from a local copy of the model in order to make those changes?

Thanks,
Sara

Hi @sturner, thanks for the question and pointing out I should enhance the documentation on this within the tutorials. Below I’ll detail some available docs, which I think you’ve already looked at but wanted to put down for good measure, and then some details on your task. We can maintain a conversation here and per usual I am happy take a look at code in your repo, or perhaps more conveniently don’t be shy about posting large blocks of code in this conversation if it isn’t proprietary!

In terms of existing code examples and docs, I’d recommend

  1. Tutorial 2 (although as you point out this is lacking in code examples for structural changes like the one you mention above)
  2. Material from our recent workshop at the AERE conference linked here, specifically in examples for updating parameters here and integrating new components into an existing model here. This Github repository also provides a link to a binder notebook, but I think the ipynb files are most familiar for you!

Now for your specific case you are looking to replace a component! I think the most useful function for you here will be replace_comp! with the specification below for your convenience. Descriptions of all exported functions should be available in the stable Documentation reference page … but there is a bug that disabled that page which I just fixed so the dev Documentation reference page should be functional and become the stable one upon our next release.

replace_comp!(m::Model, comp_id::ComponentId, comp_name::Symbol=comp_id.comp_name;
        first::NothingSymbol=nothing, last::NothingSymbol=nothing,
        before::NothingSymbol=nothing, after::NothingSymbol=nothing,
        reconnect::Bool=true)
        
Replace the component with name `comp_name` in model `m` with the component
`comp_id` using the same name.  The component is added in the same position as 
the old component, unless one of the keywords `before` or `after` is specified.
The component is added with the same first and last values, unless the keywords 
`first` or `last` are specified. Optional boolean argument `reconnect` with 
default value `true` indicates whether the existing parameter connections 
should be maintained in the new component. 

Note that the (optional) reconnect keyword argument defaults to true, meaning Mimi will expect to be able to maintain any old parameter connections, meaning parameters connected to other components either into or out of your SLR component need to have the same name and dimensions. If you want to remove that constriction, just set reconnect to false with replace_comp!(...; reconnect = false) and then reform your connections with connect_param! etc. by hand.

What this function really does under the hood is an optimized combination of functions that are also available to you. Essentially it uses Mimi.delete! to remove your old component, and then add_comp! to add your new component (with some extra bells and whistles to put it in the same place in the sequential order of components using the after and before keyword arguments), and maintains connections as stated above using the (optional) reconnect keyword argument.

Thanks @lrennels,

This is super helpful - I will keep an eye on both the dev and stable Documentation pages going forward!

One quick follow-up: the module I wrote keeps all the original FUND parameters, it just adds an additional, new parameter (not read from or connected to any other component), so it should be fine for me to use reconnect = true, right?

Effectively, in the FUND code I’m going from this:
v.protlev[t, r] = max(0.0, 1.0 - 0.5 * (v.npprotcost[t, r] + v.npwetcost[t, r]) / v.npdrycost[t, r])

to this:
v.protlev[t, r]= p.protlevpar[t,r]

in order to break the endogenous calculation of protection level.

Then yes, I believe the simplest version of replace_comp! should do the trick for you! Let me know how it goes I haven’t used that function in a while.

Hey Lisa,

I made the replacement and have some questions:

  1. Should the name of the new component have to be the same as the one you are replacing (it doesn’t appear to make a difference neither throws errors)?

eg:
replace_comp!(m_fund, impactsealevelrise, :impactsealevelrise, reconnect = true )
vs.
replace_comp!(m_fund, impactslrparameter, :impactsealevelrise, reconnect = true )

  1. I get an error when I try to evaluate the model using the new component. Thoughts or suggestions would be much appreciated. The full stacktrace is below:

MethodError: no method matching getindex(::Float64, ::Mimi.FixedTimestep{1950,1,3000}, ::Int64)

Closest candidates are:
getindex(::Number, !Matched::Integer…) at number.jl:82
getindex(::Number) at number.jl:75
getindex(::Number, !Matched::Integer) at number.jl:77

Stacktrace:
[1] macro expansion at .\In[90]:182 [inlined]

[2]run_timestep_impactsealevelrise(::Mimi.ComponentInstanceParameters{NamedTuple{(:incdens, :emcst, :immcst, :dvydl, :wvel, :wvbm, :slrwvpopdens0, :wvpdl, :wvsl, :dvbm, :slrwvypc0, :protpar, :pc, :slrprtp, :wmbm, :dlbm, :drylandlossparam, :wlbm, :coastpd, :wetmax, :wetland90, :maxlandloss, :sea, :migrate, :income, :population, :area),Tuple{Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},1},Array{Float64,2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2}}}}, ::Mimi.ComponentInstanceVariables{NamedTuple{(:wetval, :wetlandloss, :cumwetlandloss, :wetlandgrowth, :wetcost, :dryval, :landloss, :cumlandloss, :drycost, :npprotcost, :npwetcost, :npdrycost, :protlev, :protcost, :enter, :leave, :entercost, :leavecost, :imigrate),Tuple{Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Array{Float64,2}}}}, ::Mimi.DimDict, ::Mimi.FixedTimestep{1950,1,3000}) at C:\Users\sturner.julia\packages\Mimi\ULzD7\src\core\defcomp.jl:68

[3] run_timestep(::Mimi.ComponentInstance{Mimi.ComponentInstanceVariables{NamedTuple{(:wetval, :wetlandloss, :cumwetlandloss, :wetlandgrowth, :wetcost, :dryval, :landloss, :cumlandloss, :drycost, :npprotcost, :npwetcost, :npdrycost, :protlev, :protcost, :enter, :leave, :entercost, :leavecost, :imigrate),Tuple{Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Array{Float64,2}}}},Mimi.ComponentInstanceParameters{NamedTuple{(:incdens, :emcst, :immcst, :dvydl, :wvel, :wvbm, :slrwvpopdens0, :wvpdl, :wvsl, :dvbm, :slrwvypc0, :protpar, :pc, :slrprtp, :wmbm, :dlbm, :drylandlossparam, :wlbm, :coastpd, :wetmax, :wetland90, :maxlandloss, :sea, :migrate, :income, :population, :area),Tuple{Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Mimi.ScalarModelParameter{Float64},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Array{Float64,1},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},1},Array{Float64,2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2},Mimi.TimestepArray{Mimi.FixedTimestep{1950,1,LAST} where LAST,Union{Missing, Float64},2}}}}}, ::Mimi.Clock{Mimi.FixedTimestep}) at C:\Users\sturner.julia\packages\Mimi\ULzD7\src\core\instances.jl:264

[4] _run_components(::Mimi.ModelInstance, ::Mimi.Clock{Mimi.FixedTimestep}, ::Array{Int64,1}, ::Array{Int64,1}, ::Array{Mimi.Clock{Mimi.FixedTimestep},1}) at C:\Users\sturner.julia\packages\Mimi\ULzD7\src\core\instances.jl:277

[5] run(::Mimi.ModelInstance, ::Int64, ::Nothing) at C:\Users\sturner.julia\packages\Mimi\ULzD7\src\core\instances.jl:313

[6] #run#114(::Int64, ::Nothing, ::Function, ::Mimi.Model) at C:\Users\sturner.julia\packages\Mimi\ULzD7\src\core\model.jl:399

[7] run at C:\Users\sturner.julia\packages\Mimi\ULzD7\src\core\model.jl:390 [inlined]

[8] #our_model#30(::Float64, ::Float64, ::Float64, ::Float64, ::Int64, ::Int64, ::Int64, ::Int64, ::Int64, ::Int64, ::Int64, ::Float64, ::Float64, ::Float64, ::Int64, ::Nothing, ::Nothing, ::Nothing, ::Float64, ::typeof(our_model)) at .\In[91]:81

[9] (::getfield(Main, Symbol("#kw##our_model")))(::NamedTuple{(:carbon_tax_start, :carbon_tax_end, :prtp, :start_year, :end_year, :nt_impact, :wregion, :gini_year),Tuple{Float64,Float64,Float64,Int64,Int64,Int64,Int64,Int64}}, ::typeof(our_model)) at .\none:0

[10] top-level scope at In[92]:1

Hi Sara! I’ll give answers best I can later this evening but since the error is within a run_timestep function do you think you can either send me the github repo link, or copy the code of the component you wrote here? It’ll make it a bit easier for me to debug and help you!

No problem - I can make your life a little easier :slight_smile: I fixed my run timestep error. Your comment reminded me to go back and make sure all my indices matched.

The only other trick I needed to make it work was to replace the component outside the wrapper function in the notebook. I’m cleaning up the notebook and will post a Github link in a little bit.

Oh great news on that front I’m glad I could be helpful!

In terms of your other question, as you presumed it does not matter what you name the component that you create.

I do want to note however that regardless of how you name the component you construct, the default arguments of the replace_comp! function make it so the Model maintains the same identifier name for the component as was created with the original add_comp! call.

More concretely, if you have model m with component comp and variable myvar, you could previously access a variable with

m[:comp, :myvar]

then if you replace comp with newcomp that also has myvar like

replace_comp(m, newcomp, :comp)

you will still access the variable with

m[:comp, :myvar]

because the model still identifies the component with :comp, not :newcomp

Does that make sense? I can go into the internals details but I think that’s what you need to know on an API level