solph.flows._flow.Flow.__init__()   F
last analyzed

Complexity

Conditions 35

Size

Total Lines 207
Code Lines 126

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 126
dl 0
loc 207
rs 0
c 0
b 0
f 0
cc 35
nop 19

How to fix   Long Method    Complexity    Many Parameters   

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:

Complexity

Complex classes like solph.flows._flow.Flow.__init__() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
# -*- coding: utf-8 -*-
2
3
"""
4
solph version of oemof.network.Edge
5
6
SPDX-FileCopyrightText: Uwe Krien <[email protected]>
7
SPDX-FileCopyrightText: Simon Hilpert
8
SPDX-FileCopyrightText: Cord Kaldemeyer
9
SPDX-FileCopyrightText: Stephan Günther
10
SPDX-FileCopyrightText: Birgit Schachler
11
SPDX-FileCopyrightText: jnnr
12
SPDX-FileCopyrightText: jmloenneberga
13
SPDX-FileCopyrightText: Pierre-François Duc
14
SPDX-FileCopyrightText: Saeed Sayadi
15
SPDX-FileCopyrightText: Johannes Kochems
16
SPDX-FileCopyrightText: Lennart Schürmann
17
18
SPDX-License-Identifier: MIT
19
20
"""
21
22
import math
23
import numbers
24
from collections.abc import Iterable
25
from warnings import warn
26
27
import numpy as np
28
from oemof.network import Edge
29
from oemof.tools import debugging
30
31
from oemof.solph._options import Investment
32
from oemof.solph._plumbing import sequence
33
34
35
class Flow(Edge):
36
    r"""Defines a flow between two nodes.
37
38
    Keyword arguments are used to set the attributes of this flow. Parameters
39
    which are handled specially are noted below.
40
    For the case where a parameter can be either a scalar or an iterable, a
41
    scalar value will be converted to a sequence containing the scalar value at
42
    every index. This sequence is then stored under the parameter's key.
43
44
    Parameters
45
    ----------
46
    nominal_capacity : numeric, :math:`P_{nom}` or
47
            :class:`Investment <oemof.solph.options.Investment>`
48
        The nominal calacity of the flow, either fixed or as an investement
49
        optimisation. If this value is set, the corresponding optimization
50
        variable of the flow object will be bounded by this value
51
        multiplied by min(lower bound)/max(upper bound).
52
    variable_costs : numeric (iterable or scalar), default: 0, :math:`c`
53
        The costs associated with one unit of the flow per hour. The
54
        costs for each timestep (:math:`P_t \cdot c \cdot \delta(t)`)
55
        will be added to the objective expression of the optimization problem.
56
    max : numeric (iterable or scalar), default: 1, :math:`f_{max}`
57
        Normed maximum value of the flow. The flow absolute maximum will be
58
        calculated by multiplying :attr:`nominal_capacity` with :attr:`max`.
59
    min : numeric (iterable or scalar), default: 0, :math:`f_{min}`
60
        Normed minimum value of the flow (see :attr:`max`).
61
    fix : numeric (iterable or scalar), :math:`f_{fix}`
62
        Normed fixed value for the flow variable. Will be multiplied with the
63
        :attr:`nominal_capacity` to get the absolute value.
64
    positive_gradient_limit : numeric (iterable, scalar or None)
65
        the normed *upper bound* on the positive difference
66
        (`flow[t-1] < flow[t]`) of two consecutive flow values.
67
    negative_gradient_limit : numeric (iterable, scalar or None)
68
        the normed *upper bound* on the negative difference
69
        (`flow[t-1] > flow[t]`) of two consecutive flow values.
70
    full_load_time_max : numeric, :math:`t_{full\_load,max}`
71
        Maximum energy transported by the flow expressed as the time (in
72
        hours) that the flow would have to run at nominal capacity
73
        (`nominal_capacity`).
74
    full_load_time_min : numeric, :math:`t_{full\_load,min}`
75
        Minimum energy transported by the flow expressed as the time (in
76
        hours) that the flow would have to run at nominal capacity
77
        (`nominal_capacity`).
78
    integer : boolean
79
        Set True to bound the flow values to integers.
80
    nonconvex : :class:`NonConvex <oemof.solph.options.NonConvex>`
81
        If a nonconvex flow object is added here, the flow constraints will
82
        be altered significantly as the mathematical model for the flow
83
        will be different, i.e. constraint etc. from
84
        :class:`~oemof.solph.flows._non_convex_flow_block.NonConvexFlowBlock`
85
        will be used instead of
86
        :class:`~oemof.solph.flows._simple_flow_block.SimpleFlowBlock`.
87
    fixed_costs : numeric (iterable or scalar), :math:`c_{fixed}`
88
        The fixed costs associated with a flow.
89
        Note: These are only applicable for a multi-period model
90
        and given on a yearly basis.
91
    lifetime : int, :math:`l`
92
        The lifetime of a flow (usually given in years);
93
        once it reaches its lifetime (considering also
94
        an initial age), the flow is forced to 0.
95
        Note: Only applicable for a multi-period model.
96
    age : int, :math:`a`
97
        The initial age of a flow (usually given in years);
98
        once it reaches its lifetime (considering also
99
        an initial age), the flow is forced to 0.
100
        Note: Only applicable for a multi-period model.
101
102
    Notes
103
    -----
104
    See :py:class:`~oemof.solph.flows._simple_flow.SimpleFlowBlock`
105
    for the variables, constraints and objective parts, that are created for
106
    a Flow object.
107
108
    Examples
109
    --------
110
    Creating a fixed flow object:
111
112
    >>> f = Flow(nominal_capacity=2, fix=[10, 4, 4], variable_costs=5)
113
    >>> f.variable_costs[2]
114
    5
115
    >>> f.fix[2]
116
    np.int64(4)
117
118
    Creating a flow object with time-depended lower and upper bounds:
119
120
    >>> f1 = Flow(min=[0.2, 0.3], max=0.99, nominal_capacity=100)
121
    >>> f1.max[1]
122
    0.99
123
    """  # noqa: E501
