solph.flows._flow   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 288
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 33
eloc 116
dl 0
loc 288
rs 9.76
c 0
b 0
f 0

1 Method

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