1
|
|
|
# -*- coding: utf-8 -*- |
2
|
|
|
|
3
|
|
|
""" |
4
|
|
|
In-development generic compressed air energy storage. |
5
|
|
|
|
6
|
|
|
SPDX-FileCopyrightText: Uwe Krien <[email protected]> |
7
|
|
|
SPDX-FileCopyrightText: Simon Hilpert |
8
|
|
|
SPDX-FileCopyrightText: Cord Kaldemeyer |
9
|
|
|
SPDX-FileCopyrightText: Patrik Schönfeldt |
10
|
|
|
SPDX-FileCopyrightText: Johannes Röder |
11
|
|
|
SPDX-FileCopyrightText: jakob-wo |
12
|
|
|
SPDX-FileCopyrightText: gplssm |
13
|
|
|
SPDX-FileCopyrightText: jnnr |
14
|
|
|
SPDX-FileCopyrightText: Johannes Kochems |
15
|
|
|
|
16
|
|
|
SPDX-License-Identifier: MIT |
17
|
|
|
|
18
|
|
|
""" |
19
|
|
|
|
20
|
|
|
from oemof.network import Node |
21
|
|
|
from pyomo.core.base.block import ScalarBlock |
22
|
|
|
from pyomo.environ import Binary |
23
|
|
|
from pyomo.environ import Constraint |
24
|
|
|
from pyomo.environ import NonNegativeReals |
25
|
|
|
from pyomo.environ import Set |
26
|
|
|
from pyomo.environ import Var |
27
|
|
|
|
28
|
|
|
|
29
|
|
|
class GenericCAES(Node): |
30
|
|
|
""" |
31
|
|
|
Component `GenericCAES` to model arbitrary compressed air energy storages. |
32
|
|
|
|
33
|
|
|
The full set of equations is described in: |
34
|
|
|
Kaldemeyer, C.; Boysen, C.; Tuschy, I. |
35
|
|
|
A Generic Formulation of Compressed Air Energy Storage as |
36
|
|
|
Mixed Integer Linear Program – Unit Commitment of Specific |
37
|
|
|
Technical Concepts in Arbitrary Market Environments |
38
|
|
|
Materials Today: Proceedings 00 (2018) 0000–0000 |
39
|
|
|
[currently in review] |
40
|
|
|
|
41
|
|
|
Parameters |
42
|
|
|
---------- |
43
|
|
|
electrical_input : dict |
44
|
|
|
Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object |
45
|
|
|
for the electrical input. |
46
|
|
|
fuel_input : dict |
47
|
|
|
Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object |
48
|
|
|
for the fuel input. |
49
|
|
|
electrical_output : dict |
50
|
|
|
Dictionary with key-value-pair of `oemof.Bus` and `oemof.Flow` object |
51
|
|
|
for the electrical output. |
52
|
|
|
|
53
|
|
|
Note: This component is experimental. Use it with care. |
54
|
|
|
|
55
|
|
|
Notes |
56
|
|
|
----- |
57
|
|
|
The following sets, variables, constraints and objective parts are created |
58
|
|
|
* :py:class:`~oemof.solph.blocks.generic_caes.GenericCAES` |
59
|
|
|
|
60
|
|
|
Examples |
61
|
|
|
-------- |
62
|
|
|
|
63
|
|
|
>>> from oemof import solph |
64
|
|
|
>>> bel = solph.buses.Bus(label='bel') |
65
|
|
|
>>> bth = solph.buses.Bus(label='bth') |
66
|
|
|
>>> bgas = solph.buses.Bus(label='bgas') |
67
|
|
|
>>> # dictionary with parameters for a specific CAES plant |
68
|
|
|
>>> concept = { |
69
|
|
|
... 'cav_e_in_b': 0, |
70
|
|
|
... 'cav_e_in_m': 0.6457267578, |
71
|
|
|
... 'cav_e_out_b': 0, |
72
|
|
|
... 'cav_e_out_m': 0.3739636077, |
73
|
|
|
... 'cav_eta_temp': 1.0, |
74
|
|
|
... 'cav_level_max': 211.11, |
75
|
|
|
... 'cmp_p_max_b': 86.0918959849, |
76
|
|
|
... 'cmp_p_max_m': 0.0679999932, |
77
|
|
|
... 'cmp_p_min': 1, |
78
|
|
|
... 'cmp_q_out_b': -19.3996965679, |
79
|
|
|
... 'cmp_q_out_m': 1.1066036114, |
80
|
|
|
... 'cmp_q_tes_share': 0, |
81
|
|
|
... 'exp_p_max_b': 46.1294016678, |
82
|
|
|
... 'exp_p_max_m': 0.2528340303, |
83
|
|
|
... 'exp_p_min': 1, |
84
|
|
|
... 'exp_q_in_b': -2.2073411014, |
85
|
|
|
... 'exp_q_in_m': 1.129249765, |
86
|
|
|
... 'exp_q_tes_share': 0, |
87
|
|
|
... 'tes_eta_temp': 1.0, |
88
|
|
|
... 'tes_level_max': 0.0} |
89
|
|
|
>>> # generic compressed air energy storage (caes) plant |
90
|
|
|
>>> caes = solph.components.experimental.GenericCAES( |
91
|
|
|
... label='caes', |
92
|
|
|
... electrical_input={bel: solph.flows.Flow()}, |
93
|
|
|
... fuel_input={bgas: solph.flows.Flow()}, |
94
|
|
|
... electrical_output={bel: solph.flows.Flow()}, |
95
|
|
|
... params=concept) |
96
|
|
|
>>> type(caes) |
97
|
|
|
<class 'oemof.solph.components.experimental._generic_caes.GenericCAES'> |
98
|
|
|
""" |
99
|
|
|
|
100
|
|
|
def __init__( |
101
|
|
|
self, |
102
|
|
|
label, |
103
|
|
|
*, |
104
|
|
|
electrical_input, |
105
|
|
|
fuel_input, |
106
|
|
|
electrical_output, |
107
|
|
|
params, |
108
|
|
|
custom_properties=None, |
109
|
|
|
): |
110
|
|
|
super().__init__( |
111
|
|
|
label=label, |
112
|
|
|
inputs={}, |
113
|
|
|
outputs={}, |
114
|
|
|
custom_properties=custom_properties, |
115
|
|
|
) |
116
|
|
|
|
117
|
|
|
self.electrical_input = electrical_input |
118
|
|
|
self.fuel_input = fuel_input |
119
|
|
|
self.electrical_output = electrical_output |
120
|
|
|
self.params = params |
121
|
|
|
|
122
|
|
|
# map specific flows to standard API |
123
|
|
|
self.inputs.update(electrical_input) |
124
|
|
|
self.inputs.update(fuel_input) |
125
|
|
|
self.outputs.update(electrical_output) |
126
|
|
|
|
127
|
|
|
def constraint_group(self): |
128
|
|
|
return GenericCAESBlock |
129
|
|
|
|
130
|
|
|
|
131
|
|
|
class GenericCAESBlock(ScalarBlock): |
132
|
|
|
r"""Block for nodes of class:`.GenericCAES`. |
133
|
|
|
|
134
|
|
|
Note: This component is experimental. Use it with care. |
135
|
|
|
|
136
|
|
|
**The following constraints are created:** |
137
|
|
|
|
138
|
|
|
.. _GenericCAES-equations: |
139
|
|
|
|
140
|
|
|
.. math:: |
141
|
|
|
& |
142
|
|
|
(1) \qquad P_{cmp}(t) = electrical\_input (t) |
143
|
|
|
\quad \forall t \in T \\ |
144
|
|
|
& |
145
|
|
|
(2) \qquad P_{cmp\_max}(t) = m_{cmp\_max} \cdot CAS_{fil}(t-1) |
146
|
|
|
+ b_{cmp\_max} |
147
|
|
|
\quad \forall t \in\left[1, t_{max}\right] \\ |
148
|
|
|
& |
149
|
|
|
(3) \qquad P_{cmp\_max}(t) = b_{cmp\_max} |
150
|
|
|
\quad \forall t \notin\left[1, t_{max}\right] \\ |
151
|
|
|
& |
152
|
|
|
(4) \qquad P_{cmp}(t) \leq P_{cmp\_max}(t) |
153
|
|
|
\quad \forall t \in T \\ |
154
|
|
|
& |
155
|
|
|
(5) \qquad P_{cmp}(t) \geq P_{cmp\_min} \cdot ST_{cmp}(t) |
156
|
|
|
\quad \forall t \in T \\ |
157
|
|
|
& |
158
|
|
|
(6) \qquad P_{cmp}(t) = m_{cmp\_max} \cdot CAS_{fil\_max} |
159
|
|
|
+ b_{cmp\_max} \cdot ST_{cmp}(t) |
160
|
|
|
\quad \forall t \in T \\ |
161
|
|
|
& |
162
|
|
|
(7) \qquad \dot{Q}_{cmp}(t) = |
163
|
|
|
m_{cmp\_q} \cdot P_{cmp}(t) + b_{cmp\_q} \cdot ST_{cmp}(t) |
164
|
|
|
\quad \forall t \in T \\ |
165
|
|
|
& |
166
|
|
|
(8) \qquad \dot{Q}_{cmp}(t) = \dot{Q}_{cmp_out}(t) |
167
|
|
|
+ \dot{Q}_{tes\_in}(t) |
168
|
|
|
\quad \forall t \in T \\ |
169
|
|
|
& |
170
|
|
|
(9) \qquad r_{cmp\_tes} \cdot\dot{Q}_{cmp\_out}(t) = |
171
|
|
|
\left(1-r_{cmp\_tes}\right) \dot{Q}_{tes\_in}(t) |
172
|
|
|
\quad \forall t \in T \\ |
173
|
|
|
& |
174
|
|
|
(10) \quad\; P_{exp}(t) = electrical\_output (t) |
175
|
|
|
\quad \forall t \in T \\ |
176
|
|
|
& |
177
|
|
|
(11) \quad\; P_{exp\_max}(t) = m_{exp\_max} CAS_{fil}(t-1) |
178
|
|
|
+ b_{exp\_max} |
179
|
|
|
\quad \forall t \in\left[1, t_{\max }\right] \\ |
180
|
|
|
& |
181
|
|
|
(12) \quad\; P_{exp\_max}(t) = b_{exp\_max} |
182
|
|
|
\quad \forall t \notin\left[1, t_{\max }\right] \\ |
183
|
|
|
& |
184
|
|
|
(13) \quad\; P_{exp}(t) \leq P_{exp\_max}(t) |
185
|
|
|
\quad \forall t \in T \\ |
186
|
|
|
& |
187
|
|
|
(14) \quad\; P_{exp}(t) \geq P_{exp\_min}(t) \cdot ST_{exp}(t) |
188
|
|
|
\quad \forall t \in T \\ |
189
|
|
|
& |
190
|
|
|
(15) \quad\; P_{exp}(t) \leq m_{exp\_max} \cdot CAS_{fil\_max} |
191
|
|
|
+ b_{exp\_max} \cdot ST_{exp}(t) |
192
|
|
|
\quad \forall t \in T \\ |
193
|
|
|
& |
194
|
|
|
(16) \quad\; \dot{Q}_{exp}(t) = m_{exp\_q} \cdot P_{exp}(t) |
195
|
|
|
+ b_{cxp\_q} \cdot ST_{cxp}(t) |
196
|
|
|
\quad \forall t \in T \\ |
197
|
|
|
& |
198
|
|
|
(17) \quad\; \dot{Q}_{exp\_in}(t) = fuel\_input(t) |
199
|
|
|
\quad \forall t \in T \\ |
200
|
|
|
& |
201
|
|
|
(18) \quad\; \dot{Q}_{exp}(t) = \dot{Q}_{exp\_in}(t) |
202
|
|
|
+ \dot{Q}_{tes\_out}(t)+\dot{Q}_{cxp\_add}(t) |
203
|
|
|
\quad \forall t \in T \\ |
204
|
|
|
& |
205
|
|
|
(19) \quad\; r_{exp\_tes} \cdot \dot{Q}_{exp\_in}(t) = |
206
|
|
|
(1 - r_{exp\_tes})(\dot{Q}_{tes\_out}(t) + \dot{Q}_{exp\_add}(t)) |
207
|
|
|
\quad \forall t \in T \\ |
208
|
|
|
& |
209
|
|
|
(20) \quad\; \dot{E}_{cas\_in}(t) = m_{cas\_in}\cdot P_{cmp}(t) |
210
|
|
|
+ b_{cas\_in}\cdot ST_{cmp}(t) |
211
|
|
|
\quad \forall t \in T \\ |
212
|
|
|
& |
213
|
|
|
(21) \quad\; \dot{E}_{cas\_out}(t) = m_{cas\_out}\cdot P_{cmp}(t) |
214
|
|
|
+ b_{cas\_out}\cdot ST_{cmp}(t) |
215
|
|
|
\quad \forall t \in T \\ |
216
|
|
|
& |
217
|
|
|
(22) \quad\; \eta_{cas\_tmp} \cdot CAS_{fil}(t) = CAS_{fil}(t-1) |
218
|
|
|
+ \tau\left(\dot{E}_{cas\_in}(t) - \dot{E}_{cas\_out}(t)\right) |
219
|
|
|
\quad \forall t \in\left[1, t_{max}\right] \\ |
220
|
|
|
& |
221
|
|
|
(23) \quad\; \eta_{cas\_tmp} \cdot CAS_{fil}(t) = |
222
|
|
|
\tau\left(\dot{E}_{cas\_in}(t) - \dot{E}_{cas\_out}(t)\right) |
223
|
|
|
\quad \forall t \notin\left[1, t_{max}\right] \\ |
224
|
|
|
& |
225
|
|
|
(24) \quad\; CAS_{fil}(t) \leq CAS_{fil\_max} |
226
|
|
|
\quad \forall t \in T \\ |
227
|
|
|
& |
228
|
|
|
(25) \quad\; TES_{fil}(t) = TES_{fil}(t-1) |
229
|
|
|
+ \tau\left(\dot{Q}_{tes\_in}(t) |
230
|
|
|
- \dot{Q}_{tes\_out}(t)\right) |
231
|
|
|
\quad \forall t \in\left[1, t_{max}\right] \\ |
232
|
|
|
& |
233
|
|
|
(26) \quad\; TES_{fil}(t) = |
234
|
|
|
\tau\left(\dot{Q}_{tes\_in}(t) |
235
|
|
|
- \dot{Q}_{tes\_out}(t)\right) |
236
|
|
|
\quad \forall t \notin\left[1, t_{max}\right] \\ |
237
|
|
|
& |
238
|
|
|
(27) \quad\; TES_{fil}(t) \leq TES_{fil\_max} |
239
|
|
|
\quad \forall t \in T \\ |
240
|
|
|
& |
241
|
|
|
|
242
|
|
|
|
243
|
|
|
**Table: Symbols and attribute names of variables and parameters** |
244
|
|
|
|
245
|
|
|
.. csv-table:: Variables (V) and Parameters (P) |
246
|
|
|
:header: "symbol", "attribute", "type", "explanation" |
247
|
|
|
:widths: 1, 1, 1, 1 |
248
|
|
|
|
249
|
|
|
":math:`ST_{cmp}` ", "`cmp_st[n,t]` ", "V", "Status of |
250
|
|
|
compression" |
251
|
|
|
":math:`{P}_{cmp}` ", "`cmp_p[n,t]`", "V", "Compression power" |
252
|
|
|
":math:`{P}_{cmp\_max}`", "`cmp_p_max[n,t]`", "V", "Max. |
253
|
|
|
compression power" |
254
|
|
|
":math:`\dot{Q}_{cmp}` ", "`cmp_q_out_sum[n,t]`", "V", "Summed |
255
|
|
|
heat flow in compression" |
256
|
|
|
":math:`\dot{Q}_{cmp\_out}` ", "`cmp_q_waste[n,t]`", "V", " |
257
|
|
|
Waste heat flow from compression" |
258
|
|
|
":math:`ST_{exp}(t)`", "`exp_st[n,t]`", "V", "Status of |
259
|
|
|
expansion (binary)" |
260
|
|
|
":math:`P_{exp}(t)`", "`exp_p[n,t]`", "V", "Expansion power" |
261
|
|
|
":math:`P_{exp\_max}(t)`", "`exp_p_max[n,t]`", "V", "Max. |
262
|
|
|
expansion power" |
263
|
|
|
":math:`\dot{Q}_{exp}(t)`", "`exp_q_in_sum[n,t]`", "V", " |
264
|
|
|
Summed heat flow in expansion" |
265
|
|
|
":math:`\dot{Q}_{exp\_in}(t)`", "`exp_q_fuel_in[n,t]`", "V", " |
266
|
|
|
Heat (external) flow into expansion" |
267
|
|
|
":math:`\dot{Q}_{exp\_add}(t)`", "`exp_q_add_in[n,t]`", "V", " |
268
|
|
|
Additional heat flow into expansion" |
269
|
|
|
":math:`CAV_{fil}(t)`", "`cav_level[n,t]`", "V", "Filling level |
270
|
|
|
if CAE" |
271
|
|
|
":math:`\dot{E}_{cas\_in}(t)`", "`cav_e_in[n,t]`", "V", " |
272
|
|
|
Exergy flow into CAS" |
273
|
|
|
":math:`\dot{E}_{cas\_out}(t)`", "`cav_e_out[n,t]`", "V", " |
274
|
|
|
Exergy flow from CAS" |
275
|
|
|
":math:`TES_{fil}(t)`", "`tes_level[n,t]`", "V", "Filling |
276
|
|
|
level of Thermal Energy Storage (TES)" |
277
|
|
|
":math:`\dot{Q}_{tes\_in}(t)`", "`tes_e_in[n,t]`", "V", "Heat |
278
|
|
|
flow into TES" |
279
|
|
|
":math:`\dot{Q}_{tes\_out}(t)`", "`tes_e_out[n,t]`", "V", "Heat |
280
|
|
|
flow from TES" |
281
|
|
|
":math:`b_{cmp\_max}`", "`cmp_p_max_b[n,t]`", "P", "Specific |
282
|
|
|
y-intersection" |
283
|
|
|
":math:`b_{cmp\_q}`", "`cmp_q_out_b[n,t]`", "P", "Specific |
284
|
|
|
y-intersection" |
285
|
|
|
":math:`b_{exp\_max}`", "`exp_p_max_b[n,t]`", "P", "Specific |
286
|
|
|
y-intersection" |
287
|
|
|
":math:`b_{exp\_q}`", "`exp_q_in_b[n,t]`", "P", "Specific |
288
|
|
|
y-intersection" |
289
|
|
|
":math:`b_{cas\_in}`", "`cav_e_in_b[n,t]`", "P", "Specific |
290
|
|
|
y-intersection" |
291
|
|
|
":math:`b_{cas\_out}`", "`cav_e_out_b[n,t]`", "P", "Specific |
292
|
|
|
y-intersection" |
293
|
|
|
":math:`m_{cmp\_max}`", "`cmp_p_max_m[n,t]`", "P", "Specific |
294
|
|
|
slope" |
295
|
|
|
":math:`m_{cmp\_q}`", "`cmp_q_out_m[n,t]`", "P", "Specific |
296
|
|
|
slope" |
297
|
|
|
":math:`m_{exp\_max}`", "`exp_p_max_m[n,t]`", "P", "Specific |
298
|
|
|
slope" |
299
|
|
|
":math:`m_{exp\_q}`", "`exp_q_in_m[n,t]`", "P", "Specific |
300
|
|
|
slope" |
301
|
|
|
":math:`m_{cas\_in}`", "`cav_e_in_m[n,t]`", "P", "Specific |
302
|
|
|
slope" |
303
|
|
|
":math:`m_{cas\_out}`", "`cav_e_out_m[n,t]`", "P", "Specific |
304
|
|
|
slope" |
305
|
|
|
":math:`P_{cmp\_min}`", "`cmp_p_min[n,t]`", "P", "Min. |
306
|
|
|
compression power" |
307
|
|
|
":math:`r_{cmp\_tes}`", "`cmp_q_tes_share[n,t]`", "P", "Ratio |
308
|
|
|
between waste heat flow and heat flow into TES" |
309
|
|
|
":math:`r_{exp\_tes}`", "`exp_q_tes_share[n,t]`", "P", " |
310
|
|
|
| Ratio between external heat flow into expansion |
311
|
|
|
| and heat flows from TES and additional source" |
312
|
|
|
":math:`\tau`", "`m.timeincrement[n,t]`", "P", "Time interval |
313
|
|
|
length" |
314
|
|
|
":math:`TES_{fil\_max}`", "`tes_level_max[n,t]`", "P", "Max. |
315
|
|
|
filling level of TES" |
316
|
|
|
":math:`CAS_{fil\_max}`", "`cav_level_max[n,t]`", "P", "Max. |
317
|
|
|
filling level of TES" |
318
|
|
|
":math:`\tau`", "`cav_eta_tmp[n,t]`", "P", " |
319
|
|
|
| Temporal efficiency |
320
|
|
|
| (loss factor to take intertemporal losses into account)" |
321
|
|
|
":math:`electrical\_input`", " |
322
|
|
|
`flow[list(n.electrical_input.keys())[0], p, n, t]`", "P", " |
323
|
|
|
Electr. power input into compression" |
324
|
|
|
":math:`electrical\_output`", " |
325
|
|
|
`flow[n, list(n.electrical_output.keys())[0], p, t]`", "P", " |
326
|
|
|
Electr. power output of expansion" |
327
|
|
|
":math:`fuel\_input`", " |
328
|
|
|
`flow[list(n.fuel_input.keys())[0], n, p, t]`", "P", "Heat input |
329
|
|
|
(external) into Expansion" |
330
|
|
|
|
331
|
|
|
""" |
332
|
|
|
|
333
|
|
|
CONSTRAINT_GROUP = True |
334
|
|
|
|
335
|
|
|
def __init__(self, *args, **kwargs): |
336
|
|
|
super().__init__(*args, **kwargs) |
337
|
|
|
|
338
|
|
|
def _create(self, group=None): |
339
|
|
|
""" |
340
|
|
|
Create constraints for GenericCAESBlock. |
341
|
|
|
|
342
|
|
|
Parameters |
343
|
|
|
---------- |
344
|
|
|
group : list |
345
|
|
|
List containing `.GenericCAES` objects. |
346
|
|
|
e.g. groups=[gcaes1, gcaes2,..] |
347
|
|
|
""" |
348
|
|
|
m = self.parent_block() |
349
|
|
|
|
350
|
|
|
if group is None: |
351
|
|
|
return None |
352
|
|
|
|
353
|
|
|
self.GENERICCAES = Set(initialize=[n for n in group]) |
354
|
|
|
|
355
|
|
|
# Compression: Binary variable for operation status |
356
|
|
|
self.cmp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary) |
357
|
|
|
|
358
|
|
|
# Compression: Realized capacity |
359
|
|
|
self.cmp_p = Var( |
360
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
361
|
|
|
) |
362
|
|
|
|
363
|
|
|
# Compression: Max. Capacity |
364
|
|
|
self.cmp_p_max = Var( |
365
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
366
|
|
|
) |
367
|
|
|
|
368
|
|
|
# Compression: Heat flow |
369
|
|
|
self.cmp_q_out_sum = Var( |
370
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
371
|
|
|
) |
372
|
|
|
|
373
|
|
|
# Compression: Waste heat |
374
|
|
|
self.cmp_q_waste = Var( |
375
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
376
|
|
|
) |
377
|
|
|
|
378
|
|
|
# Expansion: Binary variable for operation status |
379
|
|
|
self.exp_st = Var(self.GENERICCAES, m.TIMESTEPS, within=Binary) |
380
|
|
|
|
381
|
|
|
# Expansion: Realized capacity |
382
|
|
|
self.exp_p = Var( |
383
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
384
|
|
|
) |
385
|
|
|
|
386
|
|
|
# Expansion: Max. Capacity |
387
|
|
|
self.exp_p_max = Var( |
388
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
389
|
|
|
) |
390
|
|
|
|
391
|
|
|
# Expansion: Heat flow of natural gas co-firing |
392
|
|
|
self.exp_q_in_sum = Var( |
393
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
394
|
|
|
) |
395
|
|
|
|
396
|
|
|
# Expansion: Heat flow of natural gas co-firing |
397
|
|
|
self.exp_q_fuel_in = Var( |
398
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
399
|
|
|
) |
400
|
|
|
|
401
|
|
|
# Expansion: Heat flow of additional firing |
402
|
|
|
self.exp_q_add_in = Var( |
403
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
404
|
|
|
) |
405
|
|
|
|
406
|
|
|
# Cavern: Filling levelh |
407
|
|
|
self.cav_level = Var( |
408
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
409
|
|
|
) |
410
|
|
|
|
411
|
|
|
# Cavern: Energy inflow |
412
|
|
|
self.cav_e_in = Var( |
413
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
414
|
|
|
) |
415
|
|
|
|
416
|
|
|
# Cavern: Energy outflow |
417
|
|
|
self.cav_e_out = Var( |
418
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
419
|
|
|
) |
420
|
|
|
|
421
|
|
|
# TES: Filling levelh |
422
|
|
|
self.tes_level = Var( |
423
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
424
|
|
|
) |
425
|
|
|
|
426
|
|
|
# TES: Energy inflow |
427
|
|
|
self.tes_e_in = Var( |
428
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
429
|
|
|
) |
430
|
|
|
|
431
|
|
|
# TES: Energy outflow |
432
|
|
|
self.tes_e_out = Var( |
433
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
434
|
|
|
) |
435
|
|
|
|
436
|
|
|
# Spot market: Positive capacity |
437
|
|
|
self.exp_p_spot = Var( |
438
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
439
|
|
|
) |
440
|
|
|
|
441
|
|
|
# Spot market: Negative capacity |
442
|
|
|
self.cmp_p_spot = Var( |
443
|
|
|
self.GENERICCAES, m.TIMESTEPS, within=NonNegativeReals |
444
|
|
|
) |
445
|
|
|
|
446
|
|
|
# Compression: Capacity on markets |
447
|
|
|
def cmp_p_constr_rule(block, n, t): |
448
|
|
|
expr = 0 |
449
|
|
|
expr += -self.cmp_p[n, t] |
450
|
|
|
expr += m.flow[list(n.electrical_input.keys())[0], n, t] |
451
|
|
|
return expr == 0 |
452
|
|
|
|
453
|
|
|
self.cmp_p_constr = Constraint( |
454
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_constr_rule |
455
|
|
|
) |
456
|
|
|
|
457
|
|
|
# Compression: Max. capacity depending on cavern filling level |
458
|
|
|
def cmp_p_max_constr_rule(block, n, t): |
459
|
|
|
if t != 0: |
460
|
|
|
return ( |
461
|
|
|
self.cmp_p_max[n, t] |
462
|
|
|
== n.params["cmp_p_max_m"] * self.cav_level[n, t - 1] |
463
|
|
|
+ n.params["cmp_p_max_b"] |
464
|
|
|
) |
465
|
|
|
else: |
466
|
|
|
return self.cmp_p_max[n, t] == n.params["cmp_p_max_b"] |
467
|
|
|
|
468
|
|
|
self.cmp_p_max_constr = Constraint( |
469
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_constr_rule |
470
|
|
|
) |
471
|
|
|
|
472
|
|
|
def cmp_p_max_area_constr_rule(block, n, t): |
473
|
|
|
return self.cmp_p[n, t] <= self.cmp_p_max[n, t] |
474
|
|
|
|
475
|
|
|
self.cmp_p_max_area_constr = Constraint( |
476
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_p_max_area_constr_rule |
477
|
|
|
) |
478
|
|
|
|
479
|
|
|
# Compression: Status of operation (on/off) |
480
|
|
|
def cmp_st_p_min_constr_rule(block, n, t): |
481
|
|
|
return ( |
482
|
|
|
self.cmp_p[n, t] >= n.params["cmp_p_min"] * self.cmp_st[n, t] |
483
|
|
|
) |
484
|
|
|
|
485
|
|
|
self.cmp_st_p_min_constr = Constraint( |
486
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_min_constr_rule |
487
|
|
|
) |
488
|
|
|
|
489
|
|
|
def cmp_st_p_max_constr_rule(block, n, t): |
490
|
|
|
return ( |
491
|
|
|
self.cmp_p[n, t] |
492
|
|
|
<= ( |
493
|
|
|
n.params["cmp_p_max_m"] * n.params["cav_level_max"] |
494
|
|
|
+ n.params["cmp_p_max_b"] |
495
|
|
|
) |
496
|
|
|
* self.cmp_st[n, t] |
497
|
|
|
) |
498
|
|
|
|
499
|
|
|
self.cmp_st_p_max_constr = Constraint( |
500
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_st_p_max_constr_rule |
501
|
|
|
) |
502
|
|
|
|
503
|
|
|
# (7) Compression: Heat flow out |
504
|
|
|
def cmp_q_out_constr_rule(block, n, t): |
505
|
|
|
return ( |
506
|
|
|
self.cmp_q_out_sum[n, t] |
507
|
|
|
== n.params["cmp_q_out_m"] * self.cmp_p[n, t] |
508
|
|
|
+ n.params["cmp_q_out_b"] * self.cmp_st[n, t] |
509
|
|
|
) |
510
|
|
|
|
511
|
|
|
self.cmp_q_out_constr = Constraint( |
512
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_constr_rule |
513
|
|
|
) |
514
|
|
|
|
515
|
|
|
# (8) Compression: Definition of single heat flows |
516
|
|
|
def cmp_q_out_sum_constr_rule(block, n, t): |
517
|
|
|
return ( |
518
|
|
|
self.cmp_q_out_sum[n, t] |
519
|
|
|
== self.cmp_q_waste[n, t] + self.tes_e_in[n, t] |
520
|
|
|
) |
521
|
|
|
|
522
|
|
|
self.cmp_q_out_sum_constr = Constraint( |
523
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_sum_constr_rule |
524
|
|
|
) |
525
|
|
|
|
526
|
|
|
# (9) Compression: Heat flow out ratio |
527
|
|
|
def cmp_q_out_shr_constr_rule(block, n, t): |
528
|
|
|
return self.cmp_q_waste[n, t] * n.params[ |
529
|
|
|
"cmp_q_tes_share" |
530
|
|
|
] == self.tes_e_in[n, t] * (1 - n.params["cmp_q_tes_share"]) |
531
|
|
|
|
532
|
|
|
self.cmp_q_out_shr_constr = Constraint( |
533
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cmp_q_out_shr_constr_rule |
534
|
|
|
) |
535
|
|
|
|
536
|
|
|
# (10) Expansion: Capacity on markets |
537
|
|
|
def exp_p_constr_rule(block, n, t): |
538
|
|
|
expr = 0 |
539
|
|
|
expr += -self.exp_p[n, t] |
540
|
|
|
expr += m.flow[n, list(n.electrical_output.keys())[0], t] |
541
|
|
|
return expr == 0 |
542
|
|
|
|
543
|
|
|
self.exp_p_constr = Constraint( |
544
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_p_constr_rule |
545
|
|
|
) |
546
|
|
|
|
547
|
|
|
# (11-12) Expansion: Max. capacity depending on cavern filling level |
548
|
|
|
def exp_p_max_constr_rule(block, n, t): |
549
|
|
|
if t != 0: |
550
|
|
|
return ( |
551
|
|
|
self.exp_p_max[n, t] |
552
|
|
|
== n.params["exp_p_max_m"] * self.cav_level[n, t - 1] |
553
|
|
|
+ n.params["exp_p_max_b"] |
554
|
|
|
) |
555
|
|
|
else: |
556
|
|
|
return self.exp_p_max[n, t] == n.params["exp_p_max_b"] |
557
|
|
|
|
558
|
|
|
self.exp_p_max_constr = Constraint( |
559
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_constr_rule |
560
|
|
|
) |
561
|
|
|
|
562
|
|
|
# (13) |
563
|
|
|
def exp_p_max_area_constr_rule(block, n, t): |
564
|
|
|
return self.exp_p[n, t] <= self.exp_p_max[n, t] |
565
|
|
|
|
566
|
|
|
self.exp_p_max_area_constr = Constraint( |
567
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_p_max_area_constr_rule |
568
|
|
|
) |
569
|
|
|
|
570
|
|
|
# (14) Expansion: Status of operation (on/off) |
571
|
|
|
def exp_st_p_min_constr_rule(block, n, t): |
572
|
|
|
return ( |
573
|
|
|
self.exp_p[n, t] >= n.params["exp_p_min"] * self.exp_st[n, t] |
574
|
|
|
) |
575
|
|
|
|
576
|
|
|
self.exp_st_p_min_constr = Constraint( |
577
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_min_constr_rule |
578
|
|
|
) |
579
|
|
|
|
580
|
|
|
# (15) |
581
|
|
|
def exp_st_p_max_constr_rule(block, n, t): |
582
|
|
|
return ( |
583
|
|
|
self.exp_p[n, t] |
584
|
|
|
<= ( |
585
|
|
|
n.params["exp_p_max_m"] * n.params["cav_level_max"] |
586
|
|
|
+ n.params["exp_p_max_b"] |
587
|
|
|
) |
588
|
|
|
* self.exp_st[n, t] |
589
|
|
|
) |
590
|
|
|
|
591
|
|
|
self.exp_st_p_max_constr = Constraint( |
592
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_st_p_max_constr_rule |
593
|
|
|
) |
594
|
|
|
|
595
|
|
|
# (16) Expansion: Heat flow in |
596
|
|
|
def exp_q_in_constr_rule(block, n, t): |
597
|
|
|
return ( |
598
|
|
|
self.exp_q_in_sum[n, t] |
599
|
|
|
== n.params["exp_q_in_m"] * self.exp_p[n, t] |
600
|
|
|
+ n.params["exp_q_in_b"] * self.exp_st[n, t] |
601
|
|
|
) |
602
|
|
|
|
603
|
|
|
self.exp_q_in_constr = Constraint( |
604
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_constr_rule |
605
|
|
|
) |
606
|
|
|
|
607
|
|
|
# (17) Expansion: Fuel allocation |
608
|
|
|
def exp_q_fuel_constr_rule(block, n, t): |
609
|
|
|
expr = 0 |
610
|
|
|
expr += -self.exp_q_fuel_in[n, t] |
611
|
|
|
expr += m.flow[list(n.fuel_input.keys())[0], n, t] |
612
|
|
|
return expr == 0 |
613
|
|
|
|
614
|
|
|
self.exp_q_fuel_constr = Constraint( |
615
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_fuel_constr_rule |
616
|
|
|
) |
617
|
|
|
|
618
|
|
|
# (18) Expansion: Definition of single heat flows |
619
|
|
|
def exp_q_in_sum_constr_rule(block, n, t): |
620
|
|
|
return ( |
621
|
|
|
self.exp_q_in_sum[n, t] |
622
|
|
|
== self.exp_q_fuel_in[n, t] |
623
|
|
|
+ self.tes_e_out[n, t] |
624
|
|
|
+ self.exp_q_add_in[n, t] |
625
|
|
|
) |
626
|
|
|
|
627
|
|
|
self.exp_q_in_sum_constr = Constraint( |
628
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_sum_constr_rule |
629
|
|
|
) |
630
|
|
|
|
631
|
|
|
# (19) Expansion: Heat flow in ratio |
632
|
|
|
def exp_q_in_shr_constr_rule(block, n, t): |
633
|
|
|
return n.params["exp_q_tes_share"] * self.exp_q_fuel_in[n, t] == ( |
634
|
|
|
1 - n.params["exp_q_tes_share"] |
635
|
|
|
) * (self.exp_q_add_in[n, t] + self.tes_e_out[n, t]) |
636
|
|
|
|
637
|
|
|
self.exp_q_in_shr_constr = Constraint( |
638
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=exp_q_in_shr_constr_rule |
639
|
|
|
) |
640
|
|
|
|
641
|
|
|
# (20) Cavern: Energy inflow |
642
|
|
|
def cav_e_in_constr_rule(block, n, t): |
643
|
|
|
return ( |
644
|
|
|
self.cav_e_in[n, t] |
645
|
|
|
== n.params["cav_e_in_m"] * self.cmp_p[n, t] |
646
|
|
|
+ n.params["cav_e_in_b"] |
647
|
|
|
) |
648
|
|
|
|
649
|
|
|
self.cav_e_in_constr = Constraint( |
650
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cav_e_in_constr_rule |
651
|
|
|
) |
652
|
|
|
|
653
|
|
|
# (21) Cavern: Energy outflow |
654
|
|
|
def cav_e_out_constr_rule(block, n, t): |
655
|
|
|
return ( |
656
|
|
|
self.cav_e_out[n, t] |
657
|
|
|
== n.params["cav_e_out_m"] * self.exp_p[n, t] |
658
|
|
|
+ n.params["cav_e_out_b"] |
659
|
|
|
) |
660
|
|
|
|
661
|
|
|
self.cav_e_out_constr = Constraint( |
662
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cav_e_out_constr_rule |
663
|
|
|
) |
664
|
|
|
|
665
|
|
|
# (22-23) Cavern: Storage balance |
666
|
|
|
def cav_eta_constr_rule(block, n, t): |
667
|
|
|
if t != 0: |
668
|
|
|
return n.params["cav_eta_temp"] * self.cav_level[ |
669
|
|
|
n, t |
670
|
|
|
] == self.cav_level[n, t - 1] + m.timeincrement[t] * ( |
671
|
|
|
self.cav_e_in[n, t] - self.cav_e_out[n, t] |
672
|
|
|
) |
673
|
|
|
else: |
674
|
|
|
return n.params["cav_eta_temp"] * self.cav_level[ |
675
|
|
|
n, t |
676
|
|
|
] == m.timeincrement[t] * ( |
677
|
|
|
self.cav_e_in[n, t] - self.cav_e_out[n, t] |
678
|
|
|
) |
679
|
|
|
|
680
|
|
|
self.cav_eta_constr = Constraint( |
681
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cav_eta_constr_rule |
682
|
|
|
) |
683
|
|
|
|
684
|
|
|
# (24) Cavern: Upper bound |
685
|
|
|
def cav_ub_constr_rule(block, n, t): |
686
|
|
|
return self.cav_level[n, t] <= n.params["cav_level_max"] |
687
|
|
|
|
688
|
|
|
self.cav_ub_constr = Constraint( |
689
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=cav_ub_constr_rule |
690
|
|
|
) |
691
|
|
|
|
692
|
|
|
# (25-26) TES: Storage balance |
693
|
|
|
def tes_eta_constr_rule(block, n, t): |
694
|
|
|
if t != 0: |
695
|
|
|
return self.tes_level[n, t] == self.tes_level[ |
696
|
|
|
n, t - 1 |
697
|
|
|
] + m.timeincrement[t] * ( |
698
|
|
|
self.tes_e_in[n, t] - self.tes_e_out[n, t] |
699
|
|
|
) |
700
|
|
|
else: |
701
|
|
|
return self.tes_level[n, t] == m.timeincrement[t] * ( |
702
|
|
|
self.tes_e_in[n, t] - self.tes_e_out[n, t] |
703
|
|
|
) |
704
|
|
|
|
705
|
|
|
self.tes_eta_constr = Constraint( |
706
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=tes_eta_constr_rule |
707
|
|
|
) |
708
|
|
|
|
709
|
|
|
# (27) TES: Upper bound |
710
|
|
|
def tes_ub_constr_rule(block, n, t): |
711
|
|
|
return self.tes_level[n, t] <= n.params["tes_level_max"] |
712
|
|
|
|
713
|
|
|
self.tes_ub_constr = Constraint( |
714
|
|
|
self.GENERICCAES, m.TIMESTEPS, rule=tes_ub_constr_rule |
715
|
|
|
) |
716
|
|
|
|