ThermalEnergyStorage
ThermalEnergyStorage
works mostly like a RefStorage
with the additional option to include thermal energy losses. For the ThermalEnergyStorage
node, heat losses are additionally 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.
ThermalEnergyStorage
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
. Hence, the majority of the fields are the same.
Standard fields
The standard fields are given as:
id
:
The fieldid
is only used for providing a name to the storage. This is similar to the approach utilized inEnergyModelsBase
.charge::AbstractStorageParameters
:
The charging parameters of theThermalEnergyStorage
. Depending on the chosen type, the charge parameters can include variable OPEX, fixed OPEX, and/or a capacity. More information can be found on storage parameters.level::UnionCapacity
:
The level parameters of theThermalEnergyStorage
. 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 `charge` and `level` 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::Resource
:
Thestor_res
is the storedResource
. In the case of aThermalEnergyStorage
, this resource should be aResourceHeat
.input::Dict{<:Resource,<:Real}
andoutput::Dict{<:Resource,<:Real}
:
Both fields describe theinput
andoutput
Resource
s 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
output
Resource
s is not relevant.data::Vector{<:Data}
:
An entry for providing additional data to the model. In the current version, it is used for providing additional investment data whenEnergyModelsInvestments
is used.
Additional fields
ThermalEnergyStorage
nodes add one additional field compared to RefStorage
nodes:
heat_loss_factor::Float64
:
The heat loss factore describes the heat loss relative to the storage level. It corresponds to the loss between two operational periods for a given operational duration of 1 (see UtilizeTimeStruct
for an explanation).
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
ThermalEnergyStorage
has the fieldcharge
with a capacity - $\texttt{stor\_discharge\_use}$
- $\texttt{flow\_in}$
- $\texttt{flow\_out}$
- $\texttt{stor\_level\_Δ\_op}$
- $\texttt{stor\_level\_Δ\_rp}$ if the
TimeStruct
includesRepresentativePeriods
Constraints
ThermalEnergyStorage
nodes utilize in general the standard constraints described in Constraint functions for Storage
nodes. ThermalEnergyStorage
nodes utilize the declared method for all nodes 𝒩. The following standard constraints are implemented for a ThermalEnergyStorage
node.
constraints_capacity
:\[\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] \end{aligned}\]
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) \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 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_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
only makes changes to the constraint_level_iterate
function 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
Cyclic
behaviors, 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
CyclicStrategic
and 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
CyclicRepresentative
storage 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}]\]