Completed
Push — dev ( 794b9b...9be114 )
by Patrik
20s queued 16s
created

GenericStorage.__init__()   C

Complexity

Conditions 9

Size

Total Lines 91
Code Lines 76

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 76
dl 0
loc 91
rs 5.4521
c 0
b 0
f 0
cc 9
nop 23

How to fix   Long Method    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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
SPDX-FileCopyrightText: Ekaterina Zolotarevskaia
16
SPDX-FileCopyrightText: Johannes Kochems
17
SPDX-FileCopyrightText: Johannes Giehl
18
SPDX-FileCopyrightText: Raul Ciria Aylagas
19
20
SPDX-License-Identifier: MIT
21
22
"""
23
import numbers
24
from warnings import warn
25
26
import numpy as np
27
from oemof.network import Node
28
from oemof.tools import debugging
29
from oemof.tools import economics
30
from pyomo.core.base.block import ScalarBlock
31
from pyomo.environ import Binary
32
from pyomo.environ import BuildAction
33
from pyomo.environ import Constraint
34
from pyomo.environ import Expression
35
from pyomo.environ import NonNegativeReals
36
from pyomo.environ import Set
37
from pyomo.environ import Var
38
39
from oemof.solph._helpers import check_node_object_for_missing_attribute
40
from oemof.solph._options import Investment
41
from oemof.solph._plumbing import sequence
42
from oemof.solph._plumbing import valid_sequence
43
44
45
class GenericStorage(Node):
46
    r"""
47
    Component `GenericStorage` to model with basic characteristics of storages.
48
49
    The GenericStorage is designed for one input and one output.
50
51
    Parameters
52
    ----------
53
    nominal_capacity : numeric, :math:`E_{nom}` or
54
            :class:`oemof.solph.options.Investment` object
55
        Absolute nominal capacity of the storage, fixed value or
56
        object describing parameter of investment optimisations.
57
    invest_relation_input_capacity : numeric (iterable or scalar) or None, :math:`r_{cap,in}`
58
        Ratio between the investment variable of the input Flow and the
59
        investment variable of the storage:
60
        :math:`\dot{E}_{in,invest} = E_{invest} \cdot r_{cap,in}`
61
    invest_relation_output_capacity : numeric (iterable or scalar) or None, :math:`r_{cap,out}`
62
        Ratio between the investment variable of the output Flow and the
63
        investment variable of the storage:
64
        :math:`\dot{E}_{out,invest} = E_{invest} \cdot r_{cap,out}`
65
    invest_relation_input_output : numeric (iterable or scalar) or None, :math:`r_{in,out}`
66
        Ratio between the investment variable of the output Flow and the
67
        investment variable of the input flow. This ratio used to fix the
68
        flow investments to each other.
69
        Values < 1 set the input flow lower than the output and > 1 will
70
        set the input flow higher than the output flow. If None no relation
71
        will be set:
72
        :math:`\dot{E}_{in,invest} = \dot{E}_{out,invest} \cdot r_{in,out}`
73
    initial_storage_level : numeric, :math:`c(-1)`
74
        The relative storage content in the timestep before the first
75
        time step of optimization (between 0 and 1).
76
77
        Note: When investment mode is used in a multi-period model,
78
        `initial_storage_level` is not supported.
79
        Storage output is forced to zero until the storage unit is invested in.
80
    balanced : boolean
81
        Couple storage level of first and last time step.
82
        (Total inflow and total outflow are balanced.)
83
    loss_rate : numeric (iterable or scalar)
84
        The relative loss of the storage content per hour.
85
    fixed_losses_relative : numeric (iterable or scalar), :math:`\gamma(t)`
86
        Losses per hour that are independent of the storage content but
87
        proportional to nominal storage capacity.
88
89
        Note: Fixed losses are not supported in investment mode.
90
    fixed_losses_absolute : numeric (iterable or scalar), :math:`\delta(t)`
91
        Losses per hour that are independent of storage content and independent
92
        of nominal storage capacity.
93
94
        Note: Fixed losses are not supported in investment mode.
95
    inflow_conversion_factor : numeric (iterable or scalar), :math:`\eta_i(t)`
96
        The relative conversion factor, i.e. efficiency associated with the
97
        inflow of the storage.
98
    outflow_conversion_factor : numeric (iterable or scalar), :math:`\eta_o(t)`
99
        see: inflow_conversion_factor
100
    min_storage_level : numeric (iterable or scalar), :math:`c_{min}(t)`
101
        The normed minimum storage content as fraction of the
102
        nominal storage capacity or the capacity that has been invested into
103
        (between 0 and 1).
104
        To set different values in every time step use a sequence.
105
    max_storage_level : numeric (iterable or scalar), :math:`c_{max}(t)`
106
        see: min_storage_level
107
    storage_costs : numeric (iterable or scalar), :math:`c_{storage}(t)`
108
        Cost (per energy) for having energy in the storage, starting from
109
        time point :math:`t_{1}`.
110
    lifetime_inflow : int, :math:`n_{in}`
111
        Determine the lifetime of an inflow; only applicable for multi-period
112
        models which can invest in storage capacity and have an
113
        invest_relation_input_capacity defined
114
    lifetime_outflow : int, :math:`n_{in}`
115
        Determine the lifetime of an outflow; only applicable for multi-period
116
        models which can invest in storage capacity and have an
117
        invest_relation_output_capacity defined
118
119
    Notes
120
    -----
121
    The following sets, variables, constraints and objective parts are created
122
     * :py:class:`~oemof.solph.components._generic_storage.GenericStorageBlock`
123
       (if no Investment object present)
124
     * :py:class:`~oemof.solph.components._generic_storage.GenericInvestmentStorageBlock`
125
       (if Investment object present)
126
127
    Examples
128
    --------
129
    Basic usage examples of the GenericStorage with a random selection of
130
    attributes. See the Flow class for all Flow attributes.
131
132
    >>> from oemof import solph
133
134
    >>> my_bus = solph.buses.Bus('my_bus')
135
136
    >>> my_storage = solph.components.GenericStorage(
137
    ...     label='storage',
138
    ...     nominal_capacity=1000,
139
    ...     inputs={my_bus: solph.flows.Flow(nominal_capacity=200, variable_costs=10)},
140
    ...     outputs={my_bus: solph.flows.Flow(nominal_capacity=200)},
141
    ...     loss_rate=0.01,
142
    ...     initial_storage_level=0,
143
    ...     max_storage_level = 0.9,
144
    ...     inflow_conversion_factor=0.9,
145
    ...     outflow_conversion_factor=0.93)
146
147
    >>> my_investment_storage = solph.components.GenericStorage(
148
    ...     label='storage',
149
    ...     nominal_capacity=solph.Investment(ep_costs=50),
150
    ...     inputs={my_bus: solph.flows.Flow()},
151
    ...     outputs={my_bus: solph.flows.Flow()},
152
    ...     loss_rate=0.02,
153
    ...     initial_storage_level=None,
154
    ...     invest_relation_input_capacity=1/6,
155
    ...     invest_relation_output_capacity=1/6,
156
    ...     inflow_conversion_factor=1,
157
    ...     outflow_conversion_factor=0.8)
158
    """  # noqa: E501
159
160
    def __init__(
161
        self,
162
        label=None,
163
        inputs=None,
164
        outputs=None,
165
        nominal_capacity=None,
166
        nominal_storage_capacity=None,  # Can be removed for versions >= v0.7
167
        initial_storage_level=None,
168
        invest_relation_input_output=None,
169
        invest_relation_input_capacity=None,
170
        invest_relation_output_capacity=None,
171
        min_storage_level=0,
172
        max_storage_level=1,
173
        balanced=True,
174
        loss_rate=0,
175
        fixed_losses_relative=0,
176
        fixed_losses_absolute=0,
177
        inflow_conversion_factor=1,
178
        outflow_conversion_factor=1,
179
        fixed_costs=0,
180
        storage_costs=None,
181
        lifetime_inflow=None,
182
        lifetime_outflow=None,
183
        custom_attributes=None,
184
    ):
185
        if inputs is None:
186
            inputs = {}
187
        if outputs is None:
188
            outputs = {}
189
        if custom_attributes is None:
190
            custom_attributes = {}
191
        super().__init__(
192
            label,
193
            inputs=inputs,
194
            outputs=outputs,
195
            custom_properties=custom_attributes,
196
        )
197
        # --- BEGIN: The following code can be removed for versions >= v0.7 ---
198
        if nominal_storage_capacity is not None:
199
            msg = (
200
                "For backward compatibility,"
201
                + " the option nominal_storage_capacity overwrites the option"
202
                + " nominal_capacity."
203
                + " Both options cannot be set at the same time."
204
            )
205
            if nominal_capacity is not None:
206
                raise AttributeError(msg)
207
            else:
208
                warn(msg, FutureWarning)
209
            nominal_capacity = nominal_storage_capacity
210
        # --- END ---
211
212
        self.nominal_storage_capacity = None
213
        self.investment = None
214
        self._invest_group = False
215
        if isinstance(nominal_capacity, numbers.Real):
216
            self.nominal_storage_capacity = nominal_capacity
217
        elif isinstance(nominal_capacity, Investment):
218
            self.investment = nominal_capacity
219
            self._invest_group = True
220
221
        self.initial_storage_level = initial_storage_level
222
        self.balanced = balanced
223
        self.loss_rate = sequence(loss_rate)
224
        self.fixed_losses_relative = sequence(fixed_losses_relative)
225
        self.fixed_losses_absolute = sequence(fixed_losses_absolute)
226
        self.inflow_conversion_factor = sequence(inflow_conversion_factor)
227
        self.outflow_conversion_factor = sequence(outflow_conversion_factor)
228
        self.max_storage_level = sequence(max_storage_level)
229
        self.min_storage_level = sequence(min_storage_level)
230
        self.fixed_costs = sequence(fixed_costs)
231
        self.storage_costs = sequence(storage_costs)
232
        self.invest_relation_input_output = sequence(
233
            invest_relation_input_output
234
        )
235
        self.invest_relation_input_capacity = sequence(
236
            invest_relation_input_capacity
237
        )
238
        self.invest_relation_output_capacity = sequence(
239
            invest_relation_output_capacity
240
        )
241
        self.lifetime_inflow = lifetime_inflow
242
        self.lifetime_outflow = lifetime_outflow
243
244
        # Check number of flows.
245
        self._check_number_of_flows()
246
        # Check for infeasible parameter combinations
247
        self._check_infeasible_parameter_combinations()
248
249
        if self._invest_group:
250
            self._check_invest_attributes()
251
252
    def _set_flows(self):
253
        """Define inflow / outflow as investment flows when they are
