Passed
Pull Request — dev (#1174)
by
unknown
01:49
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 "
204
        "and will be removed in a future version.\n"
205
        "Use additional_total_limit() instead, "
206
        "which requires a new structure of costume attributes. "
207
        "F.e.custom_attributes={“space”: {“linear”: 1, “offset”: 5}}",
208
        DeprecationWarning,
209
        stacklevel=2,
210
    )
211
    invest_flows = {}
212
213
    for i, o in model.flows:
214
        if hasattr(model.flows[i, o].investment, keyword):
215
            invest_flows[(i, o)] = model.flows[i, o].investment
216
217
    limit_name = "invest_limit_" + keyword
218
219
    setattr(
220
        model,
221
        limit_name,
222
        po.Expression(
223
            expr=sum(
224
                model.InvestmentFlowBlock.invest[inflow, outflow, p]
225
                * getattr(invest_flows[inflow, outflow], keyword)
226
                for (inflow, outflow) in invest_flows
227
                for p in model.PERIODS
228
            )
229
        ),
230
    )
231
232
    setattr(
233
        model,
234
        limit_name + "_constraint",
235
        po.Constraint(expr=(getattr(model, limit_name) <= limit)),
236
    )
237
238
    return model
239
240
241
def additional_total_limit(model, keyword, limit=None, consider_offset=True):
242
    r"""
243
    Global limit for investment flows and operation flows
244
    weighted by an attribute keyword.
245
246
    This constraint is  valid for Flows and for an
247
    investment storage.
248
249
    The attribute named by keyword has to be added to every Investment
250
    attribute of the flow you want to take into account.
251
    Total value of keyword attributes after optimization can be retrieved
252
    calling the `oemof.solph._models.Model.total_limit_${keyword}()`.
253
254
    .. math::
255
        \sum_{p \in \textrm{PERIODS}}
256
        \sum_{i \in IF}  P_{i}(p) \cdot w_i
257
        \sum_{i \in F_E} \sum_{t \in T} P_i(p, t) \cdot w_i(t)
258
               \cdot \tau(t) \leq limit
259
260
    With `IF` being the set of InvestmentFlows considered for the integral
261
    limit,  `F_I` being the set of flows considered for the integral limit and
262
    `T` being the set of time steps.
263
264
    The symbols used are defined as follows
265
    (with Variables (V) and Parameters (P)):
266
267
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
268
    | symbol           | attribute                             | type | explanation                                                  |
269
    +==================+=======================================+======+==============================================================+
270
    | :math:`P_{i}(p)` | `InvestmentFlowBlock.invest[i, o, p]` | V    | invested capacity of investment flow in period p             |
271
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
272
    | :math:`w_i`      | `keyword`                             | P    | weight given to investment flow named according to `keyword` |
273
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
274
    | :math:`limit`    | `limit`                               | P    | global limit given by keyword `limit`                        |
275
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
276
    | :math:`P_n(p, t)`| `limit`                               | P    | power flow :math:`n` at time index :math:`p, t`              |
277
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
278
    | :math:`w_N(t)`   | `limit`                               | P    | weight given to Flow named according to `keyword`            |
279
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
280
    | :math:`\tau(t)`  | `limit`                               | P    | width of time step :math:`t`                                 |
281
    +------------------+---------------------------------------+------+--------------------------------------------------------------+
282
283
    Parameters
284
    ----------
285
    model : oemof.solph.Model
286
        Model to which constraints are added.
287
    keyword : attribute to consider
288
        All flows with Investment attribute containing the keyword will be
289
        used.
290
    limit : numeric
291
        Global limit of keyword attribute for the energy system.
292
293
    Note
294
    ----
295
    The Investment attribute of the considered (Investment-)flows requires an
296
    attribute named like keyword!
297
298
    Examples
299
    --------
300
    >>> import pandas as pd
301
    >>> from oemof import solph
302
    >>> date_time_index = pd.date_range('1/1/2020', periods=6, freq='h')
303
    >>> es = solph.EnergySystem(
304
    ...     timeindex=date_time_index,
305
    ...     infer_last_interval=False,
306
    ... )
307
    >>> bus = solph.buses.Bus(label='bus_1')
308
    >>> sink = solph.components.Sink(label="sink", inputs={bus:
309
    ...     solph.flows.Flow(nominal_capacity=10, fix=[10, 20, 30, 40, 50])})
310
    >>> src1 = solph.components.Source(
311
    ...     label='source_0', outputs={bus: solph.flows.Flow(
312
    ...         nominal_capacity=solph.Investment(
313
    ...             ep_costs=50, custom_attributes={"space": 4},
314
    ...         ))
315
    ...     })
316
    >>> src2 = solph.components.Source(
317
    ...     label='source_1', outputs={bus: solph.flows.Flow(
318
    ...         nominal_capacity=solph.Investment(
319
    ...              ep_costs=100, custom_attributes={"space": 1},
320
    ...         ))
321
    ...     })
322
    >>> es.add(bus, sink, src1, src2)
323
    >>> model = solph.Model(es)
324
    >>> model = solph.constraints.additional_investment_flow_limit(
325
    ...     model, "space", limit=1500)
326
    >>> a = model.solve(solver="cbc")
327
    >>> int(round(model.invest_limit_space()))
328
    1500
329
    """  # noqa: E501
