|
1
|
|
|
# -*- coding: utf-8 - |
|
2
|
|
|
|
|
3
|
|
|
""" |
|
4
|
|
|
GenericStorage and associated individual constraints (blocks) and groupings. |
|
5
|
|
|
|
|
6
|
|
|
SPDX-FileCopyrightText: Uwe Krien <[email protected]> |
|
7
|
|
|
SPDX-FileCopyrightText: Simon Hilpert |
|
8
|
|
|
SPDX-FileCopyrightText: Cord Kaldemeyer |
|
9
|
|
|
SPDX-FileCopyrightText: Patrik Schönfeldt |
|
10
|
|
|
SPDX-FileCopyrightText: FranziPl |
|
11
|
|
|
SPDX-FileCopyrightText: jnnr |
|
12
|
|
|
SPDX-FileCopyrightText: Stephan Günther |
|
13
|
|
|
SPDX-FileCopyrightText: FabianTU |
|
14
|
|
|
SPDX-FileCopyrightText: Johannes Röder |
|
15
|
|
|
|
|
16
|
|
|
SPDX-License-Identifier: MIT |
|
17
|
|
|
|
|
18
|
|
|
""" |
|
19
|
|
|
|
|
20
|
|
|
from oemof.network import network |
|
21
|
|
|
from pyomo.core.base.block import ScalarBlock |
|
22
|
|
|
from pyomo.environ import Binary |
|
23
|
|
|
from pyomo.environ import Constraint |
|
24
|
|
|
from pyomo.environ import Expression |
|
25
|
|
|
from pyomo.environ import NonNegativeReals |
|
26
|
|
|
from pyomo.environ import Set |
|
27
|
|
|
from pyomo.environ import Var |
|
28
|
|
|
|
|
29
|
|
|
from oemof.solph._helpers import check_node_object_for_missing_attribute |
|
30
|
|
|
from oemof.solph._options import Investment |
|
31
|
|
|
from oemof.solph._plumbing import sequence as solph_sequence |
|
32
|
|
|
|
|
33
|
|
|
|
|
34
|
|
|
class GenericStorage(network.Node): |
|
35
|
|
|
r""" |
|
36
|
|
|
Component `GenericStorage` to model with basic characteristics of storages. |
|
37
|
|
|
|
|
38
|
|
|
The GenericStorage is designed for one input and one output. |
|
39
|
|
|
|
|
40
|
|
|
Parameters |
|
41
|
|
|
---------- |
|
42
|
|
|
nominal_storage_capacity : numeric, :math:`E_{nom}` |
|
43
|
|
|
Absolute nominal capacity of the storage |
|
44
|
|
|
invest_relation_input_capacity : numeric or None, :math:`r_{cap,in}` |
|
45
|
|
|
Ratio between the investment variable of the input Flow and the |
|
46
|
|
|
investment variable of the storage: |
|
47
|
|
|
:math:`\dot{E}_{in,invest} = E_{invest} \cdot r_{cap,in}` |
|
48
|
|
|
invest_relation_output_capacity : numeric or None, :math:`r_{cap,out}` |
|
49
|
|
|
Ratio between the investment variable of the output Flow and the |
|
50
|
|
|
investment variable of the storage: |
|
51
|
|
|
:math:`\dot{E}_{out,invest} = E_{invest} \cdot r_{cap,out}` |
|
52
|
|
|
invest_relation_input_output : numeric or None, :math:`r_{in,out}` |
|
53
|
|
|
Ratio between the investment variable of the output Flow and the |
|
54
|
|
|
investment variable of the input flow. This ratio used to fix the |
|
55
|
|
|
flow investments to each other. |
|
56
|
|
|
Values < 1 set the input flow lower than the output and > 1 will |
|
57
|
|
|
set the input flow higher than the output flow. If None no relation |
|
58
|
|
|
will be set: |
|
59
|
|
|
:math:`\dot{E}_{in,invest} = \dot{E}_{out,invest} \cdot r_{in,out}` |
|
60
|
|
|
initial_storage_level : numeric, :math:`c(-1)` |
|
61
|
|
|
The relative storage content in the timestep before the first |
|
62
|
|
|
time step of optimization (between 0 and 1). |
|
63
|
|
|
balanced : boolean |
|
64
|
|
|
Couple storage level of first and last time step. |
|
65
|
|
|
(Total inflow and total outflow are balanced.) |
|
66
|
|
|
loss_rate : numeric (iterable or scalar) |
|
67
|
|
|
The relative loss of the storage content per time unit. |
|
68
|
|
|
fixed_losses_relative : numeric (iterable or scalar), :math:`\gamma(t)` |
|
69
|
|
|
Losses independent of state of charge between two consecutive |
|
70
|
|
|
timesteps relative to nominal storage capacity. |
|
71
|
|
|
fixed_losses_absolute : numeric (iterable or scalar), :math:`\delta(t)` |
|
72
|
|
|
Losses independent of state of charge and independent of |
|
73
|
|
|
nominal storage capacity between two consecutive timesteps. |
|
74
|
|
|
inflow_conversion_factor : numeric (iterable or scalar), :math:`\eta_i(t)` |
|
75
|
|
|
The relative conversion factor, i.e. efficiency associated with the |
|
76
|
|
|
inflow of the storage. |
|
77
|
|
|
outflow_conversion_factor : numeric (iterable or scalar), :math:`\eta_o(t)` |
|
78
|
|
|
see: inflow_conversion_factor |
|
79
|
|
|
min_storage_level : numeric (iterable or scalar), :math:`c_{min}(t)` |
|
80
|
|
|
The normed minimum storage content as fraction of the |
|
81
|
|
|
nominal storage capacity (between 0 and 1). |
|
82
|
|
|
To set different values in every time step use a sequence. |
|
83
|
|
|
max_storage_level : numeric (iterable or scalar), :math:`c_{max}(t)` |
|
84
|
|
|
see: min_storage_level |
|
85
|
|
|
investment : :class:`oemof.solph.options.Investment` object |
|
86
|
|
|
Object indicating if a nominal_value of the flow is determined by |
|
87
|
|
|
the optimization problem. Note: This will refer all attributes to an |
|
88
|
|
|
investment variable instead of to the nominal_storage_capacity. The |
|
89
|
|
|
nominal_storage_capacity should not be set (or set to None) if an |
|
90
|
|
|
investment object is used. |
|
91
|
|
|
|
|
92
|
|
|
Notes |
|
93
|
|
|
----- |
|
94
|
|
|
The following sets, variables, constraints and objective parts are created |
|
95
|
|
|
* :py:class:`~oemof.solph.components._generic_storage.GenericStorageBlock` |
|
96
|
|
|
(if no Investment object present) |
|
97
|
|
|
* :py:class:`~oemof.solph.components._generic_storage.GenericInvestmentStorageBlock` |
|
98
|
|
|
(if Investment object present) |
|
99
|
|
|
|
|
100
|
|
|
Examples |
|
101
|
|
|
-------- |
|
102
|
|
|
Basic usage examples of the GenericStorage with a random selection of |
|
103
|
|
|
attributes. See the Flow class for all Flow attributes. |
|
104
|
|
|
|
|
105
|
|
|
>>> from oemof import solph |
|
106
|
|
|
|
|
107
|
|
|
>>> my_bus = solph.buses.Bus('my_bus') |
|
108
|
|
|
|
|
109
|
|
|
>>> my_storage = solph.components.GenericStorage( |
|
110
|
|
|
... label='storage', |
|
111
|
|
|
... nominal_storage_capacity=1000, |
|
112
|
|
|
... inputs={my_bus: solph.flows.Flow(nominal_value=200, variable_costs=10)}, |
|
113
|
|
|
... outputs={my_bus: solph.flows.Flow(nominal_value=200)}, |
|
114
|
|
|
... loss_rate=0.01, |
|
115
|
|
|
... initial_storage_level=0, |
|
116
|
|
|
... max_storage_level = 0.9, |
|
117
|
|
|
... inflow_conversion_factor=0.9, |
|
118
|
|
|
... outflow_conversion_factor=0.93) |
|
119
|
|
|
|
|
120
|
|
|
>>> my_investment_storage = solph.components.GenericStorage( |
|
121
|
|
|
... label='storage', |
|
122
|
|
|
... investment=solph.Investment(ep_costs=50), |
|
123
|
|
|
... inputs={my_bus: solph.flows.Flow()}, |
|
124
|
|
|
... outputs={my_bus: solph.flows.Flow()}, |
|
125
|
|
|
... loss_rate=0.02, |
|
126
|
|
|
... initial_storage_level=None, |
|
127
|
|
|
... invest_relation_input_capacity=1/6, |
|
128
|
|
|
... invest_relation_output_capacity=1/6, |
|
129
|
|
|
... inflow_conversion_factor=1, |
|
130
|
|
|
... outflow_conversion_factor=0.8) |
|
131
|
|
|
""" # noqa: E501 |
|
132
|
|
|
|
|
133
|
|
|
def __init__( |
|
134
|
|
|
self, *args, max_storage_level=1, min_storage_level=0, **kwargs |
|
135
|
|
|
): |
|
136
|
|
|
super().__init__(*args, **kwargs) |
|
137
|
|
|
self.nominal_storage_capacity = kwargs.get("nominal_storage_capacity") |
|
138
|
|
|
self.initial_storage_level = kwargs.get("initial_storage_level") |
|
139
|
|
|
self.balanced = kwargs.get("balanced", True) |
|
140
|
|
|
self.loss_rate = solph_sequence(kwargs.get("loss_rate", 0)) |
|
141
|
|
|
self.fixed_losses_relative = solph_sequence( |
|
142
|
|
|
kwargs.get("fixed_losses_relative", 0) |
|
143
|
|
|
) |
|
144
|
|
|
self.fixed_losses_absolute = solph_sequence( |
|
145
|
|
|
kwargs.get("fixed_losses_absolute", 0) |
|
146
|
|
|
) |
|
147
|
|
|
self.inflow_conversion_factor = solph_sequence( |
|
148
|
|
|
kwargs.get("inflow_conversion_factor", 1) |
|
149
|
|
|
) |
|
150
|
|
|
self.outflow_conversion_factor = solph_sequence( |
|
151
|
|
|
kwargs.get("outflow_conversion_factor", 1) |
|
152
|
|
|
) |
|
153
|
|
|
self.max_storage_level = solph_sequence(max_storage_level) |
|
154
|
|
|
self.min_storage_level = solph_sequence(min_storage_level) |
|
155
|
|
|
self.investment = kwargs.get("investment") |
|
156
|
|
|
self.invest_relation_input_output = kwargs.get( |
|
157
|
|
|
"invest_relation_input_output" |
|
158
|
|
|
) |
|
159
|
|
|
self.invest_relation_input_capacity = kwargs.get( |
|
160
|
|
|
"invest_relation_input_capacity" |
|
161
|
|
|
) |
|
162
|
|
|
self.invest_relation_output_capacity = kwargs.get( |
|
163
|
|
|
"invest_relation_output_capacity" |
|
164
|
|
|
) |
|
165
|
|
|
self._invest_group = isinstance(self.investment, Investment) |
|
166
|
|
|
|
|
167
|
|
|
# Check number of flows. |
|
168
|
|
|
self._check_number_of_flows() |
|
169
|
|
|
# Check for infeasible parameter combinations |
|
170
|
|
|
self._check_infeasible_parameter_combinations() |
|
171
|
|
|
|
|
172
|
|
|
# Check attributes for the investment mode. |
|
173
|
|
|
if self._invest_group is True: |
|
174
|
|
|
self._check_invest_attributes() |
|
175
|
|
|
|
|
176
|
|
|
# Check for old parameter names. This is a temporary fix and should |
|
177
|
|
|
# be removed once a general solution is found. |
|
178
|
|
|
# TODO: https://github.com/oemof/oemof-solph/issues/560 |
|
179
|
|
|
renamed_parameters = [ |
|
180
|
|
|
("nominal_capacity", "nominal_storage_capacity"), |
|
181
|
|
|
("initial_capacity", "initial_storage_level"), |
|
182
|
|
|
("capacity_loss", "loss_rate"), |
|
183
|
|
|
("capacity_min", "min_storage_level"), |
|
184
|
|
|
("capacity_max", "max_storage_level"), |
|
185
|
|
|
] |
|
186
|
|
|
messages = [ |
|
187
|
|
|
"`{0}` to `{1}`".format(old_name, new_name) |
|
188
|
|
|
for old_name, new_name in renamed_parameters |
|
189
|
|
|
if old_name in kwargs |
|
190
|
|
|
] |
|
191
|
|
|
if messages: |
|
192
|
|
|
message = ( |
|
193
|
|
|
"The following attributes have been renamed from v0.2 to v0.3:" |
|
194
|
|
|
"\n\n {}\n\n" |
|
195
|
|
|
"You are using the old names as parameters, thus setting " |
|
196
|
|
|
"deprecated\n" |
|
197
|
|
|
"attributes, which is not what you might have intended.\n" |
|
198
|
|
|
"Use the new names, or, if you know what you're doing, set " |
|
199
|
|
|
"these\n" |
|
200
|
|
|
"attributes explicitly after construction instead." |
|
201
|
|
|
) |
|
202
|
|
|
raise AttributeError(message.format("\n ".join(messages))) |
|
203
|
|
|
|
|
204
|
|
|
def _set_flows(self): |
|
205
|
|
|
for flow in self.inputs.values(): |
|
206
|
|
|
if ( |
|
207
|
|
|
self.invest_relation_input_capacity is not None |
|
208
|
|
|
and not isinstance(flow.investment, Investment) |
|
209
|
|
|
): |
|
210
|
|
|
flow.investment = Investment() |
|
211
|
|
|
for flow in self.outputs.values(): |
|
212
|
|
|
if ( |
|
213
|
|
|
self.invest_relation_output_capacity is not None |
|
214
|
|
|
and not isinstance(flow.investment, Investment) |
|
215
|
|
|
): |
|
216
|
|
|
flow.investment = Investment() |
|
217
|
|
|
|
|
218
|
|
|
def _check_invest_attributes(self): |
|
219
|
|
|
if self.investment and self.nominal_storage_capacity is not None: |
|
220
|
|
|
e1 = ( |
|
221
|
|
|
"If an investment object is defined the invest variable " |
|
222
|
|
|
"replaces the nominal_storage_capacity.\n Therefore the " |
|
223
|
|
|
"nominal_storage_capacity should be 'None'.\n" |
|
224
|
|
|
) |
|
225
|
|
|
raise AttributeError(e1) |
|
226
|
|
|
if ( |
|
227
|
|
|
self.invest_relation_input_output is not None |
|
228
|
|
|
and self.invest_relation_output_capacity is not None |
|
229
|
|
|
and self.invest_relation_input_capacity is not None |
|
230
|
|
|
): |
|
231
|
|
|
e2 = ( |
|
232
|
|
|
"Overdetermined. Three investment object will be coupled" |
|
233
|
|
|
"with three constraints. Set one invest relation to 'None'." |
|
234
|
|
|
) |
|
235
|
|
|
raise AttributeError(e2) |
|
236
|
|
|
if ( |
|
237
|
|
|
self.investment |
|
238
|
|
|
and sum(solph_sequence(self.fixed_losses_absolute)) != 0 |
|
239
|
|
|
and self.investment.existing == 0 |
|
240
|
|
|
and self.investment.minimum == 0 |
|
241
|
|
|
): |
|
242
|
|
|
e3 = ( |
|
243
|
|
|
"With fixed_losses_absolute > 0, either investment.existing " |
|
244
|
|
|
"or investment.minimum has to be non-zero." |
|
245
|
|
|
) |
|
246
|
|
|
raise AttributeError(e3) |
|
247
|
|
|
|
|
248
|
|
|
self._set_flows() |
|
249
|
|
|
|
|
250
|
|
|
def _check_number_of_flows(self): |
|
251
|
|
|
msg = "Only one {0} flow allowed in the GenericStorage {1}." |
|
252
|
|
|
check_node_object_for_missing_attribute(self, "inputs") |
|
253
|
|
|
check_node_object_for_missing_attribute(self, "outputs") |
|
254
|
|
|
if len(self.inputs) > 1: |
|
255
|
|
|
raise AttributeError(msg.format("input", self.label)) |
|
256
|
|
|
if len(self.outputs) > 1: |
|
257
|
|
|
raise AttributeError(msg.format("output", self.label)) |
|
258
|
|
|
|
|
259
|
|
|
def _check_infeasible_parameter_combinations(self): |
|
260
|
|
|
"""Checks for infeasible parameter combinations and raises error""" |
|
261
|
|
|
msg = ( |
|
262
|
|
|
"initial_storage_level must be greater or equal to " |
|
263
|
|
|
"min_storage_level and smaller or equal to " |
|
264
|
|
|
"max_storage_level." |
|
265
|
|
|
) |
|
266
|
|
|
if self.initial_storage_level is not None: |
|
267
|
|
|
if ( |
|
268
|
|
|
self.initial_storage_level < self.min_storage_level[0] |
|
269
|
|
|
or self.initial_storage_level > self.max_storage_level[0] |
|
270
|
|
|
): |
|
271
|
|
|
raise ValueError(msg) |
|
272
|
|
|
|
|
273
|
|
|
def constraint_group(self): |
|
274
|
|
|
if self._invest_group is True: |
|
275
|
|
|
return GenericInvestmentStorageBlock |
|
276
|
|
|
else: |
|
277
|
|
|
return GenericStorageBlock |
|
278
|
|
|
|
|
279
|
|
|
|
|
280
|
|
|
class GenericStorageBlock(ScalarBlock): |
|
281
|
|
|
r"""Storage without an :class:`.Investment` object. |
|
282
|
|
|
|
|
283
|
|
|
**The following sets are created:** (-> see basic sets at |
|
284
|
|
|
:class:`.Model` ) |
|
285
|
|
|
|
|
286
|
|
|
STORAGES |
|
287
|
|
|
A set with all :class:`.Storage` objects, which do not have an |
|
288
|
|
|
attr:`investment` of type :class:`.Investment`. |
|
289
|
|
|
|
|
290
|
|
|
STORAGES_BALANCED |
|
291
|
|
|
A set of all :py:class:`~.GenericStorage` objects, with 'balanced' attribute set |
|
292
|
|
|
to True. |
|
293
|
|
|
|
|
294
|
|
|
STORAGES_WITH_INVEST_FLOW_REL |
|
295
|
|
|
A set with all :class:`.Storage` objects with two investment flows |
|
296
|
|
|
coupled with the 'invest_relation_input_output' attribute. |
|
297
|
|
|
|
|
298
|
|
|
**The following variables are created:** |
|
299
|
|
|
|
|
300
|
|
|
storage_content |
|
301
|
|
|
Storage content for every storage and timestep. The value for the |
|
302
|
|
|
storage content at the beginning is set by the parameter |
|
303
|
|
|
`initial_storage_level` or not set if `initial_storage_level` is None. |
|
304
|
|
|
The variable of storage s and timestep t can be accessed by: |
|
305
|
|
|
`om.Storage.storage_content[s, t]` |
|
306
|
|
|
|
|
307
|
|
|
**The following constraints are created:** |
|
308
|
|
|
|
|
309
|
|
|
Set storage_content of last time step to one at t=0 if balanced == True |
|
310
|
|
|
.. math:: |
|
311
|
|
|
E(t_{last}) = &E(-1) |
|
312
|
|
|
|
|
313
|
|
|
Storage balance :attr:`om.Storage.balance[n, t]` |
|
314
|
|
|
.. math:: E(t) = &E(t-1) \cdot |
|
315
|
|
|
(1 - \beta(t)) ^{\tau(t)/(t_u)} \\ |
|
316
|
|
|
&- \gamma(t)\cdot E_{nom} \cdot {\tau(t)/(t_u)}\\ |
|
317
|
|
|
&- \delta(t) \cdot {\tau(t)/(t_u)}\\ |
|
318
|
|
|
&- \frac{\dot{E}_o(t)}{\eta_o(t)} \cdot \tau(t) |
|
319
|
|
|
+ \dot{E}_i(t) \cdot \eta_i(t) \cdot \tau(t) |
|
320
|
|
|
|
|
321
|
|
|
Connect the invest variables of the input and the output flow. |
|
322
|
|
|
.. math:: |
|
323
|
|
|
InvestmentFlowBlock.invest(source(n), n) + existing = \\ |
|
324
|
|
|
(InvestmentFlowBlock.invest(n, target(n)) + existing) * \\ |
|
325
|
|
|
invest\_relation\_input\_output(n) \\ |
|
326
|
|
|
\forall n \in \textrm{INVEST\_REL\_IN\_OUT} |
|
327
|
|
|
|
|
328
|
|
|
|
|
329
|
|
|
|
|
330
|
|
|
=========================== ======================= ========= |
|
331
|
|
|
symbol explanation attribute |
|
332
|
|
|
=========================== ======================= ========= |
|
333
|
|
|
:math:`E(t)` energy currently stored `storage_content` |
|
334
|
|
|
:math:`E_{nom}` nominal capacity of `nominal_storage_capacity` |
|
335
|
|
|
the energy storage |
|
336
|
|
|
:math:`c(-1)` state before `initial_storage_level` |
|
337
|
|
|
initial time step |
|
338
|
|
|
:math:`c_{min}(t)` minimum allowed storage `min_storage_level[t]` |
|
339
|
|
|
:math:`c_{max}(t)` maximum allowed storage `max_storage_level[t]` |
|
340
|
|
|
:math:`\beta(t)` fraction of lost energy `loss_rate[t]` |
|
341
|
|
|
as share of |
|
342
|
|
|
:math:`E(t)` |
|
343
|
|
|
per time unit |
|
344
|
|
|
:math:`\gamma(t)` fixed loss of energy `fixed_losses_relative[t]` |
|
345
|
|
|
relative to |
|
346
|
|
|
:math:`E_{nom}` per |
|
347
|
|
|
time unit |
|
348
|
|
|
:math:`\delta(t)` absolute fixed loss `fixed_losses_absolute[t]` |
|
349
|
|
|
of energy per |
|
350
|
|
|
time unit |
|
351
|
|
|
:math:`\dot{E}_i(t)` energy flowing in `inputs` |
|
352
|
|
|
:math:`\dot{E}_o(t)` energy flowing out `outputs` |
|
353
|
|
|
:math:`\eta_i(t)` conversion factor `inflow_conversion_factor[t]` |
|
354
|
|
|
(i.e. efficiency) |
|
355
|
|
|
when storing energy |
|
356
|
|
|
:math:`\eta_o(t)` conversion factor when `outflow_conversion_factor[t]` |
|
357
|
|
|
(i.e. efficiency) |
|
358
|
|
|
taking stored energy |
|
359
|
|
|
:math:`\tau(t)` duration of time step |
|
360
|
|
|
:math:`t_u` time unit of losses |
|
361
|
|
|
:math:`\beta(t)`, |
|
362
|
|
|
:math:`\gamma(t)` |
|
363
|
|
|
:math:`\delta(t)` and |
|
364
|
|
|
timeincrement |
|
365
|
|
|
:math:`\tau(t)` |
|
366
|
|
|
=========================== ======================= ========= |
|
367
|
|
|
|
|
368
|
|
|
**The following parts of the objective function are created:** |
|
369
|
|
|
|
|
370
|
|
|
Nothing added to the objective function. |
|
371
|
|
|
|
|
372
|
|
|
|
|
373
|
|
|
""" # noqa: E501 |
|
374
|
|
|
|
|
375
|
|
|
CONSTRAINT_GROUP = True |
|
376
|
|
|
|
|
377
|
|
|
def __init__(self, *args, **kwargs): |
|
378
|
|
|
super().__init__(*args, **kwargs) |
|
379
|
|
|
|
|
380
|
|
|
def _create(self, group=None): |
|
381
|
|
|
""" |
|
382
|
|
|
Parameters |
|
383
|
|
|
---------- |
|
384
|
|
|
group : list |
|
385
|
|
|
List containing storage objects. |
|
386
|
|
|
e.g. groups=[storage1, storage2,..] |
|
387
|
|
|
""" |
|
388
|
|
|
m = self.parent_block() |
|
389
|
|
|
|
|
390
|
|
|
if group is None: |
|
391
|
|
|
return None |
|
392
|
|
|
|
|
393
|
|
|
i = {n: [i for i in n.inputs][0] for n in group} |
|
394
|
|
|
o = {n: [o for o in n.outputs][0] for n in group} |
|
395
|
|
|
|
|
396
|
|
|
# ************* SETS ********************************* |
|
397
|
|
|
|
|
398
|
|
|
self.STORAGES = Set(initialize=[n for n in group]) |
|
399
|
|
|
|
|
400
|
|
|
self.STORAGES_BALANCED = Set( |
|
401
|
|
|
initialize=[n for n in group if n.balanced is True] |
|
402
|
|
|
) |
|
403
|
|
|
|
|
404
|
|
|
self.STORAGES_INITITAL_LEVEL = Set( |
|
405
|
|
|
initialize=[ |
|
406
|
|
|
n for n in group if n.initial_storage_level is not None |
|
407
|
|
|
] |
|
408
|
|
|
) |
|
409
|
|
|
|
|
410
|
|
|
self.STORAGES_WITH_INVEST_FLOW_REL = Set( |
|
411
|
|
|
initialize=[ |
|
412
|
|
|
n for n in group if n.invest_relation_input_output is not None |
|
413
|
|
|
] |
|
414
|
|
|
) |
|
415
|
|
|
|
|
416
|
|
|
# ************* VARIABLES ***************************** |
|
417
|
|
|
|
|
418
|
|
|
def _storage_content_bound_rule(block, n, t): |
|
419
|
|
|
""" |
|
420
|
|
|
Rule definition for bounds of storage_content variable of |
|
421
|
|
|
storage n in timestep t. |
|
422
|
|
|
""" |
|
423
|
|
|
bounds = ( |
|
424
|
|
|
n.nominal_storage_capacity * n.min_storage_level[t], |
|
425
|
|
|
n.nominal_storage_capacity * n.max_storage_level[t], |
|
426
|
|
|
) |
|
427
|
|
|
return bounds |
|
428
|
|
|
|
|
429
|
|
|
self.storage_content = Var( |
|
430
|
|
|
self.STORAGES, m.TIMEPOINTS, bounds=_storage_content_bound_rule |
|
431
|
|
|
) |
|
432
|
|
|
|
|
433
|
|
|
# set the initial storage content |
|
434
|
|
|
# ToDo: More elegant code possible? |
|
435
|
|
|
for n in group: |
|
436
|
|
|
if n.initial_storage_level is not None: |
|
437
|
|
|
self.storage_content[n, 0] = ( |
|
438
|
|
|
n.initial_storage_level * n.nominal_storage_capacity |
|
439
|
|
|
) |
|
440
|
|
|
self.storage_content[n, 0].fix() |
|
441
|
|
|
|
|
442
|
|
|
# ************* Constraints *************************** |
|
443
|
|
|
|
|
444
|
|
View Code Duplication |
def _storage_balance_rule(block, n, t): |
|
|
|
|
|
|
445
|
|
|
""" |
|
446
|
|
|
Rule definition for the storage balance of every storage n and |
|
447
|
|
|
every timestep. |
|
448
|
|
|
""" |
|
449
|
|
|
expr = 0 |
|
450
|
|
|
expr += block.storage_content[n, t + 1] |
|
451
|
|
|
expr += ( |
|
452
|
|
|
-block.storage_content[n, t] |
|
453
|
|
|
* (1 - n.loss_rate[t]) ** m.timeincrement[t] |
|
454
|
|
|
) |
|
455
|
|
|
expr += ( |
|
456
|
|
|
n.fixed_losses_relative[t] |
|
457
|
|
|
* n.nominal_storage_capacity |
|
458
|
|
|
* m.timeincrement[t] |
|
459
|
|
|
) |
|
460
|
|
|
expr += n.fixed_losses_absolute[t] * m.timeincrement[t] |
|
461
|
|
|
expr += ( |
|
462
|
|
|
-m.flow[i[n], n, t] * n.inflow_conversion_factor[t] |
|
|
|
|
|
|
463
|
|
|
) * m.timeincrement[t] |
|
464
|
|
|
expr += ( |
|
465
|
|
|
m.flow[n, o[n], t] / n.outflow_conversion_factor[t] |
|
|
|
|
|
|
466
|
|
|
) * m.timeincrement[t] |
|
467
|
|
|
return expr == 0 |
|
468
|
|
|
|
|
469
|
|
|
self.balance = Constraint( |
|
470
|
|
|
self.STORAGES, m.TIMESTEPS, rule=_storage_balance_rule |
|
471
|
|
|
) |
|
472
|
|
|
|
|
473
|
|
|
def _balanced_storage_rule(block, n): |
|
474
|
|
|
""" |
|
475
|
|
|
Storage content of last time step == initial storage content |
|
476
|
|
|
if balanced. |
|
477
|
|
|
""" |
|
478
|
|
|
return ( |
|
479
|
|
|
block.storage_content[n, m.TIMEPOINTS.at(-1)] |
|
480
|
|
|
== block.storage_content[n, m.TIMEPOINTS.at(1)] |
|
481
|
|
|
) |
|
482
|
|
|
|
|
483
|
|
|
self.balanced_cstr = Constraint( |
|
484
|
|
|
self.STORAGES_BALANCED, rule=_balanced_storage_rule |
|
485
|
|
|
) |
|
486
|
|
|
|
|
487
|
|
View Code Duplication |
def _power_coupled(block, n): |
|
|
|
|
|
|
488
|
|
|
""" |
|
489
|
|
|
Rule definition for constraint to connect the input power |
|
490
|
|
|
and output power |
|
491
|
|
|
""" |
|
492
|
|
|
expr = ( |
|
493
|
|
|
m.InvestmentFlowBlock.invest[n, o[n]] |
|
|
|
|
|
|
494
|
|
|
+ m.flows[n, o[n]].investment.existing |
|
495
|
|
|
) * n.invest_relation_input_output == ( |
|
496
|
|
|
m.InvestmentFlowBlock.invest[i[n], n] |
|
|
|
|
|
|
497
|
|
|
+ m.flows[i[n], n].investment.existing |
|
498
|
|
|
) |
|
499
|
|
|
return expr |
|
500
|
|
|
|
|
501
|
|
|
self.power_coupled = Constraint( |
|
502
|
|
|
self.STORAGES_WITH_INVEST_FLOW_REL, rule=_power_coupled |
|
503
|
|
|
) |
|
504
|
|
|
|
|
505
|
|
|
def _objective_expression(self): |
|
506
|
|
|
r""" |
|
507
|
|
|
Objective expression for storages with no investment. |
|
508
|
|
|
Note: This adds nothing as variable costs are already |
|
509
|
|
|
added in the Block :class:`FlowBlock`. |
|
510
|
|
|
""" |
|
511
|
|
|
if not hasattr(self, "STORAGES"): |
|
512
|
|
|
return 0 |
|
513
|
|
|
|
|
514
|
|
|
return 0 |
|
515
|
|
|
|
|
516
|
|
|
|
|
517
|
|
|
class GenericInvestmentStorageBlock(ScalarBlock): |
|
518
|
|
|
r""" |
|
519
|
|
|
Block for all storages with :attr:`Investment` being not None. |
|
520
|
|
|
See :class:`oemof.solph.options.Investment` for all parameters of the |
|
521
|
|
|
Investment class. |
|
522
|
|
|
|
|
523
|
|
|
**Variables** |
|
524
|
|
|
|
|
525
|
|
|
All Storages are indexed by :math:`n`, which is omitted in the following |
|
526
|
|
|
for the sake of convenience. |
|
527
|
|
|
The following variables are created as attributes of |
|
528
|
|
|
:attr:`om.InvestmentStorage`: |
|
529
|
|
|
|
|
530
|
|
|
* :math:`P_i(t)` |
|
531
|
|
|
|
|
532
|
|
|
Inflow of the storage |
|
533
|
|
|
(created in :class:`oemof.solph.models.BaseModel`). |
|
534
|
|
|
|
|
535
|
|
|
* :math:`P_o(t)` |
|
536
|
|
|
|
|
537
|
|
|
Outflow of the storage |
|
538
|
|
|
(created in :class:`oemof.solph.models.BaseModel`). |
|
539
|
|
|
|
|
540
|
|
|
* :math:`E(t)` |
|
541
|
|
|
|
|
542
|
|
|
Current storage content (Absolute level of stored energy). |
|
543
|
|
|
|
|
544
|
|
|
* :math:`E_{invest}` |
|
545
|
|
|
|
|
546
|
|
|
Invested (nominal) capacity of the storage. |
|
547
|
|
|
|
|
548
|
|
|
* :math:`E(-1)` |
|
549
|
|
|
|
|
550
|
|
|
Initial storage content (before timestep 0). |
|
551
|
|
|
|
|
552
|
|
|
* :math:`b_{invest}` |
|
553
|
|
|
|
|
554
|
|
|
Binary variable for the status of the investment, if |
|
555
|
|
|
:attr:`nonconvex` is `True`. |
|
556
|
|
|
|
|
557
|
|
|
**Constraints** |
|
558
|
|
|
|
|
559
|
|
|
The following constraints are created for all investment storages: |
|
560
|
|
|
|
|
561
|
|
|
Storage balance (Same as for :class:`.GenericStorageBlock`) |
|
562
|
|
|
|
|
563
|
|
|
.. math:: E(t) = &E(t-1) \cdot |
|
564
|
|
|
(1 - \beta(t)) ^{\tau(t)/(t_u)} \\ |
|
565
|
|
|
&- \gamma(t)\cdot (E_{exist} + E_{invest}) \cdot {\tau(t)/(t_u)}\\ |
|
566
|
|
|
&- \delta(t) \cdot {\tau(t)/(t_u)}\\ |
|
567
|
|
|
&- \frac{P_o(t)}{\eta_o(t)} \cdot \tau(t) |
|
568
|
|
|
+ P_i(t) \cdot \eta_i(t) \cdot \tau(t) |
|
569
|
|
|
|
|
570
|
|
|
Depending on the attribute :attr:`nonconvex`, the constraints for the |
|
571
|
|
|
bounds of the decision variable :math:`E_{invest}` are different:\ |
|
572
|
|
|
|
|
573
|
|
|
* :attr:`nonconvex = False` |
|
574
|
|
|
|
|
575
|
|
|
.. math:: |
|
576
|
|
|
E_{invest, min} \le E_{invest} \le E_{invest, max} |
|
577
|
|
|
|
|
578
|
|
|
* :attr:`nonconvex = True` |
|
579
|
|
|
|
|
580
|
|
|
.. math:: |
|
581
|
|
|
& |
|
582
|
|
|
E_{invest, min} \cdot b_{invest} \le E_{invest}\\ |
|
583
|
|
|
& |
|
584
|
|
|
E_{invest} \le E_{invest, max} \cdot b_{invest}\\ |
|
585
|
|
|
|
|
586
|
|
|
The following constraints are created depending on the attributes of |
|
587
|
|
|
the :class:`.components.GenericStorage`: |
|
588
|
|
|
|
|
589
|
|
|
* :attr:`initial_storage_level is None` |
|
590
|
|
|
|
|
591
|
|
|
Constraint for a variable initial storage content: |
|
592
|
|
|
|
|
593
|
|
|
.. math:: |
|
594
|
|
|
E(-1) \le E_{invest} + E_{exist} |
|
595
|
|
|
|
|
596
|
|
|
* :attr:`initial_storage_level is not None` |
|
597
|
|
|
|
|
598
|
|
|
An initial value for the storage content is given: |
|
599
|
|
|
|
|
600
|
|
|
.. math:: |
|
601
|
|
|
E(-1) = (E_{invest} + E_{exist}) \cdot c(-1) |
|
602
|
|
|
|
|
603
|
|
|
* :attr:`balanced=True` |
|
604
|
|
|
|
|
605
|
|
|
The energy content of storage of the first and the last timestep |
|
606
|
|
|
are set equal: |
|
607
|
|
|
|
|
608
|
|
|
.. math:: |
|
609
|
|
|
E(-1) = E(t_{last}) |
|
610
|
|
|
|
|
611
|
|
|
* :attr:`invest_relation_input_capacity is not None` |
|
612
|
|
|
|
|
613
|
|
|
Connect the invest variables of the storage and the input flow: |
|
614
|
|
|
|
|
615
|
|
|
.. math:: |
|
616
|
|
|
P_{i,invest} + P_{i,exist} = |
|
617
|
|
|
(E_{invest} + E_{exist}) \cdot r_{cap,in} |
|
618
|
|
|
|
|
619
|
|
|
* :attr:`invest_relation_output_capacity is not None` |
|
620
|
|
|
|
|
621
|
|
|
Connect the invest variables of the storage and the output flow: |
|
622
|
|
|
|
|
623
|
|
|
.. math:: |
|
624
|
|
|
P_{o,invest} + P_{o,exist} = |
|
625
|
|
|
(E_{invest} + E_{exist}) \cdot r_{cap,out} |
|
626
|
|
|
|
|
627
|
|
|
* :attr:`invest_relation_input_output is not None` |
|
628
|
|
|
|
|
629
|
|
|
Connect the invest variables of the input and the output flow: |
|
630
|
|
|
|
|
631
|
|
|
.. math:: |
|
632
|
|
|
P_{i,invest} + P_{i,exist} = |
|
633
|
|
|
(P_{o,invest} + P_{o,exist}) \cdot r_{in,out} |
|
634
|
|
|
|
|
635
|
|
|
* :attr:`max_storage_level` |
|
636
|
|
|
|
|
637
|
|
|
Rule for upper bound constraint for the storage content: |
|
638
|
|
|
|
|
639
|
|
|
.. math:: |
|
640
|
|
|
E(t) \leq E_{invest} \cdot c_{max}(t) |
|
641
|
|
|
|
|
642
|
|
|
* :attr:`min_storage_level` |
|
643
|
|
|
|
|
644
|
|
|
Rule for lower bound constraint for the storage content: |
|
645
|
|
|
|
|
646
|
|
|
.. math:: E(t) \geq E_{invest} \cdot c_{min}(t) |
|
647
|
|
|
|
|
648
|
|
|
|
|
649
|
|
|
**Objective function** |
|
650
|
|
|
|
|
651
|
|
|
The part of the objective function added by the investment storages |
|
652
|
|
|
also depends on whether a convex or nonconvex |
|
653
|
|
|
investment option is selected. The following parts of the objective |
|
654
|
|
|
function are created: |
|
655
|
|
|
|
|
656
|
|
|
* :attr:`nonconvex = False` |
|
657
|
|
|
|
|
658
|
|
|
.. math:: |
|
659
|
|
|
E_{invest} \cdot c_{invest,var} |
|
660
|
|
|
|
|
661
|
|
|
* :attr:`nonconvex = True` |
|
662
|
|
|
|
|
663
|
|
|
.. math:: |
|
664
|
|
|
E_{invest} \cdot c_{invest,var} |
|
665
|
|
|
+ c_{invest,fix} \cdot b_{invest}\\ |
|
666
|
|
|
|
|
667
|
|
|
The total value of all investment costs of all *InvestmentStorages* |
|
668
|
|
|
can be retrieved calling |
|
669
|
|
|
:meth:`om.GenericInvestmentStorageBlock.investment_costs.expr()`. |
|
670
|
|
|
|
|
671
|
|
|
.. csv-table:: List of Variables |
|
672
|
|
|
:header: "symbol", "attribute", "explanation" |
|
673
|
|
|
:widths: 1, 1, 1 |
|
674
|
|
|
|
|
675
|
|
|
":math:`P_i(t)`", ":attr:`flow[i[n], n, t]`", "Inflow of the storage" |
|
676
|
|
|
":math:`P_o(t)`", ":attr:`flow[n, o[n], t]`", "Outlfow of the storage" |
|
677
|
|
|
":math:`E(t)`", ":attr:`storage_content[n, t]`", "Current storage |
|
678
|
|
|
content (current absolute stored energy)" |
|
679
|
|
|
":math:`E_{invest}`", ":attr:`invest[n, t]`", "Invested (nominal) |
|
680
|
|
|
capacity of the storage" |
|
681
|
|
|
":math:`E(-1)`", ":attr:`init_cap[n]`", "Initial storage capacity |
|
682
|
|
|
(before timestep 0)" |
|
683
|
|
|
":math:`b_{invest}`", ":attr:`invest_status[i, o]`", "Binary variable |
|
684
|
|
|
for the status of investment" |
|
685
|
|
|
":math:`P_{i,invest}`", ":attr:`InvestmentFlowBlock.invest[i[n], n]`", |
|
686
|
|
|
"Invested (nominal) inflow (Investmentflow)" |
|
687
|
|
|
":math:`P_{o,invest}`", ":attr:`InvestmentFlowBlock.invest[n, o[n]]`", |
|
688
|
|
|
"Invested (nominal) outflow (Investmentflow)" |
|
689
|
|
|
|
|
690
|
|
|
.. csv-table:: List of Parameters |
|
691
|
|
|
:header: "symbol", "attribute", "explanation" |
|
692
|
|
|
:widths: 1, 1, 1 |
|
693
|
|
|
|
|
694
|
|
|
":math:`E_{exist}`", "`flows[i, o].investment.existing`", " |
|
695
|
|
|
Existing storage capacity" |
|
696
|
|
|
":math:`E_{invest,min}`", "`flows[i, o].investment.minimum`", " |
|
697
|
|
|
Minimum investment value" |
|
698
|
|
|
":math:`E_{invest,max}`", "`flows[i, o].investment.maximum`", " |
|
699
|
|
|
Maximum investment value" |
|
700
|
|
|
":math:`P_{i,exist}`", "`flows[i[n], n].investment.existing` |
|
701
|
|
|
", "Existing inflow capacity" |
|
702
|
|
|
":math:`P_{o,exist}`", "`flows[n, o[n]].investment.existing` |
|
703
|
|
|
", "Existing outlfow capacity" |
|
704
|
|
|
":math:`c_{invest,var}`", "`flows[i, o].investment.ep_costs` |
|
705
|
|
|
", "Variable investment costs" |
|
706
|
|
|
":math:`c_{invest,fix}`", "`flows[i, o].investment.offset`", " |
|
707
|
|
|
Fix investment costs" |
|
708
|
|
|
":math:`r_{cap,in}`", ":attr:`invest_relation_input_capacity`", " |
|
709
|
|
|
Relation of storage capacity and nominal inflow" |
|
710
|
|
|
":math:`r_{cap,out}`", ":attr:`invest_relation_output_capacity`", " |
|
711
|
|
|
Relation of storage capacity and nominal outflow" |
|
712
|
|
|
":math:`r_{in,out}`", ":attr:`invest_relation_input_output`", " |
|
713
|
|
|
Relation of nominal in- and outflow" |
|
714
|
|
|
":math:`\beta(t)`", "`loss_rate[t]`", "Fraction of lost energy |
|
715
|
|
|
as share of :math:`E(t)` per time unit" |
|
716
|
|
|
":math:`\gamma(t)`", "`fixed_losses_relative[t]`", "Fixed loss |
|
717
|
|
|
of energy relative to :math:`E_{invest} + E_{exist}` per time unit" |
|
718
|
|
|
":math:`\delta(t)`", "`fixed_losses_absolute[t]`", "Absolute |
|
719
|
|
|
fixed loss of energy per time unit" |
|
720
|
|
|
":math:`\eta_i(t)`", "`inflow_conversion_factor[t]`", " |
|
721
|
|
|
Conversion factor (i.e. efficiency) when storing energy" |
|
722
|
|
|
":math:`\eta_o(t)`", "`outflow_conversion_factor[t]`", " |
|
723
|
|
|
Conversion factor when (i.e. efficiency) taking stored energy" |
|
724
|
|
|
":math:`c(-1)`", "`initial_storage_level`", "Initial relativ |
|
725
|
|
|
storage content (before timestep 0)" |
|
726
|
|
|
":math:`c_{max}`", "`flows[i, o].max[t]`", "Normed maximum |
|
727
|
|
|
value of storage content" |
|
728
|
|
|
":math:`c_{min}`", "`flows[i, o].min[t]`", "Normed minimum |
|
729
|
|
|
value of storage content" |
|
730
|
|
|
":math:`\tau(t)`", "", "Duration of time step" |
|
731
|
|
|
":math:`t_u`", "", "Time unit of losses :math:`\beta(t)`, |
|
732
|
|
|
:math:`\gamma(t)`, :math:`\delta(t)` and timeincrement :math:`\tau(t)`" |
|
733
|
|
|
|
|
734
|
|
|
""" |
|
735
|
|
|
|
|
736
|
|
|
CONSTRAINT_GROUP = True |
|
737
|
|
|
|
|
738
|
|
|
def __init__(self, *args, **kwargs): |
|
739
|
|
|
super().__init__(*args, **kwargs) |
|
740
|
|
|
|
|
741
|
|
|
def _create(self, group=None): |
|
742
|
|
|
""" """ |
|
743
|
|
|
m = self.parent_block() |
|
744
|
|
|
if group is None: |
|
745
|
|
|
return None |
|
746
|
|
|
|
|
747
|
|
|
# ########################## SETS ##################################### |
|
748
|
|
|
|
|
749
|
|
|
self.INVESTSTORAGES = Set(initialize=[n for n in group]) |
|
750
|
|
|
|
|
751
|
|
|
self.CONVEX_INVESTSTORAGES = Set( |
|
752
|
|
|
initialize=[n for n in group if n.investment.nonconvex is False] |
|
753
|
|
|
) |
|
754
|
|
|
|
|
755
|
|
|
self.NON_CONVEX_INVESTSTORAGES = Set( |
|
756
|
|
|
initialize=[n for n in group if n.investment.nonconvex is True] |
|
757
|
|
|
) |
|
758
|
|
|
|
|
759
|
|
|
self.INVESTSTORAGES_BALANCED = Set( |
|
760
|
|
|
initialize=[n for n in group if n.balanced is True] |
|
761
|
|
|
) |
|
762
|
|
|
|
|
763
|
|
|
self.INVESTSTORAGES_NO_INIT_CONTENT = Set( |
|
764
|
|
|
initialize=[n for n in group if n.initial_storage_level is None] |
|
765
|
|
|
) |
|
766
|
|
|
|
|
767
|
|
|
self.INVESTSTORAGES_INIT_CONTENT = Set( |
|
768
|
|
|
initialize=[ |
|
769
|
|
|
n for n in group if n.initial_storage_level is not None |
|
770
|
|
|
] |
|
771
|
|
|
) |
|
772
|
|
|
|
|
773
|
|
|
self.INVEST_REL_CAP_IN = Set( |
|
774
|
|
|
initialize=[ |
|
775
|
|
|
n |
|
776
|
|
|
for n in group |
|
777
|
|
|
if n.invest_relation_input_capacity is not None |
|
778
|
|
|
] |
|
779
|
|
|
) |
|
780
|
|
|
|
|
781
|
|
|
self.INVEST_REL_CAP_OUT = Set( |
|
782
|
|
|
initialize=[ |
|
783
|
|
|
n |
|
784
|
|
|
for n in group |
|
785
|
|
|
if n.invest_relation_output_capacity is not None |
|
786
|
|
|
] |
|
787
|
|
|
) |
|
788
|
|
|
|
|
789
|
|
|
self.INVEST_REL_IN_OUT = Set( |
|
790
|
|
|
initialize=[ |
|
791
|
|
|
n for n in group if n.invest_relation_input_output is not None |
|
792
|
|
|
] |
|
793
|
|
|
) |
|
794
|
|
|
|
|
795
|
|
|
# The storage content is a non-negative variable, therefore it makes no |
|
796
|
|
|
# sense to create an additional constraint if the lower bound is zero |
|
797
|
|
|
# for all time steps. |
|
798
|
|
|
self.MIN_INVESTSTORAGES = Set( |
|
799
|
|
|
initialize=[ |
|
800
|
|
|
n |
|
801
|
|
|
for n in group |
|
802
|
|
|
if sum([n.min_storage_level[t] for t in m.TIMESTEPS]) > 0 |
|
803
|
|
|
] |
|
804
|
|
|
) |
|
805
|
|
|
|
|
806
|
|
|
# ######################### Variables ################################ |
|
807
|
|
|
self.storage_content = Var( |
|
808
|
|
|
self.INVESTSTORAGES, m.TIMESTEPS, within=NonNegativeReals |
|
809
|
|
|
) |
|
810
|
|
|
|
|
811
|
|
|
def _storage_investvar_bound_rule(block, n): |
|
812
|
|
|
""" |
|
813
|
|
|
Rule definition to bound the invested storage capacity `invest`. |
|
814
|
|
|
""" |
|
815
|
|
|
if n in self.CONVEX_INVESTSTORAGES: |
|
816
|
|
|
return n.investment.minimum, n.investment.maximum |
|
817
|
|
|
elif n in self.NON_CONVEX_INVESTSTORAGES: |
|
818
|
|
|
return 0, n.investment.maximum |
|
819
|
|
|
|
|
820
|
|
|
self.invest = Var( |
|
821
|
|
|
self.INVESTSTORAGES, |
|
822
|
|
|
within=NonNegativeReals, |
|
823
|
|
|
bounds=_storage_investvar_bound_rule, |
|
824
|
|
|
) |
|
825
|
|
|
|
|
826
|
|
|
self.init_content = Var(self.INVESTSTORAGES, within=NonNegativeReals) |
|
827
|
|
|
|
|
828
|
|
|
# create status variable for a non-convex investment storage |
|
829
|
|
|
self.invest_status = Var(self.NON_CONVEX_INVESTSTORAGES, within=Binary) |
|
830
|
|
|
|
|
831
|
|
|
# ######################### CONSTRAINTS ############################### |
|
832
|
|
|
i = {n: [i for i in n.inputs][0] for n in group} |
|
833
|
|
|
o = {n: [o for o in n.outputs][0] for n in group} |
|
834
|
|
|
|
|
835
|
|
|
reduced_timesteps = [x for x in m.TIMESTEPS if x > 0] |
|
836
|
|
|
|
|
837
|
|
|
def _inv_storage_init_content_max_rule(block, n): |
|
838
|
|
|
"""Constraint for a variable initial storage capacity.""" |
|
839
|
|
|
return ( |
|
840
|
|
|
block.init_content[n] |
|
841
|
|
|
<= n.investment.existing + block.invest[n] |
|
842
|
|
|
) |
|
843
|
|
|
|
|
844
|
|
|
self.init_content_limit = Constraint( |
|
845
|
|
|
self.INVESTSTORAGES_NO_INIT_CONTENT, |
|
846
|
|
|
rule=_inv_storage_init_content_max_rule, |
|
847
|
|
|
) |
|
848
|
|
|
|
|
849
|
|
|
def _inv_storage_init_content_fix_rule(block, n): |
|
850
|
|
|
"""Constraint for a fixed initial storage capacity.""" |
|
851
|
|
|
return block.init_content[n] == n.initial_storage_level * ( |
|
852
|
|
|
n.investment.existing + block.invest[n] |
|
853
|
|
|
) |
|
854
|
|
|
|
|
855
|
|
|
self.init_content_fix = Constraint( |
|
856
|
|
|
self.INVESTSTORAGES_INIT_CONTENT, |
|
857
|
|
|
rule=_inv_storage_init_content_fix_rule, |
|
858
|
|
|
) |
|
859
|
|
|
|
|
860
|
|
|
def _storage_balance_first_rule(block, n): |
|
861
|
|
|
""" |
|
862
|
|
|
Rule definition for the storage balance of every storage n for the |
|
863
|
|
|
first time step. |
|
864
|
|
|
""" |
|
865
|
|
|
expr = 0 |
|
866
|
|
|
expr += block.storage_content[n, 0] |
|
867
|
|
|
expr += ( |
|
868
|
|
|
-block.init_content[n] |
|
869
|
|
|
* (1 - n.loss_rate[0]) ** m.timeincrement[0] |
|
870
|
|
|
) |
|
871
|
|
|
expr += ( |
|
872
|
|
|
n.fixed_losses_relative[0] |
|
873
|
|
|
* (n.investment.existing + self.invest[n]) |
|
874
|
|
|
* m.timeincrement[0] |
|
875
|
|
|
) |
|
876
|
|
|
expr += n.fixed_losses_absolute[0] * m.timeincrement[0] |
|
877
|
|
|
expr += ( |
|
878
|
|
|
-m.flow[i[n], n, 0] * n.inflow_conversion_factor[0] |
|
|
|
|
|
|
879
|
|
|
) * m.timeincrement[0] |
|
880
|
|
|
expr += ( |
|
881
|
|
|
m.flow[n, o[n], 0] / n.outflow_conversion_factor[0] |
|
|
|
|
|
|
882
|
|
|
) * m.timeincrement[0] |
|
883
|
|
|
return expr == 0 |
|
884
|
|
|
|
|
885
|
|
|
self.balance_first = Constraint( |
|
886
|
|
|
self.INVESTSTORAGES, rule=_storage_balance_first_rule |
|
887
|
|
|
) |
|
888
|
|
|
|
|
889
|
|
View Code Duplication |
def _storage_balance_rule(block, n, t): |
|
|
|
|
|
|
890
|
|
|
""" |
|
891
|
|
|
Rule definition for the storage balance of every storage n for the |
|
892
|
|
|
every time step but the first. |
|
893
|
|
|
""" |
|
894
|
|
|
expr = 0 |
|
895
|
|
|
expr += block.storage_content[n, t] |
|
896
|
|
|
expr += ( |
|
897
|
|
|
-block.storage_content[n, t - 1] |
|
898
|
|
|
* (1 - n.loss_rate[t]) ** m.timeincrement[t] |
|
899
|
|
|
) |
|
900
|
|
|
expr += ( |
|
901
|
|
|
n.fixed_losses_relative[t] |
|
902
|
|
|
* (n.investment.existing + self.invest[n]) |
|
903
|
|
|
* m.timeincrement[t] |
|
904
|
|
|
) |
|
905
|
|
|
expr += n.fixed_losses_absolute[t] * m.timeincrement[t] |
|
906
|
|
|
expr += ( |
|
907
|
|
|
-m.flow[i[n], n, t] * n.inflow_conversion_factor[t] |
|
|
|
|
|
|
908
|
|
|
) * m.timeincrement[t] |
|
909
|
|
|
expr += ( |
|
910
|
|
|
m.flow[n, o[n], t] / n.outflow_conversion_factor[t] |
|
|
|
|
|
|
911
|
|
|
) * m.timeincrement[t] |
|
912
|
|
|
return expr == 0 |
|
913
|
|
|
|
|
914
|
|
|
self.balance = Constraint( |
|
915
|
|
|
self.INVESTSTORAGES, reduced_timesteps, rule=_storage_balance_rule |
|
916
|
|
|
) |
|
917
|
|
|
|
|
918
|
|
|
def _balanced_storage_rule(block, n): |
|
919
|
|
|
return ( |
|
920
|
|
|
block.storage_content[n, m.TIMESTEPS[-1]] |
|
921
|
|
|
== block.init_content[n] |
|
922
|
|
|
) |
|
923
|
|
|
|
|
924
|
|
|
self.balanced_cstr = Constraint( |
|
925
|
|
|
self.INVESTSTORAGES_BALANCED, rule=_balanced_storage_rule |
|
926
|
|
|
) |
|
927
|
|
|
|
|
928
|
|
View Code Duplication |
def _power_coupled(block, n): |
|
|
|
|
|
|
929
|
|
|
""" |
|
930
|
|
|
Rule definition for constraint to connect the input power |
|
931
|
|
|
and output power |
|
932
|
|
|
""" |
|
933
|
|
|
expr = ( |
|
934
|
|
|
m.InvestmentFlowBlock.invest[n, o[n]] |
|
|
|
|
|
|
935
|
|
|
+ m.flows[n, o[n]].investment.existing |
|
936
|
|
|
) * n.invest_relation_input_output == ( |
|
937
|
|
|
m.InvestmentFlowBlock.invest[i[n], n] |
|
|
|
|
|
|
938
|
|
|
+ m.flows[i[n], n].investment.existing |
|
939
|
|
|
) |
|
940
|
|
|
return expr |
|
941
|
|
|
|
|
942
|
|
|
self.power_coupled = Constraint( |
|
943
|
|
|
self.INVEST_REL_IN_OUT, rule=_power_coupled |
|
944
|
|
|
) |
|
945
|
|
|
|
|
946
|
|
|
def _storage_capacity_inflow_invest_rule(block, n): |
|
947
|
|
|
""" |
|
948
|
|
|
Rule definition of constraint connecting the inflow |
|
949
|
|
|
`InvestmentFlowBlock.invest of storage with invested capacity |
|
950
|
|
|
`invest` by nominal_storage_capacity__inflow_ratio |
|
951
|
|
|
""" |
|
952
|
|
|
expr = ( |
|
953
|
|
|
m.InvestmentFlowBlock.invest[i[n], n] |
|
|
|
|
|
|
954
|
|
|
+ m.flows[i[n], n].investment.existing |
|
955
|
|
|
) == ( |
|
956
|
|
|
n.investment.existing + self.invest[n] |
|
957
|
|
|
) * n.invest_relation_input_capacity |
|
958
|
|
|
return expr |
|
959
|
|
|
|
|
960
|
|
|
self.storage_capacity_inflow = Constraint( |
|
961
|
|
|
self.INVEST_REL_CAP_IN, rule=_storage_capacity_inflow_invest_rule |
|
962
|
|
|
) |
|
963
|
|
|
|
|
964
|
|
|
def _storage_capacity_outflow_invest_rule(block, n): |
|
965
|
|
|
""" |
|
966
|
|
|
Rule definition of constraint connecting outflow |
|
967
|
|
|
`InvestmentFlowBlock.invest` of storage and invested capacity |
|
968
|
|
|
`invest` by nominal_storage_capacity__outflow_ratio |
|
969
|
|
|
""" |
|
970
|
|
|
expr = ( |
|
971
|
|
|
m.InvestmentFlowBlock.invest[n, o[n]] |
|
|
|
|
|
|
972
|
|
|
+ m.flows[n, o[n]].investment.existing |
|
973
|
|
|
) == ( |
|
974
|
|
|
n.investment.existing + self.invest[n] |
|
975
|
|
|
) * n.invest_relation_output_capacity |
|
976
|
|
|
return expr |
|
977
|
|
|
|
|
978
|
|
|
self.storage_capacity_outflow = Constraint( |
|
979
|
|
|
self.INVEST_REL_CAP_OUT, rule=_storage_capacity_outflow_invest_rule |
|
980
|
|
|
) |
|
981
|
|
|
|
|
982
|
|
|
def _max_storage_content_invest_rule(block, n, t): |
|
983
|
|
|
""" |
|
984
|
|
|
Rule definition for upper bound constraint for the |
|
985
|
|
|
storage content. |
|
986
|
|
|
""" |
|
987
|
|
|
expr = ( |
|
988
|
|
|
self.storage_content[n, t] |
|
989
|
|
|
<= (n.investment.existing + self.invest[n]) |
|
990
|
|
|
* n.max_storage_level[t] |
|
991
|
|
|
) |
|
992
|
|
|
return expr |
|
993
|
|
|
|
|
994
|
|
|
self.max_storage_content = Constraint( |
|
995
|
|
|
self.INVESTSTORAGES, |
|
996
|
|
|
m.TIMESTEPS, |
|
997
|
|
|
rule=_max_storage_content_invest_rule, |
|
998
|
|
|
) |
|
999
|
|
|
|
|
1000
|
|
|
def _min_storage_content_invest_rule(block, n, t): |
|
1001
|
|
|
""" |
|
1002
|
|
|
Rule definition of lower bound constraint for the |
|
1003
|
|
|
storage content. |
|
1004
|
|
|
""" |
|
1005
|
|
|
expr = ( |
|
1006
|
|
|
self.storage_content[n, t] |
|
1007
|
|
|
>= (n.investment.existing + self.invest[n]) |
|
1008
|
|
|
* n.min_storage_level[t] |
|
1009
|
|
|
) |
|
1010
|
|
|
return expr |
|
1011
|
|
|
|
|
1012
|
|
|
# Set the lower bound of the storage content if the attribute exists |
|
1013
|
|
|
self.min_storage_content = Constraint( |
|
1014
|
|
|
self.MIN_INVESTSTORAGES, |
|
1015
|
|
|
m.TIMESTEPS, |
|
1016
|
|
|
rule=_min_storage_content_invest_rule, |
|
1017
|
|
|
) |
|
1018
|
|
|
|
|
1019
|
|
|
def maximum_invest_limit(block, n): |
|
1020
|
|
|
""" |
|
1021
|
|
|
Constraint for the maximal investment in non convex investment |
|
1022
|
|
|
storage. |
|
1023
|
|
|
""" |
|
1024
|
|
|
return ( |
|
1025
|
|
|
n.investment.maximum * self.invest_status[n] - self.invest[n] |
|
1026
|
|
|
) >= 0 |
|
1027
|
|
|
|
|
1028
|
|
|
self.limit_max = Constraint( |
|
1029
|
|
|
self.NON_CONVEX_INVESTSTORAGES, rule=maximum_invest_limit |
|
1030
|
|
|
) |
|
1031
|
|
|
|
|
1032
|
|
|
def smallest_invest(block, n): |
|
1033
|
|
|
""" |
|
1034
|
|
|
Constraint for the minimal investment in non convex investment |
|
1035
|
|
|
storage if the invest is greater than 0. So the invest variable |
|
1036
|
|
|
can be either 0 or greater than the minimum. |
|
1037
|
|
|
""" |
|
1038
|
|
|
return ( |
|
1039
|
|
|
self.invest[n] - (n.investment.minimum * self.invest_status[n]) |
|
1040
|
|
|
>= 0 |
|
1041
|
|
|
) |
|
1042
|
|
|
|
|
1043
|
|
|
self.limit_min = Constraint( |
|
1044
|
|
|
self.NON_CONVEX_INVESTSTORAGES, rule=smallest_invest |
|
1045
|
|
|
) |
|
1046
|
|
|
|
|
1047
|
|
|
def _objective_expression(self): |
|
1048
|
|
|
"""Objective expression with fixed and investement costs.""" |
|
1049
|
|
|
if not hasattr(self, "INVESTSTORAGES"): |
|
1050
|
|
|
return 0 |
|
1051
|
|
|
|
|
1052
|
|
|
investment_costs = 0 |
|
1053
|
|
|
|
|
1054
|
|
|
for n in self.CONVEX_INVESTSTORAGES: |
|
1055
|
|
|
investment_costs += self.invest[n] * n.investment.ep_costs |
|
1056
|
|
|
for n in self.NON_CONVEX_INVESTSTORAGES: |
|
1057
|
|
|
investment_costs += ( |
|
1058
|
|
|
self.invest[n] * n.investment.ep_costs |
|
1059
|
|
|
+ self.invest_status[n] * n.investment.offset |
|
1060
|
|
|
) |
|
1061
|
|
|
self.investment_costs = Expression(expr=investment_costs) |
|
1062
|
|
|
|
|
1063
|
|
|
return investment_costs |
|
1064
|
|
|
|