254
        coupled with storage capacity via invest relations
255
        """
256
        for flow in self.inputs.values():
257
            if self.invest_relation_input_capacity[
258
                0
259
            ] is not None and not isinstance(flow.investment, Investment):
260
                flow.investment = Investment(lifetime=self.lifetime_inflow)
261
        for flow in self.outputs.values():
262
            if self.invest_relation_output_capacity[
263
                0
264
            ] is not None and not isinstance(flow.investment, Investment):
265
                flow.investment = Investment(lifetime=self.lifetime_outflow)
266
267
    def _check_invest_attributes(self):
268
        """Raise errors for infeasible investment attribute combinations"""
269
        if (
270
            self.invest_relation_input_output[0] is not None
271
            and self.invest_relation_output_capacity[0] is not None
272
            and self.invest_relation_input_capacity[0] is not None
273
        ):
274
            e2 = (
275
                "Overdetermined. Three investment object will be coupled"
276
                "with three constraints. Set one invest relation to 'None'."
277
            )
278
            raise AttributeError(e2)
279
        if (
280
            self.investment
281
            and self.fixed_losses_absolute.max() != 0
282
            and self.investment.existing == 0
283
            and self.investment.minimum.min() == 0
284
        ):
285
            e3 = (
286
                "With fixed_losses_absolute > 0, either investment.existing "
287
                "or investment.minimum has to be non-zero."
288
            )
289
            raise AttributeError(e3)
290
291
        self._set_flows()
292
293
    def _check_number_of_flows(self):
294
        """Ensure that there is only one inflow and outflow to the storage"""
295
        msg = "Only one {0} flow allowed in the GenericStorage {1}."
296
        check_node_object_for_missing_attribute(self, "inputs")
297
        check_node_object_for_missing_attribute(self, "outputs")
298
        if len(self.inputs) > 1:
299
            raise AttributeError(msg.format("input", self.label))
300
        if len(self.outputs) > 1:
301
            raise AttributeError(msg.format("output", self.label))
302
303
    def _check_infeasible_parameter_combinations(self):
304
        """Check for infeasible parameter combinations and raise error"""
305
        msg = (
306
            "initial_storage_level must be greater or equal to "
307
            "min_storage_level and smaller or equal to "
308
            "max_storage_level."
309
        )
310
        if self.initial_storage_level is not None:
311
            if (
312
                self.initial_storage_level < self.min_storage_level[0]
313
                or self.initial_storage_level > self.max_storage_level[0]
314
            ):
315
                raise ValueError(msg)
316
317
    def constraint_group(self):
318
        if self._invest_group is True:
319
            return GenericInvestmentStorageBlock
320
        else:
321
            return GenericStorageBlock
322
323
324
class GenericStorageBlock(ScalarBlock):
325
    r"""Storage without an :class:`.Investment` object.
326
327
    **The following sets are created:** (-> see basic sets at
328
    :class:`.Model` )
329
330
    STORAGES
331
        A set with all :py:class:`~.GenericStorage` objects, which do not have an
332
        :attr:`investment` of type :class:`.Investment`.
333
334
    STORAGES_BALANCED
335
        A set of  all :py:class:`~.GenericStorage` objects, with 'balanced' attribute set
336
        to True.
337
338
    STORAGES_WITH_INVEST_FLOW_REL
339
        A set with all :py:class:`~.GenericStorage` objects with two investment
340
        flows coupled with the 'invest_relation_input_output' attribute.
341
342
    **The following variables are created:**
343
344
    storage_content
345
        Storage content for every storage and timestep. The value for the
346
        storage content at the beginning is set by the parameter
347
        `initial_storage_level` or not set if `initial_storage_level` is None.
348
        The variable of storage s and timestep t can be accessed by:
349
        `om.GenericStorageBlock.storage_content[s, t]`
350
351
    **The following constraints are created:**
352
353
    Set storage_content of last time step to one at t=0 if balanced == True
354
        .. math::
355
            E(t_{last}) = E(-1)
356
357
    Storage losses :attr:`om.Storage.losses[n, t]`
358
        .. math:: E_{loss}(t) = &E(t-1) \cdot
359
            1 - (1 - \beta(t))^{\tau(t)/(t_u)} \\
360
            &- \gamma(t)\cdot E_{nom} \cdot {\tau(t)/(t_u)}\\
361
            &- \delta(t) \cdot {\tau(t)/(t_u)}
362
363
    Storage balance :attr:`om.Storage.balance[n, t]`
364
        .. math:: E(t) = &E(t-1) - E_{loss}(t)
365
            &- \frac{\dot{E}_o(p, t)}{\eta_o(t)} \cdot \tau(t)
366
            + \dot{E}_i(p, t) \cdot \eta_i(t) \cdot \tau(t)
367
368
    Connect the invest variables of the input and the output flow.
369
        .. math::
370
          InvestmentFlowBlock.invest(source(n), n, p) + existing = \\
371
          (InvestmentFlowBlock.invest(n, target(n), p) + existing) \\
372
          * invest\_relation\_input\_output(n) \\
373
          \forall n \in \textrm{INVEST\_REL\_IN\_OUT} \\
374
          \forall p \in \textrm{PERIODS}
375
376
377
378
    =========================== ======================= =========
379
    symbol                      explanation             attribute
380
    =========================== ======================= =========
381
    :math:`E(t)`                energy currently stored `storage_content`
382
    :math:`E_{nom}`             nominal capacity of     `nominal_storage_capacity`
383
                                the energy storage
384
    :math:`c(-1)`               state before            `initial_storage_level`
385
                                initial time step
386
    :math:`c_{min}(t)`          minimum allowed storage `min_storage_level[t]`
387
    :math:`c_{max}(t)`          maximum allowed storage `max_storage_level[t]`
388
    :math:`\beta(t)`            fraction of lost energy `loss_rate[t]`
389
                                as share of
390
                                :math:`E(t)` per hour
391
    :math:`\gamma(t)`           fixed loss of energy    `fixed_losses_relative[t]`
392
                                per hour relative to
393
                                :math:`E_{nom}`
394
    :math:`\delta(t)`           absolute fixed loss     `fixed_losses_absolute[t]`
395
                                of energy per hour
396
    :math:`\dot{E}_i(t)`        energy flowing in       `inputs`
397
    :math:`\dot{E}_o(t)`        energy flowing out      `outputs`
398
    :math:`\eta_i(t)`           conversion factor       `inflow_conversion_factor[t]`
399
                                (i.e. efficiency)
400
                                when storing energy
401
    :math:`\eta_o(t)`           conversion factor when  `outflow_conversion_factor[t]`
402
                                (i.e. efficiency)
403
                                taking stored energy
404
    :math:`\tau(t)`             duration of time step
405
    :math:`t_u`                 time unit of losses
406
                                :math:`\beta(t)`,
407
                                :math:`\gamma(t)`
408
                                :math:`\delta(t)` and
409
                                timeincrement
410
                                :math:`\tau(t)`
411
    :math:`c_{storage}(t)`      costs of having         `storage_costs`
412
                                energy stored
413
    =========================== ======================= =========
414
415
    **The following parts of the objective function are created:**
416
417
    *Standard model*
418
419
    * :attr: `storage_costs` not 0
420
421
        .. math::
422
            \sum_{t \in \textrm{TIMEPOINTS} > 0} c_{storage}(t) \cdot E(t)
423
424
425
    *Multi-period model*
426
427
    * :attr:`fixed_costs` not None
428
429
        .. math::
430
            \displaystyle \sum_{pp=0}^{year_{max}} E_{nom}
431
            \cdot c_{fixed}(pp) \cdot DF^{-pp}
432
433
    where:
434
435
    * :math:`DF=(1+dr)` is the discount factor with discount rate :math:`dr`.
436
    * :math:`year_{max}` denotes the last year of the optimization
437
      horizon, i.e. at the end of the last period.
438
439
    """  # noqa: E501
440
441
    CONSTRAINT_GROUP = True
442
443
    def __init__(self, *args, **kwargs):
444
        super().__init__(*args, **kwargs)
445
446
    def _create(self, group=None):
447
        """
448
        Parameters
449
        ----------
450
        group : list
451
            List containing storage objects.
452
            e.g. groups=[storage1, storage2,..]
453
        """
454
        m = self.parent_block()
455
456
        if group is None:
457
            return None
458
459
        i = {n: [i for i in n.inputs][0] for n in group}
460
        o = {n: [o for o in n.outputs][0] for n in group}
461
462
        #  ************* SETS *********************************
463
464
        self.STORAGES = Set(initialize=[n for n in group])
465
466
        self.STORAGES_BALANCED = Set(
467
            initialize=[n for n in group if n.balanced is True]
468
        )
469
470
        self.STORAGES_INITITAL_LEVEL = Set(
471
            initialize=[
472
                n for n in group if n.initial_storage_level is not None
473
            ]
474
        )
475
476
        self.STORAGES_WITH_INVEST_FLOW_REL = Set(
477
            initialize=[
478
                n
479
                for n in group
480
                if n.invest_relation_input_output[0] is not None
481
            ]
482
        )
483
484
        #  ************* VARIABLES *****************************
485
486
        def _storage_content_bound_rule(block, n, t):
487
            """
