Passed
Pull Request — dev (#1174)
by
unknown
02:47 queued 01:01
created

additional_total_limit()   C

Complexity

Conditions 9

Size

Total Lines 163
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 58
dl 0
loc 163
rs 6.0412
c 0
b 0
f 0
cc 9
nop 4

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
"""Limits for investments.
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
from pyomo import environ as po
17
18
from oemof.solph._plumbing import sequence
19
20
21
def investment_limit(model, limit=None):
22
    r"""Set an absolute limit for the total investment costs of an investment
23
    optimization problem (over all periods in case of a multi-period model):
24
25
    .. math:: \sum_{investment\_costs} \leq limit
26
27
    Parameters
28
    ----------
29
    model : oemof.solph._models.Model
30
        Model to which the constraint is added
31
    limit : float
32
        Absolute limit of the investment (i.e. RHS of constraint)
33
    """
34
35
    def investment_rule(m):
36
        expr = 0
37
38
        if hasattr(m, "InvestmentFlowBlock"):
39
            expr += m.InvestmentFlowBlock.investment_costs
40
41
        if hasattr(m, "GenericInvestmentStorageBlock"):
42
            expr += m.GenericInvestmentStorageBlock.investment_costs
43
44
        if hasattr(m, "SinkDSMOemofInvestmentBlock"):
45
            expr += m.SinkDSMOemofInvestmentBlock.investment_costs
46
47
        if hasattr(m, "SinkDSMDIWInvestmentBlock"):
48
            expr += m.SinkDSMDIWInvestmentBlock.investment_costs
49
50
        if hasattr(m, "SinkDSMDLRInvestmentBlock"):
51
            expr += m.SinkDSMDLRInvestmentBlock.investment_costs
52
53
        return expr <= limit
54
55
    model.investment_limit = po.Constraint(rule=investment_rule)
56
57
    return model
58
59
60
def investment_limit_per_period(model, limit=None):
61
    r"""Set an absolute limit for the total investment costs of a
62
    investment optimization problem for each period
63
    of the multi-period problem.
64
65
    .. math::
66
        \sum_{investment\_costs(p)} \leq limit(p)
67
        \forall p \in \textrm{PERIODS}
68
69
    Parameters
70
    ----------
71
    model : oemof.solph.Model
72
        Model to which the constraint is added
73
    limit : sequence of float, :math:`limit(p)`
74
        Absolute limit of the investment for each period
75
        (i.e. RHS of constraint)
76
    """
77
78
    if model.es.periods is None:
79
        msg = (
80
            "investment_limit_per_period is only applicable "
81
            "for multi-period models.\nIn order to create such a model, "
82
            "explicitly set attribute `periods` of your energy system."
83
        )
84
        raise ValueError(msg)
85
86
    if limit is not None:
87
        limit = sequence(limit)
88
    else:
89
        msg = (
90
            "You have to provide an investment limit for each period!\n"
91
            "If you provide a scalar value, this will be applied as a "
92
            "limit for each period."
93
        )
94
        raise ValueError(msg)
95
96
    def investment_period_rule(m, p):
97
        expr = 0
98
99
        if hasattr(m, "InvestmentFlowBlock"):
100
            expr += m.InvestmentFlowBlock.period_investment_costs[p]
101
102
        if hasattr(m, "GenericInvestmentStorageBlock"):
103
            expr += m.GenericInvestmentStorageBlock.period_investment_costs[p]
104
105
        if hasattr(m, "SinkDSMOemofInvestmentBlock"):
106
            expr += m.SinkDSMOemofInvestmentBlock.period_investment_costs[p]
107
108
        if hasattr(m, "SinkDSMDIWInvestmentBlock"):
109
            expr += m.SinkDSMDIWInvestmentBlock.period_investment_costs[p]
110
111
        if hasattr(m, "SinkDSMDLRInvestmentBlock"):
112
            expr += m.SinkDSMDLRInvestmentBlock.period_investment_costs[p]
113
114
        return expr <= limit[p]
115
116
    model.investment_limit_per_period = po.Constraint(
117
        model.PERIODS, rule=investment_period_rule
118
    )
119
120
    return model
121
122
123
def additional_investment_flow_limit(model, keyword, limit=None):
124
    r"""
125
    Global limit for investment flows weighted by an attribute keyword.
126
127
    This constraint is only valid for Flows not for components such as an
128
    investment storage.
129
130
    The attribute named by keyword has to be added to every Investment
131
    attribute of the flow you want to take into account.
132
    Total value of keyword attributes after optimization can be retrieved
133
    calling the `oemof.solph._models.Model.invest_limit_${keyword}()`.
134
135
    .. math::
136
        \sum_{p \in \textrm{PERIODS}}
137
        \sum_{i \in IF}  P_{i}(p) \cdot w_i \leq limit
138
139
    With `IF` being the set of InvestmentFlows considered for the integral
140
    limit.
141
142
    The symbols used are defined as follows
143
    (with Variables (V) and Parameters (P)):
144
145
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
146
    | symbol           | attribute                             | type | explanation                                                  |
147
    +==================+=======================================+======+==============================================================+
148
    | :math:`P_{i}(p)` | `InvestmentFlowBlock.invest[i, o, p]` | V    | invested capacity of investment flow in period p             |
149
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
150
    | :math:`w_i`      | `keyword`                             | P    | weight given to investment flow named according to `keyword` |
151
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
152
    | :math:`limit`    | `limit`                               | P    | global limit given by keyword `limit`                        |
153
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
154
155
    Parameters
156
    ----------
157
    model : oemof.solph.Model
158
        Model to which constraints are added.
159
    keyword : attribute to consider
160
        All flows with Investment attribute containing the keyword will be
161
        used.
162
    limit : numeric
163
        Global limit of keyword attribute for the energy system.
164
165
    Note
166
    ----
167
    The Investment attribute of the considered (Investment-)flows requires an
168
    attribute named like keyword!
169
170
    Examples
171
    --------
172
    >>> import pandas as pd
173
    >>> from oemof import solph
174
    >>> date_time_index = pd.date_range('1/1/2020', periods=6, freq='h')
175
    >>> es = solph.EnergySystem(
176
    ...     timeindex=date_time_index,
177
    ...     infer_last_interval=False,
178
    ... )
179
    >>> bus = solph.buses.Bus(label='bus_1')
180
    >>> sink = solph.components.Sink(label="sink", inputs={bus:
181
    ...     solph.flows.Flow(nominal_capacity=10, fix=[10, 20, 30, 40, 50])})
182
    >>> src1 = solph.components.Source(
183
    ...     label='source_0', outputs={bus: solph.flows.Flow(
184
    ...         nominal_capacity=solph.Investment(
185
    ...             ep_costs=50, custom_attributes={"space": 4},
186
    ...         ))
187
    ...     })
188
    >>> src2 = solph.components.Source(
189
    ...     label='source_1', outputs={bus: solph.flows.Flow(
190
    ...         nominal_capacity=solph.Investment(
191
    ...              ep_costs=100, custom_attributes={"space": 1},
192
    ...         ))
193
    ...     })
194
    >>> es.add(bus, sink, src1, src2)
195
    >>> model = solph.Model(es)
196
    >>> model = solph.constraints.additional_investment_flow_limit(
197
    ...     model, "space", limit=1500)
198
    >>> a = model.solve(solver="cbc")
199
    >>> int(round(model.invest_limit_space()))
200
    1500
201
    """  # noqa: E501
202
    warnings.warn(
203
        "additional_investment_flow_limit() is deprecated and will be removed in a future version.\n"
204
        "Use additional_total_limit() instead, which requires a new structure of costume attributes. "
205
        "F.e.custom_attributes={“space”: {“linear”: 1, “offset”: 5}}",
206
        DeprecationWarning,
207
        stacklevel=2,
208
    )
209
    invest_flows = {}
210
211
    for i, o in model.flows:
212
        if hasattr(model.flows[i, o].investment, keyword):
213
            invest_flows[(i, o)] = model.flows[i, o].investment
214
215
    limit_name = "invest_limit_" + keyword
216
217
    setattr(
218
        model,
219
        limit_name,
220
        po.Expression(
221
            expr=sum(
222
                model.InvestmentFlowBlock.invest[inflow, outflow, p]
223
                * getattr(invest_flows[inflow, outflow], keyword)
224
                for (inflow, outflow) in invest_flows
225
                for p in model.PERIODS
226
            )
227
        ),
228
    )
229
230
    setattr(
231
        model,
232
        limit_name + "_constraint",
233
        po.Constraint(expr=(getattr(model, limit_name) <= limit)),
234
    )
235
236
    return model
237
238
239
def additional_total_limit(model, keyword, limit=None, consider_offset=True):
240
    r"""
241
    Global limit for investment flows and operation flows
242
    weighted by an attribute keyword.
243
244
    This constraint is  valid for Flows and for an
245
    investment storage.
246
247
    The attribute named by keyword has to be added to every Investment
248
    attribute of the flow you want to take into account.
249
    Total value of keyword attributes after optimization can be retrieved
250
    calling the `oemof.solph._models.Model.total_limit_${keyword}()`.
251
252
    .. math::
253
        \sum_{p \in \textrm{PERIODS}}
254
        \sum_{i \in IF}  P_{i}(p) \cdot w_i
255
        \sum_{i \in F_E} \sum_{t \in T} P_i(p, t) \cdot w_i(t)
256
               \cdot \tau(t) \leq limit
257
258
    With `IF` being the set of InvestmentFlows considered for the integral
259
    limit,  `F_I` being the set of flows considered for the integral limit and
260
    `T` being the set of time steps.
261
262
    The symbols used are defined as follows
263
    (with Variables (V) and Parameters (P)):
264
265
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
266
    | symbol           | attribute                             | type | explanation                                                  |
267
    +==================+=======================================+======+==============================================================+
268
    | :math:`P_{i}(p)` | `InvestmentFlowBlock.invest[i, o, p]` | V    | invested capacity of investment flow in period p             |
269
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
270
    | :math:`w_i`      | `keyword`                             | P    | weight given to investment flow named according to `keyword` |
271
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
272
    | :math:`limit`    | `limit`                               | P    | global limit given by keyword `limit`                        |
273
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
274
    | :math:`P_n(p, t)`| `limit`                               | P    | power flow :math:`n` at time index :math:`p, t`              |
275
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
276
    | :math:`w_N(t)`   | `limit`                               | P    | weight given to Flow named according to `keyword`            |
277
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
278
    | :math:`\tau(t)`  | `limit`                               | P    | width of time step :math:`t`                                 |
279
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
280
281
    Parameters
282
    ----------
283
    model : oemof.solph.Model
284
        Model to which constraints are added.
285
    keyword : attribute to consider
286
        All flows with Investment attribute containing the keyword will be
287
        used.
288
    limit : numeric
289
        Global limit of keyword attribute for the energy system.
290
291
    Note
292
    ----
293
    The Investment attribute of the considered (Investment-)flows requires an
294
    attribute named like keyword!
295
296
    Examples
297
    --------
298
    >>> import pandas as pd
299
    >>> from oemof import solph
300
    >>> date_time_index = pd.date_range('1/1/2020', periods=6, freq='h')
301
    >>> es = solph.EnergySystem(
302
    ...     timeindex=date_time_index,
303
    ...     infer_last_interval=False,
304
    ... )
305
    >>> bus = solph.buses.Bus(label='bus_1')
306
    >>> sink = solph.components.Sink(label="sink", inputs={bus:
307
    ...     solph.flows.Flow(nominal_capacity=10, fix=[10, 20, 30, 40, 50])})
308
    >>> src1 = solph.components.Source(
309
    ...     label='source_0', outputs={bus: solph.flows.Flow(
310
    ...         nominal_capacity=solph.Investment(
311
    ...             ep_costs=50, custom_attributes={"space": 4},
312
    ...         ))
313
    ...     })
314
    >>> src2 = solph.components.Source(
315
    ...     label='source_1', outputs={bus: solph.flows.Flow(
316
    ...         nominal_capacity=solph.Investment(
317
    ...              ep_costs=100, custom_attributes={"space": 1},
318
    ...         ))
319
    ...     })
320
    >>> es.add(bus, sink, src1, src2)
321
    >>> model = solph.Model(es)
322
    >>> model = solph.constraints.additional_investment_flow_limit(
323
    ...     model, "space", limit=1500)
324
    >>> a = model.solve(solver="cbc")
325
    >>> int(round(model.invest_limit_space()))
326
    1500
327
    """  # noqa: E501
328
    invest_flows = {}
329
    operational_flows = {}
330
    storages = {}
331
    for i, o in model.flows:
332
        if hasattr(model.flows[i, o].investment, keyword):
333
            invest_flows[(i, o)] = model.flows[i, o].investment
334
        if hasattr(model.flows[i, o], keyword):
335
            operational_flows[(i, o)] = model.flows[i, o]
336
    limit_name = "total_limit_" + keyword
337
338
    if hasattr(model, "GenericInvestmentStorageBlock"):
339
        for st, _ in model.GenericInvestmentStorageBlock.invest:
340
            storages[st] = [st]
341
342
    setattr(
343
        model,
344
        limit_name,
345
        po.Expression(
346
            rule=lambda m: sum(
347
                model.InvestmentFlowBlock.invest[inflow, outflow, p]
348
                * getattr(invest_flows[inflow, outflow], keyword).get(
349
                    "linear", 0
350
                )
351
                + (
352
                    model.InvestmentFlowBlock.invest_status[inflow, outflow, p]
353
                    * getattr(invest_flows[inflow, outflow], keyword).get(
354
                        "offset", 0
355
                    )
356
                    if (inflow, outflow, p)
357
                    in model.InvestmentFlowBlock.invest_status
358
                    else 0
359
                )
360
                for (inflow, outflow) in invest_flows
361
                for p in model.PERIODS
362
            )
363
            + sum(
364
                model.flow[inflow, outflow, t]
365
                * model.tsam_weighting[t]
366
                * model.timeincrement[t]
367
                * sequence(
368
                    getattr(operational_flows[inflow, outflow], keyword)
369
                )[t]
370
                for (inflow, outflow) in operational_flows
371
                for p in model.PERIODS
372
                for t in model.TIMESTEPS_IN_PERIOD[p]
373
            )
374
            + sum(
375
                (
376
                    model.GenericInvestmentStorageBlock.invest[st, p]
377
                    * getattr(storages[st][0].investment, keyword).get(
378
                        "linear", 0
379
                    )
380
                    + model.GenericInvestmentStorageBlock.invest_status[st, p]
381
                    * getattr(storages[st][0].investment, keyword).get(
382
                        "offset", 0
383
                    )
384
                    if (st, p)
385
                    in model.GenericInvestmentStorageBlock.invest_status
386
                    else 0
387
                )
388
                for st in storages
389
                for p in model.PERIODS
390
                if hasattr(model, "GenericInvestmentStorageBlock")
391
            )
392
        ),
393
    )
394
395
    setattr(
396
        model,
397
        limit_name + "_constraint",
398
        po.Constraint(expr=(getattr(model, limit_name) <= limit)),
399
    )
400
401
    return model
402