Use EnergyModelsInvestments
EnergyModelsInvestments
was initially designed as extension package for EnergyModelsBase
. Hence, many design choices are impacted by the requirements of EnergyModelsBase
. We realized however that it can be beneficial to make it independent of EnergyModelsBase
to use the incorporated methods in other energy system optimization models that may be under development.
Using EnergyModelsInvestments
requires the following implementations in your model.
If you are uncertain on how to best implement investment options, it can be beneficial to investigate the approaches chosen in EnergyModelsBase
and EnergyModelsGeography
.
Auxiliary functions
The are several additional functions which are specific for the individual types. This functions are either used as example for a simplified interface or alternatively required in EnergyModelsInvestments
. The latter requires you to implement methods within your model through multiple dispatch as they are called within EnergyModelsInvestments
.
Required methods
When you are using the type NoStartInvData
, you have to specify the function
start_cap(element, t_inv, inv_data::NoStartInvData, cap)
for your type. The main reason is that in this case you do not specify the initial capacity in EnergyModelsInvestments
, but instead deduce it from the provided initial capacity in your energiy system optimization model.
In the case of EnergyModelsBase, this is given through the methods
EMI.start_cap(n::EMB.Node, t_inv, inv_data::NoStartInvData, cap) =
capacity(n, t_inv)
EMI.start_cap(n::Storage, t_inv, inv_data::NoStartInvData, cap) =
capacity(getproperty(n, cap), t_inv)
and the application of the internal function capacity()
.
Simplified interface functions
EnergyModelsBase
creates new types for the investment data instead of using NoStartInvData
and StartInvData
. The background for this approach is that we like to have the potential for multiple capacities within a node, e.g., charge, level, and discharge capacities in Storage
nodes as well as the potential for investments in the individual capacities. Hence, we create new methods for the function investment_data()
to directly access the individual fields of the new investment data types from the node level. In addition, we incorporate the function has_investment()
to allow a limitation of investments to a subset of the nodes.
Both functions are not directly used in EnergyModelsInvestments
in this context. Hence, it is not required to declare new methods for these functions. It can however be beneficial to create methods as it may simplify the subsequent structure.
Variable declarations
As outlined in Optimization variables, we require that the user follows a given variable naming convention. The variables used within EnergyModelsInvestments
are extracted with the help of the following functions:
EMI.get_var_capex
for capital expenses variables, declared over strategic periods,EMI.get_var_inst
for capacity variables, declared over operational periods,EMI.get_var_current
for capacity variables, declared over strategic periods,EMI.get_var_add
for capacity addtion variables, declared over strategic periods,EMI.get_var_rem
for capacity removal variables, declared over strategic periods,EMI.get_var_invest_b
for helper variables for investments, declared over strategic periods, andEMI.get_var_remove_b
for helper variables for removals, declared over strategic periods.
The provided functions utilize a prefix::Symbol
argument which is the corresponding prefix for all variables. Optimization variables explains the required names for the variables with prefix = :cap
.
Although it is in general possible to provide dispatch on these functions for new types, we unfortunately require in the current implementation that the naming convention has to be followed at least for the Auxiliary variables as SparseVariables
requires the extraction of all variables with a given name before the insertion.
Hence, it is best to declare all variables as, e.g. using the prefix :cap
:
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
# Add investment variables for reference nodes for each strategic period:
@variable(m, cap_inst[𝒩ᴵⁿᵛ] >= 0)
@variable(m, cap_capex[𝒩ᴵⁿᵛ, 𝒯ᴵⁿᵛ] >= 0)
@variable(m, cap_current[𝒩ᴵⁿᵛ, 𝒯ᴵⁿᵛ] >= 0)
@variable(m, cap_add[𝒩ᴵⁿᵛ, 𝒯ᴵⁿᵛ] >= 0)
@variable(m, cap_rem[𝒩ᴵⁿᵛ, 𝒯ᴵⁿᵛ] >= 0)
@variable(m, cap_invest_b[𝒩ᴵⁿᵛ, 𝒯ᴵⁿᵛ] >= 0; container = IndexedVarArray)
@variable(m, cap_remove_b[𝒩ᴵⁿᵛ, 𝒯ᴵⁿᵛ] >= 0; container = IndexedVarArray)
with 𝒩ᴵⁿᵛ
corresponding to nodes with investments.
Inclusion of investment constraints
Investment constraints are included through the function
function add_investment_constraints(
m, # JuMP model
element, # Element for which investments are added
inv_data::AbstractInvData, # Investment data for the element
cap, # Capacity that has investments
prefix, # Used prefix in variable declaration
𝒯ᴵⁿᵛ::TS.StratPeriods, # Strategic periods
disc_rate::Float64, # Discount rate in absolute values
)
The individual input is as well described in the documentation
This functions includes constraints on the capacity and calculates the capital expenses for each element
. There are two main points one has to consider:
- We add the investments constraints for each individual
element
. In the case of multipleelement
s, it is necessary to iterate through the vector ofelement
s. - We add the investment constraints for each individual capacity
cap
. This argument is only relevant if anelement
has multiple capacities as it is the case forStorage
nodes inEnergyModelsBase
.
Consequently, you have to iterate through all element
s and their capacities cap
if you want to add investment constraints.
Given the example nodes above with single capacities, this would be given as:
for n ∈ 𝒩ᴵⁿᵛ
# Extract the investment data, the discount rate, and the strategic periods
disc_rate = discount_rate(modeltype)
inv_data = investment_data(n, :cap)
𝒯ᴵⁿᵛ = strategic_periods(𝒯)
# Add the investment constraints
EMI.add_investment_constraints(m, n, inv_data, :cap, :cap, 𝒯ᴵⁿᵛ, disc_rate)
end
Note that we included in this example a method for investment_data()
as outlined above.
Updating the objective function
EnergyModelsInvestments
does not change the objective function. This would require detailed knowledge regarding the individual contributing factors. Hence, we decided that it is beneficial to instead calculate the capital expense contributions in each strategic period. As a consequence, when using EnergyModelsInvestments
, you have to include the variable $\texttt{cap\_capex}$ (if you used prefix = :cap
) for all elements with investments to your objective function.
An illustrative example is given in the function EMB.objective(m, 𝒩, 𝒯, 𝒫, modeltype::AbstractInvestmentModel)
compared to the function objective(m, 𝒩, 𝒯, 𝒫, modeltype::EnergyModel)
in EnergyModelsBase
.
We plan to add the links to the functions once we have both updates registered.