488
            Rule definition for bounds of storage_content variable of
489
            storage n in timestep t.
490
            """
491
            bounds = (
492
                n.nominal_storage_capacity * n.min_storage_level[t],
493
                n.nominal_storage_capacity * n.max_storage_level[t],
494
            )
495
            return bounds
496
497
        self.storage_content = Var(
498
            self.STORAGES, m.TIMEPOINTS, bounds=_storage_content_bound_rule
499
        )
500
501
        self.storage_losses = Var(self.STORAGES, m.TIMESTEPS)
502
503
        # set the initial storage content
504
        # ToDo: More elegant code possible?
505
        for n in group:
506
            if n.initial_storage_level is not None:
507
                self.storage_content[n, 0] = (
508
                    n.initial_storage_level * n.nominal_storage_capacity
509
                )
510
                self.storage_content[n, 0].fix()
511
512
        #  ************* Constraints ***************************
513
514
        def _storage_losses_rule(block, n, t):
515
            expr = block.storage_content[n, t] * (
516
                1 - (1 - n.loss_rate[t]) ** m.timeincrement[t]
517
            )
518
            expr += (
519
                n.fixed_losses_relative[t]
520
                * n.nominal_storage_capacity
521
                * m.timeincrement[t]
522
            )
523
            expr += n.fixed_losses_absolute[t] * m.timeincrement[t]
524
525
            return expr == block.storage_losses[n, t]
526
527
        self.losses = Constraint(
528
            self.STORAGES, m.TIMESTEPS, rule=_storage_losses_rule
529
        )
530
531
        def _storage_balance_rule(block, n, t):
532
            """
533
            Rule definition for the storage balance of every storage n and
534
            every timestep.
535
            """
536
            expr = block.storage_content[n, t]
537
            expr -= block.storage_losses[n, t]
538
            expr += (
539
                m.flow[i[n], n, t] * n.inflow_conversion_factor[t]
0 ignored issues
show
introduced by
The variable i does not seem to be defined for all execution paths.
Loading history...
540
            ) * m.timeincrement[t]
541
            expr -= (
542
                m.flow[n, o[n], t] / n.outflow_conversion_factor[t]
0 ignored issues
show
introduced by
The variable o does not seem to be defined for all execution paths.
Loading history...
543
            ) * m.timeincrement[t]
544
            return expr == block.storage_content[n, t + 1]
545
546
        self.balance = Constraint(
547
            self.STORAGES, m.TIMESTEPS, rule=_storage_balance_rule
548
        )
549
550
        def _balanced_storage_rule(block, n):
551
            """
552
            Storage content of last time step == initial storage content
553
            if balanced.
554
            """
555
            return (
556
                block.storage_content[n, m.TIMEPOINTS.at(-1)]
557
                == block.storage_content[n, m.TIMEPOINTS.at(1)]
558
            )
559
560
        self.balanced_cstr = Constraint(
561
            self.STORAGES_BALANCED, rule=_balanced_storage_rule
562
        )
563
564
        def _power_coupled(_):
565
            """
566
            Rule definition for constraint to connect the input power
567
            and output power
568
            """
569
            for n in self.STORAGES_WITH_INVEST_FLOW_REL:
570
                for p in m.PERIODS:
571
                    expr = (
572
                        m.InvestmentFlowBlock.total[n, o[n], p]
0 ignored issues
show
introduced by
The variable o does not seem to be defined for all execution paths.
Loading history...
573
                    ) * n.invest_relation_input_output[p] == (
574
                        m.InvestmentFlowBlock.total[i[n], n, p]
0 ignored issues
show
introduced by
The variable i does not seem to be defined for all execution paths.
Loading history...
575
                    )
576
                    self.power_coupled.add((n, p), expr)
577
578
        self.power_coupled = Constraint(
579
            self.STORAGES_WITH_INVEST_FLOW_REL, m.PERIODS, noruleinit=True
580
        )
581
582
        self.power_coupled_build = BuildAction(rule=_power_coupled)
583
584
    def _objective_expression(self):
585
        r"""
586
        Objective expression for storages with no investment.
587
588
        Note
589
        ----
590
        * For standard models, this adds nothing as variable costs are
591
          already added in the Block :py:class:`~.SimpleFlowBlock`.
592
        * For multi-period models, fixed costs may be introduced
593
          and added here.
594
        """
595
        m = self.parent_block()
596
597
        fixed_costs = 0
598
599
        if m.es.periods is not None:
600
            for n in self.STORAGES:
601
                if valid_sequence(n.fixed_costs, len(m.PERIODS)):
602
                    fixed_costs += sum(
603
                        n.nominal_storage_capacity
604
                        * n.fixed_costs[pp]
605
                        * (1 + m.discount_rate) ** (-pp)
606
                        for pp in range(m.es.end_year_of_optimization)
607
                    )
608
        self.fixed_costs = Expression(expr=fixed_costs)
609
610
        storage_costs = 0
611
612
        for n in self.STORAGES:
613
            if valid_sequence(n.storage_costs, len(m.TIMESTEPS)):
614
                # We actually want to iterate over all TIMEPOINTS except the
615
                # 0th. As integers are used for the index, this is equicalent
616
                # to iterating over the TIMESTEPS with one offset.
617
                for t in m.TIMESTEPS:
618
                    storage_costs += (
619
                        self.storage_content[n, t + 1] * n.storage_costs[t]
620
                    )
621
622
        self.storage_costs = Expression(expr=storage_costs)
623
        self.costs = Expression(expr=storage_costs + fixed_costs)
624
625
        return self.costs
626
627
628
class GenericInvestmentStorageBlock(ScalarBlock):
629
    r"""
630
    Block for all storages with :attr:`Investment` being not None.
631
    See :class:`.Investment` for all parameters of the
632
    Investment class.
633
634
    **Variables**
635
636
    All Storages are indexed by :math:`n` (denoting the respective storage
637
    unit), which is omitted in the following for the sake of convenience.
638
    The following variables are created as attributes of
639
    :attr:`om.GenericInvestmentStorageBlock`:
640
641
    * :math:`P_i(p, t)`
642
643
        Inflow of the storage
644
        (created in :class:`oemof.solph.models.Model`).
645
646
    * :math:`P_o(p, t)`
647
648
        Outflow of the storage
649
        (created in :class:`oemof.solph.models.Model`).
650
651
    * :math:`E(t)`
652
653
        Current storage content (Absolute level of stored energy).
654
655
    * :math:`E_{invest}(p)`
656
657
        Invested (nominal) capacity of the storage in period p.
658
659
    * :math:`E_{total}(p)`
660
661
        Total installed (nominal) capacity of the storage in period p.
662
663
    * :math:`E_{old}(p)`
664
665
        Old (nominal) capacity of the storage to be decommissioned in period p.
666
667
    * :math:`E_{old,exo}(p)`
668
669
        Exogenous old (nominal) capacity of the storage to be decommissioned
670
        in period p; existing capacity reaching its lifetime.
671
672
    * :math:`E_{old,endo}(p)`
673
674
        Endogenous old (nominal) capacity of the storage to be decommissioned
675
        in period p; endgenous investments reaching their lifetime.
676
677
    * :math:`E(-1)`
678
679
        Initial storage content (before timestep 0).
680
        Not applicable for a multi-period model.
681
682
    * :math:`b_{invest}(p)`
683
684
        Binary variable for the status of the investment, if
685
        :attr:`nonconvex` is `True`.
686
687
    **Constraints**
688
689
    The following constraints are created for all investment storages:
690
691
        Storage balance (Same as for :class:`.GenericStorageBlock`)
692
693
        .. math:: E(t) = &E(t-1) \cdot
694
            (1 - \beta(t)) ^{\tau(t)/(t_u)} \\
695
            &- \gamma(t)\cdot (E_{total}(p)) \cdot {\tau(t)/(t_u)}\\
696
            &- \delta(t) \cdot {\tau(t)/(t_u)}\\
697
            &- \frac{\dot{E}_o(p, t))}{\eta_o(t)} \cdot \tau(t)
698
            + \dot{E}_i(p, t) \cdot \eta_i(t) \cdot \tau(t)
699
700
        Total storage capacity (p > 0 for multi-period model only)
701
702
        .. math::
703
            &
704
            if \quad p=0:\\
705
            &
706
            E_{total}(p) = E_{exist} + E_{invest}(p)\\
707
            &\\
708
            &
709
            else:\\
710
            &
711
            E_{total}(p) = E_{total}(p-1) + E_{invest}(p) - E_{old}(p)\\
712
            &\\
713
            &
714
            \forall p \in \textrm{PERIODS}
715
716
        Old storage capacity (p > 0 for multi-period model only)
717
718
        .. math::
719
            &
720
            E_{old}(p) = E_{old,exo}(p) + E_{old,end}(p)\\
721
            &\\
722
            &
723
            if \quad p=0:\\
724
            &
725
            E_{old,end}(p) = 0\\
726
            &\\
727
            &
728
            else \quad if \quad l \leq year(p):\\
729
            &
730
            E_{old,end}(p) = E_{invest}(p_{comm})\\
731
            &\\
732
            &
733
            else:\\
734
            &
735
            E_{old,end}(p)\\
736
            &\\
737
            &
738
            if \quad p=0:\\
739
            &
740
            E_{old,exo}(p) = 0\\
741
            &\\
742
            &
743
            else \quad if \quad l - a \leq year(p):\\
744
            &
745
            E_{old,exo}(p) = E_{exist} (*)\\
746
            &\\
747
            &
748
            else:\\
749
            &
750
            E_{old,exo}(p) = 0\\
751
            &\\
752
            &
753
            \forall p \in \textrm{PERIODS}
754
755
        where:
756
757
        * (*) is only performed for the first period the condition is True.
758
          A decommissioning flag is then set to True to prevent having falsely
759
          added old capacity in future periods.
760
        * :math:`year(p)` is the year corresponding to period p
761
        * :math:`p_{comm}` is the commissioning period of the storage
762
763
    Depending on the attribute :attr:`nonconvex`, the constraints for the
764
    bounds of the decision variable :math:`E_{invest}(p)` are different:\
765
766
        * :attr:`nonconvex = False`
767
768
        .. math::
769
            &
770
            E_{invest, min}(p) \le E_{invest}(p) \le E_{invest, max}(p) \\
771
            &
772
            \forall p \in \textrm{PERIODS}
773
774
        * :attr:`nonconvex = True`
775
776
        .. math::
777
            &
778
            E_{invest, min}(p) \cdot b_{invest}(p) \le E_{invest}(p)\\
779
            &
780
            E_{invest}(p) \le E_{invest, max}(p) \cdot b_{invest}(p)\\
781
            &
782
            \forall p \in \textrm{PERIODS}
783
784
    The following constraints are created depending on the attributes of
785
    the :class:`.GenericStorage`:
786
787
        * :attr:`initial_storage_level is None`;
788
          not applicable for multi-period model
789
790
            Constraint for a variable initial storage content:
791
792
        .. math::
793
               E(-1) \le E_{exist} + E_{invest}(0)
794
795
        * :attr:`initial_storage_level is not None`;
796
          not applicable for multi-period model
797
798
            An initial value for the storage content is given:
799
800
        .. math::
801
               E(-1) = (E_{invest}(0) + E_{exist}) \cdot c(-1)
802
803
        * :attr:`balanced=True`;
804
          not applicable for multi-period model
805
806
            The energy content of storage of the first and the last timestep
807
            are set equal:
808
809
        .. math::
810
            E(-1) = E(t_{last})
811
812
        * :attr:`invest_relation_input_capacity is not None`
813
814
            Connect the invest variables of the storage and the input flow:
815
816
        .. math::
817
            &
818
            P_{i,total}(p) =
819
            E_{total}(p) \cdot r_{cap,in} \\
820
            &
821
            \forall p \in \textrm{PERIODS}
822
823
        * :attr:`invest_relation_output_capacity is not None`
824
825
            Connect the invest variables of the storage and the output flow:
826
827
        .. math::
828
            &
829
            P_{o,total}(p) =
830
            E_{total}(p) \cdot r_{cap,out}\\
831
            &
832
            \forall p \in \textrm{PERIODS}
833
834
        * :attr:`invest_relation_input_output is not None`
835
836
            Connect the invest variables of the input and the output flow:
837
838
        .. math::
839
            &
840
            P_{i,total}(p) =
841
            P_{o,total}(p) \cdot r_{in,out}\\
842
            &
843
            \forall p \in \textrm{PERIODS}
844
845
        * :attr:`max_storage_level`
846
847
            Rule for upper bound constraint for the storage content:
848
849
        .. math::
850
            &
851
            E(t) \leq E_{total}(p) \cdot c_{max}(t)\\
852
            &
853
            \forall p, t \in \textrm{TIMEINDEX}
854
855
        * :attr:`min_storage_level`
856
857
            Rule for lower bound constraint for the storage content:
858
859
        .. math::
860
            &
861
            E(t) \geq E_{total}(p) \cdot c_{min}(t)\\
862
            &
863
            \forall p, t \in \textrm{TIMEINDEX}
864
865
866
    **Objective function**
867
868
    Objective terms for a standard model and a multi-period model differ
869
    quite strongly. Besides, the part of the objective function added by the
870
    investment storages also depends on whether a convex or nonconvex
871
    investment option is selected. The following parts of the objective
872
    function are created:
873
874
    *Standard model*
875
876
        * :attr:`nonconvex = False`
877
878
            .. math::
879
                E_{invest}(0) \cdot c_{invest,var}(0)
880
881
        * :attr:`nonconvex = True`
882
883
            .. math::
884
                E_{invest}(0) \cdot c_{invest,var}(0)
885
                + c_{invest,fix}(0) \cdot b_{invest}(0)\\
886
887
    Where 0 denotes the 0th (investment) period since
888
    in a standard model, there is only this one period.
889
890
    *Multi-period model*
891
892
        * :attr:`nonconvex = False`
893
894
            .. math::
895
                &
896
                E_{invest}(p) \cdot A(c_{invest,var}(p), l, ir)
897
                \cdot \frac {1}{ANF(d, ir)} \cdot DF^{-p}\\
898
                &
899
                \forall p \in \textrm{PERIODS}
900
901
        In case, the remaining lifetime of a storage is greater than 0 and
902
        attribute `use_remaining_value` of the energy system is True,
903
        the difference in value for the investment period compared to the
904
        last period of the optimization horizon is accounted for
905
        as an adder to the investment costs:
906
907
            .. math::
908
                &