124
125
    def __init__(
126
        self,
127
        nominal_capacity=None,
128
        # --- BEGIN: To be removed for versions >= v0.7 ---
129
        nominal_value=None,
130
        # --- END ---
131
        variable_costs=0,
132
        min=0,
133
        max=1,
134
        fix=None,
135
        positive_gradient_limit=None,
136
        negative_gradient_limit=None,
137
        full_load_time_max=None,
138
        full_load_time_min=None,
139
        integer=False,
140
        # --- BEGIN: To be removed for versions >= v0.7 ---
141
        bidirectional=False,
142
        # --- END
143
        nonconvex=None,
144
        lifetime=None,
145
        age=None,
146
        fixed_costs=None,
147
        custom_attributes=None,  # To be removed for versions >= v0.7
148
        custom_properties=None,
149
    ):
150
        # TODO: Check if we can inherit from pyomo.core.base.var _VarData
151
        # then we need to create the var object with
152
        # pyomo.core.base.IndexedVarWithDomain before any SimpleFlowBlock
153
        # is created. E.g. create the variable in the energy system and
154
        # populate with information afterwards when creating objects.
155
156
        # --- BEGIN: The following code can be removed for versions >= v0.7 ---
157
        if nominal_value is not None:
158
            msg = (
159
                "For backward compatibility,"
160
                + " the option nominal_value overwrites the option"
161
                + " nominal_capacity."
162
                + " Both options cannot be set at the same time."
163
            )
164
            if nominal_capacity is not None:
165
                raise AttributeError(msg)
166
            else:
167
                warn(msg, FutureWarning)
168
            nominal_capacity = nominal_value
169
170
        if custom_attributes is not None:
171
            msg = (
172
                "For backward compatibility,"
173
                + " the option custom_attributes overwrites the option"
174
                + " custom_properties."
175
                + " Both options cannot be set at the same time."
176
            )
177
            if custom_properties is not None:
178
                raise AttributeError(msg)
179
            else:
180
                warn(msg, FutureWarning)
181
            custom_properties = custom_attributes
182
        # --- END ---
183
184
        super().__init__(custom_properties=custom_properties)
185
186
        # --- BEGIN: The following code can be removed for versions >= v0.7 ---
187
        if custom_attributes is not None:
188
            for attribute, value in custom_attributes.items():
189
                setattr(self, attribute, value)
190
        # --- END ---
191
192
        self.nominal_capacity = None
193
        self.investment = None
194
195
        infinite_error_msg = (
196
            "{} must be a finite value. Passing an infinite "
197
            "value is not allowed."
198
        )
199
        if isinstance(nominal_capacity, numbers.Real):
200
            if not math.isfinite(nominal_capacity):
201
                raise ValueError(infinite_error_msg.format("nominal_capacity"))
202
            self.nominal_capacity = nominal_capacity
203
        elif isinstance(nominal_capacity, Investment):
204
            self.investment = nominal_capacity
205
206
        if fixed_costs is not None:
207
            msg = (
208
                "Be aware that the fixed costs attribute is only\n"
209
                "meant to be used for multi-period models to depict "
210
                "fixed costs that occur on a yearly basis.\n"
211
                "If you wish to set up a multi-period model, explicitly "
212
                "set the `periods` attribute of your energy system.\n"
213
                "It has been decided to remove the `fixed_costs` "
214
                "attribute with v0.2 for regular uses.\n"
215
                "If you specify `fixed_costs` for a regular model, "
216
                "this will simply be silently ignored."
217
            )
