Passed
Pull Request — dev (#1231)
by Patrik
01:41
created

solph._options.Investment._check_nonconvex()   A

Complexity

Conditions 4

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 11
rs 10
c 0
b 0
f 0
cc 4
nop 1
1
# -*- coding: utf-8 -*-
2
3
"""Optional classes to be added to a network class.
4
5
SPDX-FileCopyrightText: Uwe Krien <[email protected]>
6
SPDX-FileCopyrightText: Simon Hilpert
7
SPDX-FileCopyrightText: Cord Kaldemeyer
8
SPDX-FileCopyrightText: Stephan Günther
9
SPDX-FileCopyrightText: Patrik Schönfeldt
10
SPDX-FileCopyrightText: jmloenneberga
11
SPDX-FileCopyrightText: Johannes Kochems
12
SPDX-FileCopyrightText: Malte Fritz
13
SPDX-FileCopyrightText: Jonas Freißmann
14
15
SPDX-License-Identifier: MIT
16
17
"""
18
from warnings import warn
19
20
from oemof.tools import debugging
21
22
from oemof.solph._plumbing import sequence
23
24
25
class Investment:
26
    """Defines an Investment object holding all the specifications needed
27
    for investment modeling.
28
29
    Parameters
30
    ----------
31
    maximum : float, :math:`P_{invest,max}(p)` or :math:`E_{invest,max}(p)`
32
        Maximum of the additional invested capacity;
33
        defined per period p for a multi-period model.
34
    minimum : float, :math:`P_{invest,min}(p)` or :math:`E_{invest,min}(p)`
35
        Minimum of the additional invested capacity. If `nonconvex` is `True`,
36
        `minimum` defines the threshold for the invested capacity;
37
        defined per period p for a multi-period model.
38
    ep_costs : float, :math:`c_{invest,var}`
39
        Equivalent periodical costs or investment expenses for the investment
40
41
        * For a standard model: equivalent periodical costs for the investment
42
          per flow capacity, i.e. annuities for investments already calculated.
43
        * For a multi-period model: Investment expenses for the respective
44
          period (in nominal terms). Annuities are calculated within the
45
          objective term, also considering age and lifetime.
46
    existing : float, :math:`P_{exist}` or :math:`E_{exist}`
47
        Existing / installed capacity. The invested capacity is added on top
48
        of this value. Hence, existing capacities come at no additional costs.
49
        Not applicable if `nonconvex` is set to `True`.
50
    nonconvex : bool
51
        If `True`, a binary variable for the status of the investment is
52
        created. This enables additional fix investment costs (*offset*)
53
        independent of the invested flow capacity. Therefore, use the `offset`
54
        parameter.
55
    offset : float, :math:`c_{invest,fix}`
56
        Additional fixed investment costs. Only applicable if `nonconvex` is
57
        set to `True`.
58
    overall_maximum : float, :math:`P_{overall,max}` or :math:`E_{overall,max}`
59
        Overall maximum capacity investment, i.e. the amount of capacity
60
        that can be totally installed at maximum in any period (taking into
61
        account decommissionings); only applicable for multi-period models
62
    overall_minimum : float :math:`P_{overall,min}` or :math:`E_{overall,min}`
63
        Overall minimum capacity investment that needs to be installed
64
        in the last period of the optimization (taking into account
65
        decommissionings); only applicable for multi-period models
66
    lifetime : int, :math:`l`
67
        Units lifetime, given in years; only applicable for multi-period
68
        models
69
    age : int, :math:`a`
70
        Units start age, given in years at the beginning of the optimization;
71
        only applicable for multi-period models
72
    fixed_costs : float or list of float, :math:`c_{fixed}(p)`
73
        Fixed costs in each period (given in nominal terms);
74
        only applicable for multi-period models
75
76
77
    For the variables, constraints and parts of the objective function, which
78
    are created, see
79
    :py:class:`~oemof.solph.blocks.investment_flow.InvestmentFlow`,
80
    :py:class:`~oemof.solph.components.generic_storage.GenericInvestmentStorageBlock`
81
    :py:class:`~oemof.solph.custom.sink_dsm.SinkDSMOemofInvestmentBlock`,
82
    :py:class:`~oemof.solph.custom.sink_dsm.SinkDSMDLRInvestmentBlock` and
83
    :py:class:`~oemof.solph.custom.sink_dsm.SinkDSMDIWInvestmentBlock`.
84
85
    """  # noqa: E501
86
87
    def __init__(
88
        self,
89
        maximum=float("+inf"),
90
        minimum=0,
91
        ep_costs=0,
92
        existing=0,
93
        nonconvex=False,
94
        offset=0,
95
        overall_maximum=None,
96
        overall_minimum=None,
97
        lifetime=None,
98
        age=0,
99
        fixed_costs=None,
100
        custom_attributes=None,
101
        custom_properties=None,
102
    ):
103
        # --- BEGIN: The following code can be removed for versions >= v0.7 ---
104
        if custom_attributes is not None:
105
            msg = (
106
                "For backward compatibility,"
107
                + " the option custom_attributes overwrites the option"
108
                + " custom_properties."
109
                + " Both options cannot be set at the same time."
110
            )
111
            if custom_properties is not None:
112
                raise AttributeError(msg)
113
            else:
114
                warn(msg, FutureWarning)
115
            for attribute in custom_attributes.keys():
116
                value = custom_attributes.get(attribute)
117
                setattr(self, attribute, value)
118
119
            custom_properties = custom_attributes
120
        # --- END ---
121
        self.custom_properties = custom_properties
122
        self.maximum = sequence(maximum)
123
        self.minimum = sequence(minimum)
124
        self.ep_costs = sequence(ep_costs)
125
        self.existing = existing
126
        self.nonconvex = nonconvex
127
        self.offset = sequence(offset)
128
        self.overall_maximum = overall_maximum
129
        self.overall_minimum = overall_minimum
130
        self.lifetime = lifetime
131
        self.age = age
132
        self.fixed_costs = sequence(fixed_costs)
133
134
        self._check_invest_attributes()
135
        self._check_invest_attributes_maximum()
136
        self._check_invest_attributes_offset()
137
        self._check_age_and_lifetime()
138
        self._check_invest_attributes_nonconvex()
139
        self._check_nonconvex()
140
141
    def _check_invest_attributes(self):
142
        """Throw an error if existing is other than 0 and nonconvex is True"""
143
        if (self.existing != 0) and (self.nonconvex is True):
144
            e1 = (
145
                "Values for 'offset' and 'existing' are given in"
146
                " investement attributes. \n These two options cannot be "
147
                "considered at the same time."
148
            )
149
            raise AttributeError(e1)
150
151
    def _check_invest_attributes_maximum(self):
152
        """Throw an error if maximum is infinite and nonconvex is True"""
153
        if (self.maximum[0] == float("+inf")) and (self.nonconvex is True):
154
            e2 = (
155
                "Please provide a maximum investment value in case of"
156
                " nonconvex investment (nonconvex=True), which is in the"
157
                " expected magnitude."
158
                " \nVery high maximum values (> 10e8) as maximum investment"
159
                " limit might lead to numeric issues, so that no investment"
160
                " is done, although it is the optimal solution!"
161
            )
162
            raise AttributeError(e2)
163
164
    def _check_invest_attributes_offset(self):
165
        """Throw an error if offset is given without nonconvex=True"""
166
        if (self.offset[0] != 0) and (self.nonconvex is False):
167
            e3 = (
168
                "If `nonconvex` is `False`, the `offset` parameter will be"
169
                " ignored."
170
            )
171
            raise AttributeError(e3)
172
173
    def _check_age_and_lifetime(self):
174
        """Throw an error if age is chosen greater or equal to lifetime;
175
        only applicable for multi-period models
176
        """
177
        if self.lifetime is not None:
178
            if self.age >= self.lifetime:
179
                e4 = (
180
                    "A unit's age must be smaller than its "
181
                    "expected lifetime."
182
                )
183
                raise AttributeError(e4)
184
185
    def _check_invest_attributes_nonconvex(self):
186
        """Throw an error if nonconvex is not of type boolean."""
187
        if not isinstance(self.nonconvex, bool):
188
            e5 = (
189
                "The `nonconvex` parameter of the `Investment` class has to be"
190
                + f" of type boolean, not {type(self.nonconvex)}."
191
            )
192
            raise AttributeError(e5)
193
194
    def _check_nonconvex(self):
195
        """Checking for unnecessary setting of nonconvex"""
196
        if self.nonconvex:
197
            if (self.minimum.min() == 0) and (self.offset.min() == 0):
198
                msg = (
199
                    "It is not necessary to set the investment to `nonconvex` "
200
                    "if `minimum` and `offset` are 0.\n"
201
                    "This can lead to the `invest_status` variable becoming "
202
                    "1, even if the `nominal_capacity` is optimized to 0."
203
                )
204
                warn(msg, debugging.SuspiciousUsageWarning)
205
206
207
class NonConvex:
208
    """Defines a NonConvex object holding all the specifications for NonConvex
209
    Flows, i.e. Flows with binary variables associated to them.
210
211
    Parameters
212
    ----------
213
    startup_costs : numeric (iterable or scalar)
214
        Costs associated with a start of the flow (representing a unit).
215
    shutdown_costs : numeric (iterable or scalar)
216
        Costs associated with the shutdown of the flow (representing a unit).
217
    activity_costs : numeric (iterable or scalar)
218
        Costs associated with the active operation of the flow, independently
219
        from the actual output.
220
    inactivity_costs : numeric (iterable or scalar)
221
        Costs associated with not operating the flow.
222
    minimum_uptime : numeric or list of numeric (1 or positive integer)
223
        Minimum number of time steps that a flow must be greater then its
224
        minimum flow after startup. Be aware that minimum up and downtimes
225
        can contradict each other and may lead to infeasible problems.
226
    minimum_downtime : numeric or list of numeric (1 or positive integer)
227
        Minimum number of time steps a flow is forced to zero after
228
        shutting down. Be aware that minimum up and downtimes can
229
        contradict each other and may to infeasible problems.
230
    maximum_startups : numeric (0 or positive integer)
231
        Maximum number of start-ups in the optimization timeframe.
232
    maximum_shutdowns : numeric (0 or positive integer)
233
        Maximum number of shutdowns in the optimization timeframe.
234
    initial_status : numeric (0 or 1)
235
        Integer value indicating the status of the flow in the first time step
236
        (0 = off, 1 = on). For minimum up and downtimes, the initial status
237
        is set for the respective values in the beginning e.g. if a
238
        minimum uptime of four timesteps is defined and the initial status is
239
        set to one, the initial status is fixed for the four first timesteps
240
        of the optimization period. Otherwise if the initial status is set to
241
        zero and the first timesteps are fixed for the number of minimum
242
        downtime steps.
243
    negative_gradient_limit : numeric (iterable, scalar or None)
244
        the normed *upper bound* on the positive difference
245
        (`flow[t-1] < flow[t]`) of two consecutive flow values.
246
    positive_gradient_limit : numeric (iterable, scalar or None)
247
            the normed *upper bound* on the negative difference
248
            (`flow[t-1] > flow[t]`) of two consecutive flow values.
249
    """
250
251
    def __init__(
252
        self,
253
        initial_status=0,
254
        minimum_uptime=0,
255
        minimum_downtime=0,
256
        maximum_startups=None,
257
        maximum_shutdowns=None,
258
        startup_costs=None,
259
        shutdown_costs=None,
260
        activity_costs=None,
261
        inactivity_costs=None,
262
        negative_gradient_limit=None,
263
        positive_gradient_limit=None,
264
        custom_attributes=None,
265
    ):
266
        if custom_attributes is None:
267
            custom_attributes = {}
268
269
        self.initial_status = initial_status
270
        self.minimum_uptime = sequence(minimum_uptime)
271
        self.minimum_downtime = sequence(minimum_downtime)
272
        self.maximum_startups = maximum_startups
273
        self.maximum_shutdowns = maximum_shutdowns
274
275
        self.startup_costs = sequence(startup_costs)
276
        self.shutdown_costs = sequence(shutdown_costs)
277
        self.activity_costs = sequence(activity_costs)
278
        self.inactivity_costs = sequence(inactivity_costs)
279
        self.negative_gradient_limit = sequence(negative_gradient_limit)
280
        self.positive_gradient_limit = sequence(positive_gradient_limit)
281
282
        for attribute, value in custom_attributes.items():
283
            setattr(self, attribute, value)
284
285
        if initial_status == 0:
286
            self.first_flexible_timestep = self.minimum_downtime[0]
287
        else:
288
            self.first_flexible_timestep = self.minimum_uptime[0]
289