Passed
Pull Request — dev (#836)
by Uwe
03:17 queued 01:53
created

SinkDSMOemofInvestmentBlock._create()   F

Complexity

Conditions 16

Size

Total Lines 184
Code Lines 89

Duplication

Lines 66
Ratio 35.87 %

Importance

Changes 0
Metric Value
eloc 89
dl 66
loc 184
rs 2.04
c 0
b 0
f 0
cc 16
nop 2

How to fix   Long Method    Complexity   

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:

Complexity

Complex classes like solph.components.experimental._sink_dsm.SinkDSMOemofInvestmentBlock._create() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# -*- coding: utf-8 -*-
2
3
"""
4
In-development functionality for demand-side management.
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: Johannes Röder
11
SPDX-FileCopyrightText: jakob-wo
12
SPDX-FileCopyrightText: gplssm
13
SPDX-FileCopyrightText: jnnr
14
SPDX-FileCopyrightText: Johannes Kochems (jokochems)
15
16
SPDX-License-Identifier: MIT
17
18
"""
19
import itertools
20
21
from numpy import mean
22
from pyomo.core.base.block import ScalarBlock
23
from pyomo.environ import BuildAction
24
from pyomo.environ import Constraint
25
from pyomo.environ import Expression
26
from pyomo.environ import NonNegativeReals
27
from pyomo.environ import Set
28
from pyomo.environ import Var
29
30
from oemof.solph._options import Investment
31
from oemof.solph._plumbing import sequence
32
from oemof.solph.components._sink import Sink
33
34
35
class SinkDSM(Sink):
36
    r"""
37
    Demand Side Management implemented as Sink with flexibility potential.
38
39
    There are several approaches possible which can be selected:
40
    - DIW: Based on the paper by Zerrahn, Alexander and Schill, Wolf-Peter
41
    (2015): `On the representation of demand-side management in power system
42
    models <https://doi.org/10.1016/j.energy.2015.03.037>`_,
43
    in: Energy (84), pp. 840-845, 10.1016/j.energy.2015.03.037,
44
    accessed 08.01.2021, pp. 842-843.
45
    - DLR: Based on the PhD thesis of Gils, Hans Christian (2015):
46
    `Balancing of Intermittent Renewable Power Generation by Demand Response
47
    and Thermal Energy Storage`, Stuttgart,
48
    <http://dx.doi.org/10.18419/opus-6888>,
49
    accessed 08.01.2021, pp. 67-70.
50
    - oemof: Created by Julian Endres. A fairly simple DSM representation which
51
    demands the energy balance to be levelled out in fixed cycles
52
53
    An evaluation of different modeling approaches has been carried out and
54
    presented at the INREC 2020. Some of the results are as follows:
55
    - DIW: A solid implementation with the tendency of slight overestimization
56
    of potentials since a shift_time is not accounted for. It may get
57
    computationally expensive due to a high time-interlinkage in constraint
58
    formulations.
59
    - DLR: An extensive modeling approach for demand response which neither
60
    leads to an over- nor underestimization of potentials and balances modeling
61
    detail and computation intensity. :attr:`fixes` and :attr:`addition` should
62
    both be set to True which is the default value.
63
    - oemof: A very computationally efficient approach which only requires the
64
    energy balance to be levelled out in certain intervals. If demand response
65
    is not at the center of the research and/or parameter availability is
66
    limited, this approach should be chosen. Note that approach `oemof` does
67
    allow for load shedding, but does not impose a limit on maximum amount of
68
    shedded energy.
69
70
    SinkDSM adds additional constraints that allow to shift energy in certain
71
    time window constrained by :attr:`~capacity_up` and
72
    :attr:`~capacity_down`.
73
74
    Parameters
75
    ----------
76
    demand: numeric
77
        original electrical demand (normalized)
78
        For investment modeling, it is advised to use the maximum of the
79
        demand timeseries and the cumulated (fixed) infeed time series
80
        for normalization, because the balancing potential may be determined by
81
        both. Elsewhise, underinvestments may occur.
82
    capacity_up: int or array
83
        maximum DSM capacity that may be increased (normalized)
84
    capacity_down: int or array
85
        maximum DSM capacity that may be reduced (normalized)
86
    approach: 'oemof', 'DIW', 'DLR'
87
        Choose one of the DSM modeling approaches. Read notes about which
88
        parameters to be applied for which approach.
89
90
        oemof :
91
92
            Simple model in which the load shift must be compensated in a
93
            predefined fixed interval (:attr:`~shift_interval` is mandatory).
94
            Within time windows of the length :attr:`~shift_interval` DSM
95
            up and down shifts are balanced. See
96
            :class:`~SinkDSMOemofBlock` for details.
97
98
        DIW :
99
100
            Sophisticated model based on the formulation by
101
            Zerrahn & Schill (2015a). The load shift of the component must be
102
            compensated in a predefined delay time (:attr:`~delay_time` is
103
            mandatory).
104
            For details see :class:`~SinkDSMDIWBlock`.
105
106
        DLR :
107
108
            Sophisticated model based on the formulation by
109
            Gils (2015). The load shift of the component must be
110
            compensated in a predefined delay time (:attr:`~delay_time` is
111
            mandatory).
112
            For details see :class:`~SinkDSMDLRBlock`.
113
    shift_interval: int
114
        Only used when :attr:`~approach` is set to 'oemof'. Otherwise, can be
115
        None.
116
        It's the interval in which between :math:`DSM_{t}^{up}` and
117
        :math:`DSM_{t}^{down}` have to be compensated.
118
    delay_time: int
119
        Only used when :attr:`~approach` is set to 'DIW' or 'DLR'. Otherwise,
120
        can be None.
121
        Length of symmetrical time windows around :math:`t` in which
122
        :math:`DSM_{t}^{up}` and :math:`DSM_{t,tt}^{down}` have to be
123
        compensated.
124
        Note: For approach 'DLR', an iterable is constructed in order
125
        to model flexible delay times
126
    shift_time: int
127
        Only used when :attr:`~approach` is set to 'DLR'.
128
        Duration of a single upwards or downwards shift (half a shifting cycle
129
        if there is immediate compensation)
130
    shed_time: int
131
        Only used when :attr:`~shed_eligibility` is set to True.
132
        Maximum length of a load shedding process at full capacity
133
        (used within energy limit constraint)
134
    max_demand: numeric
135
        Maximum demand prior to demand response
136
    max_capacity_down: numeric
137
        Maximum capacity eligible for downshifts
138
        prior to demand response (used for dispatch mode)
139
    max_capacity_up: numeric
140
        Maximum capacity eligible for upshifts
141
        prior to demand response (used for dispatch mode)
142
    flex_share_down: float
143
        Flexible share of installed capacity
144
        eligible for downshifts (used for invest mode)
145
    flex_share_up: float
146
        Flexible share of installed capacity
147
        eligible for upshifts (used for invest mode)
148
    cost_dsm_up : int
149
        Cost per unit of DSM activity that increases the demand
150
    cost_dsm_down_shift : int
151
        Cost per unit of DSM activity that decreases the demand
152
        for load shifting
153
    cost_dsm_down_shed : int
154
        Cost per unit of DSM activity that decreases the demand
155
        for load shedding
156
    efficiency : float
157
        Efficiency factor for load shifts (between 0 and 1)
158
    recovery_time_shift : int
159
        Only used when :attr:`~approach` is set to 'DIW'.
160
        Minimum time between the end of one load shifting process
161
        and the start of another for load shifting processes
162
    recovery_time_shed : int
163
        Only used when :attr:`~approach` is set to 'DIW'.
164
        Minimum time between the end of one load shifting process
165
        and the start of another for load shedding processes
166
    ActivateYearLimit : boolean
167
        Only used when :attr:`~approach` is set to 'DLR'.
168
        Control parameter; activates constraints for year limit if set to True
169
    ActivateDayLimit : boolean
170
        Only used when :attr:`~approach` is set to 'DLR'.
171
        Control parameter; activates constraints for day limit if set to True
172
    n_yearLimit_shift : int
173
        Only used when :attr:`~approach` is set to 'DLR'.
174
        Maximum number of load shifts at full capacity per year, used to limit
175
        the amount of energy shifted per year. Optional parameter that is only
176
        needed when ActivateYearLimit is True
177
    n_yearLimit_shed : int
178
        Only used when :attr:`~approach` is set to 'DLR'.
179
        Maximum number of load sheds at full capacity per year, used to limit
180
        the amount of energy shedded per year. Mandatory parameter if load
181
        shedding is allowed by setting shed_eligibility to True
182
    t_dayLimit: int
183
        Only used when :attr:`~approach` is set to 'DLR'.
184
        Maximum duration of load shifts at full capacity per day, used to limit
185
        the amount of energy shifted per day. Optional parameter that is only
186
        needed when ActivateDayLimit is True
187
    addition : boolean
188
        Only used when :attr:`~approach` is set to 'DLR'.
189
        Boolean parameter indicating whether or not to include additional
190
        constraint (which corresponds to Eq. 10 from Zerrahn and Schill (2015a)
191
    fixes : boolean
192
        Only used when :attr:`~approach` is set to 'DLR'.
193
        Boolean parameter indicating whether or not to include additional
194
        fixes. These comprise prohibiting shifts which cannot be balanced
195
        within the optimization timeframe
196
    shed_eligibility : boolean
197
        Boolean parameter indicating whether unit is eligible for
198
        load shedding
199
    shift_eligibility : boolean
200
        Boolean parameter indicating whether unit is eligible for
201
        load shifting
202
203
    Note
204
    ----
205
206
    * :attr:`method` has been renamed to :attr:`approach`.
207
    * As many constraints and dependencies are created in approach 'DIW',
208
      computational cost might be high with a large 'delay_time' and with model
209
      of high temporal resolution
210
    * The approach 'DLR' preforms better in terms of calculation time,
211
      compared to the approach 'DIW'
212
    * Using :attr:`~approach` 'DIW' or 'DLR' might result in demand shifts that
213
      exceed the specified delay time by activating up and down simultaneously
214
      in the time steps between to DSM events. Thus, the purpose of this
215
      component is to model demand response portfolios rather than individual
216
      demand units.
217
    * It's not recommended to assign cost to the flow that connects
218
      :class:`~SinkDSM` with a bus. Instead, use :attr:`~SinkDSM.cost_dsm_up`
219
      or :attr:`~cost_dsm_down_shift`
220
    * Variable costs may be attributed to upshifts, downshifts or both.
221
      Costs for shedding may deviate from that for shifting
222
      (usually costs for shedding are much larger and equal to the value
223
      of lost load).
224
225
    """
226
227
    def __init__(
228
        self,
229
        demand,
230
        capacity_up,
231
        capacity_down,
232
        approach,
233
        shift_interval=None,
234
        delay_time=None,
235
        shift_time=None,
236
        shed_time=None,
237
        max_demand=None,
238
        max_capacity_down=None,
239
        max_capacity_up=None,
240
        flex_share_down=None,
241
        flex_share_up=None,
242
        cost_dsm_up=0,
243
        cost_dsm_down_shift=0,
244
        cost_dsm_down_shed=0,
245
        efficiency=1,
246
        recovery_time_shift=None,
247
        recovery_time_shed=None,
248
        ActivateYearLimit=False,
249
        ActivateDayLimit=False,
250
        n_yearLimit_shift=None,
251
        n_yearLimit_shed=None,
252
        t_dayLimit=None,
253
        addition=True,
254
        fixes=True,
255
        shed_eligibility=True,
256
        shift_eligibility=True,
257
        **kwargs,
258
    ):
259
        super().__init__(**kwargs)
260
261
        self.capacity_up = sequence(capacity_up)
262
        self.capacity_down = sequence(capacity_down)
263
        self.demand = sequence(demand)
264
        self.approach = approach
265
        self.shift_interval = shift_interval
266
        if not approach == "DLR":
267
            self.delay_time = delay_time
268
        else:
269
            self.delay_time = [el for el in range(1, delay_time + 1)]
270
        self.shift_time = shift_time
271
        self.shed_time = shed_time
272
273
        # Attributes are only needed if no investments occur
274
        self.max_capacity_down = max_capacity_down
275
        self.max_capacity_up = max_capacity_up
276
        self.max_demand = max_demand
277
278
        # Attributes for investment modeling
279
        if flex_share_down is not None:
280
            if max_capacity_down is None and max_demand is None:
281
                self.flex_share_down = flex_share_down
282
            else:
283
                e1 = (
284
                    "Please determine either **flex_share_down "
285
                    "(investment modeling)\n or set "
286
                    "**max_demand and **max_capacity_down "
287
                    "(dispatch modeling).\n"
288
                    "Otherwise, overdetermination occurs."
289
                )
290
                raise AttributeError(e1)
291
        else:
292
            if max_capacity_down is None or max_demand is None:
293
                e2 = (
294
                    "If you do not specify **flex_share_down\n"
295
                    "which should be used for investment modeling,\n"
296
                    "you have to specify **max_capacity_down "
297
                    "and **max_demand\n"
298
                    "instead which should be used for dispatch modeling."
299
                )
300
                raise AttributeError(e2)
301
            else:
302
                self.flex_share_down = self.max_capacity_down / self.max_demand
303
304
        if flex_share_up is not None:
305
            if max_capacity_up is None and max_demand is None:
306
                self.flex_share_up = flex_share_up
307
            else:
308
                e3 = (
309
                    "Please determine either flex_share_up "
310
                    "(investment modeling)\n or set "
311
                    "max_demand and max_capacity_up (dispatch modeling).\n"
312
                    "Otherwise, overdetermination occurs."
313
                )
314
                raise AttributeError(e3)
315
        else:
316
            if max_capacity_up is None or max_demand is None:
317
                e4 = (
318
                    "If you do not specify **flex_share_up\n"
319
                    "which should be used for investment modeling,\n"
320
                    "you have to specify **max_capacity_up "
321
                    "and **max_demand\n"
322
                    "instead which should be used for dispatch modeling."
323
                )
324
                raise AttributeError(e4)
325
            else:
326
                self.flex_share_up = self.max_capacity_up / self.max_demand
327
328
        self.cost_dsm_up = sequence(cost_dsm_up)
329
        self.cost_dsm_down_shift = sequence(cost_dsm_down_shift)
330
        self.cost_dsm_down_shed = sequence(cost_dsm_down_shed)
331
        self.efficiency = efficiency
332
        self.capacity_down_mean = mean(capacity_down)
333
        self.capacity_up_mean = mean(capacity_up)
334
        self.recovery_time_shift = recovery_time_shift
335
        self.recovery_time_shed = recovery_time_shed
336
        self.ActivateYearLimit = ActivateYearLimit
337
        self.ActivateDayLimit = ActivateDayLimit
338
        self.n_yearLimit_shift = n_yearLimit_shift
339
        self.n_yearLimit_shed = n_yearLimit_shed
340
        self.t_dayLimit = t_dayLimit
341
        self.addition = addition
342
        self.fixes = fixes
343
        self.shed_eligibility = shed_eligibility
344
        self.shift_eligibility = shift_eligibility
345
346
        # Check whether investment mode is active or not
347
        self.investment = kwargs.get("investment")
348
        self._invest_group = isinstance(self.investment, Investment)
349
350
        if (
351
            self.max_demand is None
352
            or self.max_capacity_up is None
353
            or self.max_capacity_down is None
354
        ) and not self._invest_group:
355
            e5 = (
356
                "If you are setting up a dispatch model, "
357
                "you have to specify **max_demand**, **max_capacity_up** "
358
                "and **max_capacity_down**.\n"
359
                "The values you might have passed for **flex_share_up** "
360
                "and **flex_share_down** will be ignored and only used in "
361
                "an investment model."
362
            )
363
            raise AttributeError(e5)
364
365
        if self._invest_group:
366
            self._check_invest_attributes()
367
368
    def _check_invest_attributes(self):
369
        if (
370
            self.investment is not None
371
            and (
372
                self.max_demand
373
                or self.max_capacity_down
374
                or self.max_capacity_up
375
            )
376
            is not None
377
        ):
378
            e6 = (
379
                "If an investment object is defined, the invest variable "
380
                "replaces the **max_demand, the **max_capacity_down "
381
                "as well as\n"
382
                "the **max_capacity_up values. Therefore, **max_demand,\n"
383
                "**max_capacity_up and **max_capacity_down values should be "
384
                "'None'.\n"
385
            )
386
            raise AttributeError(e6)
387
388
    def constraint_group(self):
389
        possible_approaches = ["DIW", "DLR", "oemof"]
390
391
        if self.approach in [possible_approaches[0], possible_approaches[1]]:
392
            if self.delay_time is None:
393
                raise ValueError(
394
                    "Please define: **delay_time" " is a mandatory parameter"
395
                )
396
            if not self.shed_eligibility and not self.shift_eligibility:
397
                raise ValueError(
398
                    "At least one of **shed_eligibility"
399
                    " and **shift_eligibility must be True"
400
                )
401
            if self.shed_eligibility:
402
                if self.recovery_time_shed is None:
403
                    raise ValueError(
404
                        "If unit is eligible for load shedding,"
405
                        " **recovery_time_shed must be defined"
406
                    )
407
408
        if self.approach == possible_approaches[0]:
409
            if self._invest_group is True:
410
                return SinkDSMDIWInvestmentBlock
411
            else:
412
                return SinkDSMDIWBlock
413
414
        elif self.approach == possible_approaches[1]:
415
            if self._invest_group is True:
416
                return SinkDSMDLRInvestmentBlock
417
            else:
418
                return SinkDSMDLRBlock
419
420
        elif self.approach == possible_approaches[2]:
421
            if self.shift_interval is None:
422
                raise ValueError(
423
                    "Please define: **shift_interval"
424
                    " is a mandatory parameter"
425
                )
426
            if self._invest_group is True:
427
                return SinkDSMOemofInvestmentBlock
428
            else:
429
                return SinkDSMOemofBlock
430
        else:
431
            raise ValueError(
432
                'The "approach" must be one of the following set: '
433
                '"{}"'.format('" or "'.join(possible_approaches))
434
            )
435
436
437
class SinkDSMOemofBlock(ScalarBlock):
438
    r"""Constraints for SinkDSM with "oemof" approach
439
440
    **The following constraints are created for approach = 'oemof':**
441
442
    .. _SinkDSMOemof equations:
443
444
    .. math::
445
        &
446
        (1) \quad DSM_{t}^{up} = 0 \quad \forall t
447
        \quad if \space eligibility_{shift} = False \\
448
        &
449
        (2) \quad DSM_{t}^{do, shed} = 0 \quad \forall t
450
        \quad if \space eligibility_{shed} = False \\
451
        &
452
        (3) \quad \dot{E}_{t} = demand_{t} \cdot demand_{max} + DSM_{t}^{up}
453
        - DSM_{t}^{do, shift} - DSM_{t}^{do, shed}
454
        \quad \forall t \in \mathbb{T} \\
455
        &
456
        (4) \quad  DSM_{t}^{up} \leq E_{t}^{up} \cdot E_{up, max}
457
        \quad \forall t \in \mathbb{T} \\
458
        &
459
        (5) \quad DSM_{t}^{do, shift} + DSM_{t}^{do, shed}
460
        \leq  E_{t}^{do} \cdot E_{do, max}
461
        \quad \forall t \in \mathbb{T} \\
462
        &
463
        (6) \quad  \sum_{t=t_s}^{t_s+\tau} DSM_{t}^{up} \cdot \eta =
464
        \sum_{t=t_s}^{t_s+\tau} DSM_{t}^{do, shift} \quad \forall t_s \in
465
        \{k \in \mathbb{T} \mid k \mod \tau = 0\} \\
466
        &
467
468
    **The following parts of the objective function are created:**
469
470
    .. math::
471
        DSM_{t}^{up} \cdot cost_{t}^{dsm, up}
472
        + DSM_{t}^{do, shift} \cdot cost_{t}^{dsm, do, shift}
473
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
474
        \quad \forall t \in \mathbb{T} \\
475
476
    **Table: Symbols and attribute names of variables and parameters**
477
478
        .. csv-table:: Variables (V) and Parameters (P)
479
            :header: "symbol", "attribute", "type", "explanation"
480
            :widths: 1, 1, 1, 1
481
482
            ":math:`DSM_{t}^{up}` ",
483
            ":attr:`~SinkDSM.dsm_up[g, t]` ","V", "DSM
484
            up shift (capacity shifted upwards)"
485
            ":math:`DSM_{t}^{do, shift}` ",
486
            ":attr:`~SinkDSM.dsm_do_shift[g, t]` ",
487
            "V","DSM down shift (capacity shifted downwards)"
488
            ":math:`DSM_{t}^{do, shed}` ",
489
            ":attr:`~SinkDSM.dsm_do_shed[g, t]` ",
490
            "V","DSM shedded (capacity shedded, i.e. not compensated for)"
491
            ":math:`\dot{E}_{t}`",":attr:`~SinkDSM.inputs`","V", "Energy
492
            flowing in from (electrical) inflow bus"
493
            ":math:`demand_{t}`",":attr:`~SinkDSM.demand[t]`","P",
494
            "(Electrical) demand series (normalized)"
495
            ":math:`demand_{max}`",":attr:`~SinkDSM.max_demand`","P",
496
            "Maximum demand value"
497
            ":math:`E_{t}^{do}`",":attr:`~SinkDSM.capacity_down[t]`","P",
498
            "Capacity  allowed for a load adjustment downwards (normalized)
499
            (DSM down shift + DSM shedded)"
500
            ":math:`E_{t}^{up}`",":attr:`~SinkDSM.capacity_up[t]`","P",
501
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
502
            ":math:`E_{do, max}`",":attr:`~SinkDSM.max_capacity_down`","P",
503
            "Maximum capacity allowed for a load adjustment downwards
504
            (DSM down shift + DSM shedded)"
505
            ":math:`E_{up, max}`",":attr:`~SinkDSM.max_capacity_up`","P",
506
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
507
            ":math:`\tau`",":attr:`~SinkDSM.shift_interval`","P", "Shift
508
            interval (time within which the energy balance must be
509
            levelled out"
510
            ":math:`\eta`",":attr:`~SinkDSM.efficiency`","P", "Efficiency
511
            loss forload shifting processes"
512
            ":math:`\mathbb{T}` "," ","P", "Time steps"
513
            ":math:`eligibility_{shift}` ",
514
            ":attr:`~SinkDSM.shift_eligibility`","P",
515
            "Boolean parameter indicating if unit can be used for
516
            load shifting"
517
            ":math:`eligibility_{shed}` ",
518
            ":attr:`~SinkDSM.shed_eligibility`","P",
519
            "Boolean parameter indicating if unit can be used for
520
            load shedding"
521
            ":math:`cost_{t}^{dsm, up}` ", ":attr:`~SinkDSM.cost_dsm_up[t]`",
522
            "P", "Variable costs for an upwards shift"
523
            ":math:`cost_{t}^{dsm, do, shift}` ",
524
            ":attr:`~SinkDSM.cost_dsm_down_shift[t]`","P",
525
            "Variable costs for a downwards shift (load shifting)"
526
            ":math:`cost_{t}^{dsm, do, shed}` ",
527
            ":attr:`~SinkDSM.cost_dsm_down_shed[t]`","P",
528
            "Variable costs for shedding load"
529
    """
530
    CONSTRAINT_GROUP = True
531
532
    def __init__(self, *args, **kwargs):
533
        super().__init__(*args, **kwargs)
534
535
    def _create(self, group=None):
536
        if group is None:
537
            return None
538
539
        m = self.parent_block()
540
541
        # for all DSM components get inflow from a bus
542
        for n in group:
543
            n.inflow = list(n.inputs)[0]
544
545
        #  ************* SETS *********************************
546
547
        # Set of DSM Components
548
        self.dsm = Set(initialize=[n for n in group])
549
550
        #  ************* VARIABLES *****************************
551
552
        # Variable load shift down
553
        self.dsm_do_shift = Var(
554
            self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
555
        )
556
557
        # Variable load shedding
558
        self.dsm_do_shed = Var(
559
            self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
560
        )
561
562
        # Variable load shift up
563
        self.dsm_up = Var(
564
            self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
565
        )
566
567
        #  ************* CONSTRAINTS *****************************
568
569
        def _shift_shed_vars_rule(block):
570
            """Force shifting resp. shedding variables to zero dependent
571
            on how boolean parameters for shift resp. shed eligibility
572
            are set.
573
            """
574
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
575
                for g in group:
576
                    if not g.shift_eligibility:
577
                        lhs = self.dsm_up[g, t]
578
                        rhs = 0
579
580
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
581
582
                    if not g.shed_eligibility:
583
                        lhs = self.dsm_do_shed[g, t]
584
                        rhs = 0
585
586
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
587
588
        self.shift_shed_vars = Constraint(group, m.TIMESTEPS, noruleinit=True)
589
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
590
591
        # Demand Production Relation
592 View Code Duplication
        def _input_output_relation_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
593
            """Relation between input data and pyomo variables.
594
            The actual demand after DSM.
595
            Generator Production == Demand_el +- DSM
596
            """
597
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
598
                for g in group:
599
                    # Inflow from bus
600
                    lhs = m.flow[g.inflow, g, t]
601
602
                    # Demand + DSM_up - DSM_down
603
                    rhs = (
604
                        g.demand[t] * g.max_demand
605
                        + self.dsm_up[g, t]
606
                        - self.dsm_do_shift[g, t]
607
                        - self.dsm_do_shed[g, t]
608
                    )
609
610
                    # add constraint
611
                    block.input_output_relation.add((g, t), (lhs == rhs))
612
613
        self.input_output_relation = Constraint(
614
            group, m.TIMESTEPS, noruleinit=True
615
        )
616
        self.input_output_relation_build = BuildAction(
617
            rule=_input_output_relation_rule
618
        )
619
620
        # Upper bounds relation
621 View Code Duplication
        def dsm_up_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
622
            """Realised upward load shift at time t has to be smaller than
623
            upward DSM capacity at time t.
624
            """
625
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
626
                for g in group:
627
                    # DSM up
628
                    lhs = self.dsm_up[g, t]
629
                    # Capacity dsm_up
630
                    rhs = g.capacity_up[t] * g.max_capacity_up
631
632
                    # add constraint
633
                    block.dsm_up_constraint.add((g, t), (lhs <= rhs))
634
635
        self.dsm_up_constraint = Constraint(
636
            group, m.TIMESTEPS, noruleinit=True
637
        )
638
        self.dsm_up_constraint_build = BuildAction(rule=dsm_up_constraint_rule)
639
640
        # Upper bounds relation
641
        def dsm_down_constraint_rule(block):
642
            """Realised downward load shift at time t has to be smaller than
643
            downward DSM capacity at time t.
644
            """
645
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
646
                for g in group:
647
                    # DSM down
648
                    lhs = self.dsm_do_shift[g, t] + self.dsm_do_shed[g, t]
649
                    # Capacity dsm_down
650
                    rhs = g.capacity_down[t] * g.max_capacity_down
651
652
                    # add constraint
653
                    block.dsm_down_constraint.add((g, t), (lhs <= rhs))
654
655
        self.dsm_down_constraint = Constraint(
656
            group, m.TIMESTEPS, noruleinit=True
657
        )
658
        self.dsm_down_constraint_build = BuildAction(
659
            rule=dsm_down_constraint_rule
660
        )
661
662 View Code Duplication
        def dsm_sum_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
663
            """Relation to compensate the total amount of positive
664
            and negative DSM in between the shift_interval.
665
            This constraint is building balance in full intervals starting
666
            with index 0. The last interval might not be full.
667
            """
668
            for g in group:
669
                intervals = range(
670
                    m.TIMESTEPS[1], m.TIMESTEPS[-1], g.shift_interval
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
671
                )
672
673
                for interval in intervals:
674
                    if (interval + g.shift_interval - 1) > m.TIMESTEPS[-1]:
675
                        timesteps = range(interval, m.TIMESTEPS[-1] + 1)
676
                    else:
677
                        timesteps = range(
678
                            interval, interval + g.shift_interval
679
                        )
680
681
                    # DSM up/down
682
                    lhs = (
683
                        sum(self.dsm_up[g, tt] for tt in timesteps)
684
                        * g.efficiency
685
                    )
686
                    # value
687
                    rhs = sum(self.dsm_do_shift[g, tt] for tt in timesteps)
688
689
                    # add constraint
690
                    block.dsm_sum_constraint.add((g, interval), (lhs == rhs))
691
692
        self.dsm_sum_constraint = Constraint(
693
            group, m.TIMESTEPS, noruleinit=True
694
        )
695
        self.dsm_sum_constraint_build = BuildAction(
696
            rule=dsm_sum_constraint_rule
697
        )
698
699 View Code Duplication
    def _objective_expression(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
700
        r"""Objective expression with variable costs for DSM activity"""
701
702
        m = self.parent_block()
703
704
        dsm_cost = 0
705
706
        for t in m.TIMESTEPS:
707
            for g in self.dsm:
708
                dsm_cost += (
709
                    self.dsm_up[g, t]
710
                    * g.cost_dsm_up[t]
711
                    * m.objective_weighting[t]
712
                )
713
                dsm_cost += (
714
                    self.dsm_do_shift[g, t] * g.cost_dsm_down_shift[t]
715
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
716
                ) * m.objective_weighting[t]
717
718
        self.cost = Expression(expr=dsm_cost)
719
720
        return self.cost
721
722
723
class SinkDSMOemofInvestmentBlock(ScalarBlock):
724
    r"""Constraints for SinkDSM with "oemof" approach and :attr:`investment`
725
726
    **The following constraints are created for approach = 'oemof' with an
727
    investment object defined:**
728
729
    .. _SinkDSMOemof equations:
730
731
    .. math::
732
        &
733
        (1) \quad invest_{min} \leq invest \leq invest_{max} \\
734
        &
735
        (2) \quad DSM_{t}^{up} = 0 \quad \forall t
736
        \quad if \space eligibility_{shift} = False \\
737
        &
738
        (3) \quad DSM_{t}^{do, shed} = 0 \quad \forall t
739
        \quad if \space eligibility_{shed} = False \\
740
        &
741
        (4) \quad \dot{E}_{t} = demand_{t} \cdot (invest + E_{exist})
742
        + DSM_{t}^{up}
743
        - DSM_{t}^{do, shift} - DSM_{t}^{do, shed}
744
        \quad \forall t \in \mathbb{T} \\
745
        &
746
        (5) \quad  DSM_{t}^{up} \leq E_{t}^{up} \cdot (invest + E_{exist})
747
        \cdot s_{flex, up}
748
        \quad \forall t \in \mathbb{T} \\
749
        &
750
        (6) \quad DSM_{t}^{do, shift} +  DSM_{t}^{do, shed} \leq
751
        E_{t}^{do} \cdot (invest + E_{exist}) \cdot s_{flex, do}
752
        \quad \forall t \in \mathbb{T} \\
753
        &
754
        (7) \quad  \sum_{t=t_s}^{t_s+\tau} DSM_{t}^{up} \cdot \eta =
755
        \sum_{t=t_s}^{t_s+\tau} DSM_{t}^{do, shift} \quad \forall t_s \in
756
        \{k \in \mathbb{T} \mid k \mod \tau = 0\} \\
757
        &
758
759
    **The following parts of the objective function are created:**
760
761
    * Investment annuity:
762
763
    .. math::
764
        invest \cdot costs_{invest} \\
765
766
    * Variable costs:
767
768
    .. math::
769
        DSM_{t}^{up} \cdot cost_{t}^{dsm, up}
770
        + DSM_{t}^{do, shift} \cdot cost_{t}^{dsm, do, shift}
771
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
772
        \quad  \forall t \in \mathbb{T} \\
773
774
    See remarks in
775
    :class:`oemof.solph.components.experimental._sink_dsm.SinkDSMOemofBlock`.
776
777
    **Symbols and attribute names of variables and parameters**
778
779
    Please refer to
780
    :class:`oemof.solph.components.experimental._sink_dsm.SinkDSMOemofBlock`.
781
782
    The following variables and parameters are exclusively used for
783
    investment modeling:
784
785
        .. csv-table:: Variables (V) and Parameters (P)
786
            :header: "symbol", "attribute", "type", "explanation"
787
            :widths: 1, 1, 1, 1
788
789
            ":math:`invest` ",":attr:`~SinkDSM.invest` ","V", "DSM capacity
790
            invested in. Equals to the additionally installed capacity.
791
            The capacity share eligible for a shift is determined
792
            by flex share(s)."
793
            ":math:`invest_{min}` ", ":attr:`~SinkDSM.investment.minimum` ",
794
            "P", "minimum investment"
795
            ":math:`invest_{max}` ", ":attr:`~SinkDSM.investment.maximum` ",
796
            "P", "maximum investment"
797
            ":math:`E_{exist}` ",":attr:`~SinkDSM.investment.existing` ",
798
            "P", "existing DSM capacity"
799
            ":math:`s_{flex, up}` ",":attr:`~SinkDSM.flex_share_up` ",
800
            "P","Share of invested capacity that may be shift upwards
801
            at maximum"
802
            ":math:`s_{flex, do}` ",":attr:`~SinkDSM.flex_share_do` ",
803
            "P", "Share of invested capacity that may be shift downwards
804
            at maximum"
805
            ":math:`costs_{invest}` ",":attr:`~SinkDSM.investment.epcosts` ",
806
            "P", "specific investment annuity"
807
    """
808
    CONSTRAINT_GROUP = True
809
810
    def __init__(self, *args, **kwargs):
811
        super().__init__(*args, **kwargs)
812
813
    def _create(self, group=None):
814
        if group is None:
815
            return None
816
817
        m = self.parent_block()
818
819
        # for all DSM components get inflow from a bus
820
        for n in group:
821
            n.inflow = list(n.inputs)[0]
822
823
        #  ************* SETS *********************************
824
825
        # Set of DSM Components
826
        self.investdsm = Set(initialize=[n for n in group])
827
828
        #  ************* VARIABLES *****************************
829
830
        # Define bounds for investments in demand response
831
        def _dsm_investvar_bound_rule(block, g):
832
            """Rule definition to bound the
833
            invested demand response capacity `invest`.
834
            """
835
            return g.investment.minimum, g.investment.maximum
836
837
        # Investment in DR capacity
838
        self.invest = Var(
839
            self.investdsm,
840
            within=NonNegativeReals,
841
            bounds=_dsm_investvar_bound_rule,
842
        )
843
844
        # Variable load shift down
845
        self.dsm_do_shift = Var(
846
            self.investdsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
847
        )
848
849
        # Variable load shedding
850
        self.dsm_do_shed = Var(
851
            self.investdsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
852
        )
853
854
        # Variable load shift up
855
        self.dsm_up = Var(
856
            self.investdsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
857
        )
858
859
        #  ************* CONSTRAINTS *****************************
860
861
        def _shift_shed_vars_rule(block):
862
            """Force shifting resp. shedding variables to zero dependent
863
            on how boolean parameters for shift resp. shed eligibility
864
            are set.
865
            """
866
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
867
                for g in group:
868
                    if not g.shift_eligibility:
869
                        lhs = self.dsm_up[g, t]
870
                        rhs = 0
871
872
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
873
874
                    if not g.shed_eligibility:
875
                        lhs = self.dsm_do_shed[g, t]
876
                        rhs = 0
877
878
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
879
880
        self.shift_shed_vars = Constraint(group, m.TIMESTEPS, noruleinit=True)
881
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
882
883
        # Demand Production Relation
884 View Code Duplication
        def _input_output_relation_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
885
            """Relation between input data and pyomo variables.
886
            The actual demand after DSM.
887
            Generator Production == Demand_el +- DSM
888
            """
889
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
890
                for g in group:
891
                    # Inflow from bus
892
                    lhs = m.flow[g.inflow, g, t]
893
894
                    # Demand + DSM_up - DSM_down
895
                    rhs = (
896
                        g.demand[t] * (self.invest[g] + g.investment.existing)
897
                        + self.dsm_up[g, t]
898
                        - self.dsm_do_shift[g, t]
899
                        - self.dsm_do_shed[g, t]
900
                    )
901
902
                    # add constraint
903
                    block.input_output_relation.add((g, t), (lhs == rhs))
904
905
        self.input_output_relation = Constraint(
906
            group, m.TIMESTEPS, noruleinit=True
907
        )
908
        self.input_output_relation_build = BuildAction(
909
            rule=_input_output_relation_rule
910
        )
911
912
        # Upper bounds relation
913 View Code Duplication
        def dsm_up_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
914
            """Realised upward load shift at time t has to be smaller than
915
            upward DSM capacity at time t.
916
            """
917
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
918
                for g in group:
919
                    # DSM up
920
                    lhs = self.dsm_up[g, t]
921
                    # Capacity dsm_up
922
                    rhs = (
923
                        g.capacity_up[t]
924
                        * (self.invest[g] + g.investment.existing)
925
                        * g.flex_share_up
926
                    )
927
928
                    # add constraint
929
                    block.dsm_up_constraint.add((g, t), (lhs <= rhs))
930
931
        self.dsm_up_constraint = Constraint(
932
            group, m.TIMESTEPS, noruleinit=True
933
        )
934
        self.dsm_up_constraint_build = BuildAction(rule=dsm_up_constraint_rule)
935
936
        # Upper bounds relation
937
        def dsm_down_constraint_rule(block):
938
            """Realised downward load shift at time t has to be smaller than
939
            downward DSM capacity at time t.
940
            """
941
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
942
                for g in group:
943
                    # DSM down
944
                    lhs = self.dsm_do_shift[g, t] + self.dsm_do_shed[g, t]
945
                    # Capacity dsm_down
946
                    rhs = (
947
                        g.capacity_down[t]
948
                        * (self.invest[g] + g.investment.existing)
949
                        * g.flex_share_down
950
                    )
951
952
                    # add constraint
953
                    block.dsm_down_constraint.add((g, t), (lhs <= rhs))
954
955
        self.dsm_down_constraint = Constraint(
956
            group, m.TIMESTEPS, noruleinit=True
957
        )
958
        self.dsm_down_constraint_build = BuildAction(
959
            rule=dsm_down_constraint_rule
960
        )
961
962 View Code Duplication
        def dsm_sum_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
963
            """Relation to compensate the total amount of positive
964
            and negative DSM in between the shift_interval.
965
            This constraint is building balance in full intervals starting
966
            with index 0. The last interval might not be full.
967
            """
968
            for g in group:
969
                intervals = range(
970
                    m.TIMESTEPS[1], m.TIMESTEPS[-1], g.shift_interval
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
971
                )
972
973
                for interval in intervals:
974
                    if (interval + g.shift_interval - 1) > m.TIMESTEPS[-1]:
975
                        timesteps = range(interval, m.TIMESTEPS[-1] + 1)
976
                    else:
977
                        timesteps = range(
978
                            interval, interval + g.shift_interval
979
                        )
980
981
                    # DSM up/down
982
                    lhs = (
983
                        sum(self.dsm_up[g, tt] for tt in timesteps)
984
                        * g.efficiency
985
                    )
986
                    # value
987
                    rhs = sum(self.dsm_do_shift[g, tt] for tt in timesteps)
988
989
                    # add constraint
990
                    block.dsm_sum_constraint.add((g, interval), (lhs == rhs))
991
992
        self.dsm_sum_constraint = Constraint(
993
            group, m.TIMESTEPS, noruleinit=True
994
        )
995
        self.dsm_sum_constraint_build = BuildAction(
996
            rule=dsm_sum_constraint_rule
997
        )
998
999 View Code Duplication
    def _objective_expression(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1000
        r"""Objective expression with variable and investment costs for DSM"""
1001
1002
        m = self.parent_block()
1003
1004
        investment_costs = 0
1005
        variable_costs = 0
1006
1007
        for g in self.investdsm:
1008
            if g.investment.ep_costs is not None:
1009
                investment_costs += self.invest[g] * g.investment.ep_costs
1010
            else:
1011
                raise ValueError("Missing value for investment costs!")
1012
            for t in m.TIMESTEPS:
1013
                variable_costs += (
1014
                    self.dsm_up[g, t]
1015
                    * g.cost_dsm_up[t]
1016
                    * m.objective_weighting[t]
1017
                )
1018
                variable_costs += (
1019
                    self.dsm_do_shift[g, t] * g.cost_dsm_down_shift[t]
1020
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
1021
                ) * m.objective_weighting[t]
1022
1023
        self.cost = Expression(expr=investment_costs + variable_costs)
1024
1025
        return self.cost
1026
1027
1028
class SinkDSMDIWBlock(ScalarBlock):
1029
    r"""Constraints for SinkDSM with "DIW" approach
1030
1031
    **The following constraints are created for approach = 'DIW':**
1032
1033
    .. _SinkDSMDIW equations:
1034
1035
    .. math::
1036
        &
1037
        (1) \quad DSM_{t}^{up} = 0 \quad \forall t
1038
        \quad if \space eligibility_{shift} = False \\
1039
        &
1040
        (2) \quad DSM_{t}^{do, shed} = 0 \quad \forall t
1041
        \quad if \space eligibility_{shed} = False \\
1042
        &
1043
        (3) \quad \dot{E}_{t} = demand_{t} \cdot demand_{max} + DSM_{t}^{up} -
1044
        \sum_{tt=t-L}^{t+L} DSM_{tt,t}^{do, shift} - DSM_{t}^{do, shed} \quad
1045
        \forall t \in \mathbb{T} \\
1046
        &
1047
        (4) \quad DSM_{t}^{up} \cdot \eta =
1048
        \sum_{tt=t-L}^{t+L} DSM_{t,tt}^{do, shift}
1049
        \quad \forall t \in \mathbb{T} \\
1050
        &
1051
        (5) \quad DSM_{t}^{up} \leq  E_{t}^{up} \cdot E_{up, max}
1052
        \quad \forall t \in \mathbb{T} \\
1053
        &
1054
        (6) \quad \sum_{t=tt-L}^{tt+L} DSM_{t,tt}^{do, shift}
1055
        + DSM_{tt}^{do, shed} \leq E_{tt}^{do} \cdot E_{do, max}
1056
        \quad \forall tt \in \mathbb{T} \\
1057
        &
1058
        (7) \quad DSM_{tt}^{up} + \sum_{t=tt-L}^{tt+L} DSM_{t,tt}^{do, shift}
1059
        + DSM_{tt}^{do, shed} \leq
1060
        max \{ E_{tt}^{up} \cdot E_{up, max}, E_{tt}^{do} \cdot E_{do, max} \}
1061
        \quad \forall tt \in \mathbb{T} \\
1062
        &
1063
        (8) \quad \sum_{tt=t}^{t+R-1} DSM_{tt}^{up}
1064
        \leq E_{t}^{up} \cdot E_{up, max} \cdot L \cdot \Delta t
1065
        \quad \forall t \in \mathbb{T} \\
1066
        &
1067
        (9) \quad \sum_{tt=t}^{t+R-1} DSM_{tt}^{do, shed}
1068
        \leq E_{t}^{do} \cdot E_{do, max} \cdot t_{shed} \cdot \Delta t
1069
        \quad \forall t \in \mathbb{T} \\
1070
        &
1071
1072
    *Note*: For the sake of readability, the handling of indices is not
1073
    displayed here. E.g. evaluating a variable for t-L may lead to a negative
1074
    and therefore infeasible index.
1075
    This is addressed by limiting the sums to non-negative indices within the
1076
    model index bounds. Please refer to the constraints implementation
1077
    themselves.
1078
1079
    **The following parts of the objective function are created:**
1080
1081
    .. math::
1082
        DSM_{t}^{up} \cdot cost_{t}^{dsm, up}
1083
        + \sum_{tt=0}^{|T|} DSM_{t, tt}^{do, shift} \cdot
1084
        cost_{t}^{dsm, do, shift}
1085
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
1086
        \quad \forall t \in \mathbb{T} \\
1087
1088
    **Table: Symbols and attribute names of variables and parameters**
1089
1090
        .. csv-table:: Variables (V) and Parameters (P)
1091
            :header: "symbol", "attribute", "type", "explanation"
1092
            :widths: 1, 1, 1, 1
1093
1094
            ":math:`DSM_{t}^{up}` ",":attr:`~SinkDSM.dsm_up[g,t]`",
1095
            "V", "DSM up shift (additional load) in hour t"
1096
            ":math:`DSM_{t,tt}^{do, shift}` ",
1097
            ":attr:`~SinkDSM.dsm_do_shift[g,t,tt]`",
1098
            "V", "DSM down shift (less load) in hour tt
1099
            to compensate for upwards shifts in hour t"
1100
            ":math:`DSM_{t}^{do, shed}` ",":attr:`~SinkDSM.dsm_do_shed[g,t]` ",
1101
            "V","DSM shedded (capacity shedded, i.e. not compensated for)"
1102
            ":math:`\dot{E}_{t}` ",":attr:`flow[g,t]`","V","Energy
1103
            flowing in from (electrical) inflow bus"
1104
            ":math:`L`",":attr:`~SinkDSM.delay_time`","P",
1105
            "Maximum delay time for load shift
1106
            (time until the energy balance has to be levelled out again;
1107
            roundtrip time of one load shifting cycle, i.e. time window
1108
            for upshift and compensating downshift)"
1109
            ":math:`t_{she}`",":attr:`~SinkDSM.shed_time`","P",
1110
            "Maximum time for one load shedding process"
1111
            ":math:`demand_{t}`",":attr:`~SinkDSM.demand[t]`","P",
1112
            "(Electrical) demand series (normalized)"
1113
            ":math:`demand_{max}`",":attr:`~SinkDSM.max_demand`","P",
1114
            "Maximum demand value"
1115
            ":math:`E_{t}^{do}`",":attr:`~SinkDSM.capacity_down[t]`","P",
1116
            "Capacity  allowed for a load adjustment downwards (normalized)
1117
            (DSM down shift + DSM shedded)"
1118
            ":math:`E_{t}^{up}`",":attr:`~SinkDSM.capacity_up[t]`","P",
1119
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
1120
            ":math:`E_{do, max}`",":attr:`~SinkDSM.max_capacity_down`","P",
1121
            "Maximum capacity allowed for a load adjustment downwards
1122
            (DSM down shift + DSM shedded)"
1123
            ":math:`E_{up, max}`",":attr:`~SinkDSM.max_capacity_up`","P",
1124
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
1125
            ":math:`\eta`",":attr:`~SinkDSM.efficiency`","P", "Efficiency
1126
            loss for load shifting processes"
1127
            ":math:`\mathbb{T}` "," ","P", "Time steps"
1128
            ":math:`eligibility_{shift}` ",
1129
            ":attr:`~SinkDSM.shift_eligibility`","P",
1130
            "Boolean parameter indicating if unit can be used for
1131
            load shifting"
1132
            ":math:`eligibility_{shed}` ",
1133
            ":attr:`~SinkDSM.shed_eligibility`","P",
1134
            "Boolean parameter indicating if unit can be used for
1135
            load shedding"
1136
            ":math:`cost_{t}^{dsm, up}` ", ":attr:`~SinkDSM.cost_dsm_up[t]`",
1137
            "P", "Variable costs for an upwards shift"
1138
            ":math:`cost_{t}^{dsm, do, shift}` ",
1139
            ":attr:`~SinkDSM.cost_dsm_down_shift[t]`","P",
1140
            "Variable costs for a downwards shift (load shifting)"
1141
            ":math:`cost_{t}^{dsm, do, shed}` ",
1142
            ":attr:`~SinkDSM.cost_dsm_down_shed[t]`","P",
1143
            "Variable costs for shedding load"
1144
            ":math:`\R`",":attr:`~SinkDSM.recovery_time_shift`","P",
1145
            "Minimum time between the end of one load shifting process
1146
            and the start of another"
1147
            ":math:`\Delta t`",":attr:`~models.Model.timeincrement`","P",
1148
            "The time increment of the model"
1149
    """
1150
    CONSTRAINT_GROUP = True
1151
1152
    def __init__(self, *args, **kwargs):
1153
        super().__init__(*args, **kwargs)
1154
1155
    def _create(self, group=None):
1156
        if group is None:
1157
            return None
1158
1159
        m = self.parent_block()
1160
1161
        # for all DSM components get inflow from a bus
1162
        for n in group:
1163
            n.inflow = list(n.inputs)[0]
1164
1165
        #  ************* SETS *********************************
1166
1167
        # Set of DSM Components
1168
        self.dsm = Set(initialize=[g for g in group])
1169
1170
        #  ************* VARIABLES *****************************
1171
1172
        # Variable load shift down
1173
        self.dsm_do_shift = Var(
1174
            self.dsm,
1175
            m.TIMESTEPS,
1176
            m.TIMESTEPS,
1177
            initialize=0,
1178
            within=NonNegativeReals,
1179
        )
1180
1181
        # Variable load shedding
1182
        self.dsm_do_shed = Var(
1183
            self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
1184
        )
1185
1186
        # Variable load shift up
1187
        self.dsm_up = Var(
1188
            self.dsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
1189
        )
1190
1191
        #  ************* CONSTRAINTS *****************************
1192
1193
        def _shift_shed_vars_rule(block):
1194
            """Force shifting resp. shedding variables to zero dependent
1195
            on how boolean parameters for shift resp. shed eligibility
1196
            are set.
1197
            """
1198
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1199
                for g in group:
1200
                    if not g.shift_eligibility:
1201
                        lhs = self.dsm_up[g, t]
1202
                        rhs = 0
1203
1204
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
1205
1206
                    if not g.shed_eligibility:
1207
                        lhs = self.dsm_do_shed[g, t]
1208
                        rhs = 0
1209
1210
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
1211
1212
        self.shift_shed_vars = Constraint(group, m.TIMESTEPS, noruleinit=True)
1213
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
1214
1215
        # Demand Production Relation
1216
        def _input_output_relation_rule(block):
1217
            """Relation between input data and pyomo variables.
1218
            The actual demand after DSM.
1219
            Sink Inflow == Demand +- DSM
1220
            """
1221
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1222
                for g in group:
1223
                    # first time steps: 0 + delay time
1224
                    if t <= g.delay_time:
1225
1226
                        # Inflow from bus
1227
                        lhs = m.flow[g.inflow, g, t]
1228
                        # Demand +- DSM
1229
                        rhs = (
1230
                            g.demand[t] * g.max_demand
1231
                            + self.dsm_up[g, t]
1232
                            - sum(
1233
                                self.dsm_do_shift[g, tt, t]
1234
                                for tt in range(t + g.delay_time + 1)
1235
                            )
1236
                            - self.dsm_do_shed[g, t]
1237
                        )
1238
1239
                        # add constraint
1240
                        block.input_output_relation.add((g, t), (lhs == rhs))
1241
1242
                    # main use case
1243
                    elif g.delay_time < t <= m.TIMESTEPS[-1] - g.delay_time:
1244
1245
                        # Inflow from bus
1246
                        lhs = m.flow[g.inflow, g, t]
1247
                        # Demand +- DSM
1248
                        rhs = (
1249
                            g.demand[t] * g.max_demand
1250
                            + self.dsm_up[g, t]
1251
                            - sum(
1252
                                self.dsm_do_shift[g, tt, t]
1253
                                for tt in range(
1254
                                    t - g.delay_time, t + g.delay_time + 1
1255
                                )
1256
                            )
1257
                            - self.dsm_do_shed[g, t]
1258
                        )
1259
1260
                        # add constraint
1261
                        block.input_output_relation.add((g, t), (lhs == rhs))
1262
1263
                    # last time steps: end - delay time
1264
                    else:
1265
1266
                        # Inflow from bus
1267
                        lhs = m.flow[g.inflow, g, t]
1268
                        # Demand +- DSM
1269
                        rhs = (
1270
                            g.demand[t] * g.max_demand
1271
                            + self.dsm_up[g, t]
1272
                            - sum(
1273
                                self.dsm_do_shift[g, tt, t]
1274
                                for tt in range(
1275
                                    t - g.delay_time, m.TIMESTEPS[-1] + 1
1276
                                )
1277
                            )
1278
                            - self.dsm_do_shed[g, t]
1279
                        )
1280
1281
                        # add constraint
1282
                        block.input_output_relation.add((g, t), (lhs == rhs))
1283
1284
        self.input_output_relation = Constraint(
1285
            group, m.TIMESTEPS, noruleinit=True
1286
        )
1287
        self.input_output_relation_build = BuildAction(
1288
            rule=_input_output_relation_rule
1289
        )
1290
1291
        # Equation 7 (resp. 7')
1292 View Code Duplication
        def dsm_up_down_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1293
            """Equation 7 (resp. 7') by Zerrahn & Schill:
1294
            Every upward load shift has to be compensated by downward load
1295
            shifts in a defined time frame. Slightly modified equations for
1296
            the first and last time steps due to variable initialization.
1297
            Efficiency value depicts possible energy losses through
1298
            load shifting (Equation 7').
1299
            """
1300
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1301
                for g in group:
1302
1303
                    # first time steps: 0 + delay time
1304
                    if t <= g.delay_time:
1305
1306
                        # DSM up
1307
                        lhs = self.dsm_up[g, t] * g.efficiency
1308
                        # DSM down
1309
                        rhs = sum(
1310
                            self.dsm_do_shift[g, t, tt]
1311
                            for tt in range(t + g.delay_time + 1)
1312
                        )
1313
1314
                        # add constraint
1315
                        block.dsm_updo_constraint.add((g, t), (lhs == rhs))
1316
1317
                    # main use case
1318
                    elif g.delay_time < t <= m.TIMESTEPS[-1] - g.delay_time:
1319
1320
                        # DSM up
1321
                        lhs = self.dsm_up[g, t] * g.efficiency
1322
                        # DSM down
1323
                        rhs = sum(
1324
                            self.dsm_do_shift[g, t, tt]
1325
                            for tt in range(
1326
                                t - g.delay_time, t + g.delay_time + 1
1327
                            )
1328
                        )
1329
1330
                        # add constraint
1331
                        block.dsm_updo_constraint.add((g, t), (lhs == rhs))
1332
1333
                    # last time steps: end - delay time
1334
                    else:
1335
1336
                        # DSM up
1337
                        lhs = self.dsm_up[g, t] * g.efficiency
1338
                        # DSM down
1339
                        rhs = sum(
1340
                            self.dsm_do_shift[g, t, tt]
1341
                            for tt in range(
1342
                                t - g.delay_time, m.TIMESTEPS[-1] + 1
1343
                            )
1344
                        )
1345
1346
                        # add constraint
1347
                        block.dsm_updo_constraint.add((g, t), (lhs == rhs))
1348
1349
        self.dsm_updo_constraint = Constraint(
1350
            group, m.TIMESTEPS, noruleinit=True
1351
        )
1352
        self.dsm_updo_constraint_build = BuildAction(
1353
            rule=dsm_up_down_constraint_rule
1354
        )
1355
1356
        # Equation 8
1357 View Code Duplication
        def dsm_up_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1358
            """Equation 8 by Zerrahn & Schill:
1359
            Realised upward load shift at time t has to be smaller than
1360
            upward DSM capacity at time t.
1361
            """
1362
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1363
                for g in group:
1364
                    # DSM up
1365
                    lhs = self.dsm_up[g, t]
1366
                    # Capacity dsm_up
1367
                    rhs = g.capacity_up[t] * g.max_capacity_up
1368
1369
                    # add constraint
1370
                    block.dsm_up_constraint.add((g, t), (lhs <= rhs))
1371
1372
        self.dsm_up_constraint = Constraint(
1373
            group, m.TIMESTEPS, noruleinit=True
1374
        )
1375
        self.dsm_up_constraint_build = BuildAction(rule=dsm_up_constraint_rule)
1376
1377
        # Equation 9 (modified)
1378
        def dsm_do_constraint_rule(block):
1379
            """Equation 9 by Zerrahn & Schill:
1380
            Realised downward load shift at time t has to be smaller than
1381
            downward DSM capacity at time t.
1382
            """
1383
            for tt in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1384
                for g in group:
1385
1386
                    # first times steps: 0 + delay
1387
                    if tt <= g.delay_time:
1388
1389
                        # DSM down
1390
                        lhs = (
1391
                            sum(
1392
                                self.dsm_do_shift[g, t, tt]
1393
                                for t in range(tt + g.delay_time + 1)
1394
                            )
1395
                            + self.dsm_do_shed[g, tt]
1396
                        )
1397
                        # Capacity DSM down
1398
                        rhs = g.capacity_down[tt] * g.max_capacity_down
1399
1400
                        # add constraint
1401
                        block.dsm_do_constraint.add((g, tt), (lhs <= rhs))
1402
1403
                    # main use case
1404
                    elif g.delay_time < tt <= m.TIMESTEPS[-1] - g.delay_time:
1405
1406
                        # DSM down
1407
                        lhs = (
1408
                            sum(
1409
                                self.dsm_do_shift[g, t, tt]
1410
                                for t in range(
1411
                                    tt - g.delay_time, tt + g.delay_time + 1
1412
                                )
1413
                            )
1414
                            + self.dsm_do_shed[g, tt]
1415
                        )
1416
                        # Capacity DSM down
1417
                        rhs = g.capacity_down[tt] * g.max_capacity_down
1418
1419
                        # add constraint
1420
                        block.dsm_do_constraint.add((g, tt), (lhs <= rhs))
1421
1422
                    # last time steps: end - delay time
1423
                    else:
1424
1425
                        # DSM down
1426
                        lhs = (
1427
                            sum(
1428
                                self.dsm_do_shift[g, t, tt]
1429
                                for t in range(
1430
                                    tt - g.delay_time, m.TIMESTEPS[-1] + 1
1431
                                )
1432
                            )
1433
                            + self.dsm_do_shed[g, tt]
1434
                        )
1435
                        # Capacity DSM down
1436
                        rhs = g.capacity_down[tt] * g.max_capacity_down
1437
1438
                        # add constraint
1439
                        block.dsm_do_constraint.add((g, tt), (lhs <= rhs))
1440
1441
        self.dsm_do_constraint = Constraint(
1442
            group, m.TIMESTEPS, noruleinit=True
1443
        )
1444
        self.dsm_do_constraint_build = BuildAction(rule=dsm_do_constraint_rule)
1445
1446
        # Equation 10
1447
        def c2_constraint_rule(block):
1448
            """Equation 10 by Zerrahn & Schill:
1449
            The realised DSM up or down at time T has to be smaller than
1450
            the maximum downward or upward capacity at time T. Therefore, in
1451
            total each individual DSM unit within the modeled portfolio
1452
            can only be shifted up OR down at a given time.
1453
            """
1454
            for tt in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1455
                for g in group:
1456
1457
                    # first times steps: 0 + delay time
1458
                    if tt <= g.delay_time:
1459
1460
                        # DSM up/down
1461
                        lhs = (
1462
                            self.dsm_up[g, tt]
1463
                            + sum(
1464
                                self.dsm_do_shift[g, t, tt]
1465
                                for t in range(tt + g.delay_time + 1)
1466
                            )
1467
                            + self.dsm_do_shed[g, tt]
1468
                        )
1469
                        # max capacity at tt
1470
                        rhs = max(
1471
                            g.capacity_up[tt] * g.max_capacity_up,
1472
                            g.capacity_down[tt] * g.max_capacity_down,
1473
                        )
1474
1475
                        # add constraint
1476
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
1477
1478
                    elif g.delay_time < tt <= m.TIMESTEPS[-1] - g.delay_time:
1479
1480
                        # DSM up/down
1481
                        lhs = (
1482
                            self.dsm_up[g, tt]
1483
                            + sum(
1484
                                self.dsm_do_shift[g, t, tt]
1485
                                for t in range(
1486
                                    tt - g.delay_time, tt + g.delay_time + 1
1487
                                )
1488
                            )
1489
                            + self.dsm_do_shed[g, tt]
1490
                        )
1491
                        # max capacity at tt
1492
                        rhs = max(
1493
                            g.capacity_up[tt] * g.max_capacity_up,
1494
                            g.capacity_down[tt] * g.max_capacity_down,
1495
                        )
1496
1497
                        # add constraint
1498
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
1499
1500
                    else:
1501
1502
                        # DSM up/down
1503
                        lhs = (
1504
                            self.dsm_up[g, tt]
1505
                            + sum(
1506
                                self.dsm_do_shift[g, t, tt]
1507
                                for t in range(
1508
                                    tt - g.delay_time, m.TIMESTEPS[-1] + 1
1509
                                )
1510
                            )
1511
                            + self.dsm_do_shed[g, tt]
1512
                        )
1513
                        # max capacity at tt
1514
                        rhs = max(
1515
                            g.capacity_up[tt] * g.max_capacity_up,
1516
                            g.capacity_down[tt] * g.max_capacity_down,
1517
                        )
1518
1519
                        # add constraint
1520
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
1521
1522
        self.C2_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True)
1523
        self.C2_constraint_build = BuildAction(rule=c2_constraint_rule)
1524
1525
        def recovery_constraint_rule(block):
1526
            """Equation 11 by Zerrahn & Schill:
1527
            A recovery time is introduced to account for the fact that
1528
            there may be some restrictions before the next load shift
1529
            may take place. Rule is only applicable if a recovery time
1530
            is defined.
1531
            """
1532
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1533
                for g in group:
1534
1535
                    # No need to build constraint if no recovery
1536
                    # time is defined.
1537
                    if g.recovery_time_shift not in [None, 0]:
1538
1539
                        # main use case
1540
                        if t <= m.TIMESTEPS[-1] - g.recovery_time_shift:
1541
1542
                            # DSM up
1543
                            lhs = sum(
1544
                                self.dsm_up[g, tt]
1545
                                for tt in range(t, t + g.recovery_time_shift)
1546
                            )
1547
                            # max energy shift for shifting process
1548
                            rhs = (
1549
                                g.capacity_up[t]
1550
                                * g.max_capacity_up
1551
                                * g.delay_time
1552
                                * m.timeincrement[t]
1553
                            )
1554
                            # add constraint
1555
                            block.recovery_constraint.add((g, t), (lhs <= rhs))
1556
1557
                        # last time steps: end - recovery time
1558
                        else:
1559
1560
                            # DSM up
1561
                            lhs = sum(
1562
                                self.dsm_up[g, tt]
1563
                                for tt in range(t, m.TIMESTEPS[-1] + 1)
1564
                            )
1565
                            # max energy shift for shifting process
1566
                            rhs = (
1567
                                g.capacity_up[t]
1568
                                * g.max_capacity_up
1569
                                * g.delay_time
1570
                                * m.timeincrement[t]
1571
                            )
1572
                            # add constraint
1573
                            block.recovery_constraint.add((g, t), (lhs <= rhs))
1574
1575
                    else:
1576
                        pass  # return(Constraint.Skip)
1577
1578
        self.recovery_constraint = Constraint(
1579
            group, m.TIMESTEPS, noruleinit=True
1580
        )
1581
        self.recovery_constraint_build = BuildAction(
1582
            rule=recovery_constraint_rule
1583
        )
1584
1585
        # Equation 9a from Zerrahn and Schill (2015b)
1586
        def shed_limit_constraint_rule(block):
1587
            """The following constraint is highly similar to equation 9a
1588
            from Zerrahn and Schill (2015b): A recovery time for load
1589
            shedding is introduced in order to limit the overall amount
1590
            of shedded energy.
1591
            """
1592
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1593
                for g in group:
1594
1595
                    # Only applicable for load shedding
1596
                    if g.shed_eligibility:
1597
1598
                        # main use case
1599
                        if t <= m.TIMESTEPS[-1] - g.recovery_time_shed:
1600
1601
                            # DSM up
1602
                            lhs = sum(
1603
                                self.dsm_do_shed[g, tt]
1604
                                for tt in range(t, t + g.recovery_time_shed)
1605
                            )
1606
                            # max energy shift for shifting process
1607
                            rhs = (
1608
                                g.capacity_down[t]
1609
                                * g.max_capacity_down
1610
                                * g.shed_time
1611
                                * m.timeincrement[t]
1612
                            )
1613
                            # add constraint
1614
                            block.shed_limit_constraint.add(
1615
                                (g, t), (lhs <= rhs)
1616
                            )
1617
1618
                        # last time steps: end - recovery time
1619
                        else:
1620
1621
                            # DSM up
1622
                            lhs = sum(
1623
                                self.dsm_do_shed[g, tt]
1624
                                for tt in range(t, m.TIMESTEPS[-1] + 1)
1625
                            )
1626
                            # max energy shift for shifting process
1627
                            rhs = (
1628
                                g.capacity_down[t]
1629
                                * g.max_capacity_down
1630
                                * g.shed_time
1631
                                * m.timeincrement[t]
1632
                            )
1633
                            # add constraint
1634
                            block.shed_limit_constraint.add(
1635
                                (g, t), (lhs <= rhs)
1636
                            )
1637
1638
                    else:
1639
                        pass  # return(Constraint.Skip)
1640
1641
        self.shed_limit_constraint = Constraint(
1642
            group, m.TIMESTEPS, noruleinit=True
1643
        )
1644
        self.shed_limit_constraint_build = BuildAction(
1645
            rule=shed_limit_constraint_rule
1646
        )
1647
1648 View Code Duplication
    def _objective_expression(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1649
        r"""Objective expression with variable costs for DSM activity"""
1650
1651
        m = self.parent_block()
1652
1653
        dsm_cost = 0
1654
1655
        for t in m.TIMESTEPS:
1656
            for g in self.dsm:
1657
                dsm_cost += (
1658
                    self.dsm_up[g, t]
1659
                    * g.cost_dsm_up[t]
1660
                    * m.objective_weighting[t]
1661
                )
1662
                dsm_cost += (
1663
                    sum(self.dsm_do_shift[g, tt, t] for tt in m.TIMESTEPS)
1664
                    * g.cost_dsm_down_shift[t]
1665
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
1666
                ) * m.objective_weighting[t]
1667
1668
        self.cost = Expression(expr=dsm_cost)
1669
1670
        return self.cost
1671
1672
1673
class SinkDSMDIWInvestmentBlock(ScalarBlock):
1674
    r"""Constraints for SinkDSM with "DIW" approach and :attr:`investment`
1675
1676
    **The following constraints are created for approach = 'DIW' with an
1677
    investment object defined:**
1678
1679
    .. _SinkDSMDIW equations:
1680
1681
    .. math::
1682
        &
1683
        (1) \quad invest_{min} \leq invest \leq invest_{max} \\
1684
        &
1685
        (2) \quad DSM_{t}^{up} = 0 \quad \forall t
1686
        \quad if \space eligibility_{shift} = False \\
1687
        &
1688
        (3) \quad DSM_{t}^{do, shed} = 0 \quad \forall t
1689
        \quad if \space eligibility_{shed} = False \\
1690
        &
1691
        (4) \quad \dot{E}_{t} = demand_{t} \cdot (invest + E_{exist})
1692
        + DSM_{t}^{up} -
1693
        \sum_{tt=t-L}^{t+L} DSM_{tt,t}^{do, shift} - DSM_{t}^{do, shed} \quad
1694
        \forall t \in \mathbb{T} \\
1695
        &
1696
        (5) \quad DSM_{t}^{up} \cdot \eta =
1697
        \sum_{tt=t-L}^{t+L} DSM_{t,tt}^{do, shift}
1698
        \quad \forall t \in \mathbb{T} \\
1699
        &
1700
        (6) \quad DSM_{t}^{up} \leq E_{t}^{up} \cdot (invest + E_{exist})
1701
        \ s_{flex, up}
1702
        \quad \forall t \in \mathbb{T} \\
1703
        &
1704
        (7) \quad \sum_{t=tt-L}^{tt+L} DSM_{t,tt}^{do, shift}
1705
        + DSM_{tt}^{do, shed} \leq E_{tt}^{do} \cdot (invest + E_{exist})
1706
        \cdot s_{flex, do}
1707
        \quad \forall tt \in \mathbb{T} \\
1708
        &
1709
        (8) \quad DSM_{tt}^{up} + \sum_{t=tt-L}^{tt+L} DSM_{t,tt}^{do, shift}
1710
        + DSM_{tt}^{do, shed} \leq
1711
        max \{ E_{tt}^{up} \cdot s_{flex, up},
1712
        E_{tt}^{do} \cdot s_{flex, do} \} \cdot (invest + E_{exist})
1713
        \quad \forall tt \in \mathbb{T} \\
1714
        &
1715
        (9) \quad \sum_{tt=t}^{t+R-1} DSM_{tt}^{up}
1716
        \leq E_{t}^{up} \cdot (invest + E_{exist})
1717
        \cdot s_{flex, up} \cdot L \cdot \Delta t
1718
        \quad \forall t \in \mathbb{T} \\
1719
        &
1720
        (10) \quad \sum_{tt=t}^{t+R-1} DSM_{tt}^{do, shed}
1721
        \leq E_{t}^{do} \cdot (invest + E_{exist})
1722
        \cdot s_{flex, do} \cdot t_{shed}
1723
        \cdot \Delta t \quad \forall t \in \mathbb{T} \\
1724
1725
    *Note*: For the sake of readability, the handling of indices is not
1726
    displayed here. E.g. evaluating a variable for t-L may lead to a negative
1727
    and therefore infeasible index.
1728
    This is addressed by limiting the sums to non-negative indices within the
1729
    model index bounds. Please refer to the constraints implementation
1730
    themselves.
1731
1732
    **The following parts of the objective function are created:**
1733
1734
    * Investment annuity:
1735
1736
    .. math::
1737
        invest \cdot costs_{invest} \\
1738
1739
    * Variable costs:
1740
1741
    .. math::
1742
        DSM_{t}^{up} \cdot cost_{t}^{dsm, up}
1743
        + \sum_{tt=0}^{T} DSM_{t, tt}^{do, shift} \cdot
1744
        cost_{t}^{dsm, do, shift}
1745
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
1746
        \quad \forall t \in \mathbb{T}
1747
1748
    **Table: Symbols and attribute names of variables and parameters**
1749
1750
    Please refer to
1751
    :class:`oemof.solph.components.experimental._sink_dsm.SinkDSMDIWBlock`.
1752
1753
    The following variables and parameters are exclusively used for
1754
    investment modeling:
1755
1756
        .. csv-table:: Variables (V) and Parameters (P)
1757
            :header: "symbol", "attribute", "type", "explanation"
1758
            :widths: 1, 1, 1, 1
1759
1760
            ":math:`invest` ",":attr:`~SinkDSM.invest` ","V", "DSM capacity
1761
            invested in. Equals to the additionally installed capacity.
1762
            The capacity share eligible for a shift is determined
1763
            by flex share(s)."
1764
            ":math:`invest_{min}` ", ":attr:`~SinkDSM.investment.minimum` ",
1765
            "P", "minimum investment"
1766
            ":math:`invest_{max}` ", ":attr:`~SinkDSM.investment.maximum` ",
1767
            "P", "maximum investment"
1768
            ":math:`E_{exist}` ",":attr:`~SinkDSM.investment.existing` ",
1769
            "P", "existing DSM capacity"
1770
            ":math:`s_{flex, up}` ",":attr:`~SinkDSM.flex_share_up` ",
1771
            "P","Share of invested capacity that may be shift upwards
1772
            at maximum"
1773
            ":math:`s_{flex, do}` ",":attr:`~SinkDSM.flex_share_do` ",
1774
            "P", "Share of invested capacity that may be shift downwards
1775
            at maximum"
1776
            ":math:`costs_{invest}` ",":attr:`~SinkDSM.investment.ep_costs` ",
1777
            "P", "specific investment annuity"
1778
            ":math:`T` "," ","P", "Overall amount of time steps (cardinality)"
1779
    """
1780
    CONSTRAINT_GROUP = True
1781
1782
    def __init__(self, *args, **kwargs):
1783
        super().__init__(*args, **kwargs)
1784
1785
    def _create(self, group=None):
1786
        if group is None:
1787
            return None
1788
1789
        m = self.parent_block()
1790
1791
        # for all DSM components get inflow from a bus
1792
        for n in group:
1793
            n.inflow = list(n.inputs)[0]
1794
1795
        #  ************* SETS *********************************
1796
1797
        # Set of DSM Components
1798
        self.investdsm = Set(initialize=[g for g in group])
1799
1800
        #  ************* VARIABLES *****************************
1801
1802
        # Define bounds for investments in demand response
1803
        def _dsm_investvar_bound_rule(block, g):
1804
            """Rule definition to bound the
1805
            demand response capacity invested in (`invest`).
1806
            """
1807
            return g.investment.minimum, g.investment.maximum
1808
1809
        # Investment in DR capacity
1810
        self.invest = Var(
1811
            self.investdsm,
1812
            within=NonNegativeReals,
1813
            bounds=_dsm_investvar_bound_rule,
1814
        )
1815
1816
        # Variable load shift down
1817
        self.dsm_do_shift = Var(
1818
            self.investdsm,
1819
            m.TIMESTEPS,
1820
            m.TIMESTEPS,
1821
            initialize=0,
1822
            within=NonNegativeReals,
1823
        )
1824
1825
        # Variable load shedding
1826
        self.dsm_do_shed = Var(
1827
            self.investdsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
1828
        )
1829
1830
        # Variable load shift up
1831
        self.dsm_up = Var(
1832
            self.investdsm, m.TIMESTEPS, initialize=0, within=NonNegativeReals
1833
        )
1834
1835
        #  ************* CONSTRAINTS *****************************
1836
1837
        def _shift_shed_vars_rule(block):
1838
            """Force shifting resp. shedding variables to zero dependent
1839
            on how boolean parameters for shift resp. shed eligibility
1840
            are set.
1841
            """
1842
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1843
                for g in group:
1844
1845
                    if not g.shift_eligibility:
1846
                        lhs = self.dsm_up[g, t]
1847
                        rhs = 0
1848
1849
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
1850
1851
                    if not g.shed_eligibility:
1852
                        lhs = self.dsm_do_shed[g, t]
1853
                        rhs = 0
1854
1855
                        block.shift_shed_vars.add((g, t), (lhs == rhs))
1856
1857
        self.shift_shed_vars = Constraint(group, m.TIMESTEPS, noruleinit=True)
1858
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
1859
1860
        # Demand Production Relation
1861
        def _input_output_relation_rule(block):
1862
            """Relation between input data and pyomo variables.
1863
            The actual demand after DSM.
1864
            Sink Inflow == Demand +- DSM
1865
            """
1866
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1867
                for g in group:
1868
1869
                    # first time steps: 0 + delay time
1870
                    if t <= g.delay_time:
1871
1872
                        # Inflow from bus
1873
                        lhs = m.flow[g.inflow, g, t]
1874
                        # Demand +- DSM
1875
                        rhs = (
1876
                            g.demand[t]
1877
                            * (self.invest[g] + g.investment.existing)
1878
                            + self.dsm_up[g, t]
1879
                            - sum(
1880
                                self.dsm_do_shift[g, tt, t]
1881
                                for tt in range(t + g.delay_time + 1)
1882
                            )
1883
                            - self.dsm_do_shed[g, t]
1884
                        )
1885
1886
                        # add constraint
1887
                        block.input_output_relation.add((g, t), (lhs == rhs))
1888
1889
                    # main use case
1890
                    elif g.delay_time < t <= m.TIMESTEPS[-1] - g.delay_time:
1891
1892
                        # Inflow from bus
1893
                        lhs = m.flow[g.inflow, g, t]
1894
                        # Demand +- DSM
1895
                        rhs = (
1896
                            g.demand[t]
1897
                            * (self.invest[g] + g.investment.existing)
1898
                            + self.dsm_up[g, t]
1899
                            - sum(
1900
                                self.dsm_do_shift[g, tt, t]
1901
                                for tt in range(
1902
                                    t - g.delay_time, t + g.delay_time + 1
1903
                                )
1904
                            )
1905
                            - self.dsm_do_shed[g, t]
1906
                        )
1907
1908
                        # add constraint
1909
                        block.input_output_relation.add((g, t), (lhs == rhs))
1910
1911
                    # last time steps: end - delay time
1912
                    else:
1913
                        # Inflow from bus
1914
                        lhs = m.flow[g.inflow, g, t]
1915
                        # Demand +- DSM
1916
                        rhs = (
1917
                            g.demand[t]
1918
                            * (self.invest[g] + g.investment.existing)
1919
                            + self.dsm_up[g, t]
1920
                            - sum(
1921
                                self.dsm_do_shift[g, tt, t]
1922
                                for tt in range(
1923
                                    t - g.delay_time, m.TIMESTEPS[-1] + 1
1924
                                )
1925
                            )
1926
                            - self.dsm_do_shed[g, t]
1927
                        )
1928
1929
                        # add constraint
1930
                        block.input_output_relation.add((g, t), (lhs == rhs))
1931
1932
        self.input_output_relation = Constraint(
1933
            group, m.TIMESTEPS, noruleinit=True
1934
        )
1935
        self.input_output_relation_build = BuildAction(
1936
            rule=_input_output_relation_rule
1937
        )
1938
1939
        # Equation 7 (resp. 7')
1940 View Code Duplication
        def dsm_up_down_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1941
            """Equation 7 (resp. 7') by Zerrahn & Schill:
1942
            Every upward load shift has to be compensated by downward load
1943
            shifts in a defined time frame. Slightly modified equations for
1944
            the first and last time steps due to variable initialization.
1945
            Efficiency value depicts possible energy losses through
1946
            load shifting (Equation 7').
1947
            """
1948
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
1949
                for g in group:
1950
1951
                    # first time steps: 0 + delay time
1952
                    if t <= g.delay_time:
1953
1954
                        # DSM up
1955
                        lhs = self.dsm_up[g, t] * g.efficiency
1956
                        # DSM down
1957
                        rhs = sum(
1958
                            self.dsm_do_shift[g, t, tt]
1959
                            for tt in range(t + g.delay_time + 1)
1960
                        )
1961
1962
                        # add constraint
1963
                        block.dsm_updo_constraint.add((g, t), (lhs == rhs))
1964
1965
                    # main use case
1966
                    elif g.delay_time < t <= m.TIMESTEPS[-1] - g.delay_time:
1967
1968
                        # DSM up
1969
                        lhs = self.dsm_up[g, t] * g.efficiency
1970
                        # DSM down
1971
                        rhs = sum(
1972
                            self.dsm_do_shift[g, t, tt]
1973
                            for tt in range(
1974
                                t - g.delay_time, t + g.delay_time + 1
1975
                            )
1976
                        )
1977
1978
                        # add constraint
1979
                        block.dsm_updo_constraint.add((g, t), (lhs == rhs))
1980
1981
                    # last time steps: end - delay time
1982
                    else:
1983
1984
                        # DSM up
1985
                        lhs = self.dsm_up[g, t] * g.efficiency
1986
                        # DSM down
1987
                        rhs = sum(
1988
                            self.dsm_do_shift[g, t, tt]
1989
                            for tt in range(
1990
                                t - g.delay_time, m.TIMESTEPS[-1] + 1
1991
                            )
1992
                        )
1993
1994
                        # add constraint
1995
                        block.dsm_updo_constraint.add((g, t), (lhs == rhs))
1996
1997
        self.dsm_updo_constraint = Constraint(
1998
            group, m.TIMESTEPS, noruleinit=True
1999
        )
2000
        self.dsm_updo_constraint_build = BuildAction(
2001
            rule=dsm_up_down_constraint_rule
2002
        )
2003
2004
        # Equation 8
2005 View Code Duplication
        def dsm_up_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2006
            """Equation 8 by Zerrahn & Schill:
2007
            Realised upward load shift at time t has to be smaller than
2008
            upward DSM capacity at time t.
2009
            """
2010
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2011
                for g in group:
2012
                    # DSM up
2013
                    lhs = self.dsm_up[g, t]
2014
                    # Capacity dsm_up
2015
                    rhs = (
2016
                        g.capacity_up[t]
2017
                        * (self.invest[g] + g.investment.existing)
2018
                        * g.flex_share_up
2019
                    )
2020
2021
                    # add constraint
2022
                    block.dsm_up_constraint.add((g, t), (lhs <= rhs))
2023
2024
        self.dsm_up_constraint = Constraint(
2025
            group, m.TIMESTEPS, noruleinit=True
2026
        )
2027
        self.dsm_up_constraint_build = BuildAction(rule=dsm_up_constraint_rule)
2028
2029
        # Equation 9 (modified)
2030
        def dsm_do_constraint_rule(block):
2031
            """Equation 9 by Zerrahn & Schill:
2032
            Realised downward load shift at time t has to be smaller than
2033
            downward DSM capacity at time t.
2034
            """
2035
            for tt in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2036
                for g in group:
2037
2038
                    # first times steps: 0 + delay
2039
                    if tt <= g.delay_time:
2040
2041
                        # DSM down
2042
                        lhs = (
2043
                            sum(
2044
                                self.dsm_do_shift[g, t, tt]
2045
                                for t in range(tt + g.delay_time + 1)
2046
                            )
2047
                            + self.dsm_do_shed[g, tt]
2048
                        )
2049
                        # Capacity DSM down
2050
                        rhs = (
2051
                            g.capacity_down[tt]
2052
                            * (self.invest[g] + g.investment.existing)
2053
                            * g.flex_share_down
2054
                        )
2055
2056
                        # add constraint
2057
                        block.dsm_do_constraint.add((g, tt), (lhs <= rhs))
2058
2059
                    # main use case
2060
                    elif g.delay_time < tt <= m.TIMESTEPS[-1] - g.delay_time:
2061
2062
                        # DSM down
2063
                        lhs = (
2064
                            sum(
2065
                                self.dsm_do_shift[g, t, tt]
2066
                                for t in range(
2067
                                    tt - g.delay_time, tt + g.delay_time + 1
2068
                                )
2069
                            )
2070
                            + self.dsm_do_shed[g, tt]
2071
                        )
2072
                        # Capacity DSM down
2073
                        rhs = (
2074
                            g.capacity_down[tt]
2075
                            * (self.invest[g] + g.investment.existing)
2076
                            * g.flex_share_down
2077
                        )
2078
2079
                        # add constraint
2080
                        block.dsm_do_constraint.add((g, tt), (lhs <= rhs))
2081
2082
                    # last time steps: end - delay time
2083
                    else:
2084
2085
                        # DSM down
2086
                        lhs = (
2087
                            sum(
2088
                                self.dsm_do_shift[g, t, tt]
2089
                                for t in range(
2090
                                    tt - g.delay_time, m.TIMESTEPS[-1] + 1
2091
                                )
2092
                            )
2093
                            + self.dsm_do_shed[g, tt]
2094
                        )
2095
                        # Capacity DSM down
2096
                        rhs = (
2097
                            g.capacity_down[tt]
2098
                            * (self.invest[g] + g.investment.existing)
2099
                            * g.flex_share_down
2100
                        )
2101
2102
                        # add constraint
2103
                        block.dsm_do_constraint.add((g, tt), (lhs <= rhs))
2104
2105
        self.dsm_do_constraint = Constraint(
2106
            group, m.TIMESTEPS, noruleinit=True
2107
        )
2108
        self.dsm_do_constraint_build = BuildAction(rule=dsm_do_constraint_rule)
2109
2110
        # Equation 10
2111
        def c2_constraint_rule(block):
2112
            """Equation 10 by Zerrahn & Schill:
2113
            The realised DSM up or down at time T has to be smaller than
2114
            the maximum downward or upward capacity at time T. Therefore, in
2115
            total each individual DSM unit within the modeled portfolio
2116
            can only be shifted up OR down at a given time.
2117
            """
2118
            for tt in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2119
                for g in group:
2120
2121
                    # first times steps: 0 + delay time
2122
                    if tt <= g.delay_time:
2123
2124
                        # DSM up/down
2125
                        lhs = (
2126
                            self.dsm_up[g, tt]
2127
                            + sum(
2128
                                self.dsm_do_shift[g, t, tt]
2129
                                for t in range(tt + g.delay_time + 1)
2130
                            )
2131
                            + self.dsm_do_shed[g, tt]
2132
                        )
2133
                        # max capacity at tt
2134
                        rhs = max(
2135
                            g.capacity_up[tt] * g.flex_share_up,
2136
                            g.capacity_down[tt] * g.flex_share_down,
2137
                        ) * (self.invest[g] + g.investment.existing)
2138
2139
                        # add constraint
2140
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
2141
2142
                    elif g.delay_time < tt <= m.TIMESTEPS[-1] - g.delay_time:
2143
2144
                        # DSM up/down
2145
                        lhs = (
2146
                            self.dsm_up[g, tt]
2147
                            + sum(
2148
                                self.dsm_do_shift[g, t, tt]
2149
                                for t in range(
2150
                                    tt - g.delay_time, tt + g.delay_time + 1
2151
                                )
2152
                            )
2153
                            + self.dsm_do_shed[g, tt]
2154
                        )
2155
                        # max capacity at tt
2156
                        rhs = max(
2157
                            g.capacity_up[tt] * g.flex_share_up,
2158
                            g.capacity_down[tt] * g.flex_share_down,
2159
                        ) * (self.invest[g] + g.investment.existing)
2160
2161
                        # add constraint
2162
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
2163
2164
                    else:
2165
2166
                        # DSM up/down
2167
                        lhs = (
2168
                            self.dsm_up[g, tt]
2169
                            + sum(
2170
                                self.dsm_do_shift[g, t, tt]
2171
                                for t in range(
2172
                                    tt - g.delay_time, m.TIMESTEPS[-1] + 1
2173
                                )
2174
                            )
2175
                            + self.dsm_do_shed[g, tt]
2176
                        )
2177
                        # max capacity at tt
2178
                        rhs = max(
2179
                            g.capacity_up[tt] * g.flex_share_up,
2180
                            g.capacity_down[tt] * g.flex_share_down,
2181
                        ) * (self.invest[g] + g.investment.existing)
2182
2183
                        # add constraint
2184
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
2185
2186
        self.C2_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True)
2187
        self.C2_constraint_build = BuildAction(rule=c2_constraint_rule)
2188
2189
        def recovery_constraint_rule(block):
2190
            """Equation 11 by Zerrahn & Schill:
2191
            A recovery time is introduced to account for the fact that
2192
            there may be some restrictions before the next load shift
2193
            may take place. Rule is only applicable if a recovery time
2194
            is defined.
2195
            """
2196
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2197
                for g in group:
2198
2199
                    # No need to build constraint if no recovery
2200
                    # time is defined.
2201
                    if g.recovery_time_shift not in [None, 0]:
2202
2203
                        # main use case
2204
                        if t <= m.TIMESTEPS[-1] - g.recovery_time_shift:
2205
2206
                            # DSM up
2207
                            lhs = sum(
2208
                                self.dsm_up[g, tt]
2209
                                for tt in range(t, t + g.recovery_time_shift)
2210
                            )
2211
                            # max energy shift for shifting process
2212
                            rhs = (
2213
                                g.capacity_up[t]
2214
                                * (self.invest[g] + g.investment.existing)
2215
                                * g.flex_share_up
2216
                                * g.delay_time
2217
                                * m.timeincrement[t]
2218
                            )
2219
                            # add constraint
2220
                            block.recovery_constraint.add((g, t), (lhs <= rhs))
2221
2222
                        # last time steps: end - recovery time
2223
                        else:
2224
2225
                            # DSM up
2226
                            lhs = sum(
2227
                                self.dsm_up[g, tt]
2228
                                for tt in range(t, m.TIMESTEPS[-1] + 1)
2229
                            )
2230
                            # max energy shift for shifting process
2231
                            rhs = (
2232
                                g.capacity_up[t]
2233
                                * (self.invest[g] + g.investment.existing)
2234
                                * g.flex_share_up
2235
                                * g.delay_time
2236
                                * m.timeincrement[t]
2237
                            )
2238
                            # add constraint
2239
                            block.recovery_constraint.add((g, t), (lhs <= rhs))
2240
2241
                    else:
2242
                        pass  # return(Constraint.Skip)
2243
2244
        self.recovery_constraint = Constraint(
2245
            group, m.TIMESTEPS, noruleinit=True
2246
        )
2247
        self.recovery_constraint_build = BuildAction(
2248
            rule=recovery_constraint_rule
2249
        )
2250
2251
        # Equation 9a from Zerrahn and Schill (2015b)
2252
        def shed_limit_constraint_rule(block):
2253
            """The following constraint is highly similar to equation 9a
2254
            from Zerrahn and Schill (2015b): A recovery time for load
2255
            shedding is introduced in order to limit the overall amount
2256
            of shedded energy.
2257
            """
2258
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2259
                for g in group:
2260
2261
                    # Only applicable for load shedding
2262
                    if g.shed_eligibility:
2263
2264
                        # main use case
2265
                        if t <= m.TIMESTEPS[-1] - g.recovery_time_shed:
2266
2267
                            # DSM up
2268
                            lhs = sum(
2269
                                self.dsm_do_shed[g, tt]
2270
                                for tt in range(t, t + g.recovery_time_shed)
2271
                            )
2272
                            # max energy shift for shifting process
2273
                            rhs = (
2274
                                g.capacity_down[t]
2275
                                * (self.invest[g] + g.investment.existing)
2276
                                * g.flex_share_down
2277
                                * g.shed_time
2278
                                * m.timeincrement[t]
2279
                            )
2280
                            # add constraint
2281
                            block.shed_limit_constraint.add(
2282
                                (g, t), (lhs <= rhs)
2283
                            )
2284
2285
                        # last time steps: end - recovery time
2286
                        else:
2287
2288
                            # DSM up
2289
                            lhs = sum(
2290
                                self.dsm_do_shed[g, tt]
2291
                                for tt in range(t, m.TIMESTEPS[-1] + 1)
2292
                            )
2293
                            # max energy shift for shifting process
2294
                            rhs = (
2295
                                g.capacity_down[t]
2296
                                * (self.invest[g] + g.investment.existing)
2297
                                * g.flex_share_down
2298
                                * g.shed_time
2299
                                * m.timeincrement[t]
2300
                            )
2301
                            # add constraint
2302
                            block.shed_limit_constraint.add(
2303
                                (g, t), (lhs <= rhs)
2304
                            )
2305
2306
                    else:
2307
                        pass  # return(Constraint.Skip)
2308
2309
        self.shed_limit_constraint = Constraint(
2310
            group, m.TIMESTEPS, noruleinit=True
2311
        )
2312
        self.shed_limit_constraint_build = BuildAction(
2313
            rule=shed_limit_constraint_rule
2314
        )
2315
2316 View Code Duplication
    def _objective_expression(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2317
        r"""Objective expression with variable and investment costs for DSM"""
2318
2319
        m = self.parent_block()
2320
2321
        investment_costs = 0
2322
        variable_costs = 0
2323
2324
        for g in self.investdsm:
2325
            if g.investment.ep_costs is not None:
2326
                investment_costs += self.invest[g] * g.investment.ep_costs
2327
            else:
2328
                raise ValueError("Missing value for investment costs!")
2329
2330
            for t in m.TIMESTEPS:
2331
                variable_costs += (
2332
                    self.dsm_up[g, t]
2333
                    * g.cost_dsm_up[t]
2334
                    * m.objective_weighting[t]
2335
                )
2336
                variable_costs += (
2337
                    sum(self.dsm_do_shift[g, tt, t] for tt in m.TIMESTEPS)
2338
                    * g.cost_dsm_down_shift[t]
2339
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
2340
                ) * m.objective_weighting[t]
2341
2342
        self.cost = Expression(expr=investment_costs + variable_costs)
2343
2344
        return self.cost
2345
2346
2347
class SinkDSMDLRBlock(ScalarBlock):
2348
    r"""Constraints for SinkDSM with "DLR" approach
2349
2350
    **The following constraints are created for approach = 'DLR':**
2351
2352
    .. _SinkDSMDLR equations:
2353
2354
    .. math::
2355
        &
2356
        (1) \quad DSM_{h, t}^{up} = 0 \quad \forall h \in H_{DR}
2357
        \forall t \in \mathbb{T}
2358
        \quad if \space eligibility_{shift} = False \\
2359
        &
2360
        (2) \quad DSM_{t}^{do, shed} = 0 \quad \forall t \in \mathbb{T}
2361
        \quad if \space eligibility_{shed} = False \\
2362
        &
2363
        (3) \quad \dot{E}_{t} = demand_{t} \cdot demand_{max} +
2364
        \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
2365
        + DSM_{h, t}^{balanceDo} - DSM_{h, t}^{do, shift}
2366
        - DSM_{h, t}^{balanceUp}) - DSM_{t}^{do, shed}
2367
        \quad \forall t \in \mathbb{T} \\
2368
        &
2369
        (4) \quad DSM_{h, t}^{balanceDo} =
2370
        \frac{DSM_{h, t - h}^{do, shift}}{\eta}
2371
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
2372
        &
2373
        (5) \quad DSM_{h, t}^{balanceUp} =
2374
        DSM_{h, t-h}^{up} \cdot \eta
2375
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
2376
        &
2377
        (6) \quad DSM_{h, t}^{do, shift} = 0
2378
        \quad \forall h \in H_{DR}
2379
        \forall t \in [T - h..T] \\
2380
        &
2381
        (7) \quad DSM_{h, t}^{up} = 0
2382
        \quad \forall h \in H_{DR}
2383
        \forall t \in [T - h..T] \\
2384
        &
2385
        (8) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift}
2386
        + DSM_{h, t}^{balanceUp}) + DSM_{t}^{do, shed}
2387
        \leq E_{t}^{do} \cdot E_{max, do}
2388
        \quad \forall t \in \mathbb{T} \\
2389
        &
2390
        (9) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
2391
        + DSM_{h, t}^{balanceDo})
2392
        \leq E_{t}^{up} \cdot E_{max, up}
2393
        \quad \forall t \in \mathbb{T} \\
2394
        &
2395
        (10) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
2396
        (DSM_{h, t}^{do, shift} - DSM_{h, t}^{balanceDo} \cdot \eta)
2397
        = W_{t}^{levelDo} - W_{t-1}^{levelDo}
2398
        \quad \forall t \in [1..T] \\
2399
        &
2400
        (11) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
2401
        (DSM_{h, t}^{up} \cdot \eta - DSM_{h, t}^{balanceUp})
2402
        = W_{t}^{levelUp} - W_{t-1}^{levelUp}
2403
        \quad \forall t \in [1..T] \\
2404
        &
2405
        (12) \quad W_{t}^{levelDo} \leq \overline{E}_{t}^{do}
2406
        \cdot E_{max, do} \cdot t_{shift}
2407
        \quad \forall t \in \mathbb{T} \\
2408
        &
2409
        (13) \quad W_{t}^{levelUp} \leq \overline{E}_{t}^{up}
2410
        \cdot E_{max, up} \cdot t_{shift}
2411
        \quad \forall t \in \mathbb{T} \\
2412
        &
2413
        (14) \quad \displaystyle\sum_{t=0}^{T} DSM_{t}^{do, shed}
2414
        \leq E_{max, do} \cdot \overline{E}_{t}^{do} \cdot t_{shed}
2415
        \cdot n^{yearLimitShed} \\
2416
        &
2417
        (15) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
2418
        DSM_{h, t}^{do, shift}
2419
        \leq E_{max, do} \cdot \overline{E}_{t}^{do} \cdot t_{shift}
2420
        \cdot n^{yearLimitShift} \\
2421
        (optional \space constraint) \\
2422
        &
2423
        (16) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
2424
        DSM_{h, t}^{up}
2425
        \leq E_{max, up} \cdot \overline{E}_{t}^{up} \cdot t_{shift}
2426
        \cdot n^{yearLimitShift} \\
2427
        (optional \space constraint) \\
2428
        &
2429
        (17) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{do, shift}
2430
        \leq E_{max, do} \cdot \overline{E}_{t}^{do}
2431
        \cdot t_{shift} -
2432
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
2433
        DSM_{h, t - t'}^{do, shift}
2434
        \quad \forall t \in [t-t_{dayLimit}..T] \\
2435
        (optional \space constraint) \\
2436
        &
2437
        (18) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{up}
2438
        \leq E_{max, up} \cdot \overline{E}_{t}^{up}
2439
        \cdot t_{shift} -
2440
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
2441
        DSM_{h, t - t'}^{up}
2442
        \quad \forall t \in [t-t_{dayLimit}..T] \\
2443
        (optional \space constraint) \\
2444
        &
2445
        (19) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
2446
        + DSM_{h, t}^{balanceDo}
2447
        + DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
2448
        + DSM_{t}^{do, shed}
2449
        \leq \max \{E_{t}^{up} \cdot E_{max, up},
2450
        E_{t}^{do} \cdot E_{max, do} \}
2451
        \quad \forall t \in \mathbb{T} \\
2452
        (optional \space constraint) \\
2453
        &
2454
2455
    *Note*: For the sake of readability, the handling of indices is not
2456
    displayed here. E.g. evaluating a variable for t-L may lead to a negative
2457
    and therefore infeasible index.
2458
    This is addressed by limiting the sums to non-negative indices within the
2459
    model index bounds. Please refer to the constraints implementation
2460
    themselves.
2461
2462
    **The following parts of the objective function are created:**
2463
2464
    .. math::
2465
        \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up} + DSM_{h, t}^{balanceDo})
2466
        \cdot cost_{t}^{dsm, up}
2467
        + \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
2468
        \cdot cost_{t}^{dsm, do, shift}
2469
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
2470
        \quad \forall t \in \mathbb{T} \\
2471
2472
    **Table: Symbols and attribute names of variables and parameters**
2473
2474
        .. csv-table:: Variables (V) and Parameters (P)
2475
            :header: "symbol", "attribute", "type", "explanation"
2476
            :widths: 1, 1, 1, 1
2477
2478
            ":math:`DSM_{h, t}^{up}` ",":attr:`~SinkDSM.dsm_up[g,h,t]`",
2479
            "V", "DSM up shift (additional load) in hour t with delay time h"
2480
            ":math:`DSM_{h, t}^{do, shift}` ",
2481
            ":attr:`~SinkDSM.dsm_do_shift[g,h, t]`",
2482
            "V", "DSM down shift (less load) in hour t with delay time h"
2483
            ":math:`DSM_{h, t}^{balanceUp}` ",
2484
            ":attr:`~SinkDSM.balance_dsm_up[g,h,t]`",
2485
            "V", "DSM down shift (less load) in hour t with delay time h
2486
            to balance previous upshift"
2487
            ":math:`DSM_{h, t}^{balanceDo}` ",
2488
            ":attr:`~SinkDSM.balance_dsm_do[g,h,t]`",
2489
            "V", "DSM up shift (additional load) in hour t with delay time h
2490
            to balance previous downshift"
2491
            ":math:`DSM_{t}^{do, shed}` ",
2492
            ":attr:`~SinkDSM.dsm_do_shed[g, t]` ",
2493
            "V","DSM shedded (capacity shedded, i.e. not compensated for)"
2494
            ":math:`\dot{E}_{t}` ",":attr:`flow[g,t]`","V","Energy
2495
            flowing in from (electrical) inflow bus"
2496
            ":math:`h`","element of :attr:`~SinkDSM.delay_time`","P",
2497
            "delay time for load shift (integer value from set of feasible
2498
            delay times per DSM portfolio)
2499
            (time until the energy balance has to be levelled out again;
2500
            roundtrip time of one load shifting cycle, i.e. time window
2501
            for upshift and compensating downshift)"
2502
            ":math:`H_{DR}`",
2503
            "`range(length(:attr:`~SinkDSM.delay_time`) + 1)`",
2504
            "P", "Set of feasible delay times for load shift of a certain
2505
            DSM portfolio
2506
            (time until the energy balance has to be levelled out again;
2507
            roundtrip time of one load shifting cycle, i.e. time window
2508
            for upshift and compensating downshift)"
2509
            ":math:`t_{shift}`",":attr:`~SinkDSM.shift_time`","P",
2510
            "Maximum time for a shift in one direction, i. e. maximum time
2511
            for an upshift or a downshift in a load shifting cycle"
2512
            ":math:`t_{she}`",":attr:`~SinkDSM.shed_time`","P",
2513
            "Maximum time for one load shedding process"
2514
            ":math:`demand_{t}`",":attr:`~SinkDSM.demand[t]`","P",
2515
            "(Electrical) demand series (normalized)"
2516
            ":math:`demand_{max}`",":attr:`~SinkDSM.max_demand`","P",
2517
            "Maximum demand value"
2518
            ":math:`E_{t}^{do}`",":attr:`~SinkDSM.capacity_down[t]`","P",
2519
            "Capacity  allowed for a load adjustment downwards (normalized)
2520
            (DSM down shift + DSM shedded)"
2521
            ":math:`E_{t}^{up}`",":attr:`~SinkDSM.capacity_up[t]`","P",
2522
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
2523
            ":math:`E_{do, max}`",":attr:`~SinkDSM.max_capacity_down`","P",
2524
            "Maximum capacity allowed for a load adjustment downwards
2525
            (DSM down shift + DSM shedded)"
2526
            ":math:`E_{up, max}`",":attr:`~SinkDSM.max_capacity_up`","P",
2527
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
2528
            ":math:`\eta`",":attr:`~SinkDSM.efficiency`","P", "Efficiency
2529
            loss for load shifting processes"
2530
            ":math:`\mathbb{T}` "," ","P", "Set of time steps"
2531
            ":math:`T` "," ","P", "Overall amount of time steps (cardinality)"
2532
            ":math:`eligibility_{shift}` ",
2533
            ":attr:`~SinkDSM.shift_eligibility`","P",
2534
            "Boolean parameter indicating if unit can be used for
2535
            load shifting"
2536
            ":math:`eligibility_{shed}` ",
2537
            ":attr:`~SinkDSM.shed_eligibility`","P",
2538
            "Boolean parameter indicating if unit can be used for
2539
            load shedding"
2540
            ":math:`cost_{t}^{dsm, up}` ", ":attr:`~SinkDSM.cost_dsm_up[t]`",
2541
            "P", "Variable costs for an upwards shift"
2542
            ":math:`cost_{t}^{dsm, do, shift}` ",
2543
            ":attr:`~SinkDSM.cost_dsm_down_shift[t]`","P",
2544
            "Variable costs for a downwards shift (load shifting)"
2545
            ":math:`cost_{t}^{dsm, do, shed}` ",
2546
            ":attr:`~SinkDSM.cost_dsm_down_shed[t]`","P",
2547
            "Variable costs for shedding load"
2548
            ":math:`\Delta t`",":attr:`~models.Model.timeincrement`","P",
2549
            "The time increment of the model"
2550
            ":math:`n_{yearLimitshift}`",":attr:`~SinkDSM.n_yearLimitShift`",
2551
            "P", "Maximum allowed number of load shifts (at full capacity)
2552
            in the optimization timeframe"
2553
            ":math:`n_{yearLimitshed}`",":attr:`~SinkDSM.n_yearLimitShed`",
2554
            "P", "Maximum allowed number of load sheds (at full capacity)
2555
            in the optimization timeframe"
2556
            ":math:`t_{dayLimit}`",":attr:`~SinkDSM.t_dayLimit`",
2557
            "P", "Maximum duration of load shifts at full capacity per day
2558
            resp. in the last hours before the current"
2559
    """
2560
    CONSTRAINT_GROUP = True
2561
2562
    def __init__(self, *args, **kwargs):
2563
        super().__init__(*args, **kwargs)
2564
2565
    def _create(self, group=None):
2566
        if group is None:
2567
            return None
2568
2569
        m = self.parent_block()
2570
2571
        # for all DSM components get inflow from a bus
2572
        for n in group:
2573
            n.inflow = list(n.inputs)[0]
2574
2575
        #  ************* SETS *********************************
2576
2577
        # Set of DR Components
2578
        self.DR = Set(initialize=[n for n in group])
2579
2580
        # Depict different delay_times per unit via a mapping
2581
        map_DR_H = {
2582
            k: v
2583
            for k, v in zip([n for n in group], [n.delay_time for n in group])
2584
        }
2585
2586
        unique_H = list(set(itertools.chain.from_iterable(map_DR_H.values())))
2587
        self.H = Set(initialize=unique_H)
2588
2589
        self.DR_H = Set(
2590
            within=self.DR * self.H,
2591
            initialize=[(dr, h) for dr in map_DR_H for h in map_DR_H[dr]],
2592
        )
2593
2594
        #  ************* VARIABLES *****************************
2595
2596
        # Variable load shift down (capacity)
2597
        self.dsm_do_shift = Var(
2598
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2599
        )
2600
2601
        # Variable for load shedding (capacity)
2602
        self.dsm_do_shed = Var(
2603
            self.DR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2604
        )
2605
2606
        # Variable load shift up (capacity)
2607
        self.dsm_up = Var(
2608
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2609
        )
2610
2611
        # Variable balance load shift down through upwards shift (capacity)
2612
        self.balance_dsm_do = Var(
2613
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2614
        )
2615
2616
        # Variable balance load shift up through downwards shift (capacity)
2617
        self.balance_dsm_up = Var(
2618
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2619
        )
2620
2621
        # Variable fictious DR storage level for downwards load shifts (energy)
2622
        self.dsm_do_level = Var(
2623
            self.DR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2624
        )
2625
2626
        # Variable fictious DR storage level for upwards load shifts (energy)
2627
        self.dsm_up_level = Var(
2628
            self.DR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2629
        )
2630
2631
        #  ************* CONSTRAINTS *****************************
2632
2633 View Code Duplication
        def _shift_shed_vars_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2634
            """Force shifting resp. shedding variables to zero dependent
2635
            on how boolean parameters for shift resp. shed eligibility
2636
            are set.
2637
            """
2638
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2639
                for g in group:
2640
                    for h in g.delay_time:
2641
2642
                        if not g.shift_eligibility:
2643
                            lhs = self.dsm_up[g, h, t]
2644
                            rhs = 0
2645
2646
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
2647
2648
                        if not g.shed_eligibility:
2649
                            lhs = self.dsm_do_shed[g, t]
2650
                            rhs = 0
2651
2652
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
2653
2654
        self.shift_shed_vars = Constraint(
2655
            group, self.H, m.TIMESTEPS, noruleinit=True
2656
        )
2657
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
2658
2659
        # Relation between inflow and effective Sink consumption
2660 View Code Duplication
        def _input_output_relation_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2661
            """Relation between input data and pyomo variables.
2662
            The actual demand after DR.
2663
            BusBlock outflow == Demand +- DR (i.e. effective Sink consumption)
2664
            """
2665
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2666
                for g in group:
2667
                    # outflow from bus
2668
                    lhs = m.flow[g.inflow, g, t]
2669
2670
                    # Demand +- DR
2671
                    rhs = (
2672
                        g.demand[t] * g.max_demand
2673
                        + sum(
2674
                            self.dsm_up[g, h, t]
2675
                            + self.balance_dsm_do[g, h, t]
2676
                            - self.dsm_do_shift[g, h, t]
2677
                            - self.balance_dsm_up[g, h, t]
2678
                            for h in g.delay_time
2679
                        )
2680
                        - self.dsm_do_shed[g, t]
2681
                    )
2682
2683
                    # add constraint
2684
                    block.input_output_relation.add((g, t), (lhs == rhs))
2685
2686
        self.input_output_relation = Constraint(
2687
            group, m.TIMESTEPS, noruleinit=True
2688
        )
2689
        self.input_output_relation_build = BuildAction(
2690
            rule=_input_output_relation_rule
2691
        )
2692
2693
        # Equation 4.8
2694 View Code Duplication
        def capacity_balance_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2695
            """Load reduction must be balanced by load increase
2696
            within delay_time
2697
            """
2698
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2699
                for g in group:
2700
                    for h in g.delay_time:
2701
2702
                        if g.shift_eligibility:
2703
2704
                            # main use case
2705
                            if t >= h:
2706
                                # balance load reduction
2707
                                lhs = self.balance_dsm_do[g, h, t]
2708
2709
                                # load reduction (efficiency considered)
2710
                                rhs = (
2711
                                    self.dsm_do_shift[g, h, t - h]
2712
                                    / g.efficiency
2713
                                )
2714
2715
                                # add constraint
2716
                                block.capacity_balance_red.add(
2717
                                    (g, h, t), (lhs == rhs)
2718
                                )
2719
2720
                            # no balancing for the first timestep
2721
                            elif t == m.TIMESTEPS[1]:
2722
                                lhs = self.balance_dsm_do[g, h, t]
2723
                                rhs = 0
2724
2725
                                block.capacity_balance_red.add(
2726
                                    (g, h, t), (lhs == rhs)
2727
                                )
2728
2729
                            else:
2730
                                pass  # return(Constraint.Skip)
2731
2732
                        # if only shedding is possible, balancing variable is 0
2733
                        else:
2734
                            lhs = self.balance_dsm_do[g, h, t]
2735
                            rhs = 0
2736
2737
                            block.capacity_balance_red.add(
2738
                                (g, h, t), (lhs == rhs)
2739
                            )
2740
2741
        self.capacity_balance_red = Constraint(
2742
            group, self.H, m.TIMESTEPS, noruleinit=True
2743
        )
2744
        self.capacity_balance_red_build = BuildAction(
2745
            rule=capacity_balance_red_rule
2746
        )
2747
2748
        # Equation 4.9
2749 View Code Duplication
        def capacity_balance_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2750
            """Load increased must be balanced by load reduction
2751
            within delay_time
2752
            """
2753
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2754
                for g in group:
2755
                    for h in g.delay_time:
2756
2757
                        if g.shift_eligibility:
2758
2759
                            # main use case
2760
                            if t >= h:
2761
                                # balance load increase
2762
                                lhs = self.balance_dsm_up[g, h, t]
2763
2764
                                # load increase (efficiency considered)
2765
                                rhs = self.dsm_up[g, h, t - h] * g.efficiency
2766
2767
                                # add constraint
2768
                                block.capacity_balance_inc.add(
2769
                                    (g, h, t), (lhs == rhs)
2770
                                )
2771
2772
                            # no balancing for the first timestep
2773
                            elif t == m.TIMESTEPS[1]:
2774
                                lhs = self.balance_dsm_up[g, h, t]
2775
                                rhs = 0
2776
2777
                                block.capacity_balance_inc.add(
2778
                                    (g, h, t), (lhs == rhs)
2779
                                )
2780
2781
                            else:
2782
                                pass  # return(Constraint.Skip)
2783
2784
                        # if only shedding is possible, balancing variable is 0
2785
                        else:
2786
                            lhs = self.balance_dsm_up[g, h, t]
2787
                            rhs = 0
2788
2789
                            block.capacity_balance_inc.add(
2790
                                (g, h, t), (lhs == rhs)
2791
                            )
2792
2793
        self.capacity_balance_inc = Constraint(
2794
            group, self.H, m.TIMESTEPS, noruleinit=True
2795
        )
2796
        self.capacity_balance_inc_build = BuildAction(
2797
            rule=capacity_balance_inc_rule
2798
        )
2799
2800
        # Fix: prevent shifts which cannot be compensated
2801 View Code Duplication
        def no_comp_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2802
            """Prevent downwards shifts that cannot be balanced anymore
2803
            within the optimization timeframe
2804
            """
2805
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2806
                for g in group:
2807
2808
                    if g.fixes:
2809
                        for h in g.delay_time:
2810
2811
                            if t > m.TIMESTEPS[-1] - h:
2812
                                # no load reduction anymore (dsm_do_shift = 0)
2813
                                lhs = self.dsm_do_shift[g, h, t]
2814
                                rhs = 0
2815
                                block.no_comp_red.add((g, h, t), (lhs == rhs))
2816
2817
                    else:
2818
                        pass  # return(Constraint.Skip)
2819
2820
        self.no_comp_red = Constraint(
2821
            group, self.H, m.TIMESTEPS, noruleinit=True
2822
        )
2823
        self.no_comp_red_build = BuildAction(rule=no_comp_red_rule)
2824
2825
        # Fix: prevent shifts which cannot be compensated
2826 View Code Duplication
        def no_comp_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2827
            """Prevent upwards shifts that cannot be balanced anymore
2828
            within the optimization timeframe
2829
            """
2830
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2831
                for g in group:
2832
2833
                    if g.fixes:
2834
                        for h in g.delay_time:
2835
2836
                            if t > m.TIMESTEPS[-1] - h:
2837
                                # no load increase anymore (dsm_up = 0)
2838
                                lhs = self.dsm_up[g, h, t]
2839
                                rhs = 0
2840
                                block.no_comp_inc.add((g, h, t), (lhs == rhs))
2841
2842
                    else:
2843
                        pass  # return(Constraint.Skip)
2844
2845
        self.no_comp_inc = Constraint(
2846
            group, self.H, m.TIMESTEPS, noruleinit=True
2847
        )
2848
        self.no_comp_inc_build = BuildAction(rule=no_comp_inc_rule)
2849
2850
        # Equation 4.11
2851
        def availability_red_rule(block):
2852
            """Load reduction must be smaller than or equal to the
2853
            (time-dependent) capacity limit
2854
            """
2855
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2856
                for g in group:
2857
                    # load reduction
2858
                    lhs = (
2859
                        sum(
2860
                            self.dsm_do_shift[g, h, t]
2861
                            + self.balance_dsm_up[g, h, t]
2862
                            for h in g.delay_time
2863
                        )
2864
                        + self.dsm_do_shed[g, t]
2865
                    )
2866
2867
                    # upper bound
2868
                    rhs = g.capacity_down[t] * g.max_capacity_down
2869
2870
                    # add constraint
2871
                    block.availability_red.add((g, t), (lhs <= rhs))
2872
2873
        self.availability_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
2874
        self.availability_red_build = BuildAction(rule=availability_red_rule)
2875
2876
        # Equation 4.12
2877
        def availability_inc_rule(block):
2878
            """Load increase must be smaller than or equal to the
2879
            (time-dependent) capacity limit
2880
            """
2881
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2882
                for g in group:
2883
                    # load increase
2884
                    lhs = sum(
2885
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
2886
                        for h in g.delay_time
2887
                    )
2888
2889
                    # upper bound
2890
                    rhs = g.capacity_up[t] * g.max_capacity_up
2891
2892
                    # add constraint
2893
                    block.availability_inc.add((g, t), (lhs <= rhs))
2894
2895
        self.availability_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
2896
        self.availability_inc_build = BuildAction(rule=availability_inc_rule)
2897
2898
        # Equation 4.13
2899 View Code Duplication
        def dr_storage_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2900
            """Fictious demand response storage level for load reductions
2901
            transition equation
2902
            """
2903
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2904
                for g in group:
2905
2906
                    # avoid timesteps prior to t = 0
2907
                    if t > 0:
2908
                        # reduction minus balancing of reductions
2909
                        lhs = m.timeincrement[t] * sum(
2910
                            (
2911
                                self.dsm_do_shift[g, h, t]
2912
                                - self.balance_dsm_do[g, h, t] * g.efficiency
2913
                            )
2914
                            for h in g.delay_time
2915
                        )
2916
2917
                        # load reduction storage level transition
2918
                        rhs = (
2919
                            self.dsm_do_level[g, t]
2920
                            - self.dsm_do_level[g, t - 1]
2921
                        )
2922
2923
                        # add constraint
2924
                        block.dr_storage_red.add((g, t), (lhs == rhs))
2925
2926
                    else:
2927
                        lhs = self.dsm_do_level[g, t]
2928
                        rhs = m.timeincrement[t] * sum(
2929
                            self.dsm_do_shift[g, h, t] for h in g.delay_time
2930
                        )
2931
                        block.dr_storage_red.add((g, t), (lhs == rhs))
2932
2933
        self.dr_storage_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
2934
        self.dr_storage_red_build = BuildAction(rule=dr_storage_red_rule)
2935
2936
        # Equation 4.14
2937 View Code Duplication
        def dr_storage_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
2938
            """Fictious demand response storage level for load increase
2939
            transition equation
2940
            """
2941
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2942
                for g in group:
2943
2944
                    # avoid timesteps prior to t = 0
2945
                    if t > 0:
2946
                        # increases minus balancing of reductions
2947
                        lhs = m.timeincrement[t] * sum(
2948
                            (
2949
                                self.dsm_up[g, h, t] * g.efficiency
2950
                                - self.balance_dsm_up[g, h, t]
2951
                            )
2952
                            for h in g.delay_time
2953
                        )
2954
2955
                        # load increase storage level transition
2956
                        rhs = (
2957
                            self.dsm_up_level[g, t]
2958
                            - self.dsm_up_level[g, t - 1]
2959
                        )
2960
2961
                        # add constraint
2962
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
2963
2964
                    else:
2965
                        # pass  # return(Constraint.Skip)
2966
                        lhs = self.dsm_up_level[g, t]
2967
                        rhs = m.timeincrement[t] * sum(
2968
                            self.dsm_up[g, h, t] for h in g.delay_time
2969
                        )
2970
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
2971
2972
        self.dr_storage_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
2973
        self.dr_storage_inc_build = BuildAction(rule=dr_storage_inc_rule)
2974
2975
        # Equation 4.15
2976
        def dr_storage_limit_red_rule(block):
2977
            """
2978
            Fictious demand response storage level for load reduction limit
2979
            """
2980
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
2981
                for g in group:
2982
2983
                    if g.shift_eligibility:
2984
                        # fictious demand response load reduction storage level
2985
                        lhs = self.dsm_do_level[g, t]
2986
2987
                        # maximum (time-dependent) available shifting capacity
2988
                        rhs = (
2989
                            g.capacity_down_mean
2990
                            * g.max_capacity_down
2991
                            * g.shift_time
2992
                        )
2993
2994
                        # add constraint
2995
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
2996
2997
                    else:
2998
                        lhs = self.dsm_do_level[g, t]
2999
                        # Force storage level and thus dsm_do_shift to 0
3000
                        rhs = 0
3001
3002
                        # add constraint
3003
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3004
3005
        self.dr_storage_limit_red = Constraint(
3006
            group, m.TIMESTEPS, noruleinit=True
3007
        )
3008
        self.dr_storage_level_red_build = BuildAction(
3009
            rule=dr_storage_limit_red_rule
3010
        )
3011
3012
        # Equation 4.16
3013
        def dr_storage_limit_inc_rule(block):
3014
            """
3015
            Fictious demand response storage level for load increase limit
3016
            """
3017
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3018
                for g in group:
3019
                    # fictious demand response load reduction storage level
3020
                    lhs = self.dsm_up_level[g, t]
3021
3022
                    # maximum (time-dependent) available shifting capacity
3023
                    rhs = g.capacity_up_mean * g.max_capacity_up * g.shift_time
3024
3025
                    # add constraint
3026
                    block.dr_storage_limit_inc.add((g, t), (lhs <= rhs))
3027
3028
        self.dr_storage_limit_inc = Constraint(
3029
            group, m.TIMESTEPS, noruleinit=True
3030
        )
3031
        self.dr_storage_level_inc_build = BuildAction(
3032
            rule=dr_storage_limit_inc_rule
3033
        )
3034
3035
        # Equation 4.17' -> load shedding
3036
        def dr_yearly_limit_shed_rule(block):
3037
            """Introduce overall annual (energy) limit for load shedding resp.
3038
            overall limit for optimization timeframe considered
3039
            A year limit in contrast to Gils (2015) is defined a mandatory
3040
            parameter here in order to achieve an approach comparable
3041
            to the others.
3042
            """
3043
            for g in group:
3044
3045
                if g.shed_eligibility:
3046
                    # sum of all load reductions
3047
                    lhs = sum(self.dsm_do_shed[g, t] for t in m.TIMESTEPS)
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3048
3049
                    # year limit
3050
                    rhs = (
3051
                        g.capacity_down_mean
3052
                        * g.max_capacity_down
3053
                        * g.shed_time
3054
                        * g.n_yearLimit_shed
3055
                    )
3056
3057
                    # add constraint
3058
                    block.dr_yearly_limit_shed.add(g, (lhs <= rhs))
3059
3060
                else:
3061
                    pass  # return(Constraint.Skip)
3062
3063
        self.dr_yearly_limit_shed = Constraint(group, noruleinit=True)
3064
        self.dr_yearly_limit_shed_build = BuildAction(
3065
            rule=dr_yearly_limit_shed_rule
3066
        )
3067
3068
        # ************* Optional Constraints *****************************
3069
3070
        # Equation 4.17
3071
        def dr_yearly_limit_red_rule(block):
3072
            """Introduce overall annual (energy) limit for load reductions
3073
            resp. overall limit for optimization timeframe considered
3074
            """
3075
            for g in group:
3076
3077
                if g.ActivateYearLimit:
3078
                    # sum of all load reductions
3079
                    lhs = sum(
3080
                        sum(self.dsm_do_shift[g, h, t] for h in g.delay_time)
3081
                        for t in m.TIMESTEPS
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3082
                    )
3083
3084
                    # year limit
3085
                    rhs = (
3086
                        g.capacity_down_mean
3087
                        * g.max_capacity_down
3088
                        * g.shift_time
3089
                        * g.n_yearLimit_shift
3090
                    )
3091
3092
                    # add constraint
3093
                    block.dr_yearly_limit_red.add(g, (lhs <= rhs))
3094
3095
                else:
3096
                    pass  # return(Constraint.Skip)
3097
3098
        self.dr_yearly_limit_red = Constraint(group, noruleinit=True)
3099
        self.dr_yearly_limit_red_build = BuildAction(
3100
            rule=dr_yearly_limit_red_rule
3101
        )
3102
3103
        # Equation 4.18
3104
        def dr_yearly_limit_inc_rule(block):
3105
            """Introduce overall annual (energy) limit for load increases
3106
            resp. overall limit for optimization timeframe considered
3107
            """
3108
            for g in group:
3109
3110
                if g.ActivateYearLimit:
3111
                    # sum of all load increases
3112
                    lhs = sum(
3113
                        sum(self.dsm_up[g, h, t] for h in g.delay_time)
3114
                        for t in m.TIMESTEPS
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3115
                    )
3116
3117
                    # year limit
3118
                    rhs = (
3119
                        g.capacity_up_mean
3120
                        * g.max_capacity_up
3121
                        * g.shift_time
3122
                        * g.n_yearLimit_shift
3123
                    )
3124
3125
                    # add constraint
3126
                    block.dr_yearly_limit_inc.add(g, (lhs <= rhs))
3127
3128
                else:
3129
                    pass  # return(Constraint.Skip)
3130
3131
        self.dr_yearly_limit_inc = Constraint(group, noruleinit=True)
3132
        self.dr_yearly_limit_inc_build = BuildAction(
3133
            rule=dr_yearly_limit_inc_rule
3134
        )
3135
3136
        # Equation 4.19
3137 View Code Duplication
        def dr_daily_limit_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3138
            """ "Introduce rolling (energy) limit for load reductions
3139
            This effectively limits DR utilization dependent on
3140
            activations within previous hours.
3141
            """
3142
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3143
                for g in group:
3144
3145
                    if g.ActivateDayLimit:
3146
                        # main use case
3147
                        if t >= g.t_dayLimit:
3148
3149
                            # load reduction
3150
                            lhs = sum(
3151
                                self.dsm_do_shift[g, h, t]
3152
                                for h in g.delay_time
3153
                            )
3154
3155
                            # daily limit
3156
                            rhs = (
3157
                                g.capacity_down_mean
3158
                                * g.max_capacity_down
3159
                                * g.shift_time
3160
                                - sum(
3161
                                    sum(
3162
                                        self.dsm_do_shift[g, h, t - t_dash]
3163
                                        for h in g.delay_time
3164
                                    )
3165
                                    for t_dash in range(
3166
                                        1, int(g.t_dayLimit) + 1
3167
                                    )
3168
                                )
3169
                            )
3170
3171
                            # add constraint
3172
                            block.dr_daily_limit_red.add((g, t), (lhs <= rhs))
3173
3174
                        else:
3175
                            pass  # return(Constraint.Skip)
3176
3177
                    else:
3178
                        pass  # return(Constraint.Skip)
3179
3180
        self.dr_daily_limit_red = Constraint(
3181
            group, m.TIMESTEPS, noruleinit=True
3182
        )
3183
        self.dr_daily_limit_red_build = BuildAction(
3184
            rule=dr_daily_limit_red_rule
3185
        )
3186
3187
        # Equation 4.20
3188 View Code Duplication
        def dr_daily_limit_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3189
            """Introduce rolling (energy) limit for load increases
3190
            This effectively limits DR utilization dependent on
3191
            activations within previous hours.
3192
            """
3193
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3194
                for g in group:
3195
3196
                    if g.ActivateDayLimit:
3197
                        # main use case
3198
                        if t >= g.t_dayLimit:
3199
3200
                            # load increase
3201
                            lhs = sum(
3202
                                self.dsm_up[g, h, t] for h in g.delay_time
3203
                            )
3204
3205
                            # daily limit
3206
                            rhs = (
3207
                                g.capacity_up_mean
3208
                                * g.max_capacity_up
3209
                                * g.shift_time
3210
                                - sum(
3211
                                    sum(
3212
                                        self.dsm_up[g, h, t - t_dash]
3213
                                        for h in g.delay_time
3214
                                    )
3215
                                    for t_dash in range(
3216
                                        1, int(g.t_dayLimit) + 1
3217
                                    )
3218
                                )
3219
                            )
3220
3221
                            # add constraint
3222
                            block.dr_daily_limit_inc.add((g, t), (lhs <= rhs))
3223
3224
                        else:
3225
                            pass  # return(Constraint.Skip)
3226
3227
                    else:
3228
                        pass  # return(Constraint.Skip)
3229
3230
        self.dr_daily_limit_inc = Constraint(
3231
            group, m.TIMESTEPS, noruleinit=True
3232
        )
3233
        self.dr_daily_limit_inc_build = BuildAction(
3234
            rule=dr_daily_limit_inc_rule
3235
        )
3236
3237
        # Addition: avoid simultaneous activations
3238 View Code Duplication
        def dr_logical_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3239
            """Similar to equation 10 from Zerrahn and Schill (2015):
3240
            The sum of upwards and downwards shifts may not be greater
3241
            than the (bigger) capacity limit.
3242
            """
3243
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3244
                for g in group:
3245
3246
                    if g.addition:
3247
                        # sum of load increases and reductions
3248
                        lhs = (
3249
                            sum(
3250
                                self.dsm_up[g, h, t]
3251
                                + self.balance_dsm_do[g, h, t]
3252
                                + self.dsm_do_shift[g, h, t]
3253
                                + self.balance_dsm_up[g, h, t]
3254
                                for h in g.delay_time
3255
                            )
3256
                            + self.dsm_do_shed[g, t]
3257
                        )
3258
3259
                        # maximum capacity eligibly for load shifting
3260
                        rhs = max(
3261
                            g.capacity_down[t] * g.max_capacity_down,
3262
                            g.capacity_up[t] * g.max_capacity_up,
3263
                        )
3264
3265
                        # add constraint
3266
                        block.dr_logical_constraint.add((g, t), (lhs <= rhs))
3267
3268
                    else:
3269
                        pass  # return(Constraint.Skip)
3270
3271
        self.dr_logical_constraint = Constraint(
3272
            group, m.TIMESTEPS, noruleinit=True
3273
        )
3274
        self.dr_logical_constraint_build = BuildAction(
3275
            rule=dr_logical_constraint_rule
3276
        )
3277
3278
    # Equation 4.23
3279
    def _objective_expression(self):
3280
        r"""Objective expression with variable costs for DSM activity;
3281
        Equation 4.23 from Gils (2015)
3282
        """
3283
        m = self.parent_block()
3284
3285
        dr_cost = 0
3286
3287
        for t in m.TIMESTEPS:
3288
            for g in self.DR:
3289
                dr_cost += (
3290
                    sum(
3291
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
3292
                        for h in g.delay_time
3293
                    )
3294
                    * g.cost_dsm_up[t]
3295
                    * m.objective_weighting[t]
3296
                )
3297
                dr_cost += (
3298
                    sum(
3299
                        self.dsm_do_shift[g, h, t]
3300
                        + self.balance_dsm_up[g, h, t]
3301
                        for h in g.delay_time
3302
                    )
3303
                    * g.cost_dsm_down_shift[t]
3304
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
3305
                ) * m.objective_weighting[t]
3306
3307
        self.cost = Expression(expr=dr_cost)
3308
3309
        return self.cost
3310
3311
3312
class SinkDSMDLRInvestmentBlock(SinkDSMDLRBlock):
3313
    r"""Constraints for SinkDSM with "DLR" approach and :attr:`investment`
3314
3315
    **The following constraints are created for approach = 'DLR' with an
3316
    investment object defined:**
3317
3318
    .. _SinkDSMDLR equations:
3319
3320
    .. math::
3321
        &
3322
        (1) \quad invest_{min} \leq invest \leq invest_{max} \\
3323
        &
3324
        (2) \quad DSM_{h, t}^{up} = 0 \quad \forall h \in H_{DR}
3325
        \forall t \in \mathbb{T}
3326
        \quad if \space eligibility_{shift} = False \\
3327
        &
3328
        (3) \quad DSM_{t}^{do, shed} = 0 \quad \forall t \in \mathbb{T}
3329
        \quad if \space eligibility_{shed} = False \\
3330
        &
3331
        (4) \quad \dot{E}_{t} = demand_{t} \cdot (invest + E_{exist})
3332
        + \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
3333
        + DSM_{h, t}^{balanceDo} - DSM_{h, t}^{do, shift}
3334
        - DSM_{h, t}^{balanceUp}) - DSM_{t}^{do, shed}
3335
        \quad \forall t \in \mathbb{T} \\
3336
        &
3337
        (5) \quad DSM_{h, t}^{balanceDo} =
3338
        \frac{DSM_{h, t - h}^{do, shift}}{\eta}
3339
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
3340
        &
3341
        (6) \quad DSM_{h, t}^{balanceUp} =
3342
        DSM_{h, t-h}^{up} \cdot \eta
3343
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
3344
        &
3345
        (7) \quad DSM_{h, t}^{do, shift} = 0
3346
        \quad \forall h \in H_{DR}
3347
        \forall t \in [T - h..T] \\
3348
        &
3349
        (8) \quad DSM_{h, t}^{up} = 0
3350
        \quad \forall h \in H_{DR}
3351
        \forall t \in [T - h..T] \\
3352
        &
3353
        (9) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift}
3354
        + DSM_{h, t}^{balanceUp}) + DSM_{t}^{do, shed}
3355
        \leq E_{t}^{do} \cdot (invest + E_{exist})
3356
        \cdot s_{flex, do}
3357
        \quad \forall t \in \mathbb{T} \\
3358
        &
3359
        (10) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
3360
        + DSM_{h, t}^{balanceDo})
3361
        \leq E_{t}^{up} \cdot (invest + E_{exist})
3362
        \cdot s_{flex, up}
3363
        \quad \forall t \in \mathbb{T} \\
3364
        &
3365
        (11) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
3366
        (DSM_{h, t}^{do, shift} - DSM_{h, t}^{balanceDo} \cdot \eta)
3367
        = W_{t}^{levelDo} - W_{t-1}^{levelDo}
3368
        \quad \forall t \in [1..T] \\
3369
        &
3370
        (12) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
3371
        (DSM_{h, t}^{up} \cdot \eta - DSM_{h, t}^{balanceUp})
3372
        = W_{t}^{levelUp} - W_{t-1}^{levelUp}
3373
        \quad \forall t \in [1..T] \\
3374
        &
3375
        (13) \quad W_{t}^{levelDo} \leq \overline{E}_{t}^{do}
3376
        \cdot (invest + E_{exist})
3377
        \cdot s_{flex, do} \cdot t_{shift}
3378
        \quad \forall t \in \mathbb{T} \\
3379
        &
3380
        (14) \quad W_{t}^{levelUp} \leq \overline{E}_{t}^{up}
3381
        \cdot (invest + E_{exist})
3382
        \cdot s_{flex, up} \cdot t_{shift}
3383
        \quad \forall t \in \mathbb{T} \\
3384
        &
3385
        (15) \quad \displaystyle\sum_{t=0}^{T} DSM_{t}^{do, shed}
3386
        \leq (invest + E_{exist})
3387
        \cdot s_{flex, do} \cdot \overline{E}_{t}^{do}
3388
        \cdot t_{shed}
3389
        \cdot n^{yearLimitShed} \\
3390
        &
3391
        (16) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
3392
        DSM_{h, t}^{do, shift}
3393
        \leq (invest + E_{exist})
3394
        \cdot s_{flex, do} \cdot \overline{E}_{t}^{do}
3395
        \cdot t_{shift}
3396
        \cdot n^{yearLimitShift} \\
3397
        (optional \space constraint) \\
3398
        &
3399
        (17) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
3400
        DSM_{h, t}^{up}
3401
        \leq (invest + E_{exist})
3402
        \cdot s_{flex, up} \cdot \overline{E}_{t}^{up}
3403
        \cdot t_{shift}
3404
        \cdot n^{yearLimitShift} \\
3405
        (optional \space constraint) \\
3406
        &
3407
        (18) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{do, shift}
3408
        \leq (invest + E_{exist})
3409
        \cdot s_{flex, do} \cdot \overline{E}_{t}^{do}
3410
        \cdot t_{shift} -
3411
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
3412
        DSM_{h, t - t'}^{do, shift}
3413
        \quad \forall t \in [t-t_{dayLimit}..T] \\
3414
        (optional \space constraint) \\
3415
        &
3416
        (19) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{up}
3417
        \leq (invest + E_{exist})
3418
        \cdot s_{flex, up} \cdot \overline{E}_{t}^{up}
3419
        \cdot t_{shift} -
3420
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
3421
        DSM_{h, t - t'}^{up}
3422
        \quad \forall t \in [t-t_{dayLimit}..T] \\
3423
        (optional \space constraint) \\
3424
        &
3425
        (20) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
3426
        + DSM_{h, t}^{balanceDo}
3427
        + DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
3428
        + DSM_{t}^{shed}
3429
        \leq \max \{E_{t}^{up} \cdot s_{flex, up},
3430
        E_{t}^{do} \cdot s_{flex, do} \} \cdot (invest + E_{exist})
3431
        \quad \forall t \in \mathbb{T} \\
3432
        (optional \space constraint) \\
3433
        &
3434
3435
    *Note*: For the sake of readability, the handling of indices is not
3436
    displayed here. E.g. evaluating a variable for t-L may lead to a negative
3437
    and therefore infeasible index.
3438
    This is addressed by limiting the sums to non-negative indices within the
3439
    model index bounds. Please refer to the constraints implementation
3440
    themselves.
3441
3442
    **The following parts of the objective function are created:**
3443
3444
    * Investment annuity:
3445
3446
    .. math::
3447
        invest \cdot costs_{invest} \\
3448
3449
    * Variable costs:
3450
3451
    .. math::
3452
        \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up} + DSM_{h, t}^{balanceDo})
3453
        \cdot cost_{t}^{dsm, up}
3454
        + \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
3455
        \cdot cost_{t}^{dsm, do, shift}
3456
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
3457
        \quad \forall t \in \mathbb{T} \\
3458
3459
    **Table: Symbols and attribute names of variables and parameters**
3460
3461
    Please refer to
3462
    :class:`oemof.solph.components.experimental._sink_dsm.SinkDSMDLRBlock`.
3463
3464
    The following variables and parameters are exclusively used for
3465
    investment modeling:
3466
3467
        .. csv-table:: Variables (V) and Parameters (P)
3468
            :header: "symbol", "attribute", "type", "explanation"
3469
            :widths: 1, 1, 1, 1
3470
3471
            ":math:`invest` ",":attr:`~SinkDSM.invest` ","V", "DSM capacity
3472
            invested in. Equals to the additionally installed capacity.
3473
            The capacity share eligible for a shift is determined
3474
            by flex share(s)."
3475
            ":math:`invest_{min}` ", ":attr:`~SinkDSM.investment.minimum` ",
3476
            "P", "minimum investment"
3477
            ":math:`invest_{max}` ", ":attr:`~SinkDSM.investment.maximum` ",
3478
            "P", "maximum investment"
3479
            ":math:`E_{exist}` ",":attr:`~SinkDSM.investment.existing` ",
3480
            "P", "existing DSM capacity"
3481
            ":math:`s_{flex, up}` ",":attr:`~SinkDSM.flex_share_up` ",
3482
            "P","Share of invested capacity that may be shift upwards
3483
            at maximum"
3484
            ":math:`s_{flex, do}` ",":attr:`~SinkDSM.flex_share_do` ",
3485
            "P", "Share of invested capacity that may be shift downwards
3486
            at maximum"
3487
            ":math:`costs_{invest}` ",":attr:`~SinkDSM.investment.ep_costs` ",
3488
            "P", "specific investment annuity"
3489
    """
3490
    CONSTRAINT_GROUP = True
3491
3492
    def __init__(self, *args, **kwargs):
3493
        super().__init__(*args, **kwargs)
3494
3495
    def _create(self, group=None):
3496
3497
        if group is None:
3498
            return None
3499
3500
        m = self.parent_block()
3501
3502
        # for all DSM components get inflow from a bus
3503
        for n in group:
3504
            n.inflow = list(n.inputs)[0]
3505
3506
        #  ************* SETS *********************************
3507
3508
        self.INVESTDR = Set(initialize=[n for n in group])
3509
3510
        # Depict different delay_times per unit via a mapping
3511
        map_INVESTDR_H = {
3512
            k: v
3513
            for k, v in zip([n for n in group], [n.delay_time for n in group])
3514
        }
3515
3516
        unique_H = list(
3517
            set(itertools.chain.from_iterable(map_INVESTDR_H.values()))
3518
        )
3519
        self.H = Set(initialize=unique_H)
3520
3521
        self.INVESTDR_H = Set(
3522
            within=self.INVESTDR * self.H,
3523
            initialize=[
3524
                (dr, h) for dr in map_INVESTDR_H for h in map_INVESTDR_H[dr]
3525
            ],
3526
        )
3527
3528
        #  ************* VARIABLES *****************************
3529
3530
        # Define bounds for investments in demand response
3531
        def _dr_investvar_bound_rule(block, g):
3532
            """Rule definition to bound the
3533
            invested demand response capacity `invest`.
3534
            """
3535
            return g.investment.minimum, g.investment.maximum
3536
3537
        # Investment in DR capacity
3538
        self.invest = Var(
3539
            self.INVESTDR,
3540
            within=NonNegativeReals,
3541
            bounds=_dr_investvar_bound_rule,
3542
        )
3543
3544
        # Variable load shift down (capacity)
3545
        self.dsm_do_shift = Var(
3546
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3547
        )
3548
3549
        # Variable for load shedding (capacity)
3550
        self.dsm_do_shed = Var(
3551
            self.INVESTDR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3552
        )
3553
3554
        # Variable load shift up (capacity)
3555
        self.dsm_up = Var(
3556
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3557
        )
3558
3559
        # Variable balance load shift down through upwards shift (capacity)
3560
        self.balance_dsm_do = Var(
3561
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3562
        )
3563
3564
        # Variable balance load shift up through downwards shift (capacity)
3565
        self.balance_dsm_up = Var(
3566
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3567
        )
3568
3569
        # Variable fictious DR storage level for downwards load shifts (energy)
3570
        self.dsm_do_level = Var(
3571
            self.INVESTDR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3572
        )
3573
3574
        # Variable fictious DR storage level for upwards load shifts (energy)
3575
        self.dsm_up_level = Var(
3576
            self.INVESTDR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3577
        )
3578
3579
        #  ************* CONSTRAINTS *****************************
3580
3581 View Code Duplication
        def _shift_shed_vars_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3582
            """Force shifting resp. shedding variables to zero dependent
3583
            on how boolean parameters for shift resp. shed eligibility
3584
            are set.
3585
            """
3586
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3587
                for g in group:
3588
                    for h in g.delay_time:
3589
3590
                        if not g.shift_eligibility:
3591
                            lhs = self.dsm_up[g, h, t]
3592
                            rhs = 0
3593
3594
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
3595
3596
                        if not g.shed_eligibility:
3597
                            lhs = self.dsm_do_shed[g, t]
3598
                            rhs = 0
3599
3600
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
3601
3602
        self.shift_shed_vars = Constraint(
3603
            group, self.H, m.TIMESTEPS, noruleinit=True
3604
        )
3605
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
3606
3607
        # Relation between inflow and effective Sink consumption
3608 View Code Duplication
        def _input_output_relation_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3609
            """Relation between input data and pyomo variables.
3610
            The actual demand after DR.
3611
            BusBlock outflow == Demand +- DR (i.e. effective Sink consumption)
3612
            """
3613
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3614
3615
                for g in group:
3616
                    # outflow from bus
3617
                    lhs = m.flow[g.inflow, g, t]
3618
3619
                    # Demand +- DR
3620
                    rhs = (
3621
                        g.demand[t] * (self.invest[g] + g.investment.existing)
3622
                        + sum(
3623
                            self.dsm_up[g, h, t]
3624
                            + self.balance_dsm_do[g, h, t]
3625
                            - self.dsm_do_shift[g, h, t]
3626
                            - self.balance_dsm_up[g, h, t]
3627
                            for h in g.delay_time
3628
                        )
3629
                        - self.dsm_do_shed[g, t]
3630
                    )
3631
3632
                    # add constraint
3633
                    block.input_output_relation.add((g, t), (lhs == rhs))
3634
3635
        self.input_output_relation = Constraint(
3636
            group, m.TIMESTEPS, noruleinit=True
3637
        )
3638
        self.input_output_relation_build = BuildAction(
3639
            rule=_input_output_relation_rule
3640
        )
3641
3642
        # Equation 4.8
3643 View Code Duplication
        def capacity_balance_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3644
            """Load reduction must be balanced by load increase
3645
            within delay_time
3646
            """
3647
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3648
                for g in group:
3649
                    for h in g.delay_time:
3650
3651
                        if g.shift_eligibility:
3652
3653
                            # main use case
3654
                            if t >= h:
3655
                                # balance load reduction
3656
                                lhs = self.balance_dsm_do[g, h, t]
3657
3658
                                # load reduction (efficiency considered)
3659
                                rhs = (
3660
                                    self.dsm_do_shift[g, h, t - h]
3661
                                    / g.efficiency
3662
                                )
3663
3664
                                # add constraint
3665
                                block.capacity_balance_red.add(
3666
                                    (g, h, t), (lhs == rhs)
3667
                                )
3668
3669
                            # no balancing for the first timestep
3670
                            elif t == m.TIMESTEPS[1]:
3671
                                lhs = self.balance_dsm_do[g, h, t]
3672
                                rhs = 0
3673
3674
                                block.capacity_balance_red.add(
3675
                                    (g, h, t), (lhs == rhs)
3676
                                )
3677
3678
                            else:
3679
                                pass  # return(Constraint.Skip)
3680
3681
                        # if only shedding is possible, balancing variable is 0
3682
                        else:
3683
                            lhs = self.balance_dsm_do[g, h, t]
3684
                            rhs = 0
3685
3686
                            block.capacity_balance_red.add(
3687
                                (g, h, t), (lhs == rhs)
3688
                            )
3689
3690
        self.capacity_balance_red = Constraint(
3691
            group, self.H, m.TIMESTEPS, noruleinit=True
3692
        )
3693
        self.capacity_balance_red_build = BuildAction(
3694
            rule=capacity_balance_red_rule
3695
        )
3696
3697
        # Equation 4.9
3698 View Code Duplication
        def capacity_balance_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3699
            """Load increased must be balanced by load reduction
3700
            within delay_time
3701
            """
3702
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3703
                for g in group:
3704
                    for h in g.delay_time:
3705
3706
                        if g.shift_eligibility:
3707
3708
                            # main use case
3709
                            if t >= h:
3710
                                # balance load increase
3711
                                lhs = self.balance_dsm_up[g, h, t]
3712
3713
                                # load increase (efficiency considered)
3714
                                rhs = self.dsm_up[g, h, t - h] * g.efficiency
3715
3716
                                # add constraint
3717
                                block.capacity_balance_inc.add(
3718
                                    (g, h, t), (lhs == rhs)
3719
                                )
3720
3721
                            # no balancing for the first timestep
3722
                            elif t == m.TIMESTEPS[1]:
3723
                                lhs = self.balance_dsm_up[g, h, t]
3724
                                rhs = 0
3725
3726
                                block.capacity_balance_inc.add(
3727
                                    (g, h, t), (lhs == rhs)
3728
                                )
3729
3730
                            else:
3731
                                pass  # return(Constraint.Skip)
3732
3733
                        # if only shedding is possible, balancing variable is 0
3734
                        else:
3735
                            lhs = self.balance_dsm_up[g, h, t]
3736
                            rhs = 0
3737
3738
                            block.capacity_balance_inc.add(
3739
                                (g, h, t), (lhs == rhs)
3740
                            )
3741
3742
        self.capacity_balance_inc = Constraint(
3743
            group, self.H, m.TIMESTEPS, noruleinit=True
3744
        )
3745
        self.capacity_balance_inc_build = BuildAction(
3746
            rule=capacity_balance_inc_rule
3747
        )
3748
3749
        # Own addition: prevent shifts which cannot be compensated
3750 View Code Duplication
        def no_comp_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3751
            """Prevent downwards shifts that cannot be balanced anymore
3752
            within the optimization timeframe
3753
            """
3754
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3755
                for g in group:
3756
3757
                    if g.fixes:
3758
                        for h in g.delay_time:
3759
3760
                            if t > m.TIMESTEPS[-1] - h:
3761
                                # no load reduction anymore (dsm_do_shift = 0)
3762
                                lhs = self.dsm_do_shift[g, h, t]
3763
                                rhs = 0
3764
                                block.no_comp_red.add((g, h, t), (lhs == rhs))
3765
3766
                    else:
3767
                        pass  # return(Constraint.Skip)
3768
3769
        self.no_comp_red = Constraint(
3770
            group, self.H, m.TIMESTEPS, noruleinit=True
3771
        )
3772
        self.no_comp_red_build = BuildAction(rule=no_comp_red_rule)
3773
3774
        # Own addition: prevent shifts which cannot be compensated
3775 View Code Duplication
        def no_comp_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3776
            """Prevent upwards shifts that cannot be balanced anymore
3777
            within the optimization timeframe
3778
            """
3779
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3780
                for g in group:
3781
3782
                    if g.fixes:
3783
                        for h in g.delay_time:
3784
3785
                            if t > m.TIMESTEPS[-1] - h:
3786
                                # no load increase anymore (dsm_up = 0)
3787
                                lhs = self.dsm_up[g, h, t]
3788
                                rhs = 0
3789
                                block.no_comp_inc.add((g, h, t), (lhs == rhs))
3790
3791
                    else:
3792
                        pass  # return(Constraint.Skip)
3793
3794
        self.no_comp_inc = Constraint(
3795
            group, self.H, m.TIMESTEPS, noruleinit=True
3796
        )
3797
        self.no_comp_inc_build = BuildAction(rule=no_comp_inc_rule)
3798
3799
        # Equation 4.11
3800
        def availability_red_rule(block):
3801
            """Load reduction must be smaller than or equal to the
3802
            (time-dependent) capacity limit
3803
            """
3804
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3805
                for g in group:
3806
                    # load reduction
3807
                    lhs = (
3808
                        sum(
3809
                            self.dsm_do_shift[g, h, t]
3810
                            + self.balance_dsm_up[g, h, t]
3811
                            for h in g.delay_time
3812
                        )
3813
                        + self.dsm_do_shed[g, t]
3814
                    )
3815
3816
                    # upper bound
3817
                    rhs = (
3818
                        g.capacity_down[t]
3819
                        * (self.invest[g] + g.investment.existing)
3820
                        * g.flex_share_down
3821
                    )
3822
3823
                    # add constraint
3824
                    block.availability_red.add((g, t), (lhs <= rhs))
3825
3826
        self.availability_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
3827
        self.availability_red_build = BuildAction(rule=availability_red_rule)
3828
3829
        # Equation 4.12
3830
        def availability_inc_rule(block):
3831
            """Load increase must be smaller than or equal to the
3832
            (time-dependent) capacity limit
3833
            """
3834
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3835
                for g in group:
3836
                    # load increase
3837
                    lhs = sum(
3838
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
3839
                        for h in g.delay_time
3840
                    )
3841
3842
                    # upper bound
3843
                    rhs = (
3844
                        g.capacity_up[t]
3845
                        * (self.invest[g] + g.investment.existing)
3846
                        * g.flex_share_up
3847
                    )
3848
3849
                    # add constraint
3850
                    block.availability_inc.add((g, t), (lhs <= rhs))
3851
3852
        self.availability_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
3853
        self.availability_inc_build = BuildAction(rule=availability_inc_rule)
3854
3855
        # Equation 4.13
3856 View Code Duplication
        def dr_storage_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3857
            """Fictious demand response storage level for load reductions
3858
            transition equation
3859
            """
3860
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3861
                for g in group:
3862
3863
                    # avoid timesteps prior to t = 0
3864
                    if t > 0:
3865
                        # reduction minus balancing of reductions
3866
                        lhs = m.timeincrement[t] * sum(
3867
                            (
3868
                                self.dsm_do_shift[g, h, t]
3869
                                - self.balance_dsm_do[g, h, t] * g.efficiency
3870
                            )
3871
                            for h in g.delay_time
3872
                        )
3873
3874
                        # load reduction storage level transition
3875
                        rhs = (
3876
                            self.dsm_do_level[g, t]
3877
                            - self.dsm_do_level[g, t - 1]
3878
                        )
3879
3880
                        # add constraint
3881
                        block.dr_storage_red.add((g, t), (lhs == rhs))
3882
3883
                    else:
3884
                        # pass  # return(Constraint.Skip)
3885
                        lhs = self.dsm_do_level[g, t]
3886
                        rhs = m.timeincrement[t] * sum(
3887
                            self.dsm_do_shift[g, h, t] for h in g.delay_time
3888
                        )
3889
                        block.dr_storage_red.add((g, t), (lhs == rhs))
3890
3891
        self.dr_storage_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
3892
        self.dr_storage_red_build = BuildAction(rule=dr_storage_red_rule)
3893
3894
        # Equation 4.14
3895 View Code Duplication
        def dr_storage_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
3896
            """Fictious demand response storage level for load increase
3897
            transition equation
3898
            """
3899
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3900
                for g in group:
3901
3902
                    # avoid timesteps prior to t = 0
3903
                    if t > 0:
3904
                        # increases minus balancing of reductions
3905
                        lhs = m.timeincrement[t] * sum(
3906
                            (
3907
                                self.dsm_up[g, h, t] * g.efficiency
3908
                                - self.balance_dsm_up[g, h, t]
3909
                            )
3910
                            for h in g.delay_time
3911
                        )
3912
3913
                        # load increase storage level transition
3914
                        rhs = (
3915
                            self.dsm_up_level[g, t]
3916
                            - self.dsm_up_level[g, t - 1]
3917
                        )
3918
3919
                        # add constraint
3920
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
3921
3922
                    else:
3923
                        # pass  # return(Constraint.Skip)
3924
                        lhs = self.dsm_up_level[g, t]
3925
                        rhs = m.timeincrement[t] * sum(
3926
                            self.dsm_up[g, h, t] for h in g.delay_time
3927
                        )
3928
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
3929
3930
        self.dr_storage_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
3931
        self.dr_storage_inc_build = BuildAction(rule=dr_storage_inc_rule)
3932
3933
        # Equation 4.15
3934
        def dr_storage_limit_red_rule(block):
3935
            """
3936
            Fictious demand response storage level for load reduction limit
3937
            """
3938
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3939
                for g in group:
3940
3941
                    if g.shift_eligibility:
3942
                        # fictious demand response load reduction storage level
3943
                        lhs = self.dsm_do_level[g, t]
3944
3945
                        # maximum (time-dependent) available shifting capacity
3946
                        rhs = (
3947
                            g.capacity_down_mean
3948
                            * (self.invest[g] + g.investment.existing)
3949
                            * g.flex_share_down
3950
                            * g.shift_time
3951
                        )
3952
3953
                        # add constraint
3954
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3955
3956
                    else:
3957
                        lhs = self.dsm_do_level[g, t]
3958
                        # Force storage level and thus dsm_do_shift to 0
3959
                        rhs = 0
3960
3961
                        # add constraint
3962
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3963
3964
        self.dr_storage_limit_red = Constraint(
3965
            group, m.TIMESTEPS, noruleinit=True
3966
        )
3967
        self.dr_storage_level_red_build = BuildAction(
3968
            rule=dr_storage_limit_red_rule
3969
        )
3970
3971
        # Equation 4.16
3972
        def dr_storage_limit_inc_rule(block):
3973
            """
3974
            Fictious demand response storage level for load increase limit
3975
            """
3976
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
3977
                for g in group:
3978
                    # fictious demand response load reduction storage level
3979
                    lhs = self.dsm_up_level[g, t]
3980
3981
                    # maximum (time-dependent) available shifting capacity
3982
                    rhs = (
3983
                        g.capacity_up_mean
3984
                        * (self.invest[g] + g.investment.existing)
3985
                        * g.flex_share_up
3986
                        * g.shift_time
3987
                    )
3988
3989
                    # add constraint
3990
                    block.dr_storage_limit_inc.add((g, t), (lhs <= rhs))
3991
3992
        self.dr_storage_limit_inc = Constraint(
3993
            group, m.TIMESTEPS, noruleinit=True
3994
        )
3995
        self.dr_storage_level_inc_build = BuildAction(
3996
            rule=dr_storage_limit_inc_rule
3997
        )
3998
3999
        # Equation 4.17' -> load shedding
4000
        def dr_yearly_limit_shed_rule(block):
4001
            """Introduce overall annual (energy) limit for load shedding
4002
            resp. overall limit for optimization timeframe considered
4003
            A year limit in contrast to Gils (2015) is defined a mandatory
4004
            parameter here in order to achieve an approach comparable
4005
            to the others.
4006
            """
4007
            for g in group:
4008
                if g.shed_eligibility:
4009
                    # sum of all load reductions
4010
                    lhs = sum(self.dsm_do_shed[g, t] for t in m.TIMESTEPS)
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
4011
4012
                    # year limit
4013
                    rhs = (
4014
                        g.capacity_down_mean
4015
                        * (self.invest[g] + g.investment.existing)
4016
                        * g.flex_share_down
4017
                        * g.shed_time
4018
                        * g.n_yearLimit_shed
4019
                    )
4020
4021
                    # add constraint
4022
                    block.dr_yearly_limit_shed.add(g, (lhs <= rhs))
4023
4024
        self.dr_yearly_limit_shed = Constraint(group, noruleinit=True)
4025
        self.dr_yearly_limit_shed_build = BuildAction(
4026
            rule=dr_yearly_limit_shed_rule
4027
        )
4028
4029
        # ************* Optional Constraints *****************************
4030
4031
        # Equation 4.17
4032
        def dr_yearly_limit_red_rule(block):
4033
            """Introduce overall annual (energy) limit for load reductions
4034
            resp. overall limit for optimization timeframe considered
4035
            """
4036
            for g in group:
4037
4038
                if g.ActivateYearLimit:
4039
                    # sum of all load reductions
4040
                    lhs = sum(
4041
                        sum(self.dsm_do_shift[g, h, t] for h in g.delay_time)
4042
                        for t in m.TIMESTEPS
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
4043
                    )
4044
4045
                    # year limit
4046
                    rhs = (
4047
                        g.capacity_down_mean
4048
                        * (self.invest[g] + g.investment.existing)
4049
                        * g.flex_share_down
4050
                        * g.shift_time
4051
                        * g.n_yearLimit_shift
4052
                    )
4053
4054
                    # add constraint
4055
                    block.dr_yearly_limit_red.add(g, (lhs <= rhs))
4056
4057
                else:
4058
                    pass  # return(Constraint.Skip)
4059
4060
        self.dr_yearly_limit_red = Constraint(group, noruleinit=True)
4061
        self.dr_yearly_limit_red_build = BuildAction(
4062
            rule=dr_yearly_limit_red_rule
4063
        )
4064
4065
        # Equation 4.18
4066
        def dr_yearly_limit_inc_rule(block):
4067
            """Introduce overall annual (energy) limit for load increases
4068
            resp. overall limit for optimization timeframe considered
4069
            """
4070
            for g in group:
4071
4072
                if g.ActivateYearLimit:
4073
                    # sum of all load increases
4074
                    lhs = sum(
4075
                        sum(self.dsm_up[g, h, t] for h in g.delay_time)
4076
                        for t in m.TIMESTEPS
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
4077
                    )
4078
4079
                    # year limit
4080
                    rhs = (
4081
                        g.capacity_up_mean
4082
                        * (self.invest[g] + g.investment.existing)
4083
                        * g.flex_share_up
4084
                        * g.shift_time
4085
                        * g.n_yearLimit_shift
4086
                    )
4087
4088
                    # add constraint
4089
                    block.dr_yearly_limit_inc.add(g, (lhs <= rhs))
4090
4091
                else:
4092
                    pass  # return(Constraint.Skip)
4093
4094
        self.dr_yearly_limit_inc = Constraint(group, noruleinit=True)
4095
        self.dr_yearly_limit_inc_build = BuildAction(
4096
            rule=dr_yearly_limit_inc_rule
4097
        )
4098
4099
        # Equation 4.19
4100 View Code Duplication
        def dr_daily_limit_red_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
4101
            """Introduce rolling (energy) limit for load reductions
4102
            This effectively limits DR utilization dependent on
4103
            activations within previous hours.
4104
            """
4105
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
4106
                for g in group:
4107
4108
                    if g.ActivateDayLimit:
4109
4110
                        # main use case
4111
                        if t >= g.t_dayLimit:
4112
4113
                            # load reduction
4114
                            lhs = sum(
4115
                                self.dsm_do_shift[g, h, t]
4116
                                for h in g.delay_time
4117
                            )
4118
4119
                            # daily limit
4120
                            rhs = g.capacity_down_mean * (
4121
                                self.invest[g] + g.investment.existing
4122
                            ) * g.flex_share_down * g.shift_time - sum(
4123
                                sum(
4124
                                    self.dsm_do_shift[g, h, t - t_dash]
4125
                                    for h in g.delay_time
4126
                                )
4127
                                for t_dash in range(1, int(g.t_dayLimit) + 1)
4128
                            )
4129
4130
                            # add constraint
4131
                            block.dr_daily_limit_red.add((g, t), (lhs <= rhs))
4132
4133
                        else:
4134
                            pass  # return(Constraint.Skip)
4135
4136
                    else:
4137
                        pass  # return(Constraint.Skip)
4138
4139
        self.dr_daily_limit_red = Constraint(
4140
            group, m.TIMESTEPS, noruleinit=True
4141
        )
4142
        self.dr_daily_limit_red_build = BuildAction(
4143
            rule=dr_daily_limit_red_rule
4144
        )
4145
4146
        # Equation 4.20
4147 View Code Duplication
        def dr_daily_limit_inc_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
4148
            """Introduce rolling (energy) limit for load increases
4149
            This effectively limits DR utilization dependent on
4150
            activations within previous hours.
4151
            """
4152
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
4153
                for g in group:
4154
4155
                    if g.ActivateDayLimit:
4156
4157
                        # main use case
4158
                        if t >= g.t_dayLimit:
4159
4160
                            # load increase
4161
                            lhs = sum(
4162
                                self.dsm_up[g, h, t] for h in g.delay_time
4163
                            )
4164
4165
                            # daily limit
4166
                            rhs = g.capacity_up_mean * (
4167
                                self.invest[g] + g.investment.existing
4168
                            ) * g.flex_share_up * g.shift_time - sum(
4169
                                sum(
4170
                                    self.dsm_up[g, h, t - t_dash]
4171
                                    for h in g.delay_time
4172
                                )
4173
                                for t_dash in range(1, int(g.t_dayLimit) + 1)
4174
                            )
4175
4176
                            # add constraint
4177
                            block.dr_daily_limit_inc.add((g, t), (lhs <= rhs))
4178
4179
                        else:
4180
                            pass  # return(Constraint.Skip)
4181
4182
                    else:
4183
                        pass  # return(Constraint.Skip)
4184
4185
        self.dr_daily_limit_inc = Constraint(
4186
            group, m.TIMESTEPS, noruleinit=True
4187
        )
4188
        self.dr_daily_limit_inc_build = BuildAction(
4189
            rule=dr_daily_limit_inc_rule
4190
        )
4191
4192
        # Addition: avoid simultaneous activations
4193 View Code Duplication
        def dr_logical_constraint_rule(block):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
4194
            """Similar to equation 10 from Zerrahn and Schill (2015):
4195
            The sum of upwards and downwards shifts may not be greater
4196
            than the (bigger) capacity limit.
4197
            """
4198
            for t in m.TIMESTEPS:
0 ignored issues
show
introduced by
The variable m does not seem to be defined for all execution paths.
Loading history...
4199
                for g in group:
4200
4201
                    if g.addition:
4202
4203
                        # sum of load increases and reductions
4204
                        lhs = (
4205
                            sum(
4206
                                self.dsm_up[g, h, t]
4207
                                + self.balance_dsm_do[g, h, t]
4208
                                + self.dsm_do_shift[g, h, t]
4209
                                + self.balance_dsm_up[g, h, t]
4210
                                for h in g.delay_time
4211
                            )
4212
                            + self.dsm_do_shed[g, t]
4213
                        )
4214
4215
                        # maximum capacity eligibly for load shifting
4216
                        rhs = max(
4217
                            g.capacity_down[t] * g.flex_share_down,
4218
                            g.capacity_up[t] * g.flex_share_up,
4219
                        ) * (self.invest[g] + g.investment.existing)
4220
4221
                        # add constraint
4222
                        block.dr_logical_constraint.add((g, t), (lhs <= rhs))
4223
4224
                    else:
4225
                        pass  # return(Constraint.Skip)
4226
4227
        self.dr_logical_constraint = Constraint(
4228
            group, m.TIMESTEPS, noruleinit=True
4229
        )
4230
        self.dr_logical_constraint_build = BuildAction(
4231
            rule=dr_logical_constraint_rule
4232
        )
4233
4234
    def _objective_expression(self):
4235
        r"""Objective expression with variable and investment costs for DSM;
4236
        Equation 4.23 from Gils (2015)
4237
        """
4238
        m = self.parent_block()
4239
4240
        investment_costs = 0
4241
        variable_costs = 0
4242
4243
        for g in self.INVESTDR:
4244
            if g.investment.ep_costs is not None:
4245
                investment_costs += self.invest[g] * g.investment.ep_costs
4246
            else:
4247
                raise ValueError("Missing value for investment costs!")
4248
            for t in m.TIMESTEPS:
4249
                variable_costs += (
4250
                    sum(
4251
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
4252
                        for h in g.delay_time
4253
                    )
4254
                    * g.cost_dsm_up[t]
4255
                    * m.objective_weighting[t]
4256
                )
4257
                variable_costs += (
4258
                    sum(
4259
                        self.dsm_do_shift[g, h, t]
4260
                        + self.balance_dsm_up[g, h, t]
4261
                        for h in g.delay_time
4262
                    )
4263
                    * g.cost_dsm_down_shift[t]
4264
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
4265
                ) * m.objective_weighting[t]
4266
4267
        self.cost = Expression(expr=investment_costs + variable_costs)
4268
4269
        return self.cost
4270