Electrolyzer nodes
Electrolysis plants can be separated between the electrolysis stack and the balance of plant. While the former is the core of the plant for the production of hydrogen, it experiences degradation resulting in a reduced efficiency when utilizing the stack. The lifetime of the stack is furthermore reduced compared to the balance of plant. Hence, incorporating the potential for stack replacement and the associated costs may impact the utilization of the electrolyser given electricity availability and price.
Stack replacement is cheaper than rebuilding a complete plant. Furthermore, it results in an improved efficiency as it resets the degradation.
Introduced types and their fields
Electrolysis is incorporated through two composite types with the same parameters. Both types are essentially equal, but SimpleElectrolyzer
does not utilize the degradation of the stack for the calculation of a reduced efficiency as Electrolyzer
. Instead, it utilizes it only for stack replacement calculations to avoid bilinear terms as constraints.
The stack degradation calculations do not consider a change in capacity. If you want to include investments or only an increased capacity over the course of time, you have to include several electrolysis nodes in which each node corresponds to the capacity in an investment period with a changing capacity.
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
.cap::TimeProfile
:
The installed capacity of the electrolysis node corresponds to the potential usage of the node. The capacity does not have to correspond to the amount of hydrogen produced. Instead, it is relative to the specifiedinput
andoutput
ratios.
If the node should contain investments through the application ofEnergyModelsInvestments
, it is important to note that you can only useFixedProfile
orStrategicProfile
for the capacity, but notRepresentativeProfile
orOperationalProfile
. In addition, all values have to be non-negative.opex_var::TimeProfile
:
The variable operational expenses of an electrolysis node are based on the capacity utilization through the variable:cap_use
. Hence, it is directly related to the specifiedinput
andoutput
ratios. The variable operating expenses can be provided asOperationalProfile
as well.opex_fixed::TimeProfile
:
The fixed operating expenses are relative to the installed capacity (through the fieldcap
) and the chosen duration of an investment period as outlined on UtilizeTimeStruct
.
It is important to note that you can only useFixedProfile
orStrategicProfile
for the fixed OPEX, but notRepresentativeProfile
orOperationalProfile
. In addition, all values have to be non-negative.input::Dict{<:Resource, <:Real}
andoutput::Dict{<:Resource, <:Real}
:
Both fields describe theinput
andoutput
Resource
s with their corresponding conversion factors as dictionaries. In the case of electrolysis,input
should include electricity and potentially water while the output is hydrogen and potentially heat, if included in the model.
All values have to be non-negative.data::Vector{Data}
:
An entry for providing additional data to the model. In the current version of electrolysis, it is only relevant for additional investment data whenEnergyModelsInvestments
is used.
The fields capacity
opex_var
, opex_fixed
, input
and output
dictionaries are directly linked.
Consider a 10 MWₑₗ electrolyzer which has a maximum electricity input of 10 MW, and variable OPEX of 5 €/MWhₕ₂ (defined via the produced hydrogen), a fixed OPEX of 20000 €/MWₑₗ (defined via the electricity capacity), and an efficiency of 69 %. In this situation, you would specify the input as
cap = FixedProfile(10)
var_opex = FixedProfile(5/.69)
fixed_opex = FixedProfile(20000)
input = Dict(Electricity => 1)
output = Dict(Hydrogen => 0.69)
As the variable OPEX is defined via the produced hydrogen, it is crucial to include the efficiency in the calculation as the model bases the calculation on a value of 1 in the input or output dictionary.
Additional fields
load_limits::LoadLimits
:
Theload_limits
specify the lower and upper limit for operating the electrolyzer. These limits are included through the typeLoadLimits
and correspond to a fraction of the installed capacity as described in Limiting the load.
The lower limit has to be non-negative while the upper limit has to be higher than the lower limit.degradation_rate::Real
:
The degradation rate is the reduction in efficiency of the electrolyser due to utilization. It has to be provided as a percentage drop in efficiency in 1000 time the length of an operational duration (see UtilizeTimeStruct
for an explanation). If a duration of 1 in an operational period corresponds to an hour, then the unit is %/1000h.
The degradation rate has to be given as $[0, 1)$.stack_replacement_cost::TimeProfile
:
The stack replacement cost corresponds to the costs associated with stack replacement. It is smaller than the capital expenditures as only the stack has to be replaced. The cost is included in the fixed operational cost variable in the investment period in which stack replacement occurs.
It is important to note that you can only useFixedProfile
orStrategicProfile
for the stack replacment cost, but notRepresentativeProfile
orOperationalProfile
. In addition, all values have to be non-negative.stack_lifetime::Real
:
The stack lifetime affects when the stack has to be replaced. The lifetime is given as multiple of the operational duration (see UtilizeTimeStruct
for an explanation). A typical value is in the range of 60000-100000 h in the case of an operational duration of 1 h.
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 electrolyser node types utilize all standard variables from the RefNetworkNode
, as described on the page Optimization variables. The variables include:
- $\texttt{opex\_var}$
- $\texttt{opex\_fixed}$
- $\texttt{cap\_use}$
- $\texttt{cap\_inst}$
- $\texttt{flow\_in}$
- $\texttt{flow\_out}$
The variable $\texttt{opex\_fixed}$ also includes the cost of stack replacement in the investment periods in which stack replacement occurs.
Additional variables
Electrolyzer nodes declare in addition several variables through dispatching on the method EnergyModelsBase.variables_node()
. These variables are:
- $\texttt{elect\_on\_b}[n_{el}, t]$: State of electrolyser node $n_{el}$ in operational period $t$.
This variable is a binary variable which indiciates whether the electrolyser is on (1) or off (0). It is used in the calculation of the stack degradation and the lifetime of the electrolyser stack. - $\texttt{elect\_prev\_use}[n_{el}, t]$: Usage of electrolyser node $n_{el}$ up to operational period $t$.
The usage of the electrolyser node always corresponds to the accumulated usage since the beginning or the last stack replacement up to the previous period. Usage of the node in operational period $t$ is not included in the calculation. - $\texttt{elect\_prev\_use\_sp}[n_{el}, t_{inv}]$: Usage of electrolyser node $n_{el}$ up to investment period $t_{inv}$.
The usage of the electrolyser node always corresponds to the accumulated usage since the beginning or the last stack replacement up to the current investment period. Usage of the node in investment period $t_{inv}$ is not included in the calculation. - $\texttt{elect\_use\_sp}[n_{el}, t_{inv}]$: Usage of electrolyser node $n_{el}$ in investment period $t_{inv}$.
This variable denotes the total usage within an investment period. - $\texttt{elect\_use\_rp}[n_{el}, t_{rp}]$: Usage of electrolyser node $n_{el}$ in representative period $t_{rp}$.
This variable denotes the total usage within a representative period, if the chosenTimeStructure
includesRepresentativePeriods
. - $\texttt{elect\_stack\_replace\_b}[n_{el}, t_{inv}]$: Indicator variable of electrolyser node $n_{el}$ in investment period $t_{inv}$ for stack replacement.
This variable is a binary variable which indiciates whether stack replacement is occuring at the beginning of investment period $t_{inv}$ (1) or not (0). - $\texttt{elect\_efficiency\_penalty}[n_{el}, t]$: Efficiency penalty of electrolyser node $n_{el}$ in operational period $t$.
The efficiency penalty is calculated irrespectively whether you use aSimpleElectrolyzer
or anElectrolyzer
node. It is a multiplicator for the efficiency for hydrogen production and reset in the investment period in which stack replacement is occuring.
The variables $\texttt{elect\_prev\_use}[n_{el}, t]$, $\texttt{elect\_use\_sp}[n_{el}, t_{inv}]$, and $\texttt{elect\_use\_rp}[n_{el}, t_{rp}]$ have the same unit. The units of the variables are given in 1000 times the operational duration of 1 (see Utilize TimeStruct
for an explanation). If you use an hourly resolution, they would hence correspond to 1000 h.
Constraints
The following sections omit the direction inclusion of the vector of electrolyzer nodes. Instead, it is implicitly assumed that the constraints are valid $\forall n_{el} ∈ N^{EL}$, that is both SimpleElectrolyzer
and Electrolyzer
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
The different electrolyzer nodes utilize only a small set of the standard constraints described on Constraint functions. These standard constraints are:
constraints_capacity_installed
:\[\texttt{cap\_inst}[n_{el}, t] = capacity(n_{el}, t)\]
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
:\[\texttt{flow\_in}[n_{el}, t, p] = inputs(n_{el}, p) \times \texttt{cap\_use}[n_{el}, t] \qquad \forall p \in inputs(n_{el})\]
constraints_opex_var
:\[\texttt{opex\_var}[n_{el}, t_{inv}] = \sum_{t \in t_{inv}} opex_var(n_{el}, t) \times \texttt{cap\_use}[n_{el}, t] \times scale\_op\_sp(t_{inv}, t)\]
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 reformer, see above.
The SimpleElectrolyzer
node utilizes in addition the default function constraints_flow_out
:
\[\texttt{flow\_out}[n_{el}, t, p] = outputs(n_{el}, p) \times \texttt{cap\_use}[n_{el}, t] \qquad \forall p \in outputs(n_{el}) \setminus \{\text{CO}_2\}\]
while the Electrolyzer
node dispatches on the function constraints_flow_out
to incorporate the efficiency penalty:
\[\begin{aligned} \texttt{flow\_out}&[n_{el}, t, p] = \\ & outputs(n_{el}, p) \times \texttt{cap\_use}[n_{el}, t] \times \texttt{elect\_efficiency\_penalty}[n_{el}, t] \qquad \forall p \in outputs(n_{el}) \end{aligned}\]
The incorporation of the efficiency penalty results in a bilinear term as it corresponds to a multiplication of two continuous variables, $\texttt{cap\_use}[n_{el}, t]$ and $\texttt{elect\_efficiency\_penalty}[n_{el}, t]$. Hence, you have to utilize a solver that supports optimization problems with bilinear constraints.
The function constraints_capacity
is extended with a new method for electrolyzer nodes to account for the minimum and maximum load:
\[\begin{aligned} \texttt{cap\_use}[n_{el}, t] & \geq min\_load(n_{el}, t) \times \texttt{elect\_on\_b}[n_{el}, t] \times capacity(n_{el}, t) \\ \texttt{cap\_use}[n_{el}, t] & \leq max\_load(n_{el}, t) \times \texttt{elect\_on\_b}[n_{el}, t] \times capacity(n_{el}, t) \end{aligned}\]
In the case of investment potential in the node, this constraint is reformulated as:
\[\begin{aligned} \texttt{cap\_use}[n_{el}, t] & \geq min\_load(n_{el}, t) \times \texttt{elect\_on\_b}[n_{el}, t] \times \texttt{cap\_inst}[n_{el}, t] \\ \texttt{cap\_use}[n_{el}, t] & \leq max\_load(n_{el}, t) \times \texttt{elect\_on\_b}[n_{el}, t] \times \texttt{cap\_inst}[n_{el}, t] \end{aligned}\]
resulting in a bilinear term of a binary and continuous variable.
Bilinearities of this type can be reformulated as linear problem through an auxiliary variable. EnergyModelsHydrogen
provides a linear reformulation through the function EnergyModelsHydrogen.linear_reformulation
. The linear reformulation is also explained in Linear reformulation. $\texttt{cap\_inst}[n_{el}, t]$ is replaced with $capacity(n_{el}, t)$ if the node does not have the potential for investments. The implementation uses the function EnergyModelsHydrogen.multiplication_variables
for determining which approach should be chosen.
The function multiplication_variables
is only called once for each bilinearity to avoid creating the auxiliary variable multiple times.
Additional constraints
Constraints calculated in create_node
The efficiency penalty is calculated as:
\[\begin{aligned} \texttt{elect\_efficiency\_penalty}&[n_{el}, t] = \\ & 1 - degradation\_rate(n_{el}) / 100 \times \texttt{elect\_prev\_use}[n_{el}, t] \end{aligned}\]
It corresponds to a linear degradation depending on how much the electrolzer is utilized. The division by 100 is necessary as the rate is defined as percentage value.
The fixed operating expenses include the stack replacement:
\[\begin{aligned} \texttt{opex\_fixed}&[n_{el}, t_{inv}] = \\ & opex\_fixed(n_{el}, t_{inv}) \times \texttt{cap\_inst}[n_{el}, first(t_{inv})] + \\ & \texttt{elect\_stack\_replace\_b}[n_{el}, t_{inv}] \times capacity[n_{el}, t_{inv}] \times \\ &stack\_replacement\_cost(n_{el}, t_{inv}) / duration\_strat(t_{inv}) \end{aligned}\]
The variables $\texttt{cap\_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}$.
There are two contributors to the fixed operating expenses,
- the standed fixed operating expenses and
- the cost for stack replacement.
The first contributions is similar to the standard function to the function constraints_opex_fixed
.
The second contribution corresponds to the cost of stack replacement. The overall contribution is divided by the value of the function $duration\_strat(t_{inv})$ as the variable $\texttt{opex\_fixed}[n_{el}, t_{inv}]$ is multiplied with the same value in the objective function. As you only have to pay once for stack replacement, irrespectively of the length of an investment period, it is necessary to include this division.
In the case of investment potential in the node, the stack replacement cost is reformulated as:
\[\begin{aligned} \texttt{opex\_fixed}&[n_{el}, t_{inv}] = \\ & opex\_fixed(n_{el}, t_{inv}) \times \texttt{cap\_inst}[n_{el}, first(t_{inv})] + \\ & \texttt{elect\_stack\_replace\_b}[n_{el}, t_{inv}] \times \texttt{cap\_current}[n_{el}, t_{inv}] \times \\ &stack\_replacement\_cost(n_{el}, t_{inv}) / duration\_strat(t_{inv}) \end{aligned}\]
resulting in a bilinear term of a binary and continuous variable. As outlined above, this bilinear term can be reformulated as linear problem, see Linear reformulation. The implementation uses the function EnergyModesHydrogen.multiplication_variables()
for determining which approach should be chosen.
Electrolyzer use constraints
The calculation of the previous usage of the electrolyzer node 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
. This is achieved through the function constraints_usage()
and the individual functions calculated from the function.
Within this function, we first calculate $\forall t_{inv, 1} \in T^{Inv},~ t_{inv, 2} \in T^{Inv}$ the linear reformulation of the product
First, the usage in each investment period $t_{inv}$ is calculated:
\[\texttt{elect\_use\_sp}[n_{el}, t_{inv}] \times 1000 = \sum_{t \in t_{inv}}\texttt{elect\_on\_b}[n_{el}, t] \times scale\_op\_sp(t_{inv}, t)\]
The previous usage up the current investment period $t_{inv}$ is calculated through the function constraints_usage_sp
. In the first investment period, the previous usage is fixed to a value of 0:
\[\texttt{elect\_prev\_use\_sp}[n_{el}, t_{inv}] = 0\]
while the implementation within subsequent investment periods require considering potential stack replacement. This is achieved through introducing the auxiliary variable $\texttt{aux\_var}$ given by
\[\begin{aligned} \texttt{aux\_var}&[n_{el}, t_{inv}] = \\ & \texttt{elect\_prev\_use\_sp}[n_{el}, t_{inv,prev}] + \\ & \texttt{elect\_use\_sp}[n_{el}, t_{inv,prev}] \times duration\_strat(t_{inv,prev}) \\ \end{aligned}\]
The previous usage in the subsequent investment periods is then given by
\[\begin{aligned} \texttt{elect\_prev\_use\_sp}&[n_{el}, t_{inv}] = \\ & \texttt{aux\_var}[n_{el}, t_{inv}] \times (1-\texttt{elect\_stack\_replace\_b}[n_{el}, t_{inv}]) \\ \end{aligned}\]
using a direct implememtation of the linear reformulation explained in the section linear reformulation
\[\begin{aligned} \texttt{elect\_prev\_use\_sp}&[n_{el}, t_{inv}] \geq 0 \\ \texttt{elect\_prev\_use\_sp}&[n_{el}, t_{inv}] \geq \\ & ub(t_{inv}) \times ((1-\texttt{elect\_stack\_replace\_b}[n_{el}, t_{inv}]) - 1) + \\ & \texttt{aux\_var}[n_{el}, t_{inv}] \\ \texttt{elect\_prev\_use\_sp}&[n_{el}, t_{inv}] \leq \\ & ub(t_{inv}) \times (1-\texttt{elect\_stack\_replace\_b}[n_{el}, t_{inv}]) \\ \texttt{elect\_prev\_use\_sp}&[n_{el}, t_{inv}] \leq \texttt{aux\_var}[n_{el}, t_{inv}] \\ \end{aligned}\]
in which the upper bound $ub$ is either the installed capacity or the maximum installed capacity, depending on whether the electrolyzer includes investments, or not. These constraints enforce that if stack replacement occurs, that is $\texttt{elect\_stack\_replace\_b} = 1$, $\texttt{elect\_prev\_use\_sp}$ is reset to a value of 0.
If the TimeStructure
includes representative periods, then the usage in each representative period $t_{rp}$ is calculated (in the function constraints_usage_sp_iterate
):
\[\texttt{elect\_use\_rp}[n_{el}, t_{rp}] \times 1000 = \sum_{t \in t_{rp}}\texttt{elect\_on\_b}[n_{el}, t] \times scale\_op\_sp(t_{inv}, t)\]
In addition, if we are in the last operational period (of the last representative period) of an investment period, we calculate (for each operational scenario) the constraint
\[\begin{aligned} stack\_&lifetime(n) \geq \\ & \texttt{elect\_prev\_use}[n_{el}, t] \times 1000 + \\ & \texttt{elect\_use\_sp}[n_{el}, t_{inv}] \times (duration\_strat(t_{inv}) - 1) \times 1000 + \\ & \texttt{elect\_on\_b}[n_{el}, t] \times scale\_op\_sp(t_{inv}, t) \end{aligned}\]
to avoid a violation of the lifetime constraint. This constraint is only necessary for the last operational period as stack replacement is only allowed at the beginning of an investment period.
The declaration of the actual constraint for the previous usage can be differentiated in four individual cases:
In the first operational period (in the first representative period) in the first investment period:
The variable $\texttt{elect\_prev\_use}$ is fixed to 0.In the first operational period (in the first representative period) in subsquent investment periods:
The constraint is given as\[\texttt{elect\_previous\_use}[n_{el}, t] = \texttt{elect\_prev\_use\_sp}[n_{el}, t_{inv}]\]
In the first operational period in subsequent representative period:
The constraint is given as\[\begin{aligned} \texttt{elect\_}&\texttt{previous\_use}[n_{el}, t] = \\ & \texttt{elect\_prev\_use}[n_{el}, first(t_{rp,prev})] + \texttt{elect\_use\_rp}[n_{el}, t_{rp,prev}] \end{aligned}\]
with $t_{rp,prev}$ denoting the previous representative period.
In all other operational periods
\[\begin{aligned} \texttt{elect\_}&\texttt{previous\_use}[n_{el}, t] = \\ & \texttt{elect\_prev\_use}[n_{el}, t_{prev}] + duration(t_{prev}) \times \texttt{elect\_on\_b}[n, t_{prev}]/1000 \end{aligned}\]
with $t_{prev}$ denoting the previous operational period.