330
    invest_flows = {}
331
    operational_flows = {}
332
    storages = {}
333
    for i, o in model.flows:
334
        if hasattr(model.flows[i, o].investment, keyword):
335
            invest_flows[(i, o)] = model.flows[i, o].investment
336
        if hasattr(model.flows[i, o], keyword):
337
            operational_flows[(i, o)] = model.flows[i, o]
338
    limit_name = "total_limit_" + keyword
339
340
    if hasattr(model, "GenericInvestmentStorageBlock"):
341
        for st, _ in model.GenericInvestmentStorageBlock.invest:
342
            storages[st] = [st]
343
344
    setattr(
345
        model,
346
        limit_name,
347
        po.Expression(
348
            rule=lambda m: sum(
349
                model.InvestmentFlowBlock.invest[inflow, outflow, p]
350
                * getattr(invest_flows[inflow, outflow], keyword).get(
351
                    "linear", 0
352
                )
353
                + (
354
                    model.InvestmentFlowBlock.invest_status[inflow, outflow, p]
355
                    * getattr(invest_flows[inflow, outflow], keyword).get(
356
                        "offset", 0
357
                    )
358
                    if (inflow, outflow, p)
359
                    in model.InvestmentFlowBlock.invest_status
360
                    else 0
361
                )
362
                for (inflow, outflow) in invest_flows
363
                for p in model.PERIODS
364
            )
365
            + sum(
366
                model.flow[inflow, outflow, t]
367
                * model.tsam_weighting[t]
368
                * model.timeincrement[t]
369
                * sequence(
370
                    getattr(operational_flows[inflow, outflow], keyword)
371
                )[t]
372
                for (inflow, outflow) in operational_flows
373
                for p in model.PERIODS
374
                for t in model.TIMESTEPS_IN_PERIOD[p]
375
            )
376
            + sum(
377
                (
378
                    model.GenericInvestmentStorageBlock.invest[st, p]
379
                    * getattr(storages[st][0].investment, keyword).get(
380
                        "linear", 0
381
                    )
382
                    + model.GenericInvestmentStorageBlock.invest_status[st, p]
383
                    * getattr(storages[st][0].investment, keyword).get(
384
                        "offset", 0
385
                    )
386
                    if (st, p)
387
                    in model.GenericInvestmentStorageBlock.invest_status
388
                    else 0
389
                )
390
                for st in storages
391
                for p in model.PERIODS
392
                if hasattr(model, "GenericInvestmentStorageBlock")
393
            )
394
        ),
395
    )
396
397
    setattr(
398
        model,
399
        limit_name + "_constraint",
400
        po.Constraint(expr=(getattr(model, limit_name) <= limit)),
401
    )
402
403
    return model
404