218
            warn(msg, debugging.SuspiciousUsageWarning)
219
220
        self.fixed_costs = sequence(fixed_costs)
221
        self.variable_costs = sequence(variable_costs)
222
        self.positive_gradient_limit = sequence(positive_gradient_limit)
223
        self.negative_gradient_limit = sequence(negative_gradient_limit)
224
225
        self.full_load_time_max = full_load_time_max
226
        self.full_load_time_min = full_load_time_min
227
        self.integer = integer
228
        self.nonconvex = nonconvex
229
        # --- BEGIN: To be removed for versions >= v0.7 ---
230
        self.bidirectional = bidirectional
231
        # --- END
232
        self.lifetime = lifetime
233
        self.age = age
234
235
        # It is not allowed to define `min` or `max` if `fix` is defined.
236
        # HINT: This also allows `flow`s with `fix` to be bidirectional, if
237
        # negative values are used in `fix`, despite `min` and `max` having
238
        # the default values (0 and 1).
239
        # TODO: Is it intended to have bidirectional fixed flows?
240
        if fix is not None and (min != 0 or max != 1):
241
            msg = (
242
                "It is not allowed to define `min`/`max` if `fix` is defined."
243
            )
244
            raise AttributeError(msg)
245
246
        # --- BEGIN: The following code can be removed for versions >= v0.7 ---
247
        if self.bidirectional:
248
            msg = "The `bidirectional` keyword is deprecated and will be "
249
            "removed in a future version, as it sets the value of `min` to -1 "
250
            "without the users explicit intent. It is recommended to set a "
251
            "negative value for `min` explicitly instead."
252
            warn(msg, FutureWarning)
253
            if min == 0:
254
                min = -1
255
        # --- END
256
257
        if sequence(min).min() < 0:
258
            msg = (
259
                "Setting `min` to negative values allows for the flow to "
260
                "become bidirectional, which is an experimental feature."
261
            )
262
            warn(msg, debugging.ExperimentalFeatureWarning)
263
264
        self.fix = sequence(fix)
265
        self.max = sequence(max)
266
        self.min = sequence(min)
267
268
        need_nominal_capacity = [
269
            "fix",
270
            "full_load_time_max",
271
            "full_load_time_min",
272
            "min",
273
            "max",
274
        ]
275
        need_nominal_capacity_defaults = {
276
            "fix": None,
277
            "full_load_time_max": None,
278
            "full_load_time_min": None,
279
            # --- BEGIN: The following code can be removed for versions >= v0.7
280
            "min": -1 if self.bidirectional else 0,
281
            # --- END
282
            # "min": 0,
283
            "max": 1,
284
        }
285
        if self.investment is None and self.nominal_capacity is None:
286
            for attr in need_nominal_capacity:
287
                if isinstance(getattr(self, attr), Iterable):
288
                    the_attr = getattr(self, attr)[0]
289
                else:
290
                    the_attr = getattr(self, attr)
291
                if the_attr != need_nominal_capacity_defaults[attr]:
292
                    raise AttributeError(
293
                        f"If {attr} is set in a flow, "
294
                        "nominal_capacity must be set as well."
295
                    )
296
297
        if self.nominal_capacity is not None and not math.isfinite(
298
            self.max[0]
299
        ):
300
            raise ValueError(infinite_error_msg.format("max"))
301
302
        # Checking for impossible gradient combinations
303
        if self.nonconvex:
304
            if self.nonconvex.positive_gradient_limit[0] is not None and (
305
                self.positive_gradient_limit[0] is not None
306
                or self.negative_gradient_limit[0] is not None
307
            ):
308
                raise ValueError(
309
                    "You specified a positive gradient in your nonconvex "
310
                    "option. This cannot be combined with a positive or a "
311
                    "negative gradient for a standard flow!"
312
                )
313
314
            if self.nonconvex.negative_gradient_limit[0] is not None and (
315
                self.positive_gradient_limit[0] is not None
316
                or self.negative_gradient_limit[0] is not None
317
            ):
318
                raise ValueError(
319
                    "You specified a negative gradient in your nonconvex "
320
                    "option. This cannot be combined with a positive or a "
321
                    "negative gradient for a standard flow!"
322
                )
323
324
        if (
325
            self.investment
326
            and self.nonconvex
327
            and not np.isfinite(self.investment.maximum.max())
328
        ):
329
            raise AttributeError(
330
                "Investment into a non-convex flows needs a maximum "
331
                + "investment to be set."
332
            )
333