909
                E_{invest}(p) \cdot (A(c_{invest,var}(p), l_{r}, ir) -
910
                A(c_{invest,var}(|P|), l_{r}, ir)\\
911
                & \cdot \frac {1}{ANF(l_{r}, ir)} \cdot DF^{-|P|}\\
912
                &\\
913
                &
914
                \forall p \in \textrm{PERIODS}
915
916
        * :attr:`nonconvex = True`
917
918
            .. math::
919
                &
920
                (E_{invest}(p) \cdot A(c_{invest,var}(p), l, ir)
921
                \cdot \frac {1}{ANF(d, ir)}\\
922
                &
923
                +  c_{invest,fix}(p) \cdot b_{invest}(p)) \cdot DF^{-p} \\
924
                &
925
                \forall p \in \textrm{PERIODS}
926
927
        In case, the remaining lifetime of a storage is greater than 0 and
928
        attribute `use_remaining_value` of the energy system is True,
929
        the difference in value for the investment period compared to the
930
        last period of the optimization horizon is accounted for
931
        as an adder to the investment costs:
932
933
            .. math::
934
                &
935
                (E_{invest}(p) \cdot (A(c_{invest,var}(p), l_{r}, ir) -
936
                A(c_{invest,var}(|P|), l_{r}, ir)\\
937
                & \cdot \frac {1}{ANF(l_{r}, ir)} \cdot DF^{-|P|}\\
938
                &
939
                +  (c_{invest,fix}(p) - c_{invest,fix}(|P|))
940
                \cdot b_{invest}(p)) \cdot DF^{-p}\\
941
                &\\
942
                &
943
                \forall p \in \textrm{PERIODS}
944
945
        * :attr:`fixed_costs` not None for investments
946
947
            .. math::
948
                &
949
                \sum_{pp=year(p)}^{limit_{end}}
950
                E_{invest}(p) \cdot c_{fixed}(pp) \cdot DF^{-pp})
951
                \cdot DF^{-p}\\
952
                &
953
                \forall p \in \textrm{PERIODS}
954
955
        * :attr:`fixed_costs` not None for existing capacity
956
957
            .. math::
958
                \sum_{pp=0}^{limit_{exo}} E_{exist} \cdot c_{fixed}(pp)
959
                \cdot DF^{-pp}
960
961
    where:
962
963
    * :math:`A(c_{invest,var}(p), l, ir)` A is the annuity for
964
      investment expenses :math:`c_{invest,var}(p)`, lifetime :math:`l`
965
      and interest rate :math:`ir`.
966
    * :math:`l_{r}` is the remaining lifetime at the end of the
967
      optimization horizon (in case it is greater than 0 and
968
      smaller than the actual lifetime).
969
    * :math:`ANF(d, ir)` is the annuity factor for duration :math:`d`
970
      and interest rate :math:`ir`.
971
    * :math:`d=min\{year_{max} - year(p), l\}` defines the
972
      number of years within the optimization horizon that investment
973
      annuities are accounted for.
974
    * :math:`year(p)` denotes the start year of period :math:`p`.
975
    * :math:`year_{max}` denotes the last year of the optimization
976
      horizon, i.e. at the end of the last period.
977
    * :math:`limit_{end}=min\{year_{max}, year(p) + l\}` is used as an
978
      upper bound to ensure fixed costs for endogenous investments
979
      to occur within the optimization horizon.
980
    * :math:`limit_{exo}=min\{year_{max}, l - a\}` is used as an
981
      upper bound to ensure fixed costs for existing capacities to occur
982
      within the optimization horizon. :math:`a` is the initial age
983
      of an asset.
984
    * :math:`DF=(1+dr)` is the discount factor.
985
986
    The annuity / annuity factor hereby is:
987
988
        .. math::
989
            &
990
            A(c_{invest,var}(p), l, ir) = c_{invest,var}(p) \cdot
991
                \frac {(1+ir)^l \cdot ir} {(1+ir)^l - 1}\\
992
            &\\
993
            &
994
            ANF(d, ir)=\frac {(1+ir)^d \cdot ir} {(1+ir)^d - 1}
995
996
    They are retrieved, using oemof.tools.economics annuity function. The
997
    interest rate :math:`ir` for the annuity is defined as weighted
998
    average costs of capital (wacc) and assumed constant over time.
999
1000
    The overall summed cost expressions for all *InvestmentFlowBlock* objects
1001
    can be accessed by
1002
1003
    * :attr:`om.GenericInvestmentStorageBlock.investment_costs`,
1004
    * :attr:`om.GenericInvestmentStorageBlock.fixed_costs` and
1005
    * :attr:`om.GenericInvestmentStorageBlock.costs`.
1006
1007
    Their values  after optimization can be retrieved by
1008
1009
    * :meth:`om.GenericInvestmentStorageBlock.investment_costs`,
1010
    * :attr:`om.GenericInvestmentStorageBlock.period_investment_costs`
1011
      (yielding a dict keyed by periods); note: this is not a Pyomo expression,
1012
      but calculated,
1013
    * :meth:`om.GenericInvestmentStorageBlock.fixed_costs` and
1014
    * :meth:`om.GenericInvestmentStorageBlock.costs`.
1015
1016
    .. csv-table:: List of Variables
1017
        :header: "symbol", "attribute", "explanation"
1018
        :widths: 1, 1, 1
1019
1020
        ":math:`P_i(p, t)`", ":attr:`flow[i[n], n, p, t]`", "Inflow
1021
        of the storage"
1022
        ":math:`P_o(p, t)`", ":attr:`flow[n, o[n], p, t]`", "Outflow
1023
        of the storage"
1024
        ":math:`E(t)`", ":attr:`storage_content[n, t]`", "Current storage
1025
        content (current absolute stored energy)"
1026
        ":math:`E_{loss}(t)`", ":attr:`storage_losses[n, t]`", "Current storage
1027
        losses (absolute losses per time step)"
1028
        ":math:`E_{invest}(p)`", ":attr:`invest[n, p]`", "Invested (nominal)
1029
        capacity of the storage"
1030
        ":math:`E_{old}(p)`", ":attr:`old[n, p]`", "
1031
        | Old (nominal) capacity of the storage
1032
        | to be decommissioned in period p"
1033
        ":math:`E_{old,exo}(p)`", ":attr:`old_exo[n, p]`", "
1034
        | Old (nominal) capacity of the storage
1035
        | to be decommissioned in period p
1036
        | which was exogenously given by :math:`E_{exist}`"
1037
        ":math:`E_{old,end}(p)`", ":attr:`old_end[n, p]`", "
1038
        | Old (nominal) capacity of the storage
1039
        | to be decommissioned in period p
1040
        | which was endogenously determined by :math:`E_{invest}(p_{comm})`
1041
        | where :math:`p_{comm}` is the commissioning period"
1042
        ":math:`E(-1)`", ":attr:`init_cap[n]`", "Initial storage capacity
1043
        (before timestep 0)"
1044
        ":math:`b_{invest}(p)`", ":attr:`invest_status[i, o, p]`", "Binary
1045
        variable for the status of investment"
1046
        ":math:`P_{i,invest}(p)`", "
1047
        :attr:`InvestmentFlowBlock.invest[i[n], n, p]`", "
1048
        Invested (nominal) inflow (InvestmentFlowBlock)"
1049
        ":math:`P_{o,invest}`", "
1050
        :attr:`InvestmentFlowBlock.invest[n, o[n]]`", "
1051
        Invested (nominal) outflow (InvestmentFlowBlock)"
1052
1053
    .. csv-table:: List of Parameters
1054
        :header: "symbol", "attribute", "explanation"
1055
        :widths: 1, 1, 1
1056
1057
        ":math:`E_{exist}`", "`flows[i, o].investment.existing`", "
1058
        Existing storage capacity"
1059
        ":math:`E_{invest,min}`", "`flows[i, o].investment.minimum`", "
1060
        Minimum investment value"
1061
        ":math:`E_{invest,max}`", "`flows[i, o].investment.maximum`", "
1062
        Maximum investment value"
1063
        ":math:`P_{i,exist}`", "`flows[i[n], n].investment.existing`
1064
        ", "Existing inflow capacity"
1065
        ":math:`P_{o,exist}`", "`flows[n, o[n]].investment.existing`
1066
        ", "Existing outflow capacity"
1067
        ":math:`c_{invest,var}`", "`flows[i, o].investment.ep_costs`
1068
        ", "Variable investment costs"
1069
        ":math:`c_{invest,fix}`", "`flows[i, o].investment.offset`", "
1070
        Fix investment costs"
1071
        ":math:`c_{fixed}`", "`flows[i, o].investment.fixed_costs`", "
1072
        Fixed costs; only allowed in multi-period model"
1073
        ":math:`r_{cap,in}`", ":attr:`invest_relation_input_capacity`", "
1074
        Relation of storage capacity and nominal inflow"
1075
        ":math:`r_{cap,out}`", ":attr:`invest_relation_output_capacity`", "
1076
        Relation of storage capacity and nominal outflow"
1077
        ":math:`r_{in,out}`", ":attr:`invest_relation_input_output`", "
1078
        Relation of nominal in- and outflow"
1079
        ":math:`\beta(t)`", "`loss_rate[t]`", "Fraction of lost energy
1080
        as share of :math:`E(t)` per hour"
1081
        ":math:`\gamma(t)`", "`fixed_losses_relative[t]`", "Fixed loss
1082
        of energy relative to :math:`E_{invest} + E_{exist}` per hour"
1083
        ":math:`\delta(t)`", "`fixed_losses_absolute[t]`", "Absolute
1084
        fixed loss of energy per hour"
1085
        ":math:`\eta_i(t)`", "`inflow_conversion_factor[t]`", "
1086
        Conversion factor (i.e. efficiency) when storing energy"
1087
        ":math:`\eta_o(t)`", "`outflow_conversion_factor[t]`", "
1088
        Conversion factor when (i.e. efficiency) taking stored energy"
1089
        ":math:`c(-1)`", "`initial_storage_level`", "Initial relative
1090
        storage content (before timestep 0)"
1091
        ":math:`c_{max}`", "`flows[i, o].max[t]`", "Normed maximum
1092
        value of storage content"
1093
        ":math:`c_{min}`", "`flows[i, o].min[t]`", "Normed minimum
1094
        value of storage content"
1095
        ":math:`l`", "`flows[i, o].investment.lifetime`", "
1096
        Lifetime for investments in storage capacity"
1097
        ":math:`a`", "`flows[i, o].investment.age`", "
1098
        Initial age of existing capacity / energy"
1099
        ":math:`ir`", "`flows[i, o].investment.interest_rate`", "
1100
        interest rate for investment"
1101
        ":math:`\tau(t)`", "", "Duration of time step"
1102
        ":math:`t_u`", "", "Time unit of losses :math:`\beta(t)`,
1103
        :math:`\gamma(t)`, :math:`\delta(t)` and timeincrement :math:`\tau(t)`"
1104
1105
    """
1106
1107
    CONSTRAINT_GROUP = True
1108
1109
    def __init__(self, *args, **kwargs):
1110
        super().__init__(*args, **kwargs)
1111
1112
    def _create(self, group):
1113
        """Create a storage block for investment modeling"""
1114
        m = self.parent_block()
1115
1116
        # ########################## CHECKS ###################################
1117
        if m.es.periods is not None:
1118
            for n in group:
1119
                error_fixed_absolute_losses = (
1120
                    "For a multi-period investment model, fixed absolute"
1121
                    " losses are not supported. Please remove parameter."
1122
                )
1123
                if n.fixed_losses_absolute[0] != 0:
1124
                    raise ValueError(error_fixed_absolute_losses)
1125
                error_initial_storage_level = (
1126
                    "For a multi-period model, initial_storage_level is"
1127
                    " not supported.\nIt needs to be removed since it"
1128
                    " has no effect.\nstorage_content will be zero,"
1129
                    " until there is some usable storage capacity installed."
1130
                )
1131
                if n.initial_storage_level is not None:
1132
                    raise ValueError(error_initial_storage_level)
1133
1134
        # ########################## SETS #####################################
1135
1136
        self.INVESTSTORAGES = Set(initialize=[n for n in group])
1137
1138
        self.CONVEX_INVESTSTORAGES = Set(
1139
            initialize=[n for n in group if n.investment.nonconvex is False]
1140
        )
1141
1142
        self.NON_CONVEX_INVESTSTORAGES = Set(
1143
            initialize=[n for n in group if n.investment.nonconvex is True]
1144
        )
1145
1146
        self.INVESTSTORAGES_BALANCED = Set(
1147
            initialize=[n for n in group if n.balanced is True]
1148
        )
1149
1150
        self.INVESTSTORAGES_NO_INIT_CONTENT = Set(
1151
            initialize=[n for n in group if n.initial_storage_level is None]
1152
        )
1153
1154
        self.INVESTSTORAGES_INIT_CONTENT = Set(
1155
            initialize=[
1156
                n for n in group if n.initial_storage_level is not None
1157
            ]
1158
        )
1159
1160
        self.INVEST_REL_CAP_IN = Set(
1161
            initialize=[
1162
                n
1163
                for n in group
1164
                if n.invest_relation_input_capacity[0] is not None
1165
            ]
1166
        )
1167
1168
        self.INVEST_REL_CAP_OUT = Set(
1169
            initialize=[
1170
                n
1171
                for n in group
1172
                if n.invest_relation_output_capacity[0] is not None
1173
            ]
1174
        )
1175
1176
        self.INVEST_REL_IN_OUT = Set(
1177
            initialize=[
1178
                n
1179
                for n in group
1180
                if n.invest_relation_input_output[0] is not None
1181
            ]
1182
        )
1183
1184
        # The storage content is a non-negative variable, therefore it makes no
1185
        # sense to create an additional constraint if the lower bound is zero
1186
        # for all time steps.
1187
        self.MIN_INVESTSTORAGES = Set(
1188
            initialize=[
1189
                n
1190
                for n in group
1191
                if sum([n.min_storage_level[t] for t in m.TIMESTEPS]) > 0
1192
            ]
1193
        )
1194
1195
        self.OVERALL_MAXIMUM_INVESTSTORAGES = Set(
1196
            initialize=[
1197
                n for n in group if n.investment.overall_maximum is not None
1198
            ]
1199
        )
1200
1201
        self.OVERALL_MINIMUM_INVESTSTORAGES = Set(
1202
            initialize=[
1203
                n for n in group if n.investment.overall_minimum is not None
1204
            ]
1205
        )
1206
1207
        self.EXISTING_INVESTSTORAGES = Set(
1208
            initialize=[n for n in group if n.investment.existing is not None]
1209
        )
1210
1211
        # ######################### Variables  ################################
1212
        self.storage_content = Var(
1213
            self.INVESTSTORAGES, m.TIMEPOINTS, within=NonNegativeReals
1214
        )
1215
1216
        def _storage_investvar_bound_rule(_, n, p):
1217
            """
1218
            Rule definition to bound the invested storage capacity `invest`.
1219
            """
1220
            if n in self.CONVEX_INVESTSTORAGES:
1221
                return n.investment.minimum[p], n.investment.maximum[p]
1222
            else:  # n in self.NON_CONVEX_INVESTSTORAGES
1223
                return 0, n.investment.maximum[p]
1224
1225
        self.invest = Var(
1226
            self.INVESTSTORAGES,
1227
            m.PERIODS,
1228
            within=NonNegativeReals,
1229
            bounds=_storage_investvar_bound_rule,
1230
        )
1231
1232
        # Total capacity
1233
        self.total = Var(
1234
            self.INVESTSTORAGES,
1235
            m.PERIODS,
1236
            within=NonNegativeReals,
1237
            initialize=0,
1238
        )
1239
1240
        if m.es.periods is not None:
1241
            # Old capacity to be decommissioned (due to lifetime)
1242
            self.old = Var(
1243
                self.INVESTSTORAGES, m.PERIODS, within=NonNegativeReals
1244
            )
1245
1246
            # Old endogenous capacity to be decommissioned (due to lifetime)
1247
            self.old_end = Var(
1248
                self.INVESTSTORAGES, m.PERIODS, within=NonNegativeReals
1249
            )
1250
1251
            # Old exogenous capacity to be decommissioned (due to lifetime)
1252
            self.old_exo = Var(
1253
                self.INVESTSTORAGES, m.PERIODS, within=NonNegativeReals
1254
            )
1255
1256
        else:
1257
            self.init_content = Var(
1258
                self.INVESTSTORAGES, within=NonNegativeReals
1259
            )
1260
1261
        # create status variable for a non-convex investment storage
1262
        self.invest_status = Var(
1263
            self.NON_CONVEX_INVESTSTORAGES, m.PERIODS, within=Binary
1264
        )
1265
1266
        # ######################### CONSTRAINTS ###############################
1267
        i = {n: [i for i in n.inputs][0] for n in group}
1268
        o = {n: [o for o in n.outputs][0] for n in group}
1269
1270
        # Handle unit lifetimes
1271
        def _total_storage_capacity_rule(block):
1272
            """Rule definition for determining total installed
1273
            capacity (taking decommissioning into account)
1274
            """
1275
            for n in self.INVESTSTORAGES:
1276
                for p in m.PERIODS:
1277
                    if p == 0:
1278
                        expr = (
1279
                            self.total[n, p]
1280
                            == self.invest[n, p] + n.investment.existing
1281
                        )
1282
                        self.total_storage_rule.add((n, p), expr)
1283
                    else:
1284
                        expr = (
1285
                            self.total[n, p]
1286
                            == self.invest[n, p]
1287
                            + self.total[n, p - 1]
1288
                            - self.old[n, p]
1289
                        )
1290
                        self.total_storage_rule.add((n, p), expr)
1291
1292
        self.total_storage_rule = Constraint(
1293
            self.INVESTSTORAGES, m.PERIODS, noruleinit=True
1294
        )
1295
1296
        self.total_storage_rule_build = BuildAction(
1297
            rule=_total_storage_capacity_rule
1298
        )
1299
1300
        # multi-period storage implementation for time intervals
1301
        if m.es.periods is not None:
1302
1303
            def _old_storage_capacity_rule_end(block):
1304
                """Rule definition for determining old endogenously installed
1305
                capacity to be decommissioned due to reaching its lifetime.
1306
                Investment and decommissioning periods are linked within
1307
                the constraint. The respective decommissioning period is
1308
                determined for every investment period based on the components
1309
                lifetime and a matrix describing its age of each endogenous
1310
                investment. Decommissioning can only occur at the beginning of
1311
                each period.
1312
1313
                Note
1314
                ----
1315
                For further information on the implementation check
1316
                PR#957 https://github.com/oemof/oemof-solph/pull/957
1317
                """
1318
                for n in self.INVESTSTORAGES:
1319
                    lifetime = n.investment.lifetime
1320
                    if lifetime is None:
1321
                        msg = (
1322
                            "You have to specify a lifetime "
1323
                            "for a Flow going into or out of "
1324
                            "a GenericStorage unit "
1325
                            "in a multi-period model!"
1326
                            f" Value for {n} is missing."
1327
                        )
1328
                        raise ValueError(msg)
1329
                    # get the period matrix describing the temporal distance
1330
                    # between all period combinations.
1331
                    periods_matrix = m.es.periods_matrix
1332
1333
                    # get the index of the minimum value in each row greater
1334
                    # equal than the lifetime. This value equals the
1335
                    # decommissioning period if not zero. The index of this
1336
                    # value represents the investment period. If np.where
1337
                    # condition is not met in any row, min value will be zero
1338
                    decomm_periods = np.argmin(
1339
                        np.where(
1340
                            (periods_matrix >= lifetime),
1341
                            periods_matrix,
1342
                            np.inf,
1343
                        ),
1344
                        axis=1,
1345
                    )
1346
1347
                    # no decommissioning in first period
1348
                    expr = self.old_end[n, 0] == 0
1349
                    self.old_rule_end.add((n, 0), expr)
1350
1351
                    # all periods not in decomm_periods have no decommissioning
1352
                    # zero is excluded
1353
                    for p in m.PERIODS:
1354
                        if p not in decomm_periods and p != 0:
1355
                            expr = self.old_end[n, p] == 0
1356
                            self.old_rule_end.add((n, p), expr)
1357
1358
                    # multiple invests can be decommissioned in the same period
1359
                    # but only sequential ones, thus a bookkeeping is
1360
                    # introduced andconstraints are added to equation one
1361
                    # iteration later.
1362
                    last_decomm_p = np.nan
1363
                    # loop over invest periods (values are decomm_periods)
1364
                    for invest_p, decomm_p in enumerate(decomm_periods):
1365
                        # Add constraint of iteration before
1366
                        # (skipped in first iteration by last_decomm_p = nan)
1367
                        if (decomm_p != last_decomm_p) and (
1368
                            last_decomm_p is not np.nan
1369
                        ):
1370
                            expr = self.old_end[n, last_decomm_p] == expr
1371
                            self.old_rule_end.add((n, last_decomm_p), expr)
1372
1373
                        # no decommissioning if decomm_p is zero
1374
                        if decomm_p == 0:
1375
                            # overwrite decomm_p with zero to avoid
1376
                            # chaining invest periods in next iteration
1377
                            last_decomm_p = 0
1378
1379
                        # if decomm_p is the same as the last one chain invest
1380
                        # period
1381
                        elif decomm_p == last_decomm_p:
1382
                            expr += self.invest[n, invest_p]
1383
                            # overwrite decomm_p
1384
                            last_decomm_p = decomm_p
1385
1386
                        # if decomm_p is not zero, not the same as the last one
1387
                        # and it's not the first period
1388
                        else:
1389
                            expr = self.invest[n, invest_p]
1390
                            # overwrite decomm_p
1391
                            last_decomm_p = decomm_p
1392
1393
                    # Add constraint of very last iteration
1394
                    if last_decomm_p != 0:
1395
                        expr = self.old_end[n, last_decomm_p] == expr
1396
                        self.old_rule_end.add((n, last_decomm_p), expr)
1397
1398
            self.old_rule_end = Constraint(
1399
                self.INVESTSTORAGES, m.PERIODS, noruleinit=True
1400
            )
1401
1402
            self.old_rule_end_build = BuildAction(
1403
                rule=_old_storage_capacity_rule_end
1404
            )
1405
1406
            def _old_storage_capacity_rule_exo(block):
1407
                """Rule definition for determining old exogenously given
1408
                capacity to be decommissioned due to reaching its lifetime
1409
                """
1410
                for n in self.INVESTSTORAGES:
1411
                    age = n.investment.age
1412
                    lifetime = n.investment.lifetime
1413
                    is_decommissioned = False
1414
                    for p in m.PERIODS:
1415
                        # No shutdown in first period
1416
                        if p == 0:
1417
                            expr = self.old_exo[n, p] == 0
1418
                            self.old_rule_exo.add((n, p), expr)
1419
                        elif lifetime - age <= m.es.periods_years[p]:
1420
                            # Track decommissioning status
1421
                            if not is_decommissioned:
1422
                                expr = (
1423
                                    self.old_exo[n, p] == n.investment.existing
1424
                                )
1425
                                is_decommissioned = True
1426
                            else:
1427
                                expr = self.old_exo[n, p] == 0
1428
                            self.old_rule_exo.add((n, p), expr)
1429
                        else:
1430
                            expr = self.old_exo[n, p] == 0
1431
                            self.old_rule_exo.add((n, p), expr)
1432
1433
            self.old_rule_exo = Constraint(
1434
                self.INVESTSTORAGES, m.PERIODS, noruleinit=True
1435
            )
1436
1437
            self.old_rule_exo_build = BuildAction(
1438
                rule=_old_storage_capacity_rule_exo
1439
            )
1440
1441
            def _old_storage_capacity_rule(block):
1442
                """Rule definition for determining (overall) old capacity
1443
                to be decommissioned due to reaching its lifetime
1444
                """
1445
                for n in self.INVESTSTORAGES:
1446
                    for p in m.PERIODS:
1447
                        expr = (
1448
                            self.old[n, p]
1449
                            == self.old_end[n, p] + self.old_exo[n, p]
1450
                        )
1451
                        self.old_rule.add((n, p), expr)
1452
1453
            self.old_rule = Constraint(
1454
                self.INVESTSTORAGES, m.PERIODS, noruleinit=True
1455
            )
1456
1457
            self.old_rule_build = BuildAction(rule=_old_storage_capacity_rule)
1458
1459
            def _initially_empty_rule(_):
1460
                """Ensure storage to be empty initially"""
1461
                for n in self.INVESTSTORAGES:
1462
                    expr = self.storage_content[n, 0] == 0
1463
                    self.initially_empty.add((n, 0), expr)
1464
1465
            self.initially_empty = Constraint(
1466
                self.INVESTSTORAGES, m.TIMESTEPS, noruleinit=True
1467
            )
1468
1469
            self.initially_empty_build = BuildAction(
1470
                rule=_initially_empty_rule
1471
            )
1472
1473
        # Standard storage implementation for discrete time points
1474
        else:
1475
1476
            def _inv_storage_init_content_max_rule(block, n):
1477
                """Constraint for a variable initial storage capacity."""
1478
                return (
1479
                    block.init_content[n]
1480
                    <= n.investment.existing + block.invest[n, 0]
1481
                )
1482
1483
            self.init_content_limit = Constraint(
1484
                self.INVESTSTORAGES_NO_INIT_CONTENT,
1485
                rule=_inv_storage_init_content_max_rule,
1486
            )
1487
1488
            def _inv_storage_init_content_fix_rule(block, n):
1489
                """Constraint for a fixed initial storage capacity."""
1490
                return block.storage_content[
1491
                    n, 0
1492
                ] == n.initial_storage_level * (
1493
                    n.investment.existing + block.invest[n, 0]
1494
                )
1495
1496
            self.init_content_fix = Constraint(
1497
                self.INVESTSTORAGES_INIT_CONTENT,
1498
                rule=_inv_storage_init_content_fix_rule,
1499
            )
1500
1501
        def _storage_balance_rule(block, n, p, t):
1502
            """
1503
            Rule definition for the storage balance of every storage n and
1504
            every timestep.
1505
            """
1506
            expr = 0
1507
            expr += block.storage_content[n, t + 1]
1508
            expr += (
1509
                -block.storage_content[n, t]
1510
                * (1 - n.loss_rate[t]) ** m.timeincrement[t]
1511
            )
1512
            expr += (
1513
                n.fixed_losses_relative[t]
1514
                * self.total[n, p]
1515
                * m.timeincrement[t]
1516
            )
1517
            expr += n.fixed_losses_absolute[t] * m.timeincrement[t]
1518
            expr += (
1519
                -m.flow[i[n], n, t] * n.inflow_conversion_factor[t]
1520
            ) * m.timeincrement[t]
1521
            expr += (
1522
                m.flow[n, o[n], t] / n.outflow_conversion_factor[t]
1523
            ) * m.timeincrement[t]
1524
            return expr == 0
1525
1526
        self.balance = Constraint(
1527
            self.INVESTSTORAGES,
1528
            m.TIMEINDEX,
1529
            rule=_storage_balance_rule,
1530
        )
1531
1532
        if m.es.periods is None:
1533
1534
            def _balanced_storage_rule(block, n):
1535
                return (
1536
                    block.storage_content[n, m.TIMESTEPS.at(-1)]
1537
                    == block.init_content[n]
1538
                )
1539
1540
            self.balanced_cstr = Constraint(
1541
                self.INVESTSTORAGES_BALANCED, rule=_balanced_storage_rule
1542
            )
1543
1544
        def _power_coupled(block):
1545
            """
1546
            Rule definition for constraint to connect the input power
1547
            and output power
1548
            """
1549
            for n in self.INVEST_REL_IN_OUT:
1550
                for p in m.PERIODS:
1551
                    expr = (
1552
                        m.InvestmentFlowBlock.total[n, o[n], p]
1553
                    ) * n.invest_relation_input_output[p] == (
1554
                        m.InvestmentFlowBlock.total[i[n], n, p]
1555
                    )
1556
                    self.power_coupled.add((n, p), expr)
1557
1558
        self.power_coupled = Constraint(
1559
            self.INVEST_REL_IN_OUT, m.PERIODS, noruleinit=True
1560
        )
1561
1562
        self.power_coupled_build = BuildAction(rule=_power_coupled)
1563
1564
        def _storage_capacity_inflow_invest_rule(block):
1565
            """
1566
            Rule definition of constraint connecting the inflow
1567
            `InvestmentFlowBlock.invest of storage with invested capacity
1568
            `invest` by nominal_storage_capacity__inflow_ratio
1569
            """
1570
            for n in self.INVEST_REL_CAP_IN:
1571
                for p in m.PERIODS:
1572
                    expr = (
1573
                        m.InvestmentFlowBlock.total[i[n], n, p]
1574
                        == self.total[n, p]
1575
                        * n.invest_relation_input_capacity[p]
1576
                    )
1577
                    self.storage_capacity_inflow.add((n, p), expr)
1578
1579
        self.storage_capacity_inflow = Constraint(
1580
            self.INVEST_REL_CAP_IN, m.PERIODS, noruleinit=True
1581
        )
1582
1583
        self.storage_capacity_inflow_build = BuildAction(
1584
            rule=_storage_capacity_inflow_invest_rule
1585
        )
1586
1587
        def _storage_capacity_outflow_invest_rule(block):
1588
            """
1589
            Rule definition of constraint connecting outflow
1590
            `InvestmentFlowBlock.invest` of storage and invested capacity
1591
            `invest` by nominal_storage_capacity__outflow_ratio
1592
            """
1593
            for n in self.INVEST_REL_CAP_OUT:
1594
                for p in m.PERIODS:
1595
                    expr = (
1596
                        m.InvestmentFlowBlock.total[n, o[n], p]
1597
                        == self.total[n, p]
1598
                        * n.invest_relation_output_capacity[p]
1599
                    )
1600
                    self.storage_capacity_outflow.add((n, p), expr)
1601
1602
        self.storage_capacity_outflow = Constraint(
1603
            self.INVEST_REL_CAP_OUT, m.PERIODS, noruleinit=True
1604
        )
1605
1606
        self.storage_capacity_outflow_build = BuildAction(
1607
            rule=_storage_capacity_outflow_invest_rule
1608
        )
1609
1610
        self._add_storage_limit_constraints()
1611
1612
        def maximum_invest_limit(block, n, p):
1613
            """
1614
            Constraint for the maximal investment in non convex investment
1615
            storage.
1616
            """
1617
            return (
1618
                n.investment.maximum[p] * self.invest_status[n, p]
1619
                - self.invest[n, p]
1620
            ) >= 0
1621
1622
        self.limit_max = Constraint(
1623
            self.NON_CONVEX_INVESTSTORAGES,
1624
            m.PERIODS,
1625
            rule=maximum_invest_limit,
1626
        )
1627
1628
        def smallest_invest(block, n, p):
1629
            """
1630
            Constraint for the minimal investment in non convex investment
1631
            storage if the invest is greater than 0. So the invest variable
1632
            can be either 0 or greater than the minimum.
1633
            """
1634
            return (
1635
                self.invest[n, p]
1636
                - n.investment.minimum[p] * self.invest_status[n, p]
1637
                >= 0
1638
            )
1639
1640
        self.limit_min = Constraint(
1641
            self.NON_CONVEX_INVESTSTORAGES, m.PERIODS, rule=smallest_invest
1642
        )
1643
1644
        if m.es.periods is not None:
1645
1646
            def _overall_storage_maximum_investflow_rule(block):
1647
                """Rule definition for maximum overall investment
1648
                in investment case.
1649
                """
1650
                for n in self.OVERALL_MAXIMUM_INVESTSTORAGES:
1651
                    for p in m.PERIODS:
1652
                        expr = self.total[n, p] <= n.investment.overall_maximum
1653
                        self.overall_storage_maximum.add((n, p), expr)
1654
1655
            self.overall_storage_maximum = Constraint(
1656
                self.OVERALL_MAXIMUM_INVESTSTORAGES, m.PERIODS, noruleinit=True
1657
            )
1658
1659
            self.overall_maximum_build = BuildAction(
1660
                rule=_overall_storage_maximum_investflow_rule
1661
            )
1662
1663
            def _overall_minimum_investflow_rule(block):
1664
                """Rule definition for minimum overall investment
1665
                in investment case.
1666
1667
                Note: This is only applicable for the last period
1668
                """
1669
                for n in self.OVERALL_MINIMUM_INVESTSTORAGES:
1670
                    expr = (
1671
                        n.investment.overall_minimum
1672
                        <= self.total[n, m.PERIODS[-1]]
1673
                    )
1674
                    self.overall_minimum.add(n, expr)
1675
1676
            self.overall_minimum = Constraint(
1677
                self.OVERALL_MINIMUM_INVESTSTORAGES, noruleinit=True
1678
            )
1679
1680
            self.overall_minimum_build = BuildAction(
1681
                rule=_overall_minimum_investflow_rule
1682
            )
1683
1684
    def _add_storage_limit_constraints(self):
1685
        m = self.parent_block()
1686
        if m.es.periods is None:
1687
1688
            def _max_storage_content_invest_rule(_, n, t):
1689
                """
1690
                Rule definition for upper bound constraint for the
1691
                storage content.
1692
                """
1693
                expr = (
1694
                    self.storage_content[n, t]
1695
                    <= self.total[n, 0] * n.max_storage_level[t]
1696
                )
1697
                return expr
1698
1699
            self.max_storage_content = Constraint(
1700
                self.INVESTSTORAGES,
1701
                m.TIMEPOINTS,
1702
                rule=_max_storage_content_invest_rule,
1703
            )
1704
1705
            def _min_storage_content_invest_rule(_, n, t):
1706
                """
1707
                Rule definition of lower bound constraint for the
1708
                storage content.
1709
                """
1710
                expr = (
1711
                    self.storage_content[n, t]
1712
                    >= self.total[n, 0] * n.min_storage_level[t]
1713
                )
1714
                return expr
1715
1716
            self.min_storage_content = Constraint(
1717
                self.MIN_INVESTSTORAGES,
1718
                m.TIMEPOINTS,
1719
                rule=_min_storage_content_invest_rule,
1720
            )
1721
        else:
1722
1723
            def _max_storage_content_invest_rule(_, n, p, t):
1724
                """
1725
                Rule definition for upper bound constraint for the
1726
                storage content.
1727
                """
1728
                expr = (
1729
                    self.storage_content[n, t]
1730
                    <= self.total[n, p] * n.max_storage_level[t]
1731
                )
1732
                return expr
1733
1734
            self.max_storage_content = Constraint(
1735
                self.INVESTSTORAGES,
1736
                m.TIMEINDEX,
1737
                rule=_max_storage_content_invest_rule,
1738
            )
1739
1740
            def _min_storage_content_invest_rule(_, n, p, t):
1741
                """
1742
                Rule definition of lower bound constraint for the
1743
                storage content.
1744
                """
1745
                expr = (
1746
                    self.storage_content[n, t]
1747
                    >= self.total[n, p] * n.min_storage_level[t]
1748
                )
1749
                return expr
1750
1751
            self.min_storage_content = Constraint(
1752
                self.MIN_INVESTSTORAGES,
1753
                m.TIMEINDEX,
1754
                rule=_min_storage_content_invest_rule,
1755
            )
1756
1757
    def _objective_expression(self):
1758
        """Objective expression with fixed and investment costs."""
1759
        m = self.parent_block()
1760
1761
        investment_costs = 0
1762
        period_investment_costs = {p: 0 for p in m.PERIODS}
1763
        fixed_costs = 0
1764
1765
        if m.es.periods is None:
1766
            for n in self.CONVEX_INVESTSTORAGES:
1767
                for p in m.PERIODS:
1768
                    investment_costs += (
1769
                        self.invest[n, p] * n.investment.ep_costs[p]
1770
                    )
1771
            for n in self.NON_CONVEX_INVESTSTORAGES:
1772
                for p in m.PERIODS:
1773
                    investment_costs += (
1774
                        self.invest[n, p] * n.investment.ep_costs[p]
1775
                        + self.invest_status[n, p] * n.investment.offset[p]
1776
                    )
1777
1778
        else:
1779
            msg = (
1780
                "You did not specify an interest rate.\n"
1781
                "It will be set equal to the discount_rate of {} "
1782
                "of the model as a default.\nThis corresponds to a "
1783
                "social planner point of view and does not reflect "
1784
                "microeconomic interest requirements."
1785
            )
1786
            for n in self.CONVEX_INVESTSTORAGES:
1787
                lifetime = n.investment.lifetime
1788
                interest = n.investment.interest_rate
1789
                if interest == 0:
1790
                    warn(
1791
                        msg.format(m.discount_rate),
1792
                        debugging.SuspiciousUsageWarning,
1793
                    )
1794
                    interest = m.discount_rate
1795
                for p in m.PERIODS:
1796
                    annuity = economics.annuity(
1797
                        capex=n.investment.ep_costs[p],
1798
                        n=lifetime,
1799
                        wacc=interest,
1800
                    )
1801
                    duration = min(
1802
                        m.es.end_year_of_optimization - m.es.periods_years[p],
1803
                        lifetime,
1804
                    )
1805
                    present_value_factor = 1 / economics.annuity(
1806
                        capex=1, n=duration, wacc=interest
1807
                    )
1808
                    investment_costs_increment = (
1809
                        self.invest[n, p] * annuity * present_value_factor
1810
                    ) * (1 + m.discount_rate) ** (-m.es.periods_years[p])
1811
                    remaining_value_difference = (
1812
                        self._evaluate_remaining_value_difference(
1813
                            m,
1814
                            p,
1815
                            n,
1816
                            m.es.end_year_of_optimization,
1817
                            lifetime,
1818
                            interest,
1819
                        )
1820
                    )
1821
                    investment_costs += (
1822
                        investment_costs_increment + remaining_value_difference
1823
                    )
1824
                    period_investment_costs[p] += investment_costs_increment
1825
1826
            for n in self.NON_CONVEX_INVESTSTORAGES:
1827
                lifetime = n.investment.lifetime
1828
                interest = n.investment.interest_rate
1829
                if interest == 0:
1830
                    warn(
1831
                        msg.format(m.discount_rate),
1832
                        debugging.SuspiciousUsageWarning,
1833
                    )
1834
                    interest = m.discount_rate
1835
                for p in m.PERIODS:
1836
                    annuity = economics.annuity(
1837
                        capex=n.investment.ep_costs[p],
1838
                        n=lifetime,
1839
                        wacc=interest,
1840
                    )
1841
                    duration = min(
1842
                        m.es.end_year_of_optimization - m.es.periods_years[p],
1843
                        lifetime,
1844
                    )
1845
                    present_value_factor = 1 / economics.annuity(
1846
                        capex=1, n=duration, wacc=interest
1847
                    )
1848
                    investment_costs_increment = (
1849
                        self.invest[n, p] * annuity * present_value_factor
1850
                        + self.invest_status[n, p] * n.investment.offset[p]
1851
                    ) * (1 + m.discount_rate) ** (-m.es.periods_years[p])
1852
                    remaining_value_difference = (
1853
                        self._evaluate_remaining_value_difference(
1854
                            m,
1855
                            p,
1856
                            n,
1857
                            m.es.end_year_of_optimization,
1858
                            lifetime,
1859
                            interest,
1860
                            nonconvex=True,
1861
                        )
1862
                    )
1863
                    investment_costs += (
1864
                        investment_costs_increment + remaining_value_difference
1865
                    )
1866
                    period_investment_costs[p] += investment_costs_increment
1867
1868
            for n in self.INVESTSTORAGES:
1869
                if valid_sequence(n.investment.fixed_costs, len(m.PERIODS)):
1870
                    lifetime = n.investment.lifetime
1871
                    for p in m.PERIODS:
1872
                        range_limit = min(
1873
                            m.es.end_year_of_optimization,
1874
                            m.es.periods_years[p] + lifetime,
1875
                        )
1876
                        fixed_costs += sum(
1877
                            self.invest[n, p]
1878
                            * n.investment.fixed_costs[pp]
1879
                            * (1 + m.discount_rate) ** (-pp)
1880
                            for pp in range(
1881
                                m.es.periods_years[p],
1882
                                range_limit,
1883
                            )
1884
                        )
1885
1886
            for n in self.EXISTING_INVESTSTORAGES:
1887
                if valid_sequence(n.investment.fixed_costs, len(m.PERIODS)):
1888
                    lifetime = n.investment.lifetime
1889
                    age = n.investment.age
1890
                    range_limit = min(
1891
                        m.es.end_year_of_optimization, lifetime - age
1892
                    )
1893
                    fixed_costs += sum(
1894
                        n.investment.existing
1895
                        * n.investment.fixed_costs[pp]
1896
                        * (1 + m.discount_rate) ** (-pp)
1897
                        for pp in range(range_limit)
1898
                    )
1899
1900
        self.investment_costs = Expression(expr=investment_costs)
1901
        self.period_investment_costs = period_investment_costs
1902
        self.fixed_costs = Expression(expr=fixed_costs)
1903
        self.costs = Expression(expr=investment_costs + fixed_costs)
1904
1905
        return self.costs
1906
1907
    def _evaluate_remaining_value_difference(
1908
        self,
1909
        m,
1910
        p,
1911
        n,
1912
        end_year_of_optimization,
1913
        lifetime,
1914
        interest,
1915
        nonconvex=False,
1916
    ):
1917
        """Evaluate and return the remaining value difference of an investment
1918
1919
        The remaining value difference in the net present values if the asset
1920
        was to be liquidated at the end of the optimization horizon and the
1921
        net present value using the original investment expenses.
1922
1923
        Parameters
1924
        ----------
1925
        m : oemof.solph.models.Model
1926
            Optimization model
1927
1928
        p : int
1929
            Period in which investment occurs
1930
1931
        n : oemof.solph.components.GenericStorage
1932
            storage unit
1933
1934
        end_year_of_optimization : int
1935
            Last year of the optimization horizon
1936
1937
        lifetime : int
1938
            lifetime of investment considered
1939
1940
        interest : float
1941
            Demanded interest rate for investment
1942
1943
        nonconvex : bool
1944
            Indicating whether considered flow is nonconvex.
1945
        """
1946
        if m.es.use_remaining_value:
1947
            if end_year_of_optimization - m.es.periods_years[p] < lifetime:
1948
                remaining_lifetime = lifetime - (
1949
                    end_year_of_optimization - m.es.periods_years[p]
1950
                )
1951
                remaining_annuity = economics.annuity(
1952
                    capex=n.investment.ep_costs[-1],
1953
                    n=remaining_lifetime,
1954
                    wacc=interest,
1955
                )
1956
                original_annuity = economics.annuity(
1957
                    capex=n.investment.ep_costs[p],
1958
                    n=remaining_lifetime,
1959
                    wacc=interest,
1960
                )
1961
                present_value_factor_remaining = 1 / economics.annuity(
1962
                    capex=1, n=remaining_lifetime, wacc=interest
1963
                )
1964
                convex_investment_costs = (
1965
                    self.invest[n, p]
1966
                    * (remaining_annuity - original_annuity)
1967
                    * present_value_factor_remaining
1968
                ) * (1 + m.discount_rate) ** (-end_year_of_optimization)
1969
                if nonconvex:
1970
                    return convex_investment_costs + self.invest_status[
1971
                        n, p
1972
                    ] * (n.investment.offset[-1] - n.investment.offset[p]) * (
1973
                        1 + m.discount_rate
1974
                    ) ** (
1975
                        -end_year_of_optimization
1976
                    )
1977
                else:
1978
                    return convex_investment_costs
1979
            else:
1980
                return 0
1981
        else:
1982
            return 0
1983