# -*- coding: utf-8 -*-
"""
In-development generic compressed air energy storage.
SPDX-FileCopyrightText: Uwe Krien <krien@uni-bremen.de>
SPDX-FileCopyrightText: Simon Hilpert
SPDX-FileCopyrightText: Cord Kaldemeyer
SPDX-FileCopyrightText: Patrik Schönfeldt
SPDX-FileCopyrightText: Johannes Röder
SPDX-FileCopyrightText: jakob-wo
SPDX-FileCopyrightText: gplssm
SPDX-FileCopyrightText: jnnr
SPDX-FileCopyrightText: Johannes Kochems
SPDX-License-Identifier: MIT
"""
from oemof.network import Node
from pyomo.core.base.block import ScalarBlock
from pyomo.environ import Binary
from pyomo.environ import Constraint
from pyomo.environ import NonNegativeReals
from pyomo.environ import Set
from pyomo.environ import Var
[docs]class GenericCAES(Node):
"""
Component `GenericCAES` to model arbitrary compressed air energy storages.
The full set of equations is described in:
Kaldemeyer, C.; Boysen, C.; Tuschy, I.
A Generic Formulation of Compressed Air Energy Storage as
Mixed Integer Linear Program – Unit Commitment of Specific
Technical Concepts in Arbitrary Market Environments
Materials Today: Proceedings 00 (2018) 0000–0000
[currently in review]
Parameters
----------
electrical_input : dict
Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object
for the electrical input.
fuel_input : dict
Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object
for the fuel input.
electrical_output : dict
Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object
for the electrical output.
Note: This component is experimental. Use it with care.
Notes
-----
The following sets, variables, constraints and objective parts are created
* :py:class:`~oemof.solph.blocks.generic_caes.GenericCAES`
Examples
--------
>>> from oemof import solph
>>> bel = solph.buses.Bus(label='bel')
>>> bth = solph.buses.Bus(label='bth')
>>> bgas = solph.buses.Bus(label='bgas')
>>> # dictionary with parameters for a specific CAES plant
>>> concept = {
... 'cav_e_in_b': 0,
... 'cav_e_in_m': 0.6457267578,
... 'cav_e_out_b': 0,
... 'cav_e_out_m': 0.3739636077,
... 'cav_eta_temp': 1.0,
... 'cav_level_max': 211.11,
... 'cmp_p_max_b': 86.0918959849,
... 'cmp_p_max_m': 0.0679999932,
... 'cmp_p_min': 1,
... 'cmp_q_out_b': -19.3996965679,
... 'cmp_q_out_m': 1.1066036114,
... 'cmp_q_tes_share': 0,
... 'exp_p_max_b': 46.1294016678,
... 'exp_p_max_m': 0.2528340303,
... 'exp_p_min': 1,
... 'exp_q_in_b': -2.2073411014,
... 'exp_q_in_m': 1.129249765,
... 'exp_q_tes_share': 0,
... 'tes_eta_temp': 1.0,
... 'tes_level_max': 0.0}
>>> # generic compressed air energy storage (caes) plant
>>> caes = solph.components.experimental.GenericCAES(
... label='caes',
... electrical_input={bel: solph.flows.Flow()},
... fuel_input={bgas: solph.flows.Flow()},
... electrical_output={bel: solph.flows.Flow()},
... params=concept)
>>> type(caes)
<class 'oemof.solph.components.experimental._generic_caes.GenericCAES'>
"""
def __init__(
self,
label,
*,
electrical_input,
fuel_input,
electrical_output,
params,
custom_properties=None,
):
super().__init__(
label=label,
inputs={},
outputs={},
custom_properties=custom_properties,
)
self.electrical_input = electrical_input
self.fuel_input = fuel_input
self.electrical_output = electrical_output
self.params = params
# map specific flows to standard API
self.inputs.update(electrical_input)
self.inputs.update(fuel_input)
self.outputs.update(electrical_output)
[docs] def constraint_group(self):
return GenericCAESBlock
[docs]class GenericCAESBlock(ScalarBlock):
r"""Block for nodes of class:`.GenericCAES`.
Note: This component is experimental. Use it with care.
**The following constraints are created:**
.. _GenericCAES-equations:
.. math::
&
(1) \qquad P_{cmp}(t) = electrical\_input (t)
\quad \forall t \in T \\
&
(2) \qquad P_{cmp\_max}(t) = m_{cmp\_max} \cdot CAS_{fil}(t-1)
+ b_{cmp\_max}
\quad \forall t \in\left[1, t_{max}\right] \\
&
(3) \qquad P_{cmp\_max}(t) = b_{cmp\_max}
\quad \forall t \notin\left[1, t_{max}\right] \\
&
(4) \qquad P_{cmp}(t) \leq P_{cmp\_max}(t)
\quad \forall t \in T \\
&
(5) \qquad P_{cmp}(t) \geq P_{cmp\_min} \cdot ST_{cmp}(t)
\quad \forall t \in T \\
&
(6) \qquad P_{cmp}(t) = m_{cmp\_max} \cdot CAS_{fil\_max}
+ b_{cmp\_max} \cdot ST_{cmp}(t)
\quad \forall t \in T \\
&
(7) \qquad \dot{Q}_{cmp}(t) =
m_{cmp\_q} \cdot P_{cmp}(t) + b_{cmp\_q} \cdot ST_{cmp}(t)
\quad \forall t \in T \\
&
(8) \qquad \dot{Q}_{cmp}(t) = \dot{Q}_{cmp_out}(t)
+ \dot{Q}_{tes\_in}(t)
\quad \forall t \in T \\
&
(9) \qquad r_{cmp\_tes} \cdot\dot{Q}_{cmp\_out}(t) =
\left(1-r_{cmp\_tes}\right) \dot{Q}_{tes\_in}(t)
\quad \forall t \in T \\
&
(10) \quad\; P_{exp}(t) = electrical\_output (t)
\quad \forall t \in T \\
&
(11) \quad\; P_{exp\_max}(t) = m_{exp\_max} CAS_{fil}(t-1)
+ b_{exp\_max}
\quad \forall t \in\left[1, t_{\max }\right] \\
&
(12) \quad\; P_{exp\_max}(t) = b_{exp\_max}
\quad \forall t \notin\left[1, t_{\max }\right] \\
&
(13) \quad\; P_{exp}(t) \leq P_{exp\_max}(t)
\quad \forall t \in T \\
&
(14) \quad\; P_{exp}(t) \geq P_{exp\_min}(t) \cdot ST_{exp}(t)
\quad \forall t \in T \\
&
(15) \quad\; P_{exp}(t) \leq m_{exp\_max} \cdot CAS_{fil\_max}
+ b_{exp\_max} \cdot ST_{exp}(t)
\quad \forall t \in T \\
&
(16) \quad\; \dot{Q}_{exp}(t) = m_{exp\_q} \cdot P_{exp}(t)
+ b_{cxp\_q} \cdot ST_{cxp}(t)
\quad \forall t \in T \\
&
(17) \quad\; \dot{Q}_{exp\_in}(t) = fuel\_input(t)
\quad \forall t \in T \\
&
(18) \quad\; \dot{Q}_{exp}(t) = \dot{Q}_{exp\_in}(t)
+ \dot{Q}_{tes\_out}(t)+\dot{Q}_{cxp\_add}(t)
\quad \forall t \in T \\
&
(19) \quad\; r_{exp\_tes} \cdot \dot{Q}_{exp\_in}(t) =
(1 - r_{exp\_tes})(\dot{Q}_{tes\_out}(t) + \dot{Q}_{exp\_add}(t))
\quad \forall t \in T \\
&
(20) \quad\; \dot{E}_{cas\_in}(t) = m_{cas\_in}\cdot P_{cmp}(t)
+ b_{cas\_in}\cdot ST_{cmp}(t)
\quad \forall t \in T \\
&
(21) \quad\; \dot{E}_{cas\_out}(t) = m_{cas\_out}\cdot P_{cmp}(t)
+ b_{cas\_out}\cdot ST_{cmp}(t)
\quad \forall t \in T \\
&
(22) \quad\; \eta_{cas\_tmp} \cdot CAS_{fil}(t) = CAS_{fil}(t-1)
+ \tau\left(\dot{E}_{cas\_in}(t) - \dot{E}_{cas\_out}(t)\right)
\quad \forall t \in\left[1, t_{max}\right] \\
&
(23) \quad\; \eta_{cas\_tmp} \cdot CAS_{fil}(t) =
\tau\left(\dot{E}_{cas\_in}(t) - \dot{E}_{cas\_out}(t)\right)
\quad \forall t \notin\left[1, t_{max}\right] \\
&
(24) \quad\; CAS_{fil}(t) \leq CAS_{fil\_max}
\quad \forall t \in T \\
&
(25) \quad\; TES_{fil}(t) = TES_{fil}(t-1)
+ \tau\left(\dot{Q}_{tes\_in}(t)
- \dot{Q}_{tes\_out}(t)\right)
\quad \forall t \in\left[1, t_{max}\right] \\
&
(26) \quad\; TES_{fil}(t) =
\tau\left(\dot{Q}_{tes\_in}(t)
- \dot{Q}_{tes\_out}(t)\right)
\quad \forall t \notin\left[1, t_{max}\right] \\
&
(27) \quad\; TES_{fil}(t) \leq TES_{fil\_max}
\quad \forall t \in T \\
&
**Table: Symbols and attribute names of variables and parameters**
.. csv-table:: Variables (V) and Parameters (P)
:header: "symbol", "attribute", "type", "explanation"
:widths: 1, 1, 1, 1
":math:`ST_{cmp}` ", "`cmp_st[n,t]` ", "V", "Status of
compression"
":math:`{P}_{cmp}` ", "`cmp_p[n,t]`", "V", "Compression power"
":math:`{P}_{cmp\_max}`", "`cmp_p_max[n,t]`", "V", "Max.
compression power"
":math:`\dot{Q}_{cmp}` ", "`cmp_q_out_sum[n,t]`", "V", "Summed
heat flow in compression"
":math:`\dot{Q}_{cmp\_out}` ", "`cmp_q_waste[n,t]`", "V", "
Waste heat flow from compression"
":math:`ST_{exp}(t)`", "`exp_st[n,t]`", "V", "Status of
expansion (binary)"
":math:`P_{exp}(t)`", "`exp_p[n,t]`", "V", "Expansion power"
":math:`P_{exp\_max}(t)`", "`exp_p_max[n,t]`", "V", "Max.
expansion power"
":math:`\dot{Q}_{exp}(t)`", "`exp_q_in_sum[n,t]`", "V", "
Summed heat flow in expansion"
":math:`\dot{Q}_{exp\_in}(t)`", "`exp_q_fuel_in[n,t]`", "V", "
Heat (external) flow into expansion"
":math:`\dot{Q}_{exp\_add}(t)`", "`exp_q_add_in[n,t]`", "V", "
Additional heat flow into expansion"
":math:`CAV_{fil}(t)`", "`cav_level[n,t]`", "V", "Filling level
if CAE"
":math:`\dot{E}_{cas\_in}(t)`", "`cav_e_in[n,t]`", "V", "
Exergy flow into CAS"
":math:`\dot{E}_{cas\_out}(t)`", "`cav_e_out[n,t]`", "V", "
Exergy flow from CAS"
":math:`TES_{fil}(t)`", "`tes_level[n,t]`", "V", "Filling
level of Thermal Energy Storage (TES)"
":math:`\dot{Q}_{tes\_in}(t)`", "`tes_e_in[n,t]`", "V", "Heat
flow into TES"
":math:`\dot{Q}_{tes\_out}(t)`", "`tes_e_out[n,t]`", "V", "Heat
flow from TES"
":math:`b_{cmp\_max}`", "`cmp_p_max_b[n,t]`", "P", "Specific
y-intersection"
":math:`b_{cmp\_q}`", "`cmp_q_out_b[n,t]`", "P", "Specific
y-intersection"
":math:`b_{exp\_max}`", "`exp_p_max_b[n,t]`", "P", "Specific
y-intersection"
":math:`b_{exp\_q}`", "`exp_q_in_b[n,t]`", "P", "Specific
y-intersection"
":math:`b_{cas\_in}`", "`cav_e_in_b[n,t]`", "P", "Specific
y-intersection"
":math:`b_{cas\_out}`", "`cav_e_out_b[n,t]`", "P", "Specific
y-intersection"
":math:`m_{cmp\_max}`", "`cmp_p_max_m[n,t]`", "P", "Specific
slope"
":math:`m_{cmp\_q}`", "`cmp_q_out_m[n,t]`", "P", "Specific
slope"
":math:`m_{exp\_max}`", "`exp_p_max_m[n,t]`", "P", "Specific
slope"
":math:`m_{exp\_q}`", "`exp_q_in_m[n,t]`", "P", "Specific
slope"
":math:`m_{cas\_in}`", "`cav_e_in_m[n,t]`", "P", "Specific
slope"
":math:`m_{cas\_out}`", "`cav_e_out_m[n,t]`", "P", "Specific
slope"
":math:`P_{cmp\_min}`", "`cmp_p_min[n,t]`", "P", "Min.
compression power"
":math:`r_{cmp\_tes}`", "`cmp_q_tes_share[n,t]`", "P", "Ratio
between waste heat flow and heat flow into TES"
":math:`r_{exp\_tes}`", "`exp_q_tes_share[n,t]`", "P", "
| Ratio between external heat flow into expansion
| and heat flows from TES and additional source"
":math:`\tau`", "`m.timeincrement[n,t]`", "P", "Time interval
length"
":math:`TES_{fil\_max}`", "`tes_level_max[n,t]`", "P", "Max.
filling level of TES"
":math:`CAS_{fil\_max}`", "`cav_level_max[n,t]`", "P", "Max.
filling level of TES"
":math:`\tau`", "`cav_eta_tmp[n,t]`", "P", "
| Temporal efficiency
| (loss factor to take intertemporal losses into account)"
":math:`electrical\_input`", "
`flow[list(n.electrical_input.keys())[0], p, n, t]`", "P", "
Electr. power input into compression"
":math:`electrical\_output`", "
`flow[n, list(n.electrical_output.keys())[0], p, t]`", "P", "
Electr. power output of expansion"
":math:`fuel\_input`", "
`flow[list(n.fuel_input.keys())[0], n, p, t]`", "P", "Heat input
(external) into Expansion"
"""
CONSTRAINT_GROUP = True
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def _create(self, group=None):
"""
Create constraints for GenericCAESBlock.
Parameters
----------
group : list
List containing `.GenericCAES` objects.
e.g. groups=[gcaes1, gcaes2,..]
"""
m = self.parent_block()
if group is None:
return None
self.GENERICCAES = Set(initialize=[n for n in group])
# Compression: Binary variable for operation status
self.cmp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary)
# Compression: Realized capacity
self.cmp_p = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Compression: Max. Capacity
self.cmp_p_max = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Compression: Heat flow
self.cmp_q_out_sum = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Compression: Waste heat
self.cmp_q_waste = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Expansion: Binary variable for operation status
self.exp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary)
# Expansion: Realized capacity
self.exp_p = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Expansion: Max. Capacity
self.exp_p_max = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Expansion: Heat flow of natural gas co-firing
self.exp_q_in_sum = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Expansion: Heat flow of natural gas co-firing
self.exp_q_fuel_in = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Expansion: Heat flow of additional firing
self.exp_q_add_in = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Cavern: Filling levelh
self.cav_level = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Cavern: Energy inflow
self.cav_e_in = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Cavern: Energy outflow
self.cav_e_out = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# TES: Filling levelh
self.tes_level = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# TES: Energy inflow
self.tes_e_in = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# TES: Energy outflow
self.tes_e_out = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Spot market: Positive capacity
self.exp_p_spot = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Spot market: Negative capacity
self.cmp_p_spot = Var(
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals
)
# Compression: Capacity on markets
def cmp_p_constr_rule(block, n, p, t):
expr = 0
expr += -self.cmp_p[n, t]
expr += m.flow[list(n.electrical_input.keys())[0], n, p, t]
return expr == 0
self.cmp_p_constr = Constraint(
self.GENERICCAES, m.TIMEINDEX, rule=cmp_p_constr_rule
)
# Compression: Max. capacity depending on cavern filling level
def cmp_p_max_constr_rule(block, n, t):
if t != 0:
return (
self.cmp_p_max[n, t]
== n.params["cmp_p_max_m"] * self.cav_level[n, t - 1]
+ n.params["cmp_p_max_b"]
)
else:
return self.cmp_p_max[n, t] == n.params["cmp_p_max_b"]
self.cmp_p_max_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_constr_rule
)
def cmp_p_max_area_constr_rule(block, n, t):
return self.cmp_p[n, t] <= self.cmp_p_max[n, t]
self.cmp_p_max_area_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_area_constr_rule
)
# Compression: Status of operation (on/off)
def cmp_st_p_min_constr_rule(block, n, t):
return (
self.cmp_p[n, t] >= n.params["cmp_p_min"] * self.cmp_st[n, t]
)
self.cmp_st_p_min_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_min_constr_rule
)
def cmp_st_p_max_constr_rule(block, n, t):
return (
self.cmp_p[n, t]
<= (
n.params["cmp_p_max_m"] * n.params["cav_level_max"]
+ n.params["cmp_p_max_b"]
)
* self.cmp_st[n, t]
)
self.cmp_st_p_max_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_max_constr_rule
)
# (7) Compression: Heat flow out
def cmp_q_out_constr_rule(block, n, t):
return (
self.cmp_q_out_sum[n, t]
== n.params["cmp_q_out_m"] * self.cmp_p[n, t]
+ n.params["cmp_q_out_b"] * self.cmp_st[n, t]
)
self.cmp_q_out_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_constr_rule
)
# (8) Compression: Definition of single heat flows
def cmp_q_out_sum_constr_rule(block, n, t):
return (
self.cmp_q_out_sum[n, t]
== self.cmp_q_waste[n, t] + self.tes_e_in[n, t]
)
self.cmp_q_out_sum_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_sum_constr_rule
)
# (9) Compression: Heat flow out ratio
def cmp_q_out_shr_constr_rule(block, n, t):
return self.cmp_q_waste[n, t] * n.params[
"cmp_q_tes_share"
] == self.tes_e_in[n, t] * (1 - n.params["cmp_q_tes_share"])
self.cmp_q_out_shr_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_shr_constr_rule
)
# (10) Expansion: Capacity on markets
def exp_p_constr_rule(block, n, p, t):
expr = 0
expr += -self.exp_p[n, t]
expr += m.flow[n, list(n.electrical_output.keys())[0], p, t]
return expr == 0
self.exp_p_constr = Constraint(
self.GENERICCAES, m.TIMEINDEX, rule=exp_p_constr_rule
)
# (11-12) Expansion: Max. capacity depending on cavern filling level
def exp_p_max_constr_rule(block, n, t):
if t != 0:
return (
self.exp_p_max[n, t]
== n.params["exp_p_max_m"] * self.cav_level[n, t - 1]
+ n.params["exp_p_max_b"]
)
else:
return self.exp_p_max[n, t] == n.params["exp_p_max_b"]
self.exp_p_max_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_constr_rule
)
# (13)
def exp_p_max_area_constr_rule(block, n, t):
return self.exp_p[n, t] <= self.exp_p_max[n, t]
self.exp_p_max_area_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_area_constr_rule
)
# (14) Expansion: Status of operation (on/off)
def exp_st_p_min_constr_rule(block, n, t):
return (
self.exp_p[n, t] >= n.params["exp_p_min"] * self.exp_st[n, t]
)
self.exp_st_p_min_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_min_constr_rule
)
# (15)
def exp_st_p_max_constr_rule(block, n, t):
return (
self.exp_p[n, t]
<= (
n.params["exp_p_max_m"] * n.params["cav_level_max"]
+ n.params["exp_p_max_b"]
)
* self.exp_st[n, t]
)
self.exp_st_p_max_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_max_constr_rule
)
# (16) Expansion: Heat flow in
def exp_q_in_constr_rule(block, n, t):
return (
self.exp_q_in_sum[n, t]
== n.params["exp_q_in_m"] * self.exp_p[n, t]
+ n.params["exp_q_in_b"] * self.exp_st[n, t]
)
self.exp_q_in_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_constr_rule
)
# (17) Expansion: Fuel allocation
def exp_q_fuel_constr_rule(block, n, p, t):
expr = 0
expr += -self.exp_q_fuel_in[n, t]
expr += m.flow[list(n.fuel_input.keys())[0], n, p, t]
return expr == 0
self.exp_q_fuel_constr = Constraint(
self.GENERICCAES, m.TIMEINDEX, rule=exp_q_fuel_constr_rule
)
# (18) Expansion: Definition of single heat flows
def exp_q_in_sum_constr_rule(block, n, t):
return (
self.exp_q_in_sum[n, t]
== self.exp_q_fuel_in[n, t]
+ self.tes_e_out[n, t]
+ self.exp_q_add_in[n, t]
)
self.exp_q_in_sum_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_sum_constr_rule
)
# (19) Expansion: Heat flow in ratio
def exp_q_in_shr_constr_rule(block, n, t):
return n.params["exp_q_tes_share"] * self.exp_q_fuel_in[n, t] == (
1 - n.params["exp_q_tes_share"]
) * (self.exp_q_add_in[n, t] + self.tes_e_out[n, t])
self.exp_q_in_shr_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_shr_constr_rule
)
# (20) Cavern: Energy inflow
def cav_e_in_constr_rule(block, n, t):
return (
self.cav_e_in[n, t]
== n.params["cav_e_in_m"] * self.cmp_p[n, t]
+ n.params["cav_e_in_b"]
)
self.cav_e_in_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cav_e_in_constr_rule
)
# (21) Cavern: Energy outflow
def cav_e_out_constr_rule(block, n, t):
return (
self.cav_e_out[n, t]
== n.params["cav_e_out_m"] * self.exp_p[n, t]
+ n.params["cav_e_out_b"]
)
self.cav_e_out_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cav_e_out_constr_rule
)
# (22-23) Cavern: Storage balance
def cav_eta_constr_rule(block, n, t):
if t != 0:
return n.params["cav_eta_temp"] * self.cav_level[
n, t
] == self.cav_level[n, t - 1] + m.timeincrement[t] * (
self.cav_e_in[n, t] - self.cav_e_out[n, t]
)
else:
return n.params["cav_eta_temp"] * self.cav_level[
n, t
] == m.timeincrement[t] * (
self.cav_e_in[n, t] - self.cav_e_out[n, t]
)
self.cav_eta_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cav_eta_constr_rule
)
# (24) Cavern: Upper bound
def cav_ub_constr_rule(block, n, t):
return self.cav_level[n, t] <= n.params["cav_level_max"]
self.cav_ub_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=cav_ub_constr_rule
)
# (25-26) TES: Storage balance
def tes_eta_constr_rule(block, n, t):
if t != 0:
return self.tes_level[n, t] == self.tes_level[
n, t - 1
] + m.timeincrement[t] * (
self.tes_e_in[n, t] - self.tes_e_out[n, t]
)
else:
return self.tes_level[n, t] == m.timeincrement[t] * (
self.tes_e_in[n, t] - self.tes_e_out[n, t]
)
self.tes_eta_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=tes_eta_constr_rule
)
# (27) TES: Upper bound
def tes_ub_constr_rule(block, n, t):
return self.tes_level[n, t] <= n.params["tes_level_max"]
self.tes_ub_constr = Constraint(
self.GENERICCAES, m.TIMESTEPS, rule=tes_ub_constr_rule
)