Passed
Push — dev ( f78b4e...7790ca )
by Patrik
08:19 queued 06:19
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
    ):
102
        if custom_attributes is None:
103
            custom_attributes = {}
104
        self.maximum = sequence(maximum)
105
        self.minimum = sequence(minimum)
106
        self.ep_costs = sequence(ep_costs)
107
        self.existing = existing
108
        self.nonconvex = nonconvex
109
        self.offset = sequence(offset)
110
        self.overall_maximum = overall_maximum
111
        self.overall_minimum = overall_minimum
112
        self.lifetime = lifetime
113
        self.age = age
114
        self.fixed_costs = sequence(fixed_costs)
115
116
        for attribute in custom_attributes.keys():
117
            value = custom_attributes.get(attribute)
118
            setattr(self, attribute, value)
119
120
        self._check_invest_attributes()
121
        self._check_invest_attributes_maximum()
122
        self._check_invest_attributes_offset()
123
        self._check_age_and_lifetime()
124
        self._check_nonconvex()
125
126
    def _check_invest_attributes(self):
127
        """Throw an error if existing is other than 0 and nonconvex is True"""
128
        if (self.existing != 0) and (self.nonconvex is True):
129
            e1 = (
130
                "Values for 'offset' and 'existing' are given in"
131
                " investement attributes. \n These two options cannot be "
132
                "considered at the same time."
133
            )
134
            raise AttributeError(e1)
135
136
    def _check_invest_attributes_maximum(self):
137
        """Throw an error if maximum is infinite and nonconvex is True"""
138
        if (self.maximum[0] == float("+inf")) and (self.nonconvex is True):
139
            e2 = (
140
                "Please provide a maximum investment value in case of"
141
                " nonconvex investment (nonconvex=True), which is in the"
142
                " expected magnitude."
143
                " \nVery high maximum values (> 10e8) as maximum investment"
144
                " limit might lead to numeric issues, so that no investment"
145
                " is done, although it is the optimal solution!"
146
            )
147
            raise AttributeError(e2)
148
149
    def _check_invest_attributes_offset(self):
150
        """Throw an error if offset is given without nonconvex=True"""
151
        if (self.offset[0] != 0) and (self.nonconvex is False):
152
            e3 = (
153
                "If `nonconvex` is `False`, the `offset` parameter will be"
154
                " ignored."
155
            )
156
            raise AttributeError(e3)
157
158
    def _check_age_and_lifetime(self):
159
        """Throw an error if age is chosen greater or equal to lifetime;
160
        only applicable for multi-period models
161
        """
162
        if self.lifetime is not None:
163
            if self.age >= self.lifetime:
164
                e4 = (
165
                    "A unit's age must be smaller than its "
166
                    "expected lifetime."
167
                )
168
                raise AttributeError(e4)
169
170
    def _check_nonconvex(self):
171
        """Checking for unnecessary setting of nonconvex"""
172
        if self.nonconvex:
173
            if (self.minimum.min() == 0) and (self.offset.min() == 0):
174
                msg = (
175
                    "It is not necessary to set the investment to `nonconvex` "
176
                    "if `minimum` and `offset` are 0.\n"
177
                    "This can lead to the `invest_status` variable becoming "
178
                    "1, even if the `nominal_capacity` is optimized to 0."
179
                )
180
                warn(msg, debugging.SuspiciousUsageWarning)
181
182
183
class NonConvex:
184
    """Defines a NonConvex object holding all the specifications for NonConvex
185
    Flows, i.e. Flows with binary variables associated to them.
186
187
    Parameters
188
    ----------
189
    startup_costs : numeric (iterable or scalar)
190
        Costs associated with a start of the flow (representing a unit).
191
    shutdown_costs : numeric (iterable or scalar)
192
        Costs associated with the shutdown of the flow (representing a unit).
193
    activity_costs : numeric (iterable or scalar)
194
        Costs associated with the active operation of the flow, independently
195
        from the actual output.
196
    inactivity_costs : numeric (iterable or scalar)
197
        Costs associated with not operating the flow.
198
    minimum_uptime : numeric or list of numeric (1 or positive integer)
199
        Minimum number of time steps that a flow must be greater then its
200
        minimum flow after startup. Be aware that minimum up and downtimes
201
        can contradict each other and may lead to infeasible problems.
202
    minimum_downtime : numeric or list of numeric (1 or positive integer)
203
        Minimum number of time steps a flow is forced to zero after
204
        shutting down. Be aware that minimum up and downtimes can
205
        contradict each other and may to infeasible problems.
206
    maximum_startups : numeric (0 or positive integer)
207
        Maximum number of start-ups in the optimization timeframe.
208
    maximum_shutdowns : numeric (0 or positive integer)
209
        Maximum number of shutdowns in the optimization timeframe.
210
    initial_status : numeric (0 or 1)
211
        Integer value indicating the status of the flow in the first time step
212
        (0 = off, 1 = on). For minimum up and downtimes, the initial status
213
        is set for the respective values in the beginning e.g. if a
214
        minimum uptime of four timesteps is defined and the initial status is
215
        set to one, the initial status is fixed for the four first timesteps
216
        of the optimization period. Otherwise if the initial status is set to
217
        zero and the first timesteps are fixed for the number of minimum
218
        downtime steps.
219
    negative_gradient_limit : numeric (iterable, scalar or None)
220
        the normed *upper bound* on the positive difference
221
        (`flow[t-1] < flow[t]`) of two consecutive flow values.
222
    positive_gradient_limit : numeric (iterable, scalar or None)
223
            the normed *upper bound* on the negative difference
224
            (`flow[t-1] > flow[t]`) of two consecutive flow values.
225
    """
226
227
    def __init__(
228
        self,
229
        initial_status=0,
230
        minimum_uptime=0,
231
        minimum_downtime=0,
232
        maximum_startups=None,
233
        maximum_shutdowns=None,
234
        startup_costs=None,
235
        shutdown_costs=None,
236
        activity_costs=None,
237
        inactivity_costs=None,
238
        negative_gradient_limit=None,
239
        positive_gradient_limit=None,
240
        custom_attributes=None,
241
    ):
242
        if custom_attributes is None:
243
            custom_attributes = {}
244
245
        self.initial_status = initial_status
246
        self.minimum_uptime = sequence(minimum_uptime)
247
        self.minimum_downtime = sequence(minimum_downtime)
248
        self.maximum_startups = maximum_startups
249
        self.maximum_shutdowns = maximum_shutdowns
250
251
        self.startup_costs = sequence(startup_costs)
252
        self.shutdown_costs = sequence(shutdown_costs)
253
        self.activity_costs = sequence(activity_costs)
254
        self.inactivity_costs = sequence(inactivity_costs)
255
        self.negative_gradient_limit = sequence(negative_gradient_limit)
256
        self.positive_gradient_limit = sequence(positive_gradient_limit)
257
258
        for attribute, value in custom_attributes.items():
259
            setattr(self, attribute, value)
260
261
        if initial_status == 0:
262
            self.first_flexible_timestep = self.minimum_downtime[0]
263
        else:
264
            self.first_flexible_timestep = self.minimum_uptime[0]
265