Passed
Pull Request — dev (#1231)
by Patrik
01:53
created

GenericCHP._calculate_alphas()   B

Complexity

Conditions 4

Size

Total Lines 68
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 48
dl 0
loc 68
rs 8.7018
c 0
b 0
f 0
cc 4
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# -*- coding: utf-8 -
2
3
"""
4
GenericCHP and associated individual constraints (blocks) and groupings.
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: FranziPl
11
SPDX-FileCopyrightText: jnnr
12
SPDX-FileCopyrightText: Stephan Günther
13
SPDX-FileCopyrightText: FabianTU
14
SPDX-FileCopyrightText: Johannes Röder
15
SPDX-FileCopyrightText: Johannes Kochems
16
17
SPDX-License-Identifier: MIT
18
19
"""
20
21
import numpy as np
22
from oemof.network import Node
23
from pyomo.core.base.block import ScalarBlock
24
from pyomo.environ import Binary
25
from pyomo.environ import Constraint
26
from pyomo.environ import NonNegativeReals
27
from pyomo.environ import Set
28
from pyomo.environ import Var
29
30
from oemof.solph._plumbing import sequence
31
32
33
class GenericCHP(Node):
34
    r"""
35
    Component `GenericCHP` to model combined heat and power plants.
36
37
    Can be used to model (combined cycle) extraction or back-pressure turbines
38
    and used a mixed-integer linear formulation. Thus, it induces more
39
    computational effort than the `ExtractionTurbineCHP` for the
40
    benefit of higher accuracy.
41
42
    The full set of equations is described in:
43
    Mollenhauer, E., Christidis, A. & Tsatsaronis, G.
44
    Evaluation of an energy- and exergy-based generic modeling
45
    approach of combined heat and power plants
46
    Int J Energy Environ Eng (2016) 7: 167.
47
    https://doi.org/10.1007/s40095-016-0204-6
48
49
    For a general understanding of (MI)LP CHP representation, see:
50
    Fabricio I. Salgado, P.
51
    Short - Term Operation Planning on Cogeneration Systems: A Survey
52
    Electric Power Systems Research (2007)
53
    Electric Power Systems Research
54
    Volume 78, Issue 5, May 2008, Pages 835-848
55
    https://doi.org/10.1016/j.epsr.2007.06.001
56
57
    Note
58
    ----
59
    * An adaption for the flow parameter `H_L_FG_share_max` has been made to
60
      set the flue gas losses at maximum heat extraction `H_L_FG_max` as share
61
      of the fuel flow `H_F` e.g. for combined cycle extraction turbines.
62
    * The flow parameter `H_L_FG_share_min` can be used to set the flue gas
63
      losses at minimum heat extraction `H_L_FG_min` as share of
64
      the fuel flow `H_F` e.g. for motoric CHPs.
65
    * The boolean component parameter `back_pressure` can be set to model
66
      back-pressure characteristics.
67
68
    Also have a look at the examples on how to use it.
69
70
    Parameters
71
    ----------
72
    fuel_input : dict
73
        Dictionary with key-value-pair of `oemof.solph.Bus` and
74
        `oemof.solph.Flow` objects for the fuel input.
75
    electrical_output : dict
76
        Dictionary with key-value-pair of `oemof.solph.Bus` and
77
        `oemof.solph.Flow` objects for the electrical output.
78
        Related parameters like `P_max_woDH` are passed as
79
        attributes of the `oemof.Flow` object.
80
    heat_output : dict
81
        Dictionary with key-value-pair of `oemof.solph.Bus`
82
        and `oemof.solph.Flow` objects for the heat output.
83
        Related parameters like `Q_CW_min` are passed as
84
        attributes of the `oemof.Flow` object.
85
    beta : list of numerical values
86
        beta values in same dimension as all other parameters (length of
87
        optimization period).
88
    back_pressure : boolean
89
        Flag to use back-pressure characteristics. Set to `True` and
90
        `Q_CW_min` to zero for back-pressure turbines. See paper above for more
91
        information.
92
93
    Note
94
    ----
95
    The following sets, variables, constraints and objective parts are created
96
     * :py:class:`~oemof.solph.components._generic_chp.GenericCHPBlock`
97
98
    Examples
99
    --------
100
    >>> from oemof import solph
101
    >>> bel = solph.buses.Bus(label='electricityBus')
102
    >>> bth = solph.buses.Bus(label='heatBus')
103
    >>> bgas = solph.buses.Bus(label='commodityBus')
104
    >>> ccet = solph.components.GenericCHP(
105
    ...    label='combined_cycle_extraction_turbine',
106
    ...    fuel_input={bgas: solph.flows.Flow(
107
    ...        custom_properties={"H_L_FG_share_max": [0.183]})},
108
    ...    electrical_output={bel: solph.flows.Flow(
109
    ...        custom_properties={
110
    ...            "P_max_woDH": [155.946],
111
    ...            "P_min_woDH": [68.787],
112
    ...            "Eta_el_max_woDH": [0.525],
113
    ...            "Eta_el_min_woDH": [0.444],
114
    ...        })},
115
    ...    heat_output={bth: solph.flows.Flow(
116
    ...        custom_properties={"Q_CW_min": [10.552]})},
117
    ...    beta=[0.122], back_pressure=False)
118
    >>> type(ccet)
119
    <class 'oemof.solph.components._generic_chp.GenericCHP'>
120
    """
121
122
    def __init__(
123
        self,
124
        fuel_input,
125
        electrical_output,
126
        heat_output,
127
        beta,
128
        back_pressure,
129
        parent_node=None,
130
        label=None,
131
        custom_properties=None,
132
    ):
133
        if custom_properties is None:
134
            custom_properties = {}
135
        super().__init__(
136
            label, parent_node=parent_node, custom_properties=custom_properties
137
        )
138
139
        self.fuel_input = fuel_input
140
        self.electrical_output = electrical_output
141
        self.heat_output = heat_output
142
        self.beta = sequence(beta)
143
        self.back_pressure = back_pressure
144
        self._alphas = None
145
146
        # map specific flows to standard API
147
        fuel_bus = list(self.fuel_input.keys())[0]
148
        fuel_flow = list(self.fuel_input.values())[0]
149
        fuel_bus.outputs.update({self: fuel_flow})
150
151
        self.outputs.update(electrical_output)
152
        self.outputs.update(heat_output)
153
154
    def _calculate_alphas(self):
155
        """
156
        Calculate alpha coefficients.
157
158
        A system of linear equations is created from passed capacities and
159
        efficiencies and solved to calculate both coefficients.
160
        """
161
        alphas = [[], []]
162
163
        eb = list(self.electrical_output.keys())[0]
164
165
        attrs = [
166
            self.electrical_output[eb].custom_properties["P_min_woDH"],
167
            self.electrical_output[eb].custom_properties["Eta_el_min_woDH"],
168
            self.electrical_output[eb].custom_properties["P_max_woDH"],
169
            self.electrical_output[eb].custom_properties["Eta_el_max_woDH"],
170
        ]
171
172
        length = [len(a) for a in attrs if not isinstance(a, (int, float))]
173
        max_length = max(length)
174
175
        if all(len(a) == max_length for a in attrs):
176
            if max_length == 0:
177
                max_length += 1  # increment dimension for scalars from 0 to 1
178
            for i in range(0, max_length):
179
                A = np.array(
180
                    [
181
                        [
182
                            1,
183
                            self.electrical_output[eb].custom_properties[
184
                                "P_min_woDH"
185
                            ][i],
186
                        ],
187
                        [
188
                            1,
189
                            self.electrical_output[eb].custom_properties[
190
                                "P_max_woDH"
191
                            ][i],
192
                        ],
193
                    ]
194
                )
195
                b = np.array(
196
                    [
197
                        self.electrical_output[eb].custom_properties[
198
                            "P_min_woDH"
199
                        ][i]
200
                        / self.electrical_output[eb].custom_properties[
201
                            "Eta_el_min_woDH"
202
                        ][i],
203
                        self.electrical_output[eb].custom_properties[
204
                            "P_max_woDH"
205
                        ][i]
206
                        / self.electrical_output[eb].custom_properties[
207
                            "Eta_el_max_woDH"
208
                        ][i],
209
                    ]
210
                )
211
                x = np.linalg.solve(A, b)
212
                alphas[0].append(x[0])
213
                alphas[1].append(x[1])
214
        else:
215
            error_message = (
216
                "Attributes to calculate alphas "
217
                + "must be of same dimension."
218
            )
219
            raise ValueError(error_message)
220
221
        self._alphas = alphas
222
223
    @property
224
    def alphas(self):
225
        """Compute or return the _alphas attribute."""
226
        if self._alphas is None:
227
            self._calculate_alphas()
228
        return self._alphas
229
230
    def constraint_group(self):
231
        return GenericCHPBlock
232
233
234
class GenericCHPBlock(ScalarBlock):
235
    r"""
236
    Block for the relation of the :math:`n` nodes with
237
    type class:`.GenericCHP`.
238
239
    **The following constraints are created:**
240
241
    .. _GenericCHP-equations1-10:
242
243
    .. math::
244
        &
245
        (1)\qquad \dot{H}_F(t) = fuel\ input \\
246
        &
247
        (2)\qquad \dot{Q}(t) = heat\ output \\
248
        &
249
        (3)\qquad P_{el}(t) = power\ output\\
250
        &
251
        (4)\qquad \dot{H}_F(t) = \alpha_0(t) \cdot Y(t) + \alpha_1(t) \cdot
252
        P_{el,woDH}(t)\\
253
        &
254
        (5)\qquad \dot{H}_F(t) = \alpha_0(t) \cdot Y(t) + \alpha_1(t) \cdot
255
        ( P_{el}(t) + \beta \cdot \dot{Q}(t) )\\
256
        &
257
        (6)\qquad \dot{H}_F(t) \leq Y(t) \cdot
258
        \frac{P_{el, max, woDH}(t)}{\eta_{el,max,woDH}(t)}\\
259
        &
260
        (7)\qquad \dot{H}_F(t) \geq Y(t) \cdot
261
        \frac{P_{el, min, woDH}(t)}{\eta_{el,min,woDH}(t)}\\
262
        &
263
        (8)\qquad \dot{H}_{L,FG,max}(t) = \dot{H}_F(t) \cdot
264
        \dot{H}_{L,FG,sharemax}(t)\\
265
        &
266
        (9)\qquad \dot{H}_{L,FG,min}(t) = \dot{H}_F(t) \cdot
267
        \dot{H}_{L,FG,sharemin}(t)\\
268
        &
269
        (10)\qquad P_{el}(t) + \dot{Q}(t) + \dot{H}_{L,FG,max}(t) +
270
        \dot{Q}_{CW, min}(t) \cdot Y(t) = / \leq \dot{H}_F(t)\\
271
272
    where :math:`= / \leq` depends on the CHP being back pressure or not.
273
274
    The coefficients :math:`\alpha_0` and :math:`\alpha_1`
275
    can be determined given the efficiencies maximal/minimal load:
276
277
    .. math::
278
        &
279
        \eta_{el,max,woDH}(t) = \frac{P_{el,max,woDH}(t)}{\alpha_0(t)
280
        \cdot Y(t) + \alpha_1(t) \cdot P_{el,max,woDH}(t)}\\
281
        &
282
        \eta_{el,min,woDH}(t) = \frac{P_{el,min,woDH}(t)}{\alpha_0(t)
283
        \cdot Y(t) + \alpha_1(t) \cdot P_{el,min,woDH}(t)}\\
284
285
286
    **For the attribute** :math:`\dot{H}_{L,FG,min}` **being not None**,
287
    e.g. for a motoric CHP, **the following is created:**
288
289
        **Constraint:**
290
291
    .. _GenericCHP-equations11:
292
293
    .. math::
294
        &
295
        (11)\qquad P_{el}(t) + \dot{Q}(t) + \dot{H}_{L,FG,min}(t) +
296
        \dot{Q}_{CW, min}(t) \cdot Y(t) \geq \dot{H}_F(t)\\[10pt]
297
298
    The symbols used are defined as follows (with Variables (V) and Parameters (P)):
299
300
    =============================== ======================= ==== =============================================
301
    math. symbol                    attribute               type explanation
302
    =============================== ======================= ==== =============================================
303
    :math:`\dot{H}_{F}`             `H_F[n,t]`              V    input of enthalpy through fuel input
304
    :math:`P_{el}`                  `P[n,t]`                V    provided electric power
305
    :math:`P_{el,woDH}`             `P_woDH[n,t]`           V    electric power without district heating
306
    :math:`P_{el,min,woDH}`         `P_min_woDH[n,t]`       P    min. electric power without district heating
307
    :math:`P_{el,max,woDH}`         `P_max_woDH[n,t]`       P    max. electric power without district heating
308
    :math:`\dot{Q}`                 `Q[n,t]`                V    provided heat
309
    :math:`\dot{Q}_{CW, min}`       `Q_CW_min[n,t]`         P    minimal therm. condenser load to cooling water
310
    :math:`\dot{H}_{L,FG,min}`      `H_L_FG_min[n,t]`       V    flue gas enthalpy loss at min heat extraction
311
    :math:`\dot{H}_{L,FG,max}`      `H_L_FG_max[n,t]`       V    flue gas enthalpy loss at max heat extraction
312
    :math:`\dot{H}_{L,FG,sharemin}` `H_L_FG_share_min[n,t]` P    share of flue gas loss at min heat extraction
313
    :math:`\dot{H}_{L,FG,sharemax}` `H_L_FG_share_max[n,t]` P    share of flue gas loss at max heat extraction
314
    :math:`Y`                       `Y[n,t]`                V    status variable on/off
315
    :math:`\alpha_0`                `n.alphas[0][n,t]`      P    coefficient describing efficiency
316
    :math:`\alpha_1`                `n.alphas[1][n,t]`      P    coefficient describing efficiency
317
    :math:`\beta`                   `beta[n,t]`             P    power loss index
318
    :math:`\eta_{el,min,woDH}`      `Eta_el_min_woDH[n,t]`  P    el. eff. at min. fuel flow w/o distr. heating
319
    :math:`\eta_{el,max,woDH}`      `Eta_el_max_woDH[n,t]`  P    el. eff. at max. fuel flow w/o distr. heating
320
    =============================== ======================= ==== =============================================
321
322
    """  # noqa: E501
323
324
    CONSTRAINT_GROUP = True
325
326
    def __init__(self, *args, **kwargs):
327
        super().__init__(*args, **kwargs)
328
329
    def _create(self, group=None):
330
        """
331
        Create constraints for GenericCHPBlock.
332
333
        Parameters
334
        ----------
335
        group : list
336
            List containing `GenericCHP` objects.
337
            e.g. groups=[ghcp1, gchp2,..]
338
        """
339
        m = self.parent_block()
340
341
        if group is None:
342
            return None
343
344
        self.GENERICCHPS = Set(initialize=[n for n in group])
345
346
        # variables
347
        self.H_F = Var(self.GENERICCHPS, m.TIMESTEPS, within=NonNegativeReals)
348
        self.H_L_FG_max = Var(
349
            self.GENERICCHPS, m.TIMESTEPS, within=NonNegativeReals
350
        )
351
        self.H_L_FG_min = Var(
352
            self.GENERICCHPS, m.TIMESTEPS, within=NonNegativeReals
353
        )
354
        self.P_woDH = Var(
355
            self.GENERICCHPS, m.TIMESTEPS, within=NonNegativeReals
356
        )
357
        self.P = Var(self.GENERICCHPS, m.TIMESTEPS, within=NonNegativeReals)
358
        self.Q = Var(self.GENERICCHPS, m.TIMESTEPS, within=NonNegativeReals)
359
        self.Y = Var(self.GENERICCHPS, m.TIMESTEPS, within=Binary)
360
361
        # constraint rules
362
        def _H_flow_rule(block, n, t):
363
            """Link fuel consumption to component inflow."""
364
            expr = 0
365
            expr += self.H_F[n, t]
366
            expr += -m.flow[list(n.fuel_input.keys())[0], n, t]
367
            return expr == 0
368
369
        self.H_flow = Constraint(
370
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_flow_rule
371
        )
372
373
        def _Q_flow_rule(block, n, t):
374
            """Link heat flow to component outflow."""
375
            expr = 0
376
            expr += self.Q[n, t]
377
            expr += -m.flow[n, list(n.heat_output.keys())[0], t]
378
            return expr == 0
379
380
        self.Q_flow = Constraint(
381
            self.GENERICCHPS, m.TIMESTEPS, rule=_Q_flow_rule
382
        )
383
384
        def _P_flow_rule(block, n, t):
385
            """Link power flow to component outflow."""
386
            expr = 0
387
            expr += self.P[n, t]
388
            expr += -m.flow[n, list(n.electrical_output.keys())[0], t]
389
            return expr == 0
390
391
        self.P_flow = Constraint(
392
            self.GENERICCHPS, m.TIMESTEPS, rule=_P_flow_rule
393
        )
394
395
        def _H_F_1_rule(block, n, t):
396
            """Set P_woDH depending on H_F."""
397
            expr = 0
398
            expr += -self.H_F[n, t]
399
            expr += n.alphas[0][t] * self.Y[n, t]
400
            expr += n.alphas[1][t] * self.P_woDH[n, t]
401
            return expr == 0
402
403
        self.H_F_1 = Constraint(
404
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_1_rule
405
        )
406
407
        def _H_F_2_rule(block, n, t):
408
            """Determine relation between H_F, P and Q."""
409
            expr = 0
410
            expr += -self.H_F[n, t]
411
            expr += n.alphas[0][t] * self.Y[n, t]
412
            expr += n.alphas[1][t] * (self.P[n, t] + n.beta[t] * self.Q[n, t])
413
            return expr == 0
414
415
        self.H_F_2 = Constraint(
416
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_2_rule
417
        )
418
419 View Code Duplication
        def _H_F_3_rule(block, n, t):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
420
            """Set upper value of operating range via H_F."""
421
            expr = 0
422
            expr += self.H_F[n, t]
423
            expr += -self.Y[n, t] * (
424
                list(n.electrical_output.values())[0].custom_properties[
425
                    "P_max_woDH"
426
                ][t]
427
                / list(n.electrical_output.values())[0].custom_properties[
428
                    "Eta_el_max_woDH"
429
                ][t]
430
            )
431
            return expr <= 0
432
433
        self.H_F_3 = Constraint(
434
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_3_rule
435
        )
436
437 View Code Duplication
        def _H_F_4_rule(block, n, t):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
438
            """Set lower value of operating range via H_F."""
439
            expr = 0
440
            expr += self.H_F[n, t]
441
            expr += -self.Y[n, t] * (
442
                list(n.electrical_output.values())[0].custom_properties[
443
                    "P_min_woDH"
444
                ][t]
445
                / list(n.electrical_output.values())[0].custom_properties[
446
                    "Eta_el_min_woDH"
447
                ][t]
448
            )
449
            return expr >= 0
450
451
        self.H_F_4 = Constraint(
452
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_F_4_rule
453
        )
454
455
        def _H_L_FG_max_rule(block, n, t):
456
            """Set max. flue gas loss as share fuel flow share."""
457
            expr = 0
458
            expr += -self.H_L_FG_max[n, t]
459
            expr += (
460
                self.H_F[n, t]
461
                * list(n.fuel_input.values())[0].custom_properties[
462
                    "H_L_FG_share_max"
463
                ][t]
464
            )
465
            return expr == 0
466
467
        self.H_L_FG_max_def = Constraint(
468
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_L_FG_max_rule
469
        )
470
471
        def _Q_max_res_rule(block, n, t):
472
            """Set maximum Q depending on fuel and electrical flow."""
473
            expr = 0
474
            expr += self.P[n, t] + self.Q[n, t] + self.H_L_FG_max[n, t]
475
            expr += (
476
                list(n.heat_output.values())[0].custom_properties["Q_CW_min"][
477
                    t
478
                ]
479
                * self.Y[n, t]
480
            )
481
            expr += -self.H_F[n, t]
482
            # back-pressure characteristics or one-segment model
483
            if n.back_pressure is True:
484
                return expr == 0
485
            else:
486
                return expr <= 0
487
488
        self.Q_max_res = Constraint(
489
            self.GENERICCHPS, m.TIMESTEPS, rule=_Q_max_res_rule
490
        )
491
492
        def _H_L_FG_min_rule(block, n, t):
493
            """Set min. flue gas loss as fuel flow share."""
494
            # minimum flue gas losses e.g. for motoric CHPs
495
            if getattr(
496
                list(n.fuel_input.values())[0], "H_L_FG_share_min", None
497
            ):
498
                expr = 0
499
                expr += -self.H_L_FG_min[n, t]
500
                expr += (
501
                    self.H_F[n, t]
502
                    * list(n.fuel_input.values())[0].custom_properties[
503
                        "H_L_FG_share_min"
504
                    ][t]
505
                )
506
                return expr == 0
507
            else:
508
                return Constraint.Skip
509
510
        self.H_L_FG_min_def = Constraint(
511
            self.GENERICCHPS, m.TIMESTEPS, rule=_H_L_FG_min_rule
512
        )
513
514
        def _Q_min_res_rule(block, n, t):
515
            """Set minimum Q depending on fuel and eletrical flow."""
516
            # minimum restriction for heat flows e.g. for motoric CHPs
517
            if getattr(
518
                list(n.fuel_input.values())[0], "H_L_FG_share_min", None
519
            ):
520
                expr = 0
521
                expr += self.P[n, t] + self.Q[n, t] + self.H_L_FG_min[n, t]
522
                expr += (
523
                    list(n.heat_output.values())[0].Q_CW_min[t] * self.Y[n, t]
524
                )
525
                expr += -self.H_F[n, t]
526
                return expr >= 0
527
            else:
528
                return Constraint.Skip
529
530
        self.Q_min_res = Constraint(
531
            self.GENERICCHPS, m.TIMESTEPS, rule=_Q_min_res_rule
532
        )
533
534
    def _objective_expression(self):
535
        r"""Objective expression for generic CHPs with no investment.
536
537
        Note: This adds nothing as variable costs are already
538
        added in the Block :class:`SimpleFlowBlock`.
539
        """
540
        if not hasattr(self, "GENERICCHPS"):
541
            return 0
542
543
        return 0
544