Passed
Pull Request — dev (#815)
by Uwe
01:21
created

SinkDSMDIWBlock._create()   F

Complexity

Conditions 33

Size

Total Lines 491
Code Lines 231

Duplication

Lines 88
Ratio 17.92 %

Importance

Changes 0
Metric Value
eloc 231
dl 88
loc 491
rs 0
c 0
b 0
f 0
cc 33
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.SinkDSMDIWBlock._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 SimpleBlock
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(SimpleBlock):
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 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...
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(SimpleBlock):
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 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...
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(SimpleBlock):
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 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...
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(SimpleBlock):
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 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...
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 = (
2135
                            max(
2136
                                g.capacity_up[tt] * g.flex_share_up,
2137
                                g.capacity_down[tt] * g.flex_share_down,
2138
                            )
2139
                            * (self.invest[g] + g.investment.existing)
2140
                        )
2141
2142
                        # add constraint
2143
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
2144
2145
                    elif g.delay_time < tt <= m.TIMESTEPS[-1] - g.delay_time:
2146
2147
                        # DSM up/down
2148
                        lhs = (
2149
                            self.dsm_up[g, tt]
2150
                            + sum(
2151
                                self.dsm_do_shift[g, t, tt]
2152
                                for t in range(
2153
                                    tt - g.delay_time, tt + g.delay_time + 1
2154
                                )
2155
                            )
2156
                            + self.dsm_do_shed[g, tt]
2157
                        )
2158
                        # max capacity at tt
2159
                        rhs = (
2160
                            max(
2161
                                g.capacity_up[tt] * g.flex_share_up,
2162
                                g.capacity_down[tt] * g.flex_share_down,
2163
                            )
2164
                            * (self.invest[g] + g.investment.existing)
2165
                        )
2166
2167
                        # add constraint
2168
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
2169
2170
                    else:
2171
2172
                        # DSM up/down
2173
                        lhs = (
2174
                            self.dsm_up[g, tt]
2175
                            + sum(
2176
                                self.dsm_do_shift[g, t, tt]
2177
                                for t in range(
2178
                                    tt - g.delay_time, m.TIMESTEPS[-1] + 1
2179
                                )
2180
                            )
2181
                            + self.dsm_do_shed[g, tt]
2182
                        )
2183
                        # max capacity at tt
2184
                        rhs = (
2185
                            max(
2186
                                g.capacity_up[tt] * g.flex_share_up,
2187
                                g.capacity_down[tt] * g.flex_share_down,
2188
                            )
2189
                            * (self.invest[g] + g.investment.existing)
2190
                        )
2191
2192
                        # add constraint
2193
                        block.C2_constraint.add((g, tt), (lhs <= rhs))
2194
2195
        self.C2_constraint = Constraint(group, m.TIMESTEPS, noruleinit=True)
2196
        self.C2_constraint_build = BuildAction(rule=c2_constraint_rule)
2197
2198
        def recovery_constraint_rule(block):
2199
            """Equation 11 by Zerrahn & Schill:
2200
            A recovery time is introduced to account for the fact that
2201
            there may be some restrictions before the next load shift
2202
            may take place. Rule is only applicable if a recovery time
2203
            is defined.
2204
            """
2205
            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...
2206
                for g in group:
2207
2208
                    # No need to build constraint if no recovery
2209
                    # time is defined.
2210
                    if g.recovery_time_shift not in [None, 0]:
2211
2212
                        # main use case
2213
                        if t <= m.TIMESTEPS[-1] - g.recovery_time_shift:
2214
2215
                            # DSM up
2216
                            lhs = sum(
2217
                                self.dsm_up[g, tt]
2218
                                for tt in range(t, t + g.recovery_time_shift)
2219
                            )
2220
                            # max energy shift for shifting process
2221
                            rhs = (
2222
                                g.capacity_up[t]
2223
                                * (self.invest[g] + g.investment.existing)
2224
                                * g.flex_share_up
2225
                                * g.delay_time
2226
                                * m.timeincrement[t]
2227
                            )
2228
                            # add constraint
2229
                            block.recovery_constraint.add((g, t), (lhs <= rhs))
2230
2231
                        # last time steps: end - recovery time
2232
                        else:
2233
2234
                            # DSM up
2235
                            lhs = sum(
2236
                                self.dsm_up[g, tt]
2237
                                for tt in range(t, m.TIMESTEPS[-1] + 1)
2238
                            )
2239
                            # max energy shift for shifting process
2240
                            rhs = (
2241
                                g.capacity_up[t]
2242
                                * (self.invest[g] + g.investment.existing)
2243
                                * g.flex_share_up
2244
                                * g.delay_time
2245
                                * m.timeincrement[t]
2246
                            )
2247
                            # add constraint
2248
                            block.recovery_constraint.add((g, t), (lhs <= rhs))
2249
2250
                    else:
2251
                        pass  # return(Constraint.Skip)
2252
2253
        self.recovery_constraint = Constraint(
2254
            group, m.TIMESTEPS, noruleinit=True
2255
        )
2256
        self.recovery_constraint_build = BuildAction(
2257
            rule=recovery_constraint_rule
2258
        )
2259
2260
        # Equation 9a from Zerrahn and Schill (2015b)
2261
        def shed_limit_constraint_rule(block):
2262
            """The following constraint is highly similar to equation 9a
2263
            from Zerrahn and Schill (2015b): A recovery time for load
2264
            shedding is introduced in order to limit the overall amount
2265
            of shedded energy.
2266
            """
2267
            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...
2268
                for g in group:
2269
2270
                    # Only applicable for load shedding
2271
                    if g.shed_eligibility:
2272
2273
                        # main use case
2274
                        if t <= m.TIMESTEPS[-1] - g.recovery_time_shed:
2275
2276
                            # DSM up
2277
                            lhs = sum(
2278
                                self.dsm_do_shed[g, tt]
2279
                                for tt in range(t, t + g.recovery_time_shed)
2280
                            )
2281
                            # max energy shift for shifting process
2282
                            rhs = (
2283
                                g.capacity_down[t]
2284
                                * (self.invest[g] + g.investment.existing)
2285
                                * g.flex_share_down
2286
                                * g.shed_time
2287
                                * m.timeincrement[t]
2288
                            )
2289
                            # add constraint
2290
                            block.shed_limit_constraint.add(
2291
                                (g, t), (lhs <= rhs)
2292
                            )
2293
2294
                        # last time steps: end - recovery time
2295
                        else:
2296
2297
                            # DSM up
2298
                            lhs = sum(
2299
                                self.dsm_do_shed[g, tt]
2300
                                for tt in range(t, m.TIMESTEPS[-1] + 1)
2301
                            )
2302
                            # max energy shift for shifting process
2303
                            rhs = (
2304
                                g.capacity_down[t]
2305
                                * (self.invest[g] + g.investment.existing)
2306
                                * g.flex_share_down
2307
                                * g.shed_time
2308
                                * m.timeincrement[t]
2309
                            )
2310
                            # add constraint
2311
                            block.shed_limit_constraint.add(
2312
                                (g, t), (lhs <= rhs)
2313
                            )
2314
2315
                    else:
2316
                        pass  # return(Constraint.Skip)
2317
2318
        self.shed_limit_constraint = Constraint(
2319
            group, m.TIMESTEPS, noruleinit=True
2320
        )
2321
        self.shed_limit_constraint_build = BuildAction(
2322
            rule=shed_limit_constraint_rule
2323
        )
2324
2325 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...
2326
        r"""Objective expression with variable and investment costs for DSM"""
2327
2328
        m = self.parent_block()
2329
2330
        investment_costs = 0
2331
        variable_costs = 0
2332
2333
        for g in self.investdsm:
2334
            if g.investment.ep_costs is not None:
2335
                investment_costs += self.invest[g] * g.investment.ep_costs
2336
            else:
2337
                raise ValueError("Missing value for investment costs!")
2338
2339
            for t in m.TIMESTEPS:
2340
                variable_costs += (
2341
                    self.dsm_up[g, t]
2342
                    * g.cost_dsm_up[t]
2343
                    * m.objective_weighting[t]
2344
                )
2345
                variable_costs += (
2346
                    sum(self.dsm_do_shift[g, tt, t] for tt in m.TIMESTEPS)
2347
                    * g.cost_dsm_down_shift[t]
2348
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
2349
                ) * m.objective_weighting[t]
2350
2351
        self.cost = Expression(expr=investment_costs + variable_costs)
2352
2353
        return self.cost
2354
2355
2356
class SinkDSMDLRBlock(SimpleBlock):
2357
    r"""Constraints for SinkDSM with "DLR" approach
2358
2359
    **The following constraints are created for approach = 'DLR':**
2360
2361
    .. _SinkDSMDLR equations:
2362
2363
    .. math::
2364
        &
2365
        (1) \quad DSM_{h, t}^{up} = 0 \quad \forall h \in H_{DR}
2366
        \forall t \in \mathbb{T}
2367
        \quad if \space eligibility_{shift} = False \\
2368
        &
2369
        (2) \quad DSM_{t}^{do, shed} = 0 \quad \forall t \in \mathbb{T}
2370
        \quad if \space eligibility_{shed} = False \\
2371
        &
2372
        (3) \quad \dot{E}_{t} = demand_{t} \cdot demand_{max} +
2373
        \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
2374
        + DSM_{h, t}^{balanceDo} - DSM_{h, t}^{do, shift}
2375
        - DSM_{h, t}^{balanceUp}) - DSM_{t}^{do, shed}
2376
        \quad \forall t \in \mathbb{T} \\
2377
        &
2378
        (4) \quad DSM_{h, t}^{balanceDo} =
2379
        \frac{DSM_{h, t - h}^{do, shift}}{\eta}
2380
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
2381
        &
2382
        (5) \quad DSM_{h, t}^{balanceUp} =
2383
        DSM_{h, t-h}^{up} \cdot \eta
2384
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
2385
        &
2386
        (6) \quad DSM_{h, t}^{do, shift} = 0
2387
        \quad \forall h \in H_{DR}
2388
        \forall t \in [T - h..T] \\
2389
        &
2390
        (7) \quad DSM_{h, t}^{up} = 0
2391
        \quad \forall h \in H_{DR}
2392
        \forall t \in [T - h..T] \\
2393
        &
2394
        (8) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift}
2395
        + DSM_{h, t}^{balanceUp}) + DSM_{t}^{do, shed}
2396
        \leq E_{t}^{do} \cdot E_{max, do}
2397
        \quad \forall t \in \mathbb{T} \\
2398
        &
2399
        (9) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
2400
        + DSM_{h, t}^{balanceDo})
2401
        \leq E_{t}^{up} \cdot E_{max, up}
2402
        \quad \forall t \in \mathbb{T} \\
2403
        &
2404
        (10) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
2405
        (DSM_{h, t}^{do, shift} - DSM_{h, t}^{balanceDo} \cdot \eta)
2406
        = W_{t}^{levelDo} - W_{t-1}^{levelDo}
2407
        \quad \forall t \in [1..T] \\
2408
        &
2409
        (11) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
2410
        (DSM_{h, t}^{up} \cdot \eta - DSM_{h, t}^{balanceUp})
2411
        = W_{t}^{levelUp} - W_{t-1}^{levelUp}
2412
        \quad \forall t \in [1..T] \\
2413
        &
2414
        (12) \quad W_{t}^{levelDo} \leq \overline{E}_{t}^{do}
2415
        \cdot E_{max, do} \cdot t_{shift}
2416
        \quad \forall t \in \mathbb{T} \\
2417
        &
2418
        (13) \quad W_{t}^{levelUp} \leq \overline{E}_{t}^{up}
2419
        \cdot E_{max, up} \cdot t_{shift}
2420
        \quad \forall t \in \mathbb{T} \\
2421
        &
2422
        (14) \quad \displaystyle\sum_{t=0}^{T} DSM_{t}^{do, shed}
2423
        \leq E_{max, do} \cdot \overline{E}_{t}^{do} \cdot t_{shed}
2424
        \cdot n^{yearLimitShed} \\
2425
        &
2426
        (15) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
2427
        DSM_{h, t}^{do, shift}
2428
        \leq E_{max, do} \cdot \overline{E}_{t}^{do} \cdot t_{shift}
2429
        \cdot n^{yearLimitShift} \\
2430
        (optional \space constraint) \\
2431
        &
2432
        (16) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
2433
        DSM_{h, t}^{up}
2434
        \leq E_{max, up} \cdot \overline{E}_{t}^{up} \cdot t_{shift}
2435
        \cdot n^{yearLimitShift} \\
2436
        (optional \space constraint) \\
2437
        &
2438
        (17) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{do, shift}
2439
        \leq E_{max, do} \cdot \overline{E}_{t}^{do}
2440
        \cdot t_{shift} -
2441
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
2442
        DSM_{h, t - t'}^{do, shift}
2443
        \quad \forall t \in [t-t_{dayLimit}..T] \\
2444
        (optional \space constraint) \\
2445
        &
2446
        (18) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{up}
2447
        \leq E_{max, up} \cdot \overline{E}_{t}^{up}
2448
        \cdot t_{shift} -
2449
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
2450
        DSM_{h, t - t'}^{up}
2451
        \quad \forall t \in [t-t_{dayLimit}..T] \\
2452
        (optional \space constraint) \\
2453
        &
2454
        (19) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
2455
        + DSM_{h, t}^{balanceDo}
2456
        + DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
2457
        + DSM_{t}^{do, shed}
2458
        \leq \max \{E_{t}^{up} \cdot E_{max, up},
2459
        E_{t}^{do} \cdot E_{max, do} \}
2460
        \quad \forall t \in \mathbb{T} \\
2461
        (optional \space constraint) \\
2462
        &
2463
2464
    *Note*: For the sake of readability, the handling of indices is not
2465
    displayed here. E.g. evaluating a variable for t-L may lead to a negative
2466
    and therefore infeasible index.
2467
    This is addressed by limiting the sums to non-negative indices within the
2468
    model index bounds. Please refer to the constraints implementation
2469
    themselves.
2470
2471
    **The following parts of the objective function are created:**
2472
2473
    .. math::
2474
        \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up} + DSM_{h, t}^{balanceDo})
2475
        \cdot cost_{t}^{dsm, up}
2476
        + \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
2477
        \cdot cost_{t}^{dsm, do, shift}
2478
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
2479
        \quad \forall t \in \mathbb{T} \\
2480
2481
    **Table: Symbols and attribute names of variables and parameters**
2482
2483
        .. csv-table:: Variables (V) and Parameters (P)
2484
            :header: "symbol", "attribute", "type", "explanation"
2485
            :widths: 1, 1, 1, 1
2486
2487
            ":math:`DSM_{h, t}^{up}` ",":attr:`~SinkDSM.dsm_up[g,h,t]`",
2488
            "V", "DSM up shift (additional load) in hour t with delay time h"
2489
            ":math:`DSM_{h, t}^{do, shift}` ",
2490
            ":attr:`~SinkDSM.dsm_do_shift[g,h, t]`",
2491
            "V", "DSM down shift (less load) in hour t with delay time h"
2492
            ":math:`DSM_{h, t}^{balanceUp}` ",
2493
            ":attr:`~SinkDSM.balance_dsm_up[g,h,t]`",
2494
            "V", "DSM down shift (less load) in hour t with delay time h
2495
            to balance previous upshift"
2496
            ":math:`DSM_{h, t}^{balanceDo}` ",
2497
            ":attr:`~SinkDSM.balance_dsm_do[g,h,t]`",
2498
            "V", "DSM up shift (additional load) in hour t with delay time h
2499
            to balance previous downshift"
2500
            ":math:`DSM_{t}^{do, shed}` ",
2501
            ":attr:`~SinkDSM.dsm_do_shed[g, t]` ",
2502
            "V","DSM shedded (capacity shedded, i.e. not compensated for)"
2503
            ":math:`\dot{E}_{t}` ",":attr:`flow[g,t]`","V","Energy
2504
            flowing in from (electrical) inflow bus"
2505
            ":math:`h`","element of :attr:`~SinkDSM.delay_time`","P",
2506
            "delay time for load shift (integer value from set of feasible
2507
            delay times per DSM portfolio)
2508
            (time until the energy balance has to be levelled out again;
2509
            roundtrip time of one load shifting cycle, i.e. time window
2510
            for upshift and compensating downshift)"
2511
            ":math:`H_{DR}`",
2512
            "`range(length(:attr:`~SinkDSM.delay_time`) + 1)`",
2513
            "P", "Set of feasible delay times for load shift of a certain
2514
            DSM portfolio
2515
            (time until the energy balance has to be levelled out again;
2516
            roundtrip time of one load shifting cycle, i.e. time window
2517
            for upshift and compensating downshift)"
2518
            ":math:`t_{shift}`",":attr:`~SinkDSM.shift_time`","P",
2519
            "Maximum time for a shift in one direction, i. e. maximum time
2520
            for an upshift or a downshift in a load shifting cycle"
2521
            ":math:`t_{she}`",":attr:`~SinkDSM.shed_time`","P",
2522
            "Maximum time for one load shedding process"
2523
            ":math:`demand_{t}`",":attr:`~SinkDSM.demand[t]`","P",
2524
            "(Electrical) demand series (normalized)"
2525
            ":math:`demand_{max}`",":attr:`~SinkDSM.max_demand`","P",
2526
            "Maximum demand value"
2527
            ":math:`E_{t}^{do}`",":attr:`~SinkDSM.capacity_down[t]`","P",
2528
            "Capacity  allowed for a load adjustment downwards (normalized)
2529
            (DSM down shift + DSM shedded)"
2530
            ":math:`E_{t}^{up}`",":attr:`~SinkDSM.capacity_up[t]`","P",
2531
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
2532
            ":math:`E_{do, max}`",":attr:`~SinkDSM.max_capacity_down`","P",
2533
            "Maximum capacity allowed for a load adjustment downwards
2534
            (DSM down shift + DSM shedded)"
2535
            ":math:`E_{up, max}`",":attr:`~SinkDSM.max_capacity_up`","P",
2536
            "Capacity allowed for a shift upwards (normalized) (DSM up shift)"
2537
            ":math:`\eta`",":attr:`~SinkDSM.efficiency`","P", "Efficiency
2538
            loss for load shifting processes"
2539
            ":math:`\mathbb{T}` "," ","P", "Set of time steps"
2540
            ":math:`T` "," ","P", "Overall amount of time steps (cardinality)"
2541
            ":math:`eligibility_{shift}` ",
2542
            ":attr:`~SinkDSM.shift_eligibility`","P",
2543
            "Boolean parameter indicating if unit can be used for
2544
            load shifting"
2545
            ":math:`eligibility_{shed}` ",
2546
            ":attr:`~SinkDSM.shed_eligibility`","P",
2547
            "Boolean parameter indicating if unit can be used for
2548
            load shedding"
2549
            ":math:`cost_{t}^{dsm, up}` ", ":attr:`~SinkDSM.cost_dsm_up[t]`",
2550
            "P", "Variable costs for an upwards shift"
2551
            ":math:`cost_{t}^{dsm, do, shift}` ",
2552
            ":attr:`~SinkDSM.cost_dsm_down_shift[t]`","P",
2553
            "Variable costs for a downwards shift (load shifting)"
2554
            ":math:`cost_{t}^{dsm, do, shed}` ",
2555
            ":attr:`~SinkDSM.cost_dsm_down_shed[t]`","P",
2556
            "Variable costs for shedding load"
2557
            ":math:`\Delta t`",":attr:`~models.Model.timeincrement`","P",
2558
            "The time increment of the model"
2559
            ":math:`n_{yearLimitshift}`",":attr:`~SinkDSM.n_yearLimitShift`",
2560
            "P", "Maximum allowed number of load shifts (at full capacity)
2561
            in the optimization timeframe"
2562
            ":math:`n_{yearLimitshed}`",":attr:`~SinkDSM.n_yearLimitShed`",
2563
            "P", "Maximum allowed number of load sheds (at full capacity)
2564
            in the optimization timeframe"
2565
            ":math:`t_{dayLimit}`",":attr:`~SinkDSM.t_dayLimit`",
2566
            "P", "Maximum duration of load shifts at full capacity per day
2567
            resp. in the last hours before the current"
2568
    """
2569
    CONSTRAINT_GROUP = True
2570
2571
    def __init__(self, *args, **kwargs):
2572
        super().__init__(*args, **kwargs)
2573
2574
    def _create(self, group=None):
2575
        if group is None:
2576
            return None
2577
2578
        m = self.parent_block()
2579
2580
        # for all DSM components get inflow from a bus
2581
        for n in group:
2582
            n.inflow = list(n.inputs)[0]
2583
2584
        #  ************* SETS *********************************
2585
2586
        # Set of DR Components
2587
        self.DR = Set(initialize=[n for n in group])
2588
2589
        # Depict different delay_times per unit via a mapping
2590
        map_DR_H = {
2591
            k: v
2592
            for k, v in zip([n for n in group], [n.delay_time for n in group])
2593
        }
2594
2595
        unique_H = list(set(itertools.chain.from_iterable(map_DR_H.values())))
2596
        self.H = Set(initialize=unique_H)
2597
2598
        self.DR_H = Set(
2599
            within=self.DR * self.H,
2600
            initialize=[(dr, h) for dr in map_DR_H for h in map_DR_H[dr]],
2601
        )
2602
2603
        #  ************* VARIABLES *****************************
2604
2605
        # Variable load shift down (capacity)
2606
        self.dsm_do_shift = Var(
2607
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2608
        )
2609
2610
        # Variable for load shedding (capacity)
2611
        self.dsm_do_shed = Var(
2612
            self.DR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2613
        )
2614
2615
        # Variable load shift up (capacity)
2616
        self.dsm_up = Var(
2617
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2618
        )
2619
2620
        # Variable balance load shift down through upwards shift (capacity)
2621
        self.balance_dsm_do = Var(
2622
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2623
        )
2624
2625
        # Variable balance load shift up through downwards shift (capacity)
2626
        self.balance_dsm_up = Var(
2627
            self.DR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2628
        )
2629
2630
        # Variable fictious DR storage level for downwards load shifts (energy)
2631
        self.dsm_do_level = Var(
2632
            self.DR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2633
        )
2634
2635
        # Variable fictious DR storage level for upwards load shifts (energy)
2636
        self.dsm_up_level = Var(
2637
            self.DR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
2638
        )
2639
2640
        #  ************* CONSTRAINTS *****************************
2641
2642 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...
2643
            """Force shifting resp. shedding variables to zero dependent
2644
            on how boolean parameters for shift resp. shed eligibility
2645
            are set.
2646
            """
2647
            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...
2648
                for g in group:
2649
                    for h in g.delay_time:
2650
2651
                        if not g.shift_eligibility:
2652
                            lhs = self.dsm_up[g, h, t]
2653
                            rhs = 0
2654
2655
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
2656
2657
                        if not g.shed_eligibility:
2658
                            lhs = self.dsm_do_shed[g, t]
2659
                            rhs = 0
2660
2661
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
2662
2663
        self.shift_shed_vars = Constraint(
2664
            group, self.H, m.TIMESTEPS, noruleinit=True
2665
        )
2666
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
2667
2668
        # Relation between inflow and effective Sink consumption
2669 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...
2670
            """Relation between input data and pyomo variables.
2671
            The actual demand after DR.
2672
            BusBlock outflow == Demand +- DR (i.e. effective Sink consumption)
2673
            """
2674
            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...
2675
                for g in group:
2676
                    # outflow from bus
2677
                    lhs = m.flow[g.inflow, g, t]
2678
2679
                    # Demand +- DR
2680
                    rhs = (
2681
                        g.demand[t] * g.max_demand
2682
                        + sum(
2683
                            self.dsm_up[g, h, t]
2684
                            + self.balance_dsm_do[g, h, t]
2685
                            - self.dsm_do_shift[g, h, t]
2686
                            - self.balance_dsm_up[g, h, t]
2687
                            for h in g.delay_time
2688
                        )
2689
                        - self.dsm_do_shed[g, t]
2690
                    )
2691
2692
                    # add constraint
2693
                    block.input_output_relation.add((g, t), (lhs == rhs))
2694
2695
        self.input_output_relation = Constraint(
2696
            group, m.TIMESTEPS, noruleinit=True
2697
        )
2698
        self.input_output_relation_build = BuildAction(
2699
            rule=_input_output_relation_rule
2700
        )
2701
2702
        # Equation 4.8
2703 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...
2704
            """Load reduction must be balanced by load increase
2705
            within delay_time
2706
            """
2707
            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...
2708
                for g in group:
2709
                    for h in g.delay_time:
2710
2711
                        if g.shift_eligibility:
2712
2713
                            # main use case
2714
                            if t >= h:
2715
                                # balance load reduction
2716
                                lhs = self.balance_dsm_do[g, h, t]
2717
2718
                                # load reduction (efficiency considered)
2719
                                rhs = (
2720
                                    self.dsm_do_shift[g, h, t - h]
2721
                                    / g.efficiency
2722
                                )
2723
2724
                                # add constraint
2725
                                block.capacity_balance_red.add(
2726
                                    (g, h, t), (lhs == rhs)
2727
                                )
2728
2729
                            # no balancing for the first timestep
2730
                            elif t == m.TIMESTEPS[1]:
2731
                                lhs = self.balance_dsm_do[g, h, t]
2732
                                rhs = 0
2733
2734
                                block.capacity_balance_red.add(
2735
                                    (g, h, t), (lhs == rhs)
2736
                                )
2737
2738
                            else:
2739
                                pass  # return(Constraint.Skip)
2740
2741
                        # if only shedding is possible, balancing variable is 0
2742
                        else:
2743
                            lhs = self.balance_dsm_do[g, h, t]
2744
                            rhs = 0
2745
2746
                            block.capacity_balance_red.add(
2747
                                (g, h, t), (lhs == rhs)
2748
                            )
2749
2750
        self.capacity_balance_red = Constraint(
2751
            group, self.H, m.TIMESTEPS, noruleinit=True
2752
        )
2753
        self.capacity_balance_red_build = BuildAction(
2754
            rule=capacity_balance_red_rule
2755
        )
2756
2757
        # Equation 4.9
2758 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...
2759
            """Load increased must be balanced by load reduction
2760
            within delay_time
2761
            """
2762
            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...
2763
                for g in group:
2764
                    for h in g.delay_time:
2765
2766
                        if g.shift_eligibility:
2767
2768
                            # main use case
2769
                            if t >= h:
2770
                                # balance load increase
2771
                                lhs = self.balance_dsm_up[g, h, t]
2772
2773
                                # load increase (efficiency considered)
2774
                                rhs = self.dsm_up[g, h, t - h] * g.efficiency
2775
2776
                                # add constraint
2777
                                block.capacity_balance_inc.add(
2778
                                    (g, h, t), (lhs == rhs)
2779
                                )
2780
2781
                            # no balancing for the first timestep
2782
                            elif t == m.TIMESTEPS[1]:
2783
                                lhs = self.balance_dsm_up[g, h, t]
2784
                                rhs = 0
2785
2786
                                block.capacity_balance_inc.add(
2787
                                    (g, h, t), (lhs == rhs)
2788
                                )
2789
2790
                            else:
2791
                                pass  # return(Constraint.Skip)
2792
2793
                        # if only shedding is possible, balancing variable is 0
2794
                        else:
2795
                            lhs = self.balance_dsm_up[g, h, t]
2796
                            rhs = 0
2797
2798
                            block.capacity_balance_inc.add(
2799
                                (g, h, t), (lhs == rhs)
2800
                            )
2801
2802
        self.capacity_balance_inc = Constraint(
2803
            group, self.H, m.TIMESTEPS, noruleinit=True
2804
        )
2805
        self.capacity_balance_inc_build = BuildAction(
2806
            rule=capacity_balance_inc_rule
2807
        )
2808
2809
        # Fix: prevent shifts which cannot be compensated
2810 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...
2811
            """Prevent downwards shifts that cannot be balanced anymore
2812
            within the optimization timeframe
2813
            """
2814
            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...
2815
                for g in group:
2816
2817
                    if g.fixes:
2818
                        for h in g.delay_time:
2819
2820
                            if t > m.TIMESTEPS[-1] - h:
2821
                                # no load reduction anymore (dsm_do_shift = 0)
2822
                                lhs = self.dsm_do_shift[g, h, t]
2823
                                rhs = 0
2824
                                block.no_comp_red.add((g, h, t), (lhs == rhs))
2825
2826
                    else:
2827
                        pass  # return(Constraint.Skip)
2828
2829
        self.no_comp_red = Constraint(
2830
            group, self.H, m.TIMESTEPS, noruleinit=True
2831
        )
2832
        self.no_comp_red_build = BuildAction(rule=no_comp_red_rule)
2833
2834
        # Fix: prevent shifts which cannot be compensated
2835 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...
2836
            """Prevent upwards shifts that cannot be balanced anymore
2837
            within the optimization timeframe
2838
            """
2839
            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...
2840
                for g in group:
2841
2842
                    if g.fixes:
2843
                        for h in g.delay_time:
2844
2845
                            if t > m.TIMESTEPS[-1] - h:
2846
                                # no load increase anymore (dsm_up = 0)
2847
                                lhs = self.dsm_up[g, h, t]
2848
                                rhs = 0
2849
                                block.no_comp_inc.add((g, h, t), (lhs == rhs))
2850
2851
                    else:
2852
                        pass  # return(Constraint.Skip)
2853
2854
        self.no_comp_inc = Constraint(
2855
            group, self.H, m.TIMESTEPS, noruleinit=True
2856
        )
2857
        self.no_comp_inc_build = BuildAction(rule=no_comp_inc_rule)
2858
2859
        # Equation 4.11
2860
        def availability_red_rule(block):
2861
            """Load reduction must be smaller than or equal to the
2862
            (time-dependent) capacity limit
2863
            """
2864
            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...
2865
                for g in group:
2866
                    # load reduction
2867
                    lhs = (
2868
                        sum(
2869
                            self.dsm_do_shift[g, h, t]
2870
                            + self.balance_dsm_up[g, h, t]
2871
                            for h in g.delay_time
2872
                        )
2873
                        + self.dsm_do_shed[g, t]
2874
                    )
2875
2876
                    # upper bound
2877
                    rhs = g.capacity_down[t] * g.max_capacity_down
2878
2879
                    # add constraint
2880
                    block.availability_red.add((g, t), (lhs <= rhs))
2881
2882
        self.availability_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
2883
        self.availability_red_build = BuildAction(rule=availability_red_rule)
2884
2885
        # Equation 4.12
2886
        def availability_inc_rule(block):
2887
            """Load increase must be smaller than or equal to the
2888
            (time-dependent) capacity limit
2889
            """
2890
            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...
2891
                for g in group:
2892
                    # load increase
2893
                    lhs = sum(
2894
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
2895
                        for h in g.delay_time
2896
                    )
2897
2898
                    # upper bound
2899
                    rhs = g.capacity_up[t] * g.max_capacity_up
2900
2901
                    # add constraint
2902
                    block.availability_inc.add((g, t), (lhs <= rhs))
2903
2904
        self.availability_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
2905
        self.availability_inc_build = BuildAction(rule=availability_inc_rule)
2906
2907
        # Equation 4.13
2908 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...
2909
            """Fictious demand response storage level for load reductions
2910
            transition equation
2911
            """
2912
            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...
2913
                for g in group:
2914
2915
                    # avoid timesteps prior to t = 0
2916
                    if t > 0:
2917
                        # reduction minus balancing of reductions
2918
                        lhs = m.timeincrement[t] * sum(
2919
                            (
2920
                                self.dsm_do_shift[g, h, t]
2921
                                - self.balance_dsm_do[g, h, t] * g.efficiency
2922
                            )
2923
                            for h in g.delay_time
2924
                        )
2925
2926
                        # load reduction storage level transition
2927
                        rhs = (
2928
                            self.dsm_do_level[g, t]
2929
                            - self.dsm_do_level[g, t - 1]
2930
                        )
2931
2932
                        # add constraint
2933
                        block.dr_storage_red.add((g, t), (lhs == rhs))
2934
2935
                    else:
2936
                        lhs = self.dsm_do_level[g, t]
2937
                        rhs = m.timeincrement[t] * sum(
2938
                            self.dsm_do_shift[g, h, t] for h in g.delay_time
2939
                        )
2940
                        block.dr_storage_red.add((g, t), (lhs == rhs))
2941
2942
        self.dr_storage_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
2943
        self.dr_storage_red_build = BuildAction(rule=dr_storage_red_rule)
2944
2945
        # Equation 4.14
2946 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...
2947
            """Fictious demand response storage level for load increase
2948
            transition equation
2949
            """
2950
            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...
2951
                for g in group:
2952
2953
                    # avoid timesteps prior to t = 0
2954
                    if t > 0:
2955
                        # increases minus balancing of reductions
2956
                        lhs = m.timeincrement[t] * sum(
2957
                            (
2958
                                self.dsm_up[g, h, t] * g.efficiency
2959
                                - self.balance_dsm_up[g, h, t]
2960
                            )
2961
                            for h in g.delay_time
2962
                        )
2963
2964
                        # load increase storage level transition
2965
                        rhs = (
2966
                            self.dsm_up_level[g, t]
2967
                            - self.dsm_up_level[g, t - 1]
2968
                        )
2969
2970
                        # add constraint
2971
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
2972
2973
                    else:
2974
                        # pass  # return(Constraint.Skip)
2975
                        lhs = self.dsm_up_level[g, t]
2976
                        rhs = m.timeincrement[t] * sum(
2977
                            self.dsm_up[g, h, t] for h in g.delay_time
2978
                        )
2979
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
2980
2981
        self.dr_storage_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
2982
        self.dr_storage_inc_build = BuildAction(rule=dr_storage_inc_rule)
2983
2984
        # Equation 4.15
2985
        def dr_storage_limit_red_rule(block):
2986
            """
2987
            Fictious demand response storage level for load reduction limit
2988
            """
2989
            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...
2990
                for g in group:
2991
2992
                    if g.shift_eligibility:
2993
                        # fictious demand response load reduction storage level
2994
                        lhs = self.dsm_do_level[g, t]
2995
2996
                        # maximum (time-dependent) available shifting capacity
2997
                        rhs = (
2998
                            g.capacity_down_mean
2999
                            * g.max_capacity_down
3000
                            * g.shift_time
3001
                        )
3002
3003
                        # add constraint
3004
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3005
3006
                    else:
3007
                        lhs = self.dsm_do_level[g, t]
3008
                        # Force storage level and thus dsm_do_shift to 0
3009
                        rhs = 0
3010
3011
                        # add constraint
3012
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3013
3014
        self.dr_storage_limit_red = Constraint(
3015
            group, m.TIMESTEPS, noruleinit=True
3016
        )
3017
        self.dr_storage_level_red_build = BuildAction(
3018
            rule=dr_storage_limit_red_rule
3019
        )
3020
3021
        # Equation 4.16
3022
        def dr_storage_limit_inc_rule(block):
3023
            """
3024
            Fictious demand response storage level for load increase limit
3025
            """
3026
            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...
3027
                for g in group:
3028
                    # fictious demand response load reduction storage level
3029
                    lhs = self.dsm_up_level[g, t]
3030
3031
                    # maximum (time-dependent) available shifting capacity
3032
                    rhs = g.capacity_up_mean * g.max_capacity_up * g.shift_time
3033
3034
                    # add constraint
3035
                    block.dr_storage_limit_inc.add((g, t), (lhs <= rhs))
3036
3037
        self.dr_storage_limit_inc = Constraint(
3038
            group, m.TIMESTEPS, noruleinit=True
3039
        )
3040
        self.dr_storage_level_inc_build = BuildAction(
3041
            rule=dr_storage_limit_inc_rule
3042
        )
3043
3044
        # Equation 4.17' -> load shedding
3045
        def dr_yearly_limit_shed_rule(block):
3046
            """Introduce overall annual (energy) limit for load shedding resp.
3047
            overall limit for optimization timeframe considered
3048
            A year limit in contrast to Gils (2015) is defined a mandatory
3049
            parameter here in order to achieve an approach comparable
3050
            to the others.
3051
            """
3052
            for g in group:
3053
3054
                if g.shed_eligibility:
3055
                    # sum of all load reductions
3056
                    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...
3057
3058
                    # year limit
3059
                    rhs = (
3060
                        g.capacity_down_mean
3061
                        * g.max_capacity_down
3062
                        * g.shed_time
3063
                        * g.n_yearLimit_shed
3064
                    )
3065
3066
                    # add constraint
3067
                    block.dr_yearly_limit_shed.add(g, (lhs <= rhs))
3068
3069
                else:
3070
                    pass  # return(Constraint.Skip)
3071
3072
        self.dr_yearly_limit_shed = Constraint(group, noruleinit=True)
3073
        self.dr_yearly_limit_shed_build = BuildAction(
3074
            rule=dr_yearly_limit_shed_rule
3075
        )
3076
3077
        # ************* Optional Constraints *****************************
3078
3079
        # Equation 4.17
3080
        def dr_yearly_limit_red_rule(block):
3081
            """Introduce overall annual (energy) limit for load reductions
3082
            resp. overall limit for optimization timeframe considered
3083
            """
3084
            for g in group:
3085
3086
                if g.ActivateYearLimit:
3087
                    # sum of all load reductions
3088
                    lhs = sum(
3089
                        sum(self.dsm_do_shift[g, h, t] for h in g.delay_time)
3090
                        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...
3091
                    )
3092
3093
                    # year limit
3094
                    rhs = (
3095
                        g.capacity_down_mean
3096
                        * g.max_capacity_down
3097
                        * g.shift_time
3098
                        * g.n_yearLimit_shift
3099
                    )
3100
3101
                    # add constraint
3102
                    block.dr_yearly_limit_red.add(g, (lhs <= rhs))
3103
3104
                else:
3105
                    pass  # return(Constraint.Skip)
3106
3107
        self.dr_yearly_limit_red = Constraint(group, noruleinit=True)
3108
        self.dr_yearly_limit_red_build = BuildAction(
3109
            rule=dr_yearly_limit_red_rule
3110
        )
3111
3112
        # Equation 4.18
3113
        def dr_yearly_limit_inc_rule(block):
3114
            """Introduce overall annual (energy) limit for load increases
3115
            resp. overall limit for optimization timeframe considered
3116
            """
3117
            for g in group:
3118
3119
                if g.ActivateYearLimit:
3120
                    # sum of all load increases
3121
                    lhs = sum(
3122
                        sum(self.dsm_up[g, h, t] for h in g.delay_time)
3123
                        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...
3124
                    )
3125
3126
                    # year limit
3127
                    rhs = (
3128
                        g.capacity_up_mean
3129
                        * g.max_capacity_up
3130
                        * g.shift_time
3131
                        * g.n_yearLimit_shift
3132
                    )
3133
3134
                    # add constraint
3135
                    block.dr_yearly_limit_inc.add(g, (lhs <= rhs))
3136
3137
                else:
3138
                    pass  # return(Constraint.Skip)
3139
3140
        self.dr_yearly_limit_inc = Constraint(group, noruleinit=True)
3141
        self.dr_yearly_limit_inc_build = BuildAction(
3142
            rule=dr_yearly_limit_inc_rule
3143
        )
3144
3145
        # Equation 4.19
3146 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...
3147
            """ "Introduce rolling (energy) limit for load reductions
3148
            This effectively limits DR utilization dependent on
3149
            activations within previous hours.
3150
            """
3151
            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...
3152
                for g in group:
3153
3154
                    if g.ActivateDayLimit:
3155
                        # main use case
3156
                        if t >= g.t_dayLimit:
3157
3158
                            # load reduction
3159
                            lhs = sum(
3160
                                self.dsm_do_shift[g, h, t]
3161
                                for h in g.delay_time
3162
                            )
3163
3164
                            # daily limit
3165
                            rhs = (
3166
                                g.capacity_down_mean
3167
                                * g.max_capacity_down
3168
                                * g.shift_time
3169
                                - sum(
3170
                                    sum(
3171
                                        self.dsm_do_shift[g, h, t - t_dash]
3172
                                        for h in g.delay_time
3173
                                    )
3174
                                    for t_dash in range(
3175
                                        1, int(g.t_dayLimit) + 1
3176
                                    )
3177
                                )
3178
                            )
3179
3180
                            # add constraint
3181
                            block.dr_daily_limit_red.add((g, t), (lhs <= rhs))
3182
3183
                        else:
3184
                            pass  # return(Constraint.Skip)
3185
3186
                    else:
3187
                        pass  # return(Constraint.Skip)
3188
3189
        self.dr_daily_limit_red = Constraint(
3190
            group, m.TIMESTEPS, noruleinit=True
3191
        )
3192
        self.dr_daily_limit_red_build = BuildAction(
3193
            rule=dr_daily_limit_red_rule
3194
        )
3195
3196
        # Equation 4.20
3197 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...
3198
            """Introduce rolling (energy) limit for load increases
3199
            This effectively limits DR utilization dependent on
3200
            activations within previous hours.
3201
            """
3202
            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...
3203
                for g in group:
3204
3205
                    if g.ActivateDayLimit:
3206
                        # main use case
3207
                        if t >= g.t_dayLimit:
3208
3209
                            # load increase
3210
                            lhs = sum(
3211
                                self.dsm_up[g, h, t] for h in g.delay_time
3212
                            )
3213
3214
                            # daily limit
3215
                            rhs = (
3216
                                g.capacity_up_mean
3217
                                * g.max_capacity_up
3218
                                * g.shift_time
3219
                                - sum(
3220
                                    sum(
3221
                                        self.dsm_up[g, h, t - t_dash]
3222
                                        for h in g.delay_time
3223
                                    )
3224
                                    for t_dash in range(
3225
                                        1, int(g.t_dayLimit) + 1
3226
                                    )
3227
                                )
3228
                            )
3229
3230
                            # add constraint
3231
                            block.dr_daily_limit_inc.add((g, t), (lhs <= rhs))
3232
3233
                        else:
3234
                            pass  # return(Constraint.Skip)
3235
3236
                    else:
3237
                        pass  # return(Constraint.Skip)
3238
3239
        self.dr_daily_limit_inc = Constraint(
3240
            group, m.TIMESTEPS, noruleinit=True
3241
        )
3242
        self.dr_daily_limit_inc_build = BuildAction(
3243
            rule=dr_daily_limit_inc_rule
3244
        )
3245
3246
        # Addition: avoid simultaneous activations
3247 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...
3248
            """Similar to equation 10 from Zerrahn and Schill (2015):
3249
            The sum of upwards and downwards shifts may not be greater
3250
            than the (bigger) capacity limit.
3251
            """
3252
            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...
3253
                for g in group:
3254
3255
                    if g.addition:
3256
                        # sum of load increases and reductions
3257
                        lhs = (
3258
                            sum(
3259
                                self.dsm_up[g, h, t]
3260
                                + self.balance_dsm_do[g, h, t]
3261
                                + self.dsm_do_shift[g, h, t]
3262
                                + self.balance_dsm_up[g, h, t]
3263
                                for h in g.delay_time
3264
                            )
3265
                            + self.dsm_do_shed[g, t]
3266
                        )
3267
3268
                        # maximum capacity eligibly for load shifting
3269
                        rhs = max(
3270
                            g.capacity_down[t] * g.max_capacity_down,
3271
                            g.capacity_up[t] * g.max_capacity_up,
3272
                        )
3273
3274
                        # add constraint
3275
                        block.dr_logical_constraint.add((g, t), (lhs <= rhs))
3276
3277
                    else:
3278
                        pass  # return(Constraint.Skip)
3279
3280
        self.dr_logical_constraint = Constraint(
3281
            group, m.TIMESTEPS, noruleinit=True
3282
        )
3283
        self.dr_logical_constraint_build = BuildAction(
3284
            rule=dr_logical_constraint_rule
3285
        )
3286
3287
    # Equation 4.23
3288
    def _objective_expression(self):
3289
        r"""Objective expression with variable costs for DSM activity;
3290
        Equation 4.23 from Gils (2015)
3291
        """
3292
        m = self.parent_block()
3293
3294
        dr_cost = 0
3295
3296
        for t in m.TIMESTEPS:
3297
            for g in self.DR:
3298
                dr_cost += (
3299
                    sum(
3300
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
3301
                        for h in g.delay_time
3302
                    )
3303
                    * g.cost_dsm_up[t]
3304
                    * m.objective_weighting[t]
3305
                )
3306
                dr_cost += (
3307
                    sum(
3308
                        self.dsm_do_shift[g, h, t]
3309
                        + self.balance_dsm_up[g, h, t]
3310
                        for h in g.delay_time
3311
                    )
3312
                    * g.cost_dsm_down_shift[t]
3313
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
3314
                ) * m.objective_weighting[t]
3315
3316
        self.cost = Expression(expr=dr_cost)
3317
3318
        return self.cost
3319
3320
3321
class SinkDSMDLRInvestmentBlock(SinkDSMDLRBlock):
3322
    r"""Constraints for SinkDSM with "DLR" approach and :attr:`investment`
3323
3324
    **The following constraints are created for approach = 'DLR' with an
3325
    investment object defined:**
3326
3327
    .. _SinkDSMDLR equations:
3328
3329
    .. math::
3330
        &
3331
        (1) \quad invest_{min} \leq invest \leq invest_{max} \\
3332
        &
3333
        (2) \quad DSM_{h, t}^{up} = 0 \quad \forall h \in H_{DR}
3334
        \forall t \in \mathbb{T}
3335
        \quad if \space eligibility_{shift} = False \\
3336
        &
3337
        (3) \quad DSM_{t}^{do, shed} = 0 \quad \forall t \in \mathbb{T}
3338
        \quad if \space eligibility_{shed} = False \\
3339
        &
3340
        (4) \quad \dot{E}_{t} = demand_{t} \cdot (invest + E_{exist})
3341
        + \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
3342
        + DSM_{h, t}^{balanceDo} - DSM_{h, t}^{do, shift}
3343
        - DSM_{h, t}^{balanceUp}) - DSM_{t}^{do, shed}
3344
        \quad \forall t \in \mathbb{T} \\
3345
        &
3346
        (5) \quad DSM_{h, t}^{balanceDo} =
3347
        \frac{DSM_{h, t - h}^{do, shift}}{\eta}
3348
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
3349
        &
3350
        (6) \quad DSM_{h, t}^{balanceUp} =
3351
        DSM_{h, t-h}^{up} \cdot \eta
3352
        \quad \forall h \in H_{DR} \forall t \in [h..T] \\
3353
        &
3354
        (7) \quad DSM_{h, t}^{do, shift} = 0
3355
        \quad \forall h \in H_{DR}
3356
        \forall t \in [T - h..T] \\
3357
        &
3358
        (8) \quad DSM_{h, t}^{up} = 0
3359
        \quad \forall h \in H_{DR}
3360
        \forall t \in [T - h..T] \\
3361
        &
3362
        (9) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift}
3363
        + DSM_{h, t}^{balanceUp}) + DSM_{t}^{do, shed}
3364
        \leq E_{t}^{do} \cdot (invest + E_{exist})
3365
        \cdot s_{flex, do}
3366
        \quad \forall t \in \mathbb{T} \\
3367
        &
3368
        (10) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
3369
        + DSM_{h, t}^{balanceDo})
3370
        \leq E_{t}^{up} \cdot (invest + E_{exist})
3371
        \cdot s_{flex, up}
3372
        \quad \forall t \in \mathbb{T} \\
3373
        &
3374
        (11) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
3375
        (DSM_{h, t}^{do, shift} - DSM_{h, t}^{balanceDo} \cdot \eta)
3376
        = W_{t}^{levelDo} - W_{t-1}^{levelDo}
3377
        \quad \forall t \in [1..T] \\
3378
        &
3379
        (12) \quad \Delta t \cdot \displaystyle\sum_{h=1}^{H_{DR}}
3380
        (DSM_{h, t}^{up} \cdot \eta - DSM_{h, t}^{balanceUp})
3381
        = W_{t}^{levelUp} - W_{t-1}^{levelUp}
3382
        \quad \forall t \in [1..T] \\
3383
        &
3384
        (13) \quad W_{t}^{levelDo} \leq \overline{E}_{t}^{do}
3385
        \cdot (invest + E_{exist})
3386
        \cdot s_{flex, do} \cdot t_{shift}
3387
        \quad \forall t \in \mathbb{T} \\
3388
        &
3389
        (14) \quad W_{t}^{levelUp} \leq \overline{E}_{t}^{up}
3390
        \cdot (invest + E_{exist})
3391
        \cdot s_{flex, up} \cdot t_{shift}
3392
        \quad \forall t \in \mathbb{T} \\
3393
        &
3394
        (15) \quad \displaystyle\sum_{t=0}^{T} DSM_{t}^{do, shed}
3395
        \leq (invest + E_{exist})
3396
        \cdot s_{flex, do} \cdot \overline{E}_{t}^{do}
3397
        \cdot t_{shed}
3398
        \cdot n^{yearLimitShed} \\
3399
        &
3400
        (16) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
3401
        DSM_{h, t}^{do, shift}
3402
        \leq (invest + E_{exist})
3403
        \cdot s_{flex, do} \cdot \overline{E}_{t}^{do}
3404
        \cdot t_{shift}
3405
        \cdot n^{yearLimitShift} \\
3406
        (optional \space constraint) \\
3407
        &
3408
        (17) \quad \displaystyle\sum_{t=0}^{T} \sum_{h=1}^{H_{DR}}
3409
        DSM_{h, t}^{up}
3410
        \leq (invest + E_{exist})
3411
        \cdot s_{flex, up} \cdot \overline{E}_{t}^{up}
3412
        \cdot t_{shift}
3413
        \cdot n^{yearLimitShift} \\
3414
        (optional \space constraint) \\
3415
        &
3416
        (18) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{do, shift}
3417
        \leq (invest + E_{exist})
3418
        \cdot s_{flex, do} \cdot \overline{E}_{t}^{do}
3419
        \cdot t_{shift} -
3420
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
3421
        DSM_{h, t - t'}^{do, shift}
3422
        \quad \forall t \in [t-t_{dayLimit}..T] \\
3423
        (optional \space constraint) \\
3424
        &
3425
        (19) \quad \displaystyle\sum_{h=1}^{H_{DR}} DSM_{h, t}^{up}
3426
        \leq (invest + E_{exist})
3427
        \cdot s_{flex, up} \cdot \overline{E}_{t}^{up}
3428
        \cdot t_{shift} -
3429
        \displaystyle\sum_{t'=1}^{t_{dayLimit}} \sum_{h=1}^{H_{DR}}
3430
        DSM_{h, t - t'}^{up}
3431
        \quad \forall t \in [t-t_{dayLimit}..T] \\
3432
        (optional \space constraint) \\
3433
        &
3434
        (20) \quad \displaystyle\sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up}
3435
        + DSM_{h, t}^{balanceDo}
3436
        + DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
3437
        + DSM_{t}^{shed}
3438
        \leq \max \{E_{t}^{up} \cdot s_{flex, up},
3439
        E_{t}^{do} \cdot s_{flex, do} \} \cdot (invest + E_{exist})
3440
        \quad \forall t \in \mathbb{T} \\
3441
        (optional \space constraint) \\
3442
        &
3443
3444
    *Note*: For the sake of readability, the handling of indices is not
3445
    displayed here. E.g. evaluating a variable for t-L may lead to a negative
3446
    and therefore infeasible index.
3447
    This is addressed by limiting the sums to non-negative indices within the
3448
    model index bounds. Please refer to the constraints implementation
3449
    themselves.
3450
3451
    **The following parts of the objective function are created:**
3452
3453
    * Investment annuity:
3454
3455
    .. math::
3456
        invest \cdot costs_{invest} \\
3457
3458
    * Variable costs:
3459
3460
    .. math::
3461
        \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{up} + DSM_{h, t}^{balanceDo})
3462
        \cdot cost_{t}^{dsm, up}
3463
        + \sum_{h=1}^{H_{DR}} (DSM_{h, t}^{do, shift} + DSM_{h, t}^{balanceUp})
3464
        \cdot cost_{t}^{dsm, do, shift}
3465
        + DSM_{t}^{do, shed} \cdot cost_{t}^{dsm, do, shed}
3466
        \quad \forall t \in \mathbb{T} \\
3467
3468
    **Table: Symbols and attribute names of variables and parameters**
3469
3470
    Please refer to
3471
    :class:`oemof.solph.components.experimental._sink_dsm.SinkDSMDLRBlock`.
3472
3473
    The following variables and parameters are exclusively used for
3474
    investment modeling:
3475
3476
        .. csv-table:: Variables (V) and Parameters (P)
3477
            :header: "symbol", "attribute", "type", "explanation"
3478
            :widths: 1, 1, 1, 1
3479
3480
            ":math:`invest` ",":attr:`~SinkDSM.invest` ","V", "DSM capacity
3481
            invested in. Equals to the additionally installed capacity.
3482
            The capacity share eligible for a shift is determined
3483
            by flex share(s)."
3484
            ":math:`invest_{min}` ", ":attr:`~SinkDSM.investment.minimum` ",
3485
            "P", "minimum investment"
3486
            ":math:`invest_{max}` ", ":attr:`~SinkDSM.investment.maximum` ",
3487
            "P", "maximum investment"
3488
            ":math:`E_{exist}` ",":attr:`~SinkDSM.investment.existing` ",
3489
            "P", "existing DSM capacity"
3490
            ":math:`s_{flex, up}` ",":attr:`~SinkDSM.flex_share_up` ",
3491
            "P","Share of invested capacity that may be shift upwards
3492
            at maximum"
3493
            ":math:`s_{flex, do}` ",":attr:`~SinkDSM.flex_share_do` ",
3494
            "P", "Share of invested capacity that may be shift downwards
3495
            at maximum"
3496
            ":math:`costs_{invest}` ",":attr:`~SinkDSM.investment.ep_costs` ",
3497
            "P", "specific investment annuity"
3498
    """
3499
    CONSTRAINT_GROUP = True
3500
3501
    def __init__(self, *args, **kwargs):
3502
        super().__init__(*args, **kwargs)
3503
3504
    def _create(self, group=None):
3505
3506
        if group is None:
3507
            return None
3508
3509
        m = self.parent_block()
3510
3511
        # for all DSM components get inflow from a bus
3512
        for n in group:
3513
            n.inflow = list(n.inputs)[0]
3514
3515
        #  ************* SETS *********************************
3516
3517
        self.INVESTDR = Set(initialize=[n for n in group])
3518
3519
        # Depict different delay_times per unit via a mapping
3520
        map_INVESTDR_H = {
3521
            k: v
3522
            for k, v in zip([n for n in group], [n.delay_time for n in group])
3523
        }
3524
3525
        unique_H = list(
3526
            set(itertools.chain.from_iterable(map_INVESTDR_H.values()))
3527
        )
3528
        self.H = Set(initialize=unique_H)
3529
3530
        self.INVESTDR_H = Set(
3531
            within=self.INVESTDR * self.H,
3532
            initialize=[
3533
                (dr, h) for dr in map_INVESTDR_H for h in map_INVESTDR_H[dr]
3534
            ],
3535
        )
3536
3537
        #  ************* VARIABLES *****************************
3538
3539
        # Define bounds for investments in demand response
3540
        def _dr_investvar_bound_rule(block, g):
3541
            """Rule definition to bound the
3542
            invested demand response capacity `invest`.
3543
            """
3544
            return g.investment.minimum, g.investment.maximum
3545
3546
        # Investment in DR capacity
3547
        self.invest = Var(
3548
            self.INVESTDR,
3549
            within=NonNegativeReals,
3550
            bounds=_dr_investvar_bound_rule,
3551
        )
3552
3553
        # Variable load shift down (capacity)
3554
        self.dsm_do_shift = Var(
3555
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3556
        )
3557
3558
        # Variable for load shedding (capacity)
3559
        self.dsm_do_shed = Var(
3560
            self.INVESTDR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3561
        )
3562
3563
        # Variable load shift up (capacity)
3564
        self.dsm_up = Var(
3565
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3566
        )
3567
3568
        # Variable balance load shift down through upwards shift (capacity)
3569
        self.balance_dsm_do = Var(
3570
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3571
        )
3572
3573
        # Variable balance load shift up through downwards shift (capacity)
3574
        self.balance_dsm_up = Var(
3575
            self.INVESTDR_H, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3576
        )
3577
3578
        # Variable fictious DR storage level for downwards load shifts (energy)
3579
        self.dsm_do_level = Var(
3580
            self.INVESTDR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3581
        )
3582
3583
        # Variable fictious DR storage level for upwards load shifts (energy)
3584
        self.dsm_up_level = Var(
3585
            self.INVESTDR, m.TIMESTEPS, initialize=0, within=NonNegativeReals
3586
        )
3587
3588
        #  ************* CONSTRAINTS *****************************
3589
3590 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...
3591
            """Force shifting resp. shedding variables to zero dependent
3592
            on how boolean parameters for shift resp. shed eligibility
3593
            are set.
3594
            """
3595
            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...
3596
                for g in group:
3597
                    for h in g.delay_time:
3598
3599
                        if not g.shift_eligibility:
3600
                            lhs = self.dsm_up[g, h, t]
3601
                            rhs = 0
3602
3603
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
3604
3605
                        if not g.shed_eligibility:
3606
                            lhs = self.dsm_do_shed[g, t]
3607
                            rhs = 0
3608
3609
                            block.shift_shed_vars.add((g, h, t), (lhs == rhs))
3610
3611
        self.shift_shed_vars = Constraint(
3612
            group, self.H, m.TIMESTEPS, noruleinit=True
3613
        )
3614
        self.shift_shed_vars_build = BuildAction(rule=_shift_shed_vars_rule)
3615
3616
        # Relation between inflow and effective Sink consumption
3617 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...
3618
            """Relation between input data and pyomo variables.
3619
            The actual demand after DR.
3620
            BusBlock outflow == Demand +- DR (i.e. effective Sink consumption)
3621
            """
3622
            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...
3623
3624
                for g in group:
3625
                    # outflow from bus
3626
                    lhs = m.flow[g.inflow, g, t]
3627
3628
                    # Demand +- DR
3629
                    rhs = (
3630
                        g.demand[t] * (self.invest[g] + g.investment.existing)
3631
                        + sum(
3632
                            self.dsm_up[g, h, t]
3633
                            + self.balance_dsm_do[g, h, t]
3634
                            - self.dsm_do_shift[g, h, t]
3635
                            - self.balance_dsm_up[g, h, t]
3636
                            for h in g.delay_time
3637
                        )
3638
                        - self.dsm_do_shed[g, t]
3639
                    )
3640
3641
                    # add constraint
3642
                    block.input_output_relation.add((g, t), (lhs == rhs))
3643
3644
        self.input_output_relation = Constraint(
3645
            group, m.TIMESTEPS, noruleinit=True
3646
        )
3647
        self.input_output_relation_build = BuildAction(
3648
            rule=_input_output_relation_rule
3649
        )
3650
3651
        # Equation 4.8
3652 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...
3653
            """Load reduction must be balanced by load increase
3654
            within delay_time
3655
            """
3656
            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...
3657
                for g in group:
3658
                    for h in g.delay_time:
3659
3660
                        if g.shift_eligibility:
3661
3662
                            # main use case
3663
                            if t >= h:
3664
                                # balance load reduction
3665
                                lhs = self.balance_dsm_do[g, h, t]
3666
3667
                                # load reduction (efficiency considered)
3668
                                rhs = (
3669
                                    self.dsm_do_shift[g, h, t - h]
3670
                                    / g.efficiency
3671
                                )
3672
3673
                                # add constraint
3674
                                block.capacity_balance_red.add(
3675
                                    (g, h, t), (lhs == rhs)
3676
                                )
3677
3678
                            # no balancing for the first timestep
3679
                            elif t == m.TIMESTEPS[1]:
3680
                                lhs = self.balance_dsm_do[g, h, t]
3681
                                rhs = 0
3682
3683
                                block.capacity_balance_red.add(
3684
                                    (g, h, t), (lhs == rhs)
3685
                                )
3686
3687
                            else:
3688
                                pass  # return(Constraint.Skip)
3689
3690
                        # if only shedding is possible, balancing variable is 0
3691
                        else:
3692
                            lhs = self.balance_dsm_do[g, h, t]
3693
                            rhs = 0
3694
3695
                            block.capacity_balance_red.add(
3696
                                (g, h, t), (lhs == rhs)
3697
                            )
3698
3699
        self.capacity_balance_red = Constraint(
3700
            group, self.H, m.TIMESTEPS, noruleinit=True
3701
        )
3702
        self.capacity_balance_red_build = BuildAction(
3703
            rule=capacity_balance_red_rule
3704
        )
3705
3706
        # Equation 4.9
3707 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...
3708
            """Load increased must be balanced by load reduction
3709
            within delay_time
3710
            """
3711
            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...
3712
                for g in group:
3713
                    for h in g.delay_time:
3714
3715
                        if g.shift_eligibility:
3716
3717
                            # main use case
3718
                            if t >= h:
3719
                                # balance load increase
3720
                                lhs = self.balance_dsm_up[g, h, t]
3721
3722
                                # load increase (efficiency considered)
3723
                                rhs = self.dsm_up[g, h, t - h] * g.efficiency
3724
3725
                                # add constraint
3726
                                block.capacity_balance_inc.add(
3727
                                    (g, h, t), (lhs == rhs)
3728
                                )
3729
3730
                            # no balancing for the first timestep
3731
                            elif t == m.TIMESTEPS[1]:
3732
                                lhs = self.balance_dsm_up[g, h, t]
3733
                                rhs = 0
3734
3735
                                block.capacity_balance_inc.add(
3736
                                    (g, h, t), (lhs == rhs)
3737
                                )
3738
3739
                            else:
3740
                                pass  # return(Constraint.Skip)
3741
3742
                        # if only shedding is possible, balancing variable is 0
3743
                        else:
3744
                            lhs = self.balance_dsm_up[g, h, t]
3745
                            rhs = 0
3746
3747
                            block.capacity_balance_inc.add(
3748
                                (g, h, t), (lhs == rhs)
3749
                            )
3750
3751
        self.capacity_balance_inc = Constraint(
3752
            group, self.H, m.TIMESTEPS, noruleinit=True
3753
        )
3754
        self.capacity_balance_inc_build = BuildAction(
3755
            rule=capacity_balance_inc_rule
3756
        )
3757
3758
        # Own addition: prevent shifts which cannot be compensated
3759 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...
3760
            """Prevent downwards shifts that cannot be balanced anymore
3761
            within the optimization timeframe
3762
            """
3763
            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...
3764
                for g in group:
3765
3766
                    if g.fixes:
3767
                        for h in g.delay_time:
3768
3769
                            if t > m.TIMESTEPS[-1] - h:
3770
                                # no load reduction anymore (dsm_do_shift = 0)
3771
                                lhs = self.dsm_do_shift[g, h, t]
3772
                                rhs = 0
3773
                                block.no_comp_red.add((g, h, t), (lhs == rhs))
3774
3775
                    else:
3776
                        pass  # return(Constraint.Skip)
3777
3778
        self.no_comp_red = Constraint(
3779
            group, self.H, m.TIMESTEPS, noruleinit=True
3780
        )
3781
        self.no_comp_red_build = BuildAction(rule=no_comp_red_rule)
3782
3783
        # Own addition: prevent shifts which cannot be compensated
3784 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...
3785
            """Prevent upwards shifts that cannot be balanced anymore
3786
            within the optimization timeframe
3787
            """
3788
            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...
3789
                for g in group:
3790
3791
                    if g.fixes:
3792
                        for h in g.delay_time:
3793
3794
                            if t > m.TIMESTEPS[-1] - h:
3795
                                # no load increase anymore (dsm_up = 0)
3796
                                lhs = self.dsm_up[g, h, t]
3797
                                rhs = 0
3798
                                block.no_comp_inc.add((g, h, t), (lhs == rhs))
3799
3800
                    else:
3801
                        pass  # return(Constraint.Skip)
3802
3803
        self.no_comp_inc = Constraint(
3804
            group, self.H, m.TIMESTEPS, noruleinit=True
3805
        )
3806
        self.no_comp_inc_build = BuildAction(rule=no_comp_inc_rule)
3807
3808
        # Equation 4.11
3809
        def availability_red_rule(block):
3810
            """Load reduction must be smaller than or equal to the
3811
            (time-dependent) capacity limit
3812
            """
3813
            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...
3814
                for g in group:
3815
                    # load reduction
3816
                    lhs = (
3817
                        sum(
3818
                            self.dsm_do_shift[g, h, t]
3819
                            + self.balance_dsm_up[g, h, t]
3820
                            for h in g.delay_time
3821
                        )
3822
                        + self.dsm_do_shed[g, t]
3823
                    )
3824
3825
                    # upper bound
3826
                    rhs = (
3827
                        g.capacity_down[t]
3828
                        * (self.invest[g] + g.investment.existing)
3829
                        * g.flex_share_down
3830
                    )
3831
3832
                    # add constraint
3833
                    block.availability_red.add((g, t), (lhs <= rhs))
3834
3835
        self.availability_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
3836
        self.availability_red_build = BuildAction(rule=availability_red_rule)
3837
3838
        # Equation 4.12
3839
        def availability_inc_rule(block):
3840
            """Load increase must be smaller than or equal to the
3841
            (time-dependent) capacity limit
3842
            """
3843
            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...
3844
                for g in group:
3845
                    # load increase
3846
                    lhs = sum(
3847
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
3848
                        for h in g.delay_time
3849
                    )
3850
3851
                    # upper bound
3852
                    rhs = (
3853
                        g.capacity_up[t]
3854
                        * (self.invest[g] + g.investment.existing)
3855
                        * g.flex_share_up
3856
                    )
3857
3858
                    # add constraint
3859
                    block.availability_inc.add((g, t), (lhs <= rhs))
3860
3861
        self.availability_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
3862
        self.availability_inc_build = BuildAction(rule=availability_inc_rule)
3863
3864
        # Equation 4.13
3865 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...
3866
            """Fictious demand response storage level for load reductions
3867
            transition equation
3868
            """
3869
            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...
3870
                for g in group:
3871
3872
                    # avoid timesteps prior to t = 0
3873
                    if t > 0:
3874
                        # reduction minus balancing of reductions
3875
                        lhs = m.timeincrement[t] * sum(
3876
                            (
3877
                                self.dsm_do_shift[g, h, t]
3878
                                - self.balance_dsm_do[g, h, t] * g.efficiency
3879
                            )
3880
                            for h in g.delay_time
3881
                        )
3882
3883
                        # load reduction storage level transition
3884
                        rhs = (
3885
                            self.dsm_do_level[g, t]
3886
                            - self.dsm_do_level[g, t - 1]
3887
                        )
3888
3889
                        # add constraint
3890
                        block.dr_storage_red.add((g, t), (lhs == rhs))
3891
3892
                    else:
3893
                        # pass  # return(Constraint.Skip)
3894
                        lhs = self.dsm_do_level[g, t]
3895
                        rhs = m.timeincrement[t] * sum(
3896
                            self.dsm_do_shift[g, h, t] for h in g.delay_time
3897
                        )
3898
                        block.dr_storage_red.add((g, t), (lhs == rhs))
3899
3900
        self.dr_storage_red = Constraint(group, m.TIMESTEPS, noruleinit=True)
3901
        self.dr_storage_red_build = BuildAction(rule=dr_storage_red_rule)
3902
3903
        # Equation 4.14
3904 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...
3905
            """Fictious demand response storage level for load increase
3906
            transition equation
3907
            """
3908
            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...
3909
                for g in group:
3910
3911
                    # avoid timesteps prior to t = 0
3912
                    if t > 0:
3913
                        # increases minus balancing of reductions
3914
                        lhs = m.timeincrement[t] * sum(
3915
                            (
3916
                                self.dsm_up[g, h, t] * g.efficiency
3917
                                - self.balance_dsm_up[g, h, t]
3918
                            )
3919
                            for h in g.delay_time
3920
                        )
3921
3922
                        # load increase storage level transition
3923
                        rhs = (
3924
                            self.dsm_up_level[g, t]
3925
                            - self.dsm_up_level[g, t - 1]
3926
                        )
3927
3928
                        # add constraint
3929
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
3930
3931
                    else:
3932
                        # pass  # return(Constraint.Skip)
3933
                        lhs = self.dsm_up_level[g, t]
3934
                        rhs = m.timeincrement[t] * sum(
3935
                            self.dsm_up[g, h, t] for h in g.delay_time
3936
                        )
3937
                        block.dr_storage_inc.add((g, t), (lhs == rhs))
3938
3939
        self.dr_storage_inc = Constraint(group, m.TIMESTEPS, noruleinit=True)
3940
        self.dr_storage_inc_build = BuildAction(rule=dr_storage_inc_rule)
3941
3942
        # Equation 4.15
3943
        def dr_storage_limit_red_rule(block):
3944
            """
3945
            Fictious demand response storage level for load reduction limit
3946
            """
3947
            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...
3948
                for g in group:
3949
3950
                    if g.shift_eligibility:
3951
                        # fictious demand response load reduction storage level
3952
                        lhs = self.dsm_do_level[g, t]
3953
3954
                        # maximum (time-dependent) available shifting capacity
3955
                        rhs = (
3956
                            g.capacity_down_mean
3957
                            * (self.invest[g] + g.investment.existing)
3958
                            * g.flex_share_down
3959
                            * g.shift_time
3960
                        )
3961
3962
                        # add constraint
3963
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3964
3965
                    else:
3966
                        lhs = self.dsm_do_level[g, t]
3967
                        # Force storage level and thus dsm_do_shift to 0
3968
                        rhs = 0
3969
3970
                        # add constraint
3971
                        block.dr_storage_limit_red.add((g, t), (lhs <= rhs))
3972
3973
        self.dr_storage_limit_red = Constraint(
3974
            group, m.TIMESTEPS, noruleinit=True
3975
        )
3976
        self.dr_storage_level_red_build = BuildAction(
3977
            rule=dr_storage_limit_red_rule
3978
        )
3979
3980
        # Equation 4.16
3981
        def dr_storage_limit_inc_rule(block):
3982
            """
3983
            Fictious demand response storage level for load increase limit
3984
            """
3985
            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...
3986
                for g in group:
3987
                    # fictious demand response load reduction storage level
3988
                    lhs = self.dsm_up_level[g, t]
3989
3990
                    # maximum (time-dependent) available shifting capacity
3991
                    rhs = (
3992
                        g.capacity_up_mean
3993
                        * (self.invest[g] + g.investment.existing)
3994
                        * g.flex_share_up
3995
                        * g.shift_time
3996
                    )
3997
3998
                    # add constraint
3999
                    block.dr_storage_limit_inc.add((g, t), (lhs <= rhs))
4000
4001
        self.dr_storage_limit_inc = Constraint(
4002
            group, m.TIMESTEPS, noruleinit=True
4003
        )
4004
        self.dr_storage_level_inc_build = BuildAction(
4005
            rule=dr_storage_limit_inc_rule
4006
        )
4007
4008
        # Equation 4.17' -> load shedding
4009
        def dr_yearly_limit_shed_rule(block):
4010
            """Introduce overall annual (energy) limit for load shedding
4011
            resp. overall limit for optimization timeframe considered
4012
            A year limit in contrast to Gils (2015) is defined a mandatory
4013
            parameter here in order to achieve an approach comparable
4014
            to the others.
4015
            """
4016
            for g in group:
4017
                if g.shed_eligibility:
4018
                    # sum of all load reductions
4019
                    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...
4020
4021
                    # year limit
4022
                    rhs = (
4023
                        g.capacity_down_mean
4024
                        * (self.invest[g] + g.investment.existing)
4025
                        * g.flex_share_down
4026
                        * g.shed_time
4027
                        * g.n_yearLimit_shed
4028
                    )
4029
4030
                    # add constraint
4031
                    block.dr_yearly_limit_shed.add(g, (lhs <= rhs))
4032
4033
        self.dr_yearly_limit_shed = Constraint(group, noruleinit=True)
4034
        self.dr_yearly_limit_shed_build = BuildAction(
4035
            rule=dr_yearly_limit_shed_rule
4036
        )
4037
4038
        # ************* Optional Constraints *****************************
4039
4040
        # Equation 4.17
4041
        def dr_yearly_limit_red_rule(block):
4042
            """Introduce overall annual (energy) limit for load reductions
4043
            resp. overall limit for optimization timeframe considered
4044
            """
4045
            for g in group:
4046
4047
                if g.ActivateYearLimit:
4048
                    # sum of all load reductions
4049
                    lhs = sum(
4050
                        sum(self.dsm_do_shift[g, h, t] for h in g.delay_time)
4051
                        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...
4052
                    )
4053
4054
                    # year limit
4055
                    rhs = (
4056
                        g.capacity_down_mean
4057
                        * (self.invest[g] + g.investment.existing)
4058
                        * g.flex_share_down
4059
                        * g.shift_time
4060
                        * g.n_yearLimit_shift
4061
                    )
4062
4063
                    # add constraint
4064
                    block.dr_yearly_limit_red.add(g, (lhs <= rhs))
4065
4066
                else:
4067
                    pass  # return(Constraint.Skip)
4068
4069
        self.dr_yearly_limit_red = Constraint(group, noruleinit=True)
4070
        self.dr_yearly_limit_red_build = BuildAction(
4071
            rule=dr_yearly_limit_red_rule
4072
        )
4073
4074
        # Equation 4.18
4075
        def dr_yearly_limit_inc_rule(block):
4076
            """Introduce overall annual (energy) limit for load increases
4077
            resp. overall limit for optimization timeframe considered
4078
            """
4079
            for g in group:
4080
4081
                if g.ActivateYearLimit:
4082
                    # sum of all load increases
4083
                    lhs = sum(
4084
                        sum(self.dsm_up[g, h, t] for h in g.delay_time)
4085
                        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...
4086
                    )
4087
4088
                    # year limit
4089
                    rhs = (
4090
                        g.capacity_up_mean
4091
                        * (self.invest[g] + g.investment.existing)
4092
                        * g.flex_share_up
4093
                        * g.shift_time
4094
                        * g.n_yearLimit_shift
4095
                    )
4096
4097
                    # add constraint
4098
                    block.dr_yearly_limit_inc.add(g, (lhs <= rhs))
4099
4100
                else:
4101
                    pass  # return(Constraint.Skip)
4102
4103
        self.dr_yearly_limit_inc = Constraint(group, noruleinit=True)
4104
        self.dr_yearly_limit_inc_build = BuildAction(
4105
            rule=dr_yearly_limit_inc_rule
4106
        )
4107
4108
        # Equation 4.19
4109 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...
4110
            """Introduce rolling (energy) limit for load reductions
4111
            This effectively limits DR utilization dependent on
4112
            activations within previous hours.
4113
            """
4114
            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...
4115
                for g in group:
4116
4117
                    if g.ActivateDayLimit:
4118
4119
                        # main use case
4120
                        if t >= g.t_dayLimit:
4121
4122
                            # load reduction
4123
                            lhs = sum(
4124
                                self.dsm_do_shift[g, h, t]
4125
                                for h in g.delay_time
4126
                            )
4127
4128
                            # daily limit
4129
                            rhs = g.capacity_down_mean * (
4130
                                self.invest[g] + g.investment.existing
4131
                            ) * g.flex_share_down * g.shift_time - sum(
4132
                                sum(
4133
                                    self.dsm_do_shift[g, h, t - t_dash]
4134
                                    for h in g.delay_time
4135
                                )
4136
                                for t_dash in range(1, int(g.t_dayLimit) + 1)
4137
                            )
4138
4139
                            # add constraint
4140
                            block.dr_daily_limit_red.add((g, t), (lhs <= rhs))
4141
4142
                        else:
4143
                            pass  # return(Constraint.Skip)
4144
4145
                    else:
4146
                        pass  # return(Constraint.Skip)
4147
4148
        self.dr_daily_limit_red = Constraint(
4149
            group, m.TIMESTEPS, noruleinit=True
4150
        )
4151
        self.dr_daily_limit_red_build = BuildAction(
4152
            rule=dr_daily_limit_red_rule
4153
        )
4154
4155
        # Equation 4.20
4156 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...
4157
            """Introduce rolling (energy) limit for load increases
4158
            This effectively limits DR utilization dependent on
4159
            activations within previous hours.
4160
            """
4161
            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...
4162
                for g in group:
4163
4164
                    if g.ActivateDayLimit:
4165
4166
                        # main use case
4167
                        if t >= g.t_dayLimit:
4168
4169
                            # load increase
4170
                            lhs = sum(
4171
                                self.dsm_up[g, h, t] for h in g.delay_time
4172
                            )
4173
4174
                            # daily limit
4175
                            rhs = g.capacity_up_mean * (
4176
                                self.invest[g] + g.investment.existing
4177
                            ) * g.flex_share_up * g.shift_time - sum(
4178
                                sum(
4179
                                    self.dsm_up[g, h, t - t_dash]
4180
                                    for h in g.delay_time
4181
                                )
4182
                                for t_dash in range(1, int(g.t_dayLimit) + 1)
4183
                            )
4184
4185
                            # add constraint
4186
                            block.dr_daily_limit_inc.add((g, t), (lhs <= rhs))
4187
4188
                        else:
4189
                            pass  # return(Constraint.Skip)
4190
4191
                    else:
4192
                        pass  # return(Constraint.Skip)
4193
4194
        self.dr_daily_limit_inc = Constraint(
4195
            group, m.TIMESTEPS, noruleinit=True
4196
        )
4197
        self.dr_daily_limit_inc_build = BuildAction(
4198
            rule=dr_daily_limit_inc_rule
4199
        )
4200
4201
        # Addition: avoid simultaneous activations
4202 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...
4203
            """Similar to equation 10 from Zerrahn and Schill (2015):
4204
            The sum of upwards and downwards shifts may not be greater
4205
            than the (bigger) capacity limit.
4206
            """
4207
            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...
4208
                for g in group:
4209
4210
                    if g.addition:
4211
4212
                        # sum of load increases and reductions
4213
                        lhs = (
4214
                            sum(
4215
                                self.dsm_up[g, h, t]
4216
                                + self.balance_dsm_do[g, h, t]
4217
                                + self.dsm_do_shift[g, h, t]
4218
                                + self.balance_dsm_up[g, h, t]
4219
                                for h in g.delay_time
4220
                            )
4221
                            + self.dsm_do_shed[g, t]
4222
                        )
4223
4224
                        # maximum capacity eligibly for load shifting
4225
                        rhs = (
4226
                            max(
4227
                                g.capacity_down[t] * g.flex_share_down,
4228
                                g.capacity_up[t] * g.flex_share_up,
4229
                            )
4230
                            * (self.invest[g] + g.investment.existing)
4231
                        )
4232
4233
                        # add constraint
4234
                        block.dr_logical_constraint.add((g, t), (lhs <= rhs))
4235
4236
                    else:
4237
                        pass  # return(Constraint.Skip)
4238
4239
        self.dr_logical_constraint = Constraint(
4240
            group, m.TIMESTEPS, noruleinit=True
4241
        )
4242
        self.dr_logical_constraint_build = BuildAction(
4243
            rule=dr_logical_constraint_rule
4244
        )
4245
4246
    def _objective_expression(self):
4247
        r"""Objective expression with variable and investment costs for DSM;
4248
        Equation 4.23 from Gils (2015)
4249
        """
4250
        m = self.parent_block()
4251
4252
        investment_costs = 0
4253
        variable_costs = 0
4254
4255
        for g in self.INVESTDR:
4256
            if g.investment.ep_costs is not None:
4257
                investment_costs += self.invest[g] * g.investment.ep_costs
4258
            else:
4259
                raise ValueError("Missing value for investment costs!")
4260
            for t in m.TIMESTEPS:
4261
                variable_costs += (
4262
                    sum(
4263
                        self.dsm_up[g, h, t] + self.balance_dsm_do[g, h, t]
4264
                        for h in g.delay_time
4265
                    )
4266
                    * g.cost_dsm_up[t]
4267
                    * m.objective_weighting[t]
4268
                )
4269
                variable_costs += (
4270
                    sum(
4271
                        self.dsm_do_shift[g, h, t]
4272
                        + self.balance_dsm_up[g, h, t]
4273
                        for h in g.delay_time
4274
                    )
4275
                    * g.cost_dsm_down_shift[t]
4276
                    + self.dsm_do_shed[g, t] * g.cost_dsm_down_shed[t]
4277
                ) * m.objective_weighting[t]
4278
4279
        self.cost = Expression(expr=investment_costs + variable_costs)
4280
4281
        return self.cost
4282