GenericCAES.constraint_group()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 2
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
cc 1
nop 1
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