Passed
Push — dev ( a73941...7ed0fa )
by Patrik
01:36 queued 14s
created

generic_integral_limit()   B

Complexity

Conditions 6

Size

Total Lines 125
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 33
dl 0
loc 125
rs 8.1546
c 0
b 0
f 0
cc 6
nop 6

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
"""Constraints to limit total values that are dependent on several Flows.
4
5
SPDX-FileCopyrightText: Uwe Krien <[email protected]>
6
SPDX-FileCopyrightText: Simon Hilpert
7
SPDX-FileCopyrightText: Patrik Schönfeldt
8
SPDX-FileCopyrightText: Johannes Röder
9
SPDX-FileCopyrightText: Johannes Kochems
10
SPDX-FileCopyrightText: Johannes Giehl
11
12
SPDX-License-Identifier: MIT
13
14
"""
15
import warnings
16
17
from pyomo import environ as po
18
19
from oemof.solph._plumbing import sequence
20
21
22
def emission_limit(om, flows=None, limit=None):
23
    r"""
24
    Short handle for generic_integral_limit() with keyword="emission_factor".
25
    Can be used to impose an emissions budget limit in a multi-period model.
26
27
    Note
28
    ----
29
    Flow objects require an attribute "emission_factor"!
30
31
    """
32
    generic_integral_limit(
33
        om, keyword="emission_factor", flows=flows, upper_limit=limit
34
    )
35
36
37
def emission_limit_per_period(om, flows=None, limit=None):
38
    r"""
39
    Short handle for generic_periodical_integral_limit()
40
    with keyword="emission_factor". Only applicable for multi-period models.
41
    Puts a limit on each period's emissions.
42
43
    Note
44
    ----
45
    Flow objects required an attribute "emission_factor"!
46
47
    """
48
    generic_periodical_integral_limit(
49
        om, keyword="emission_factor", flows=flows, limit=limit
50
    )
51
52
53
def generic_integral_limit(
54
    om, keyword, flows=None, upper_limit=None, lower_limit=None, limit=None
55
):
56
    r"""Set a global limit for flows weighted by attribute named keyword.
57
    The attribute named keyword has to be added
58
    to every flow you want to take into account.
59
60
    Total value of keyword attributes after optimization can be retrieved
61
    calling the
62
    `om.oemof.solph._models.Model.integral_limit_${keyword}()`.
63
64
    Parameters
65
    ----------
66
    om : oemof.solph.Model
67
        Model to which constraints are added.
68
    flows : dict
69
        Dictionary holding the flows that should be considered in constraint.
70
        Keys are (source, target) objects of the Flow. If no dictionary is
71
        given all flows containing the keyword attribute will be
72
        used.
73
    keyword : string
74
        attribute to consider
75
    upper_limit : numeric
76
        Absolute upper limit of keyword attribute for the energy system.
77
    lower_limit : numeric
78
        Absolute lower limit of keyword attribute for the energy system.
79
80
    Note
81
    ----
82
    Flow objects require an attribute named like keyword!
83
84
85
    **Constraint:**
86
87
    .. math:: \sum_{i \in F_E} \sum_{t \in T} P_i(p, t) \cdot w_i(t)
88
               \cdot \tau(t) \leq UB
89
90
    .. math:: \sum_{i \in F_E} \sum_{t \in T} P_i(p, t) \cdot w_i(t)
91
               \cdot \tau(t) \geq LB
92
93
94
    With `F_I` being the set of flows considered for the integral limit and
95
    `T` being the set of time steps.
96
97
    The symbols used are defined as follows
98
    (with Variables (V) and Parameters (P)):
99
100
    ================= ==== ====================================================
101
    math. symbol      type explanation
102
    ================= ==== ====================================================
103
    :math:`P_n(p, t)` V    power flow :math:`n` at time index :math:`p, t`
104
    :math:`w_N(t)`    P    weight given to Flow named according to `keyword`
105
    :math:`\tau(t)`   P    width of time step :math:`t`
106
    :math:`UB`        P    global limit given by keyword `upper_limit`
107
    :math:`LB`        P    global limit given by keyword `lower_limit`
108
    ================= ==== ====================================================
109
110
    Examples
111
    --------
112
    >>> import pandas as pd
113
    >>> from oemof import solph
114
    >>> date_time_index = pd.date_range('1/1/2012', periods=6, freq='h')
115
    >>> energysystem = solph.EnergySystem(
116
    ...     timeindex=date_time_index,
117
    ...     infer_last_interval=False,
118
    ... )
119
    >>> bel = solph.buses.Bus(label='electricityBus')
120
    >>> flow1 = solph.flows.Flow(
121
    ...     nominal_value=100,
122
    ...     custom_attributes={"my_factor": 0.8},
123
    ... )
124
    >>> flow2 = solph.flows.Flow(nominal_value=50)
125
    >>> src1 = solph.components.Source(label='source1', outputs={bel: flow1})
126
    >>> src2 = solph.components.Source(label='source2', outputs={bel: flow2})
127
    >>> energysystem.add(bel, src1, src2)
128
    >>> model = solph.Model(energysystem)
129
    >>> flow_with_keyword = {(src1, bel): flow1, }
130
    >>> model = solph.constraints.generic_integral_limit(
131
    ...     model, "my_factor", flow_with_keyword, limit=777)
132
    """
