ThermalEnergyStorage nodes
Thermal energy storage nodes work mostly like a RefStorage with the additional option to include thermal energy losses. Heat losses are quantified through a heat loss factor that describes the amount of thermal energy that is lost in relation to the storage level of the respective operational period. The main difference to RefStorage is that these heat losses occur independently of the storage use, i.e., in every operational period unless the storage level is zero. In practice, this approach with a constant relative heat loss factor does not accurately represent heat loss mechanisms based on temperature difference between the inside and outside of the TES. However, it is a reasonable approximation that at least reflects the dependence of the SOC on the absolute losses of the storage. Additionally, thermal energy storage nodes allow for the definition of both charge and discharge rate.
Thermal energy storage nodes can only utilize Cyclic storage behaviors. When using RepresentativePeriods, this is furthermore reduced toCyclicRepresentative. The reason for this limitation is that we have not yet implemented heat losses over a representative period.
Introduced type and its fields
ThermalEnergyStorage is similar to a RefStorage, with the addition of discharge rate limitations and heat losses. BoundRateTES serves the same fundamental purpose as ThermalEnergyStorage, but its maximum charge and discharge rates are defined relative to the installed storage capacity. This offers an advantage in an InvestmentModel, as it allows a fixed ratio between storage capacity and (dis-)charge capacity to be maintained when scaling the storage size. In contrast, ThermalEnergyStorage allows the (dis-)charge capacities to be scaled independently of the storage capacity.
Standard fields
The standard fields are given as:
id:
The fieldidis only used for providing a name to the storage. This is similar to the approach utilized inEnergyModelsBase.level::UnionCapacity:
The level parameters of the thermal energy storage node. The level storage parameters must include a capacity. Depending on the chosen type, the level parameters can include in addition variable OPEX and/or fixed OPEX. More information can be found on storage parameters.Permitted values for storage parameters in `level` If the node should contain investments through the application of
EnergyModelsInvestments, it is important to note that you can only useFixedProfileorStrategicProfilefor the capacity, but notRepresentativeProfileorOperationalProfile. Similarly, you can only useFixedProfileorStrategicProfilefor the fixed OPEX, but notRepresentativeProfileorOperationalProfile. The variable operating expenses can be provided asOperationalProfileas well. In addition, all capacity and fixed OPEX values have to be non-negative.stor_res::Resource:
Thestor_resis the storedResource. In the case of aThermalEnergyStorage, this resource should be aResourceHeat.input::Dict{<:Resource,<:Real}andoutput::Dict{<:Resource,<:Real}:
Both fields describe theinputandoutputResources with their corresponding conversion factors as dictionaries. All values have to be non-negative.Conversion factors While the field input includes a proper conversion factor and may allow for multiple resources, the value of the
outputResources is not relevant.data::Vector{<:ExtensionData}:
An entry for providing additional data to the model. In the current version, it is used for providing additional investment data whenEnergyModelsInvestmentsis used.
Additional fields
Both ThermalEnergyStorage and BoundRateTES nodes introduce an additional field for the heat loss factor:
heat_loss_factor::Float64:
The heat loss factor describes the heat lost relative to the storage level. It corresponds to the loss occurring between two operational periods for a given operational duration of 1 (see UtilizeTimeStructfor details).
The allowed charging and discharging rates are specified in two different ways:
ThermalEnergyStorage nodes use the same field for the charging capacity as RefStorage nodes, and extend it by adding a field for discharging:
charge::AbstractStorageParametersanddischarge::AbstractStorageParameters:
The charging and discharging parameters of theThermalEnergyStorage. Depending on the chosen type, these parameters can include variable OPEX, fixed OPEX, and/or capacity. More information can be found in storage parameters.
For BoundRateTES, two additional fields specify the maximum charging and discharging rates relative to the installed storage capacity:
level_charge::Float64andlevel_discharge::Float64:
The maximum charging and discharging rates of theBoundRateTESrelative to the installed storage capacity. This implies that the unit is given as per operational period duration. Mathematically, this can be expressed as\[\begin{aligned} level\_charge & = max\_charge / installed\_storage\_level \\ level\_discharge & = max\_discharge / installed\_storage\_level \end{aligned}\]
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
The ThermalEnergyStorage utilizes all standard variables from RefStorage, as described on the page Optimization variables:
- $\texttt{opex\_var}$
- $\texttt{opex\_fixed}$
- $\texttt{stor\_level}$
- $\texttt{stor\_level\_inst}$
- $\texttt{stor\_charge\_use}$
- $\texttt{stor\_charge\_inst}$ if the
ThermalEnergyStoragehas the fieldchargewith a capacity - $\texttt{stor\_discharge\_use}$
- $\texttt{stor\_discharge\_inst}$ if the
ThermalEnergyStoragehas the fielddischargewith a capacity - $\texttt{flow\_in}$
- $\texttt{flow\_out}$
- $\texttt{stor\_level\_Δ\_op}$
- $\texttt{stor\_level\_Δ\_rp}$ if the
TimeStructincludesRepresentativePeriods
Constraints
ThermalEnergyStorage and BoundRateTES nodes utilize in general the standard constraints described in Constraint functions for Storage nodes. ThermalEnergyStorage and BoundRateTES nodes utilize the declared method for all nodes 𝒩. The following standard constraints are implemented for ThermalEnergyStorage and BoundRateTES nodes. ThermalEnergyStorage and BoundRateTES use the same methods, except for constraints_capacity.
constraints_capacity: ForThermalEnergyStorage\[\begin{aligned} \texttt{stor\_level\_use}[n, t] & ≤ \texttt{stor\_level\_inst}[n, t] \\ \texttt{stor\_charge\_use}[n, t] & ≤ \texttt{stor\_charge\_inst}[n, t] \\ \texttt{stor\_discharge\_use}[n, t] & ≤ \texttt{stor\_discharge\_inst}[n, t] \end{aligned}\]
For
BoundRateTES\[\begin{aligned} \texttt{stor\_level\_use}[n, t] & ≤ \texttt{stor\_level\_inst}[n, t] \\ \texttt{stor\_charge\_use}[n, t] & ≤ \texttt{stor\_level\_inst}[n, t] \times level\_charge(n) \\ \texttt{stor\_discharge\_use}[n, t] & ≤ \texttt{stor\_level\_inst}[n, t] \times level\_discharge(n) \\ \end{aligned}\]
constraints_capacity_installed: ForThermalEnergyStorage\[\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(discharge(n), t) \end{aligned}\]
For
BoundRateTES\[\begin{aligned} \texttt{stor\_level\_inst}[n, t] & = capacity(level(n), t) \end{aligned}\]
Using investments The function
constraints_capacity_installedis also used inEnergyModelsInvestmentsto 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 do not include an efficiency:
\[\texttt{flow\_in}[n, t, stor\_res(n)] = \texttt{stor\_charge\_use}[n, t]\]
constraints_flow_out:\[\texttt{flow\_out}[n, t, stor\_res(n)] = \texttt{stor\_discharge\_use}[n, t]\]
constraints_level:
The level constraints are more complex compared to the standard constraints. They are explained in detail below in Level constraints.constraints_opex_fixed:\[\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})] \end{aligned}\]
Why do we use `first()` 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 strategic period $t_{inv}$ in the function
constraints_opex_fixed.constraints_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) \end{aligned}\]
The function `scale_op_sp` The function $scale\_op\_sp(t_{inv}, t)$ calculates the scaling factor between operational and strategic periods. It also takes into account potential operational scenarios and their probability as well as representative periods.
constraints_ext_data:
This function is only called for specified data of the storage node, see above.
The capacity constraints, both constraints_capacity and constraints_capacity_installed are only set for capacities that are included through the corresponding field and if the corresponding storage parameters have a field capacity. Otherwise, they are omitted. The field level is required to have a storage parameter with capacity.
Level constraints
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. Note: ThermalEnergyStorage and BoundRateTES only make changes to the constraint_level_iteratefunction when CyclicStrategic is chosen as storage behaviour.
The constraints introduced in constraints_level_aux are given by
\[\texttt{stor\_level\_Δ\_op}[n, t] = \texttt{stor\_charge\_use}[n, t] - \texttt{stor\_discharge\_use}[n, t]\]
If the time structure includes representative periods, we calculate the change of the storage level in each representative period within the function constraints_level_iterate:
\[\texttt{stor\_level\_Δ\_rp}[n, t_{rp}] = \sum_{t \in t_{rp}} \texttt{stor\_level\_Δ\_op}[n, t] \times scale\_op\_sp(t_{rp}, t)\]
In the case of CyclicStrategic, we add an additional constraint to the change in the function constraints_level_rp:
\[\sum_{t_{rp} \in T^{rp}} \texttt{stor\_level\_Δ\_rp}[n, t_{rp}] = 0\]
while we fix the value in the case of CyclicRepresentative to 0:
\[\texttt{stor\_level\_Δ\_rp}[n, t_{rp}] = 0\]
If the time structure includes operational scenarios using CyclicRepresentative, we enforce that the last value in each operational scenario is the same within the function constraints_level_scp.
The general level constraint is eventually calculated in the function constraints_level_iterate:
\[\texttt{stor\_level}[n, t] = prev\_level + \texttt{stor\_level\_Δ\_op}[n, t] \times duration(t) - prev\_level \times heat\_loss\_factor(n) \times duration(t)\]
in which the value $prev\_level$ is depending on the type of the previous operational ($t_{prev}$) and strategic level ($t_{inv,prev}$) (as well as the previous representative period ($t_{rp,prev}$)). It is calculated through the function previous_level.
This constraint is the only constraint adjusted by [ThermalEnergyStorage] nodes. All other functions and constraints are unchanged.
We can distinguish the following cases:
The first operational period (in the first representative period) in a strategic period (given by $typeof(t_{prev}) = typeof(t_{rp, prev}) = = nothing$). In this situation, the previous level is dependent on the chosen storage behavior. In the default case of a
Cyclicbehaviors, it is given by the last operational period of either the strategic or representative period:\[\begin{aligned} prev\_level & = \texttt{stor\_level}[n, last(t_{sp})] prev\_level & = \texttt{stor\_level}[n, last(t_{rp})] \end{aligned}\]
If the storage behavior is instead given by
CyclicStrategicand the time structure includes representative periods, we calculate the previous level instead as:\[\begin{aligned} t_{rp,last} = & last(repr\_periods(t_{sp})) \\ 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}] \end{aligned}\]
$t_{rp,last}$ corresponds in this situation to the last representative period in the current strategic period.
If the storage behavior is instead given by
CyclicStrategic, the previous level is set to 0:\[prev\_level = 0\]
The first operational period in subsequent representative periods in any strategic period (given by $typeof(t_{prev}) = nothing$). The previous level is again dependent on the chosen storage behavior. The default approach calculates it as:
\[\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}\]
while a
CyclicRepresentativestorage behavior calculates it as:\[prev\_level = \texttt{stor\_level}[n, last(t_{rp})]\]
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}]\]