Hydro storage node
As outlined in the section on simple hydro storage, the reference storage nodeRefStorage
has some built in limitations. In the case of battery storage, the main limitations are related to not limiting the discharge use, not being able to include charge and discharge efficiencies, and not allowing for degradation of the storage capacity.
Hence, it is necessary to include specific battery nodes.
Concepts used for Batteries
Battery lifetime
Batteries experience storage capacity degradation. The degradation can be differentiated in 1. degradation through time and 2. degradation through charging and discharging the battery. Degradation through time is not included in the developed nodes. Instead, degradation through charging is included.
The lifetime is implemented through AbstractBatteryLife
types. There are two options implemented, InfLife
and CycleLife
. InfLife
does not include any storage capacity degradation. The battery life is unlimited and unimpacted by the use of the battery. CycleLife
includes both linear battery degradation and a lifetime through the maximum number of cycles of the battery. The linear degradation is dependent on the charging of the battery although this is equivalent to the discharging. The type allows for replacement of the battery stack to reduce battery degradation to 0 at the beginning of an investment period. The cost for replacement has to be accessible through a strategic period. Hence, it can be either a FixedProfile
or a StrategicProfile
, but cannot include, e.g. OperationalProfile
.
If you use batteries with varying capacities, it is important to implement one battery node for each investment period. The key reason is that the use is not calculated based on changing capacities. Similarly, if you plan to use batteries in investment models, it is necessary to specify one battery node for each investment periods with limited investments in an investment period.
Philosophy of ReserveBattery
ReserveBattery
nodes allow modelling a system that requires reserve capacity. The reserve capacity must be specified as a sink with a demand for a given reserve resource and a potential penalty for violating the demand. The ReserveBattery
is subsequently coupled to the link to satisfy the potential demand.
Reserve resources and demands are not included in the energy balances. Instead, they can be used to specify a minimum amount of dispatchable power to both increase either the electricity generation capacity or the electricity demand.
Introduced types and their fields
The AbstractBattery
abstract type is used to simplify the design of the constraints. It has in its current stage two concrete subtypes, Battery
and ReserveBattery
. Both types utilize the same main functionality with respect to efficiencies and battery lifes. ReserveBattery
is included as a first nodal type to be able to provide a reserve capacity for the energy system. This allows the user to specify a minimum of required reserve capacity at each individual operational period.
The two nodes are designed to work with the cyclic storage behaviors. In practice, battery storages should utilize CyclicRepresentative
as battery storage is in general not considered for seasonal energy storage.
Standard fields
The standard fields are given as:
id
:
The fieldid
is only used for providing a name to the node. This is similar to the approach utilized inEnergyModelsBase
.charge::EMB.UnionCapacity
:
The charge storage parameters must include a capacity. The charge capacity is the capacity before taking into account the charging efficiency from the viewpoint of the storage level. This is also shown in the section on the level constraints More information can be found on storage parameters.level::EMB.UnionCapacity
:
The level storage parameters must include a capacity. More information can be found on storage parameters.discharge::EMB.UnionCapacity
:
The discharge storage parameters must include a capacity. The discharge capacity is the capacity after taking into account the discharging efficiency from the viewpoint of the storage level. This is also shown in the section on the level constraints More information can be found on storage parameters.Permitted values for storage parameters in `charge`, `level`, and `discharge` If the node should contain investments through the application of
EnergyModelsInvestments
, it is important to note that you can only useFixedProfile
orStrategicProfile
for the capacity, but notRepresentativeProfile
orOperationalProfile
. Similarly, you can only useFixedProfile
orStrategicProfile
for the fixed OPEX, but notRepresentativeProfile
orOperationalProfile
. The variable operating expenses can be provided asOperationalProfile
as well. In addition, all capacity and fixed OPEX values have to be non-negative.stor_res::ResourceEmit
:
Thestor_res
is the storedResource
. In the case of a battery, you must specifiy your electricity resource.input::Dict{<:Resource, <:Real}
andoutput::Dict{<:Resource, <:Real}
:
Both fields describe theinput
andoutput
Resource
s with their corresponding conversion factors as dictionaries. The values correspond to charge and discharge efficiencies from theAbstractBattery
nodes.
All values have to be in the range $[0, 1]$.data::Vector{Data}
:
An entry for providing additional data to the model. In the current version, it is only relevant for additional investment data whenEnergyModelsInvestments
is used.
Additional fields
AbstractBattery
nodes add additional fields compared to RefStorage
nodes. These fields are located below the field output
and before the field data
.
The individual fields are related to specifics of batteries. These fields are given as:
battery_life::AbstractBatteryLife
:
The battery life is incorporated to include the potential for either battery type to include battery storage degradation. It is explained in more detail above.reserve_up::Vector{<:ResourceCarrier}
:
The upwards reserve is only included forReserveBattery
nodes. It corresponds to the potential of the node to deliver reserve electricity to the system in the given operational period.
The specified resources cannot be part of theinput
oroutput
dictionaries.reserve_down::Vector{<:ResourceCarrier}
:
The downwards reserve is only included forReserveBattery
nodes. It corresponds to the potential of the node to receive reserve electricity from the system in the given operational period.
The specified resources cannot be part of theinput
oroutput
dictionaries.
Mathematical description
In the following mathematical equations, we use the name for variables and functions used in the model. Variables are in general represented as
$\texttt{var\_example}[index_1, index_2]$
with square brackets, while functions are represented as
$func\_example(index_1, index_2)$
with paranthesis.
Variables
Standard variables
The battery node types utilize all standard variables from RefStorage
, as described on the page Optimization variables. The variables include:
- $\texttt{opex\_var}$
- $\texttt{opex\_fixed}$
- $\texttt{stor\_level}$
- $\texttt{stor\_level\_inst}$
- $\texttt{stor\_charge\_use}$
- $\texttt{stor\_charge\_inst}$
- $\texttt{stor\_discharge\_use}$
- $\texttt{stor\_discharge\_inst}$
- $\texttt{flow\_in}$
- $\texttt{flow\_out}$
- $\texttt{stor\_level\_Δ\_op}$
- $\texttt{stor\_level\_Δ\_rp}$ if the
TimeStruct
includesRepresentativePeriods
Additional variables
AbstractBattery
nodes by default calculate the charging of a battery in the individual periods, even if the battery life is modelled as InfLife
Hence, the following additional variables are included through providing a new methods to EnergyModelsBase.variables_node()
:
- $\texttt{bat\_prev\_use}[n, t]$: Previous charging of battery $n$ at the beginning of operational period $t$ with a typical unit of MWh.
The previous use corresponds to how much the battery is charged up to the current operational period. It is utilized for calculating the degradation of the battery. - $\texttt{bat\_prev\_use\_sp}[n, t_{inv}]$: Previous charging of battery $n$ at the beginning of investmet period $t_{inv}$ with a typical unit of MWh.
The previous use corresponds to how much the battery is charged up to the current investment period. It is utilized for calculating the degradation of the battery. - $\texttt{bat\_use\_sp}[n, t_{inv}]$: Charging of battery $n$ in investment period $t_{inv}$ with a typical unit of MWh.
The use allows to see how many charging cycles are conducted within an investment period. It is utilized for calculating the degradation of the battery. - $\texttt{bat\_use\_rp}[n, t_{rp}]$: Charging of battery $n$ in representantive period $t_{rp}$ with a typical unit of MWh.
The use allows to see how many charging cycles are conducted within a representative period. It is utilized for calculating the degradation of the battery.
It is only created if the time structure includes representative periods. - $\texttt{bat\_stack\_replace\_b}[n, t_{inv}]$: Binary variable representing replacement of the battery stack.
The battery stack replacement is occuring at the beginning of an investment period and results in setting the previous use to 0. Consequently, the initial capacity is available again.
It is only created for battery nodes withCycleLife
.
ReserveBattery
nodes create two additional variables:
- $\texttt{bat\_res\_up}[n, t]$: Available upwards reserve of battery $n$ in operational period $t$ with a typical unit of MW.
The upwards reserve corresponds to the potential of the battery to provide additional capacity to the system. The capacity is required to be sufficient for a duration of 1 of an operational period as outlined in UtilizeTimeStruct
. - $\texttt{bat\_res\_down}[n, t]$: Available downwards reserve of battery $n$ in operational period $t$ with a typical unit of MW.
The downwards reserve corresponds to the potential of the battery to use surplus capacity in the system for charging the battery. The capacity is required to be sufficient for a duration of 1 of an operational period as outlined in UtilizeTimeStruct
.
Constraints
The following sections omit the direct inclusion of the vector of battery storage nodes. Instead, it is implicitly assumed that the constraints are valid $\forall n ∈ N$ for all Battery
or ReserveBattery
types if not stated differently. In addition, all constraints are valid $\forall t \in T$ (that is in all operational periods) or $\forall t_{inv} \in T^{Inv}$ (that is in all investment periods).
Standard constraints
Battery storages nodes utilize in general the standard constraints described on Constraint functions for RefStorage
nodes.
These standard constraints are:
constraints_capacity_installed
:\[\begin{aligned} \texttt{stor\_level\_inst}[n, t] & = capacity(level(n), t) \\ \texttt{stor\_charge\_inst}[n, t] & = capacity(charge(n), t) \\ \texttt{stor\_discharge\_inst}[n, t] & = capacity(charge(n), t) \end{aligned}\]
Using investments The function
constraints_capacity_installed
is also used inEnergyModelsInvestments
to incorporate the potential for investment. Nodes with investments are then no longer constrained by the parameter capacity.constraints_flow_in
:
The auxiliary resource constraints are independent of the chosen storage behavior:\[\texttt{flow\_in}[n, t, p] = inputs(n, p) \times \texttt{flow\_in}[n, stor\_res(n)] \qquad \forall p \in inputs(n) \setminus \{stor\_res(n)\}\]
The stored resource constraints are depending on the chosen storage behavior. it is given by
\[\texttt{flow\_in}[n, t, stor\_res(n)] = \texttt{stor\_charge\_use}[n, t]\]
constraints_level
: The level constraints are in general following the default approach with minor modifications. They are explained in detail below in Level constraintsconstraints_opex_var
:\[\begin{aligned} \texttt{opex\_var}&[n, t_{inv}] = \\ \sum_{t \in t_{inv}}& opex\_var(level(n), t) \times \texttt{stor\_level}[n, t] \times scale\_op\_sp(t_{inv}, t) + \\ & opex\_var(charge(n), t) \times \texttt{stor\_charge\_use}[n, t] \times scale\_op\_sp(t_{inv}, t) + \\ & opex\_var(discharge(n), t) \times \texttt{stor\_discharge\_use}[n, t] \times scale\_op\_sp(t_{inv}, t) \end{aligned}\]
The function `scale_op_sp` The function $scale\_op\_sp(t_{inv}, t)$ calculates the scaling factor between operational and investment periods. It also takes into account potential operational scenarios and their probability as well as representative periods.
constraints_data
:
This function is only called for specified data of the CO₂ storage node, see above.
The fixed and variable OPEX constribubtion for the charge, the level, and the discharge capacities are only included if the corresponding storage parameters have a field opex_fixed
and opex_var
, respectively. Otherwise, they are omitted.
Batteries require a new method for constraints_capacity
. While the charge and discharge capacities are unaffected by the storage level degradation,
\[\begin{aligned} \texttt{stor\_charge\_inst}[n, t] & \geq \texttt{stor\_charge\_use}[n, t] \\ \texttt{stor\_discharge\_inst}[n, t] & \geq \texttt{stor\_discharge\_use}[n, t] \\ \end{aligned}\]
is is impacting the storage level capacity. If the node utilizes InfLife
, the function reverts to the default behavior given by
\[\texttt{stor\_level\_inst}[n, t] \geq \texttt{stor\_level}[n, t] \\\]
However, if the node utilizes CycleLife
, we have to include battery degradation through
\[\begin{aligned} \texttt{stor\_level\_inst}[n, t] - & degradation(n) \times \texttt{bat\_prev\_use}[n, t] / cycles(n) \\ & \geq \texttt{stor\_level}[n, t] \end{aligned}\]
The degradation corresponds to a linear degradation between a fresh battery with 0 cycles and a worn out battery at the maximum nubmer of cycles. It is implemented through the function capacity_reduction
.
The outlet flow of a Battery
node is similar to a RefStorage
node. It is reusing the functionality of the function constraints_flow_out
for Storage
nodes given by
\[\texttt{flow\_out}[n, t, stor\_res(n)] = \texttt{stor\_discharge\_use}[n, t]\]
ReserveBattery
nodes include a new method to also include the outflow of the reserve resources. This outflow does not correspond to a physical flow of energy or mass. Instead, it is purely utilized for accounting purposes.
The provided reserve for the specified reserve resources $P^{up} = reserve\_up(n)$ and $P^{down} = reserve\_down(n)$ is given by
\[\begin{aligned} \texttt{bat\_res\_up}[n, t] & = \sum_{p \in P^{up}} \texttt{flow\_out}[n, t, p] \\ \texttt{bat\_res\_down}[n, t] & \sum_{p \in P^{down}} \texttt{flow\_out}[n, t, p] \\ \end{aligned}\]
The fixed operating expenditures, calculated through the function constraints_opex_fixed
, requires a new method to include the costs for the battery stack replacement. It first creates an auxiliary variable $stack\_replace$ which is given as
\[stack\_replace[t_{inv}] = stor\_level\_current[n, t_{inv}] \times bat\_stack\_replace\_b[n, t_{inv}]\]
if the battery includes investment options or
\[stack\_replace[t_{inv}] = capacity(level(n), t_{inv}) \times bat\_stack\_replace\_b[n, t_{inv}]\]
if the battery does not include investments. The bilinearity is reformulated using the function linear_reformulation
. This auxiliary variable is subsequently utilized in the calculation of the
\[\begin{aligned} \texttt{opex\_fixed}&[n, t_{inv}] = \\ & opex\_fixed(level(n), t_{inv}) \times \texttt{stor\_level\_inst}[n, first(t_{inv})] + \\ & opex\_fixed(charge(n), t_{inv}) \times \texttt{stor\_charge\_inst}[n, first(t_{inv})] + \\ & opex\_fixed(discharge(n), t_{inv}) \times \texttt{stor\_discharge\_inst}[n, first(t_{inv})] + \\ & stack\_cost(n) \times stack\_replace[t_{inv}] / duration\_strat(t_{inv}) \end{aligned}\]
Stack replace occurs always at the beginning of an investment period $t_{inv}$ and only once in an investment period. The variable $\texttt{opex\_fixed}[n, t_{inv}]$ is multiplied in the objective function with $duration\_strat(t_{inv})$. As a consequence, if the duration of the investment period is larger than 1, we would invest multiple times into a new stack. Consequently, we have to divide the costs by the duration of the investment period.
The variables $\texttt{stor\_level\_inst}$ are declared over all operational periods (see the section on Capacity variables for further explanations). Hence, we use the function $first(t_{inv})$ to retrieve the installed capacities in the first operational period of a given investment period $t_{inv}$ in the function constraints_opex_fixed
.
Additional constraints
Battery use constraints
The calculation of the previous battery use requires the definition of new constraint functions as the approach differs depending on the chosen TimeStructure
. The overall approach is similar to the calculation of the level constraints in EnergyModelsBase
. The core function is constraints_usage
from which the individual iteration is achieved.
Within this function, we first calculate $\forall t_{inv} \in T^{Inv}$ the use of a battery within each investment period:
\[\begin{aligned} \texttt{bat\_use\_sp}[n, t_{inv}] = \sum_{t \in t_{inv}} & \texttt{stor\_charge\_use}[n, t] \times \\ & inputs(n, p_{stor}) \times scale\_op\_sp(t_{inv}, t) \\ \end{aligned}\]
In addition, we calculate the charging of the storage up to the current investment period $t_{inv}$ through the subfunction constraints_usage_sp
. This function has two distinctive methods:
If the current investment period is the first, we add the following constraint:
\[\texttt{bat\_prev\_use\_sp}[n, t_{inv}] = 0\]
In all other investment periods it is given by
\[\texttt{bat\_prev\_use\_sp}[n, t_{inv}] = \texttt{disjunct}[t_{inv}]\]
in which we introduce an auxiliary variable $\texttt{disjunct}[t_{inv}]$. The meaning of the auxiliary variable is depending on the chosen battery life type. For all
AbstractBatteryLife
, if not specified differently, it is simply given as:\[\begin{aligned} \texttt{disjunct}[t_{inv}] = & \texttt{bat\_prev\_use\_sp}[n, t_{inv, prev}] + \\ & \texttt{bat\_use\_sp}[n, t_{inv}] \times duration\_strat(t_{inv, prev}) \\ \end{aligned}\]
we have to include a bilinear term:
\[\begin{aligned} \texttt{disjunct}[t_{inv}] = & ( \texttt{bat\_prev\_use\_sp}[n, t_{inv, prev}] + \\ & \texttt{bat\_use\_sp}[n, t_{inv}] \times duration\_strat(t_{inv, prev}) ) \times \\ & \texttt{bat\_stack\_replace\_n}[n, t_{inv}] \\ \end{aligned}\]
which is reformulated within the function directly. The differentiation is achieved through the subfunction
replace_disjunct
.
The function subsequently calls the function constraints_usage_iterate
which iterates through the time structure and adds relevant constraints.
If the TimeStructure
includes representative periods, then the use in each representative period $t_{rp}$ is calculated (in the function constraints_usage_iterate
):
\[\begin{aligned} \texttt{bat\_use\_rp}[n, t_{rp}] = \sum_{t \in t_{rp}} & \texttt{stor\_charge\_use}[n, t] \times \\ & inputs(n, p_{stor}) \times scale\_op\_sp(t_{rp}, t) \\ \end{aligned}\]
Once we reach the lowest time structure, i.e., SimpleTimes
, we enforce in the case of CycleLife
the upper bound on the number of cycles of the battery for the last operational period $t$ (in the last representative period for each operational scenario, if used) of an investment period as:
\[\begin{aligned} cycles(n) \times & \texttt{stor\_level\_inst}[n, t] \geq \\ & \texttt{bat\_prev\_use}[n, t] + \texttt{bat\_use\_sp}[n, t_{inv}] \times duration\_strat(t_{inv, prev}) \\ \end{aligned}\]
The declaration of the actual constraint for the previous use utilises the helper variable $prev\_use$ in the following constraint:
\[\begin{aligned} \texttt{bat\_}&\texttt{prev\_use}[n, t] = \\ & prev\_use[t] + \\ & \texttt{stor\_charge\_use}[n, t] \times \\ & inputs(n, p_{stor}) \times duration(t) \\ & \end{aligned}\]
The individual value of the auxiliary variable $prev\_use$ can be differentiated in three individual cases:
In the first operational period (in the first representative period) in investment periods:
The constraint is given as\[prev\_use[t] = \texttt{bat\_prev\_use\_sp}[n, t_{inv}]\]
with $t_{inv, pre} < t_{inv}$.
In the first operational period in subsequent representative periods:
The constraint is given as\[\begin{aligned} prev\_use[t] = & \texttt{bat\_prev\_use}[n, first(t_{rp,prev})] - \\ & \texttt{stor\_charge\_use}[n, first(t_{rp,prev})] \times \\ & inputs(n, p_{stor}) \times duration(first(t_{rp,prev})) + \\ & \texttt{bat\_use\_rp}[n, t_{rp,prev}] \\ \end{aligned}\]
with $t_{rp,prev}$ denoting the previous representative period. The subtraction is necessary as the use is calculated at the end of a period.
In all other operational periods
\[prev\_use[t] = \texttt{bat\_prev\_use}[n, t_{prev}]\]
with $t_{prev}$ denoting the previous operational period.
Note, that the auxiliary variable is not directly implemented. Instead, the JuMP macro @expression
is utilized.
Reserve constraints
The reserve requires new constraints for calculating the potential of the ReserveBattery
to provide reserve capacity. This is achieved through the function constraints_reserve
called from the create_node
function.
A standard AbstractBattery
node does not add any constraints when calling the function. A ReserveBattery
adds the following additional constraints for specifying the available upwards reserve:
\[\begin{aligned} \texttt{stor\_discharge\_use}[n, t] - & \texttt{stor\_charge\_use}[n, t] \leq \\ & \texttt{stor\_discharge\_inst}[n, t] - \texttt{bat\_res\_up}[n, t] \\ \texttt{stor\_level}[n, t] - & \texttt{bat\_res\_up}[n, t] \geq 0 \\ \end{aligned}\]
and downwards reserve:
\[\begin{aligned} \texttt{stor\_charge\_use}[n, t] - & \texttt{stor\_discharge\_use}[n, t] \leq \\ & \texttt{stor\_charge\_inst}[n, t] - \texttt{bat\_res\_down}[n, t] \\ \texttt{stor\_level}[n, t] + & \texttt{bat\_res\_down}[n, t] \leq \\ & \texttt{stor\_level\_inst}[n, t] - \\ & degradation(n) \times \texttt{bat\_prev\_use}[n, t] / cycles(n) \\ \end{aligned}\]
The second constraint is replaced by
\[\texttt{stor\_level}[n, t] + \texttt{bat\_res\_down}[n, t] \leq \texttt{stor\_level\_inst}[n, t]\]
if the node uses InfLife
, and hence, does not include battery degradation.
As can be seens from above constraints, it is necessary to provide the reserve capacity for at least a duration of 1 of an operational period. If you use hourly resolution, it corresponds to an hour, even if the duration of the representative periods is longer (specified through a value differeing from 1 in the time structure). This concept is explained in the section utilize TimeStruct
.
Level constraints
The level constraints are in general slightly more complex to understand. The overall structure is outlined on Constraint functions. The level constraints are called through the function constraints_level
which then calls additional functions depending on the chosen time structure (whether it includes representative periods and/or operational scenarios) and the chosen storage behaviour.
The battery nodes utilize the majority of the concepts from EnergyModelsBase
but require adjustments for both constraining the variable $\texttt{stor\_level\_Δ\_op}$ and specifying how the storage node has to behave in the first operational period of an investment period. This is achieved through dispatching on the functions constraints_level_aux
.
The constraints introduced in constraints_level_aux
are given by the energy balance with $p_{stor}$ corresponding to the stored resource:
\[\begin{aligned} \texttt{stor\_level\_Δ\_op}&[n, t] = \\ & \texttt{stor\_charge\_use}[n, t] \times inputs(n, p_{stor}) - \\ & \texttt{stor\_discharge\_use}[n, t] / outputs(n, p_{stor}) \end{aligned}\]
If the time structure includes representative periods, we also calculate the change of the storage level in each representative period within the function constraints_level_iterate
(from EnergyModelsBase
):
\[ \texttt{stor\_level\_Δ\_rp}[n, t_{rp}] = \sum_{t \in t_{rp}} \texttt{stor\_level\_Δ\_op}[n, t] \times scale\_op\_sp(t_{inv}, t)\]
The general level constraint is calculated in the function constraints_level_iterate
(from EnergyModelsBase
):
\[\texttt{stor\_level}[n, t] = prev\_level + \texttt{stor\_level\_Δ\_op}[n, t] \times duration(t)\]
in which the value $prev\_level$ is depending on the type of the previous operational ($t_{prev}$) and investment period ($t_{inv,prev}$) (as well as the previous representative period ($t_{rp,prev}$)). It is calculated through the function previous_level
.
In the case of battery nodes, we can distinguish the following cases:
The first operational period in the first representative period in any investment period (given by $typeof(t_{prev}) = typeof(t_{rp, prev})$ and $typeof(t_{inv,prev}) = NothingPeriod$). In this situation, we can distinguish three cases, the time structure does not include representative periods:
\[prev\_level = \texttt{stor\_level}[n, last(t_{inv})]\]
the time structure includes representative periods and the storage behavior is given as
CyclicRepresentative
:\[prev\_level = \texttt{stor\_level}[n, last(t_{rp})]\]
the time structure includes representative periods and the storage behavior is given as
CyclicStrategic
:\[\begin{aligned} prev\_level = & \texttt{stor\_level}[n, first(t_{rp,last})] - \\ & \texttt{stor\_level\_Δ\_op}[n, first(t_{rp,last})] \times duration(first(t_{rp,last})) + \\ & \texttt{stor\_level\_Δ\_rp}[n, t_{rp,last}] \times duration\_strat(t_{rp,last}) \end{aligned}\]
The first operational period in subsequent representative periods in any investment period (given by $typeof(t_{prev}) = nothing$) if the the storage behavior is given as
CyclicStrategic
:\[\begin{aligned} prev\_level = & \texttt{stor\_level}[n, first(t_{rp,prev})] - \\ & \texttt{stor\_level\_Δ\_op}[n, first(t_{rp,prev})] \times duration(first(t_{rp,prev})) + \\ & \texttt{stor\_level\_Δ\_rp}[n, t_{rp,prev}] \end{aligned}\]
This situation only occurs in cases in which the time structure includes representative periods.
All other operational periods:
\[ prev\_level = \texttt{stor\_level}[n, t_{prev}]\]
All cases are implemented in EnergyModelsBase
simplifying the design of the system.