133
    flows = _check_and_set_flows(om, flows, keyword)
134
    limit_name = "integral_limit_" + keyword
135
136
    if limit is not None:
137
        msg = (
138
            "The keyword argument 'limit' to generic_integral_limit has been"
139
            "renamed to 'upper_limit'. The transitional wrapper will be"
140
            "deleted in a future version."
141
        )
142
        warnings.warn(msg, FutureWarning)
143
        upper_limit = limit
144
145
    if upper_limit is None and lower_limit is None:
146
        raise ValueError(
147
            "At least one of upper_limit and lower_limit needs to be defined."
148
        )
149
150
    setattr(
151
        om,
152
        limit_name,
153
        po.Expression(
154
            expr=sum(
155
                om.flow[inflow, outflow, t]
156
                * om.timeincrement[t]
157
                * sequence(getattr(flows[inflow, outflow], keyword))[t]
158
                for (inflow, outflow) in flows
159
                for t in om.TIMESTEPS
160
            )
161
        ),
162
    )
163
164
    if upper_limit is not None:
165
        setattr(
166
            om,
167
            limit_name + "_upper_limit",
168
            po.Constraint(expr=(getattr(om, limit_name) <= upper_limit)),
169
        )
170
    if lower_limit is not None:
171
        setattr(
172
            om,
173
            limit_name + "_lower_limit",
174
            po.Constraint(expr=(getattr(om, limit_name) >= lower_limit)),
175
        )
176
177
    return om
178
179
180
def generic_periodical_integral_limit(om, keyword, flows=None, limit=None):
181
    r"""Set a global limit for flows for each period of a multi-period model
182
    which is weighted by attribute called keyword.
183
    The attribute named by keyword has to be added
184
    to every flow you want to take into account.
185
186
    Total value of keyword attributes after optimization can be retrieved
187
    calling the :attr:`om.oemof.solph.Model.integral_limit_${keyword}()`.
188
189
    Parameters
190
    ----------
191
    om : oemof.solph.Model
192
        Model to which constraints are added.
193
    flows : dict
194
        Dictionary holding the flows that should be considered in constraint.
195
        Keys are (source, target) objects of the Flow. If no dictionary is
196
        given all flows containing the keyword attribute will be
197
        used.
198
    keyword : string
199
        attribute to consider
200
    limit : sequence of float
201
        Absolute limit of keyword attribute for the energy system.
202
203
    Note
204
    ----
205
    Flow objects required an attribute named like keyword!
206
207
208
    **Constraint:**
209
210
    .. math:: \sum_{i \in F_I} \sum_{t \in T} P_i(t) \cdot w_i(t)
211
               \cdot \tau(t) \leq L(p) \forall p \in \textrm{PERIODS}
212
213
214
    For the parameter and variable explanation, please refer to the docs
215
    of generic_integral_limit.
216
217
    """
218
    flows = _check_and_set_flows(om, flows, keyword)
219
    limit_name = "integral_limit_" + keyword
220
221
    if om.es.periods is None:
222
        msg = (
223
            "generic_periodical_integral_limit is only applicable\n"
224
            "for multi-period models.\nFor standard models, use "
225
            "generic_integral_limit instead."
226
        )
227
        raise ValueError(msg)
228
229
    if limit is not None:
230
        limit = sequence(limit)
231
    else:
232
        msg = (
233
            "You have to provide a limit for each period!\n"
234
            "If you provide a scalar value, this will be applied as a "
235
            "limit for each period."
236
        )
237
        raise ValueError(msg)
238
239
    def _periodical_integral_limit_rule(m, p):
240
        expr = sum(
241
            om.flow[inflow, outflow, t]
242
            * om.timeincrement[t]
243
            * sequence(getattr(flows[inflow, outflow], keyword))[t]
244
            for (inflow, outflow) in flows
245
            for t in m.TIMESTEPS_IN_PERIOD[p]
246
        )
247
248
        return expr <= limit[p]
249
250
    om.periodical_integral_limit = po.Constraint(
251
        om.PERIODS,
252
        rule=_periodical_integral_limit_rule,
253
        name=limit_name + "_constraint",
254
    )
255
256
    return om
257
258
259
def _check_and_set_flows(om, flows, keyword):
260
    """Checks and sets flows if needed
261
262
    Parameters
263
    ----------
264
    om : oemof.solph.Model
265
        Model to which constraints are added.
266
267
    flows : dict
268
        Dictionary holding the flows that should be considered in constraint.
269
        Keys are (source, target) objects of the Flow. If no dictionary is
270
        given all flows containing the keyword attribute will be
271
        used.
272
273
    keyword : string
274
        attribute to consider
275
276
    Returns
277
    -------
278
    flows : dict
279
        the flows to be considered
280
    """
281
    if flows is None:
282
        flows = {}
283
        for i, o in om.flows:
284
            if hasattr(om.flows[i, o], keyword):
285
                flows[(i, o)] = om.flows[i, o]
286
287
    else:
288
        for i, o in flows:
289
            if not hasattr(flows[i, o], keyword):
290
                raise AttributeError(
291
                    (
292
                        "Flow with source: {0} and target: {1} "
293
                        "has no attribute {2}."
294
                    ).format(i.label, o.label, keyword)
295
                )
296
297
    return flows
298