Passed
Pull Request — dev (#1183)
by Patrik
04:02
created

Investment._check_invest_attributes_offset()   A

Complexity

Conditions 3

Size

Total Lines 8
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 5
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 3
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
67
68
    For the variables, constraints and parts of the objective function, which
69
    are created, see
70
    :py:class:`~oemof.solph.blocks.investment_flow.InvestmentFlow`,
71
    :py:class:`~oemof.solph.components.generic_storage.GenericInvestmentStorageBlock`
72
    :py:class:`~oemof.solph.custom.sink_dsm.SinkDSMOemofInvestmentBlock`,
73
    :py:class:`~oemof.solph.custom.sink_dsm.SinkDSMDLRInvestmentBlock` and
74
    :py:class:`~oemof.solph.custom.sink_dsm.SinkDSMDIWInvestmentBlock`.
75
76
    """  # noqa: E501
77
78
    def __init__(
79
        self,
80
        maximum=float("+inf"),
81
        minimum=0,
82
        ep_costs=0,
83
        existing=0,
84
        nonconvex=False,
85
        offset=0,
86
        overall_maximum=None,
87
        overall_minimum=None,
88
        custom_attributes=None,
89
    ):
90
        if custom_attributes is None:
91
            custom_attributes = {}
92
        self.maximum = sequence(maximum)
93
        self.minimum = sequence(minimum)
94
        self.ep_costs = sequence(ep_costs)
95
        self.existing = existing
96
        self.nonconvex = nonconvex
97
        self.offset = sequence(offset)
98
        self.overall_maximum = overall_maximum
99
        self.overall_minimum = overall_minimum
100
101
        for attribute in custom_attributes.keys():
102
            value = custom_attributes.get(attribute)
103
            setattr(self, attribute, value)
104
105
        self._check_invest_attributes()
106
        self._check_invest_attributes_maximum()
107
        self._check_invest_attributes_offset()
108
        self._check_invest_attributes_nonconvex()
109
        self._check_nonconvex()
110
111
    def _check_invest_attributes(self):
112
        """Throw an error if existing is other than 0 and nonconvex is True"""
113
        if (self.existing != 0) and (self.nonconvex is True):
114
            e1 = (
115
                "Values for 'offset' and 'existing' are given in"
116
                " investement attributes. \n These two options cannot be "
117
                "considered at the same time."
118
            )
119
            raise AttributeError(e1)
120
121
    def _check_invest_attributes_maximum(self):
122
        """Throw an error if maximum is infinite and nonconvex is True"""
123
        if (self.maximum[0] == float("+inf")) and (self.nonconvex is True):
124
            e2 = (
125
                "Please provide a maximum investment value in case of"
126
                " nonconvex investment (nonconvex=True), which is in the"
127
                " expected magnitude."
128
                " \nVery high maximum values (> 10e8) as maximum investment"
129
                " limit might lead to numeric issues, so that no investment"
130
                " is done, although it is the optimal solution!"
131
            )
132
            raise AttributeError(e2)
133
134
    def _check_invest_attributes_offset(self):
135
        """Throw an error if offset is given without nonconvex=True"""
136
        if (self.offset[0] != 0) and (self.nonconvex is False):
137
            e3 = (
138
                "If `nonconvex` is `False`, the `offset` parameter will be"
139
                " ignored."
140
            )
141
            raise AttributeError(e3)
142
143
    def _check_invest_attributes_nonconvex(self):
144
        """Throw an error if nonconvex is not of type boolean."""
145
        if not isinstance(self.nonconvex, bool):
146
            e5 = (
147
                "The `nonconvex` parameter of the `Investment` class has to be"
148
                + f" of type boolean, not {type(self.nonconvex)}."
149
            )
150
            raise AttributeError(e5)
151
152
    def _check_nonconvex(self):
153
        """Checking for unnecessary setting of nonconvex"""
154
        if self.nonconvex:
155
            if (self.minimum.min() == 0) and (self.offset.min() == 0):
156
                msg = (
157
                    "It is not necessary to set the investment to `nonconvex` "
158
                    "if `minimum` and `offset` are 0.\n"
159
                    "This can lead to the `invest_status` variable becoming "
160
                    "1, even if the `nominal_capacity` is optimized to 0."
161
                )
162
                warn(msg, debugging.SuspiciousUsageWarning)
163
164
165
class NonConvex:
166
    """Defines a NonConvex object holding all the specifications for NonConvex
167
    Flows, i.e. Flows with binary variables associated to them.
168
169
    Parameters
170
    ----------
171
    startup_costs : numeric (iterable or scalar)
172
        Costs associated with a start of the flow (representing a unit).
173
    shutdown_costs : numeric (iterable or scalar)
174
        Costs associated with the shutdown of the flow (representing a unit).
175
    activity_costs : numeric (iterable or scalar)
176
        Costs associated with the active operation of the flow, independently
177
        from the actual output.
178
    inactivity_costs : numeric (iterable or scalar)
179
        Costs associated with not operating the flow.
180
    minimum_uptime : numeric or list of numeric (1 or positive integer)
181
        Minimum number of time steps that a flow must be greater then its
182
        minimum flow after startup. Be aware that minimum up and downtimes
183
        can contradict each other and may lead to infeasible problems.
184
    minimum_downtime : numeric or list of numeric (1 or positive integer)
185
        Minimum number of time steps a flow is forced to zero after
186
        shutting down. Be aware that minimum up and downtimes can
187
        contradict each other and may to infeasible problems.
188
    maximum_startups : numeric (0 or positive integer)
189
        Maximum number of start-ups in the optimization timeframe.
190
    maximum_shutdowns : numeric (0 or positive integer)
191
        Maximum number of shutdowns in the optimization timeframe.
192
    initial_status : numeric (0 or 1)
193
        Integer value indicating the status of the flow in the first time step
194
        (0 = off, 1 = on). For minimum up and downtimes, the initial status
195
        is set for the respective values in the beginning e.g. if a
196
        minimum uptime of four timesteps is defined and the initial status is
197
        set to one, the initial status is fixed for the four first timesteps
198
        of the optimization period. Otherwise if the initial status is set to
199
        zero and the first timesteps are fixed for the number of minimum
200
        downtime steps.
201
    negative_gradient_limit : numeric (iterable, scalar or None)
202
        the normed *upper bound* on the positive difference
203
        (`flow[t-1] < flow[t]`) of two consecutive flow values.
204
    positive_gradient_limit : numeric (iterable, scalar or None)
205
            the normed *upper bound* on the negative difference
206
            (`flow[t-1] > flow[t]`) of two consecutive flow values.
207
    """
208
209
    def __init__(
210
        self,
211
        initial_status=0,
212
        minimum_uptime=0,
213
        minimum_downtime=0,
214
        maximum_startups=None,
215
        maximum_shutdowns=None,
216
        startup_costs=None,
217
        shutdown_costs=None,
218
        activity_costs=None,
219
        inactivity_costs=None,
220
        negative_gradient_limit=None,
221
        positive_gradient_limit=None,
222
        custom_attributes=None,
223
    ):
224
        if custom_attributes is None:
225
            custom_attributes = {}
226
227
        self.initial_status = initial_status
228
        self.minimum_uptime = sequence(minimum_uptime)
229
        self.minimum_downtime = sequence(minimum_downtime)
230
        self.maximum_startups = maximum_startups
231
        self.maximum_shutdowns = maximum_shutdowns
232
233
        self.startup_costs = sequence(startup_costs)
234
        self.shutdown_costs = sequence(shutdown_costs)
235
        self.activity_costs = sequence(activity_costs)
236
        self.inactivity_costs = sequence(inactivity_costs)
237
        self.negative_gradient_limit = sequence(negative_gradient_limit)
238
        self.positive_gradient_limit = sequence(positive_gradient_limit)
239
240
        for attribute, value in custom_attributes.items():
241
            setattr(self, attribute, value)
242
243
        if initial_status == 0:
244
            self.first_flexible_timestep = self.minimum_downtime[0]
245
        else:
246
            self.first_flexible_timestep = self.minimum_uptime[0]
247