Passed
Pull Request — dev (#1052)
by
unknown
03:47
created

data.datasets.DSM_cts_ind.DsmPotential.__init__()   A

Complexity

Conditions 1

Size

Total Lines 6
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 6
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 1
nop 2
1
from sqlalchemy import ARRAY, Column, Float, Integer, String
2
from sqlalchemy.ext.declarative import declarative_base
3
import geopandas as gpd
4
import numpy as np
5
import pandas as pd
6
7
from egon.data import config, db
8
from egon.data.datasets import Dataset
9
from egon.data.datasets.electricity_demand.temporal import calc_load_curve
10
from egon.data.datasets.industry.temporal import identify_bus
11
12
# CONSTANTS
13
# TODO: move to datasets.yml
14
CON = db.engine()
15
16
# CTS
17
CTS_COOL_VENT_AC_SHARE = 0.22
18
19
S_FLEX_CTS = 0.5
20
S_UTIL_CTS = 0.67
21
S_INC_CTS = 1
22
S_DEC_CTS = 0
23
DELTA_T_CTS = 1
24
25
# industry
26
IND_VENT_COOL_SHARE = 0.039
27
IND_VENT_SHARE = 0.017
28
29
# OSM
30
S_FLEX_OSM = 0.5
31
S_UTIL_OSM = 0.73
32
S_INC_OSM = 0.9
33
S_DEC_OSM = 0.5
34
DELTA_T_OSM = 1
35
36
# paper
37
S_FLEX_PAPER = 0.15
38
S_UTIL_PAPER = 0.86
39
S_INC_PAPER = 0.95
40
S_DEC_PAPER = 0
41
DELTA_T_PAPER = 3
42
43
# recycled paper
44
S_FLEX_RECYCLED_PAPER = 0.7
45
S_UTIL_RECYCLED_PAPER = 0.85
46
S_INC_RECYCLED_PAPER = 0.95
47
S_DEC_RECYCLED_PAPER = 0
48
DELTA_T_RECYCLED_PAPER = 3
49
50
# pulp
51
S_FLEX_PULP = 0.7
52
S_UTIL_PULP = 0.83
53
S_INC_PULP = 0.95
54
S_DEC_PULP = 0
55
DELTA_T_PULP = 2
56
57
# cement
58
S_FLEX_CEMENT = 0.61
59
S_UTIL_CEMENT = 0.65
60
S_INC_CEMENT = 0.95
61
S_DEC_CEMENT = 0
62
DELTA_T_CEMENT = 4
63
64
# wz 23
65
WZ = 23
66
67
S_FLEX_WZ = 0.5
68
S_UTIL_WZ = 0.8
69
S_INC_WZ = 1
70
S_DEC_WZ = 0.5
71
DELTA_T_WZ = 1
72
73
Base = declarative_base()
74
75
76
class DsmPotential(Dataset):
77
    def __init__(self, dependencies):
78
        super().__init__(
79
            name="DsmPotential",
80
            version="0.0.4.dev",
81
            dependencies=dependencies,
82
            tasks=(dsm_cts_ind_processing),
83
        )
84
85
86
# Datasets
87 View Code Duplication
class EgonEtragoElectricityCtsDsmTimeseries(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
88
    target = config.datasets()["DSM_CTS_industry"]["targets"][
89
        "cts_loadcurves_dsm"
90
    ]
91
92
    __tablename__ = target["table"]
93
    __table_args__ = {"schema": target["schema"]}
94
95
    bus = Column(Integer, primary_key=True, index=True)
96
    scn_name = Column(String, primary_key=True, index=True)
97
    p_nom = Column(Float)
98
    e_nom = Column(Float)
99
    p_set = Column(ARRAY(Float))
100
    p_max_pu = Column(ARRAY(Float))
101
    p_min_pu = Column(ARRAY(Float))
102
    e_max_pu = Column(ARRAY(Float))
103
    e_min_pu = Column(ARRAY(Float))
104
105
106 View Code Duplication
class EgonOsmIndLoadCurvesIndividualDsmTimeseries(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
107
    target = config.datasets()["DSM_CTS_industry"]["targets"][
108
        "ind_osm_loadcurves_individual_dsm"
109
    ]
110
111
    __tablename__ = target["table"]
112
    __table_args__ = {"schema": target["schema"]}
113
114
    osm_id = Column(Integer, primary_key=True, index=True)
115
    scn_name = Column(String, primary_key=True, index=True)
116
    bus = Column(Integer)
117
    p_nom = Column(Float)
118
    e_nom = Column(Float)
119
    p_set = Column(ARRAY(Float))
120
    p_max_pu = Column(ARRAY(Float))
121
    p_min_pu = Column(ARRAY(Float))
122
    e_max_pu = Column(ARRAY(Float))
123
    e_min_pu = Column(ARRAY(Float))
124
125
126 View Code Duplication
class EgonDemandregioSitesIndElectricityDsmTimeseries(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
127
    target = config.datasets()["DSM_CTS_industry"]["targets"][
128
        "demandregio_ind_sites_dsm"
129
    ]
130
131
    __tablename__ = target["table"]
132
    __table_args__ = {"schema": target["schema"]}
133
134
    industrial_sites_id = Column(Integer, primary_key=True, index=True)
135
    scn_name = Column(String, primary_key=True, index=True)
136
    bus = Column(Integer)
137
    application = Column(String)
138
    p_nom = Column(Float)
139
    e_nom = Column(Float)
140
    p_set = Column(ARRAY(Float))
141
    p_max_pu = Column(ARRAY(Float))
142
    p_min_pu = Column(ARRAY(Float))
143
    e_max_pu = Column(ARRAY(Float))
144
    e_min_pu = Column(ARRAY(Float))
145
146
147 View Code Duplication
class EgonSitesIndLoadCurvesIndividualDsmTimeseries(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
148
    target = config.datasets()["DSM_CTS_industry"]["targets"][
149
        "ind_sites_loadcurves_individual"
150
    ]
151
152
    __tablename__ = target["table"]
153
    __table_args__ = {"schema": target["schema"]}
154
155
    site_id = Column(Integer, primary_key=True, index=True)
156
    scn_name = Column(String, primary_key=True, index=True)
157
    bus = Column(Integer)
158
    p_nom = Column(Float)
159
    e_nom = Column(Float)
160
    p_set = Column(ARRAY(Float))
161
    p_max_pu = Column(ARRAY(Float))
162
    p_min_pu = Column(ARRAY(Float))
163
    e_max_pu = Column(ARRAY(Float))
164
    e_min_pu = Column(ARRAY(Float))
165
166
167
# Code
168
def cts_data_import(cts_cool_vent_ac_share):
169
    """
170
    Import CTS data necessary to identify DSM-potential.
171
172
    ----------
173
    cts_share: float
174
        Share of cooling, ventilation and AC in CTS demand
175
    """
176
177
    # import load data
178
179
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
180
        "cts_loadcurves"
181
    ]
182
183
    ts = db.select_dataframe(
184
        f"""SELECT bus_id, scn_name, p_set FROM
185
        {sources['schema']}.{sources['table']}"""
186
    )
187
188
    # identify relevant columns and prepare df to be returned
189
190
    dsm = pd.DataFrame(index=ts.index)
191
192
    dsm["bus"] = ts["bus_id"].copy()
193
    dsm["scn_name"] = ts["scn_name"].copy()
194
    dsm["p_set"] = ts["p_set"].copy()
195
196
    # calculate share of timeseries for air conditioning, cooling and
197
    # ventilation out of CTS-data
198
199
    timeseries = dsm["p_set"].copy()
200
201
    for index, liste in timeseries.items():
202
        share = [float(item) * cts_cool_vent_ac_share for item in liste]
203
        timeseries.loc[index] = share
204
205
    dsm["p_set"] = timeseries.copy()
206
207
    return dsm
208
209
210 View Code Duplication
def ind_osm_data_import(ind_vent_cool_share):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
211
    """
212
    Import industry data per osm-area necessary to identify DSM-potential.
213
        ----------
214
    ind_share: float
215
        Share of considered application in industry demand
216
    """
217
218
    # import load data
219
220
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
221
        "ind_osm_loadcurves"
222
    ]
223
224
    dsm = db.select_dataframe(
225
        f"""
226
        SELECT bus, scn_name, p_set FROM
227
        {sources['schema']}.{sources['table']}
228
        """
229
    )
230
231
    # calculate share of timeseries for cooling and ventilation out of
232
    # industry-data
233
234
    timeseries = dsm["p_set"].copy()
235
236
    for index, liste in timeseries.items():
237
        share = [float(item) * ind_vent_cool_share for item in liste]
238
239
        timeseries.loc[index] = share
240
241
    dsm["p_set"] = timeseries.copy()
242
243
    return dsm
244
245
246 View Code Duplication
def ind_osm_data_import_individual(ind_vent_cool_share):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
247
    """
248
    Import industry data per osm-area necessary to identify DSM-potential.
249
        ----------
250
    ind_share: float
251
        Share of considered application in industry demand
252
    """
253
254
    # import load data
255
256
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
257
        "ind_osm_loadcurves_individual"
258
    ]
259
260
    dsm = db.select_dataframe(
261
        f"""
262
        SELECT osm_id, bus_id as bus, scn_name, p_set FROM
263
        {sources['schema']}.{sources['table']}
264
        """
265
    )
266
267
    # calculate share of timeseries for cooling and ventilation out of
268
    # industry-data
269
270
    timeseries = dsm["p_set"].copy()
271
272
    for index, liste in timeseries.items():
273
        share = [float(item) * ind_vent_cool_share for item in liste]
274
275
        timeseries.loc[index] = share
276
277
    dsm["p_set"] = timeseries.copy()
278
279
    return dsm
280
281
282 View Code Duplication
def ind_sites_vent_data_import(ind_vent_share, wz):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
283
    """
284
    Import industry sites necessary to identify DSM-potential.
285
        ----------
286
    ind_vent_share: float
287
        Share of considered application in industry demand
288
    wz: int
289
        Wirtschaftszweig to be considered within industry sites
290
    """
291
292
    # import load data
293
294
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
295
        "ind_sites_loadcurves"
296
    ]
297
298
    dsm = db.select_dataframe(
299
        f"""
300
        SELECT bus, scn_name, p_set FROM
301
        {sources['schema']}.{sources['table']}
302
        WHERE wz = {wz}
303
        """
304
    )
305
306
    # calculate share of timeseries for ventilation
307
308
    timeseries = dsm["p_set"].copy()
309
310
    for index, liste in timeseries.items():
311
        share = [float(item) * ind_vent_share for item in liste]
312
        timeseries.loc[index] = share
313
314
    dsm["p_set"] = timeseries.copy()
315
316
    return dsm
317
318
319 View Code Duplication
def ind_sites_vent_data_import_individual(ind_vent_share, wz):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
320
    """
321
    Import industry sites necessary to identify DSM-potential.
322
        ----------
323
    ind_vent_share: float
324
        Share of considered application in industry demand
325
    wz: int
326
        Wirtschaftszweig to be considered within industry sites
327
    """
328
329
    # import load data
330
331
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
332
        "ind_sites_loadcurves_individual"
333
    ]
334
335
    dsm = db.select_dataframe(
336
        f"""
337
        SELECT site_id, bus_id as bus, scn_name, p_set FROM
338
        {sources['schema']}.{sources['table']}
339
        WHERE wz = {wz}
340
        """
341
    )
342
343
    # calculate share of timeseries for ventilation
344
345
    timeseries = dsm["p_set"].copy()
346
347
    for index, liste in timeseries.items():
348
        share = [float(item) * ind_vent_share for item in liste]
349
        timeseries.loc[index] = share
350
351
    dsm["p_set"] = timeseries.copy()
352
353
    return dsm
354
355
356
def calc_ind_site_timeseries(scenario):
357
    # calculate timeseries per site
358
    # -> using code from egon.data.datasets.industry.temporal:
359
    # calc_load_curves_ind_sites
360
361
    # select demands per industrial site including the subsector information
362
    source1 = config.datasets()["DSM_CTS_industry"]["sources"][
363
        "demandregio_ind_sites"
364
    ]
365
366
    demands_ind_sites = db.select_dataframe(
367
        f"""SELECT industrial_sites_id, wz, demand
368
            FROM {source1['schema']}.{source1['table']}
369
            WHERE scenario = '{scenario}'
370
            AND demand > 0
371
            """
372
    ).set_index(["industrial_sites_id"])
373
374
    # select industrial sites as demand_areas from database
375
    source2 = config.datasets()["DSM_CTS_industry"]["sources"]["ind_sites"]
376
377
    demand_area = db.select_geodataframe(
378
        f"""SELECT id, geom, subsector FROM
379
            {source2['schema']}.{source2['table']}""",
380
        index_col="id",
381
        geom_col="geom",
382
        epsg=3035,
383
    )
384
385
    # replace entries to bring it in line with demandregio's subsector
386
    # definitions
387
    demands_ind_sites.replace(1718, 17, inplace=True)
388
    share_wz_sites = demands_ind_sites.copy()
389
390
    # create additional df on wz_share per industrial site, which is always set
391
    # to one as the industrial demand per site is subsector specific
392
    share_wz_sites.demand = 1
393
    share_wz_sites.reset_index(inplace=True)
394
395
    share_transpose = pd.DataFrame(
396
        index=share_wz_sites.industrial_sites_id.unique(),
397
        columns=share_wz_sites.wz.unique(),
398
    )
399
    share_transpose.index.rename("industrial_sites_id", inplace=True)
400
    for wz in share_transpose.columns:
401
        share_transpose[wz] = (
402
            share_wz_sites[share_wz_sites.wz == wz]
403
            .set_index("industrial_sites_id")
404
            .demand
405
        )
406
407
    # calculate load curves
408
    load_curves = calc_load_curve(share_transpose, demands_ind_sites["demand"])
409
410
    # identify bus per industrial site
411
    curves_bus = identify_bus(load_curves, demand_area)
412
    curves_bus.index = curves_bus["id"].astype(int)
413
414
    # initialize dataframe to be returned
415
416
    ts = pd.DataFrame(
417
        data=curves_bus["bus_id"], index=curves_bus["id"].astype(int)
418
    )
419
    ts["scenario_name"] = scenario
420
    curves_bus.drop({"id", "bus_id", "geom"}, axis=1, inplace=True)
421
    ts["p_set"] = curves_bus.values.tolist()
422
423
    # add subsector to relate to Schmidt's tables afterwards
424
    ts["application"] = demand_area["subsector"]
425
426
    return ts
427
428
429
def relate_to_schmidt_sites(dsm):
430
    # import industrial sites by Schmidt
431
432
    source = config.datasets()["DSM_CTS_industry"]["sources"][
433
        "ind_sites_schmidt"
434
    ]
435
436
    schmidt = db.select_dataframe(
437
        f"""SELECT application, geom FROM
438
            {source['schema']}.{source['table']}"""
439
    )
440
441
    # relate calculated timeseries (dsm) to Schmidt's industrial sites
442
443
    applications = np.unique(schmidt["application"])
444
    dsm = pd.DataFrame(dsm[dsm["application"].isin(applications)])
445
446
    # initialize dataframe to be returned
447
448
    dsm.rename(
449
        columns={"scenario_name": "scn_name", "bus_id": "bus"},
450
        inplace=True,
451
    )
452
453
    return dsm
454
455
456
def ind_sites_data_import():
457
    """
458
    Import industry sites data necessary to identify DSM-potential.
459
    """
460
    # calculate timeseries per site
461
462
    # scenario eGon2035
463
    dsm_2035 = calc_ind_site_timeseries("eGon2035")
464
    dsm_2035.reset_index(inplace=True)
465
    # scenario eGon100RE
466
    dsm_100 = calc_ind_site_timeseries("eGon100RE")
467
    dsm_100.reset_index(inplace=True)
468
    # bring df for both scenarios together
469
    dsm_100.index = range(len(dsm_2035), (len(dsm_2035) + len((dsm_100))))
470
    dsm = dsm_2035.append(dsm_100)
471
472
    # relate calculated timeseries to Schmidt's industrial sites
473
474
    dsm = relate_to_schmidt_sites(dsm)
475
476
    return dsm[["application", "id", "bus", "scn_name", "p_set"]]
477
478
479
def calculate_potentials(s_flex, s_util, s_inc, s_dec, delta_t, dsm):
480
    """
481
    Calculate DSM-potential per bus using the methods by Heitkoetter et. al.:
482
        https://doi.org/10.1016/j.adapen.2020.100001
483
    Parameters
484
        ----------
485
    s_flex: float
486
        Feasability factor to account for socio-technical restrictions
487
    s_util: float
488
        Average annual utilisation rate
489
    s_inc: float
490
        Shiftable share of installed capacity up to which load can be
491
        increased considering technical limitations
492
    s_dec: float
493
        Shiftable share of installed capacity up to which load can be
494
        decreased considering technical limitations
495
    delta_t: int
496
        Maximum shift duration in hours
497
    dsm: DataFrame
498
        List of existing buses with DSM-potential including timeseries of
499
        loads
500
    """
501
502
    # copy relevant timeseries
503
    timeseries = dsm["p_set"].copy()
504
505
    # calculate scheduled load L(t)
506
507
    scheduled_load = timeseries.copy()
508
509
    for index, liste in scheduled_load.items():
510
        share = []
511
        for item in liste:
512
            share.append(item * s_flex)
513
        scheduled_load.loc[index] = share
514
515
    # calculate maximum capacity Lambda
516
517
    # calculate energy annual requirement
518
    energy_annual = pd.Series(index=timeseries.index, dtype=float)
519
    for index, liste in timeseries.items():
520
        energy_annual.loc[index] = sum(liste)
521
522
    # calculate Lambda
523
    lam = (energy_annual * s_flex) / (8760 * s_util)
524
525
    # calculation of P_max and P_min
526
527
    # P_max
528
    p_max = scheduled_load.copy()
529
    for index, liste in scheduled_load.items():
530
        lamb = lam.loc[index]
531
        p = []
532
        for item in liste:
533
            value = lamb * s_inc - item
534
            if value < 0:
535
                value = 0
536
            p.append(value)
537
        p_max.loc[index] = p
538
539
    # P_min
540
    p_min = scheduled_load.copy()
541
    for index, liste in scheduled_load.items():
542
        lamb = lam.loc[index]
543
        p = []
544
        for item in liste:
545
            value = -(item - lamb * s_dec)
546
            if value > 0:
547
                value = 0
548
            p.append(value)
549
        p_min.loc[index] = p
550
551
    # calculation of E_max and E_min
552
553
    e_max = scheduled_load.copy()
554
    e_min = scheduled_load.copy()
555
556
    for index, liste in scheduled_load.items():
557
        emin = []
558
        emax = []
559
        for i in range(len(liste)):
560
            if i + delta_t > len(liste):
561
                emax.append(
562
                    (sum(liste[i:]) + sum(liste[: delta_t - (len(liste) - i)]))
563
                )
564
            else:
565
                emax.append(sum(liste[i : i + delta_t]))
566
            if i - delta_t < 0:
567
                emin.append(
568
                    (
569
                        -1
570
                        * (
571
                            (
572
                                sum(liste[:i])
573
                                + sum(liste[len(liste) - delta_t + i :])
574
                            )
575
                        )
576
                    )
577
                )
578
            else:
579
                emin.append(-1 * sum(liste[i - delta_t : i]))
580
        e_max.loc[index] = emax
581
        e_min.loc[index] = emin
582
583
    return p_max, p_min, e_max, e_min
584
585
586
def create_dsm_components(con, p_max, p_min, e_max, e_min, dsm):
587
    """
588
    Create components representing DSM.
589
    Parameters
590
        ----------
591
    con :
592
        Connection to database
593
    p_max: DataFrame
594
        Timeseries identifying maximum load increase
595
    p_min: DataFrame
596
        Timeseries identifying maximum load decrease
597
    e_max: DataFrame
598
        Timeseries identifying maximum energy amount to be preponed
599
    e_min: DataFrame
600
        Timeseries identifying maximum energy amount to be postponed
601
    dsm: DataFrame
602
        List of existing buses with DSM-potential including timeseries of loads
603
    """
604
605
    # calculate P_nom and P per unit
606
    p_nom = pd.Series(index=p_max.index, dtype=float)
607
    for index, row in p_max.items():
608
        nom = max(max(row), abs(min(p_min.loc[index])))
609
        p_nom.loc[index] = nom
610
        new = [element / nom for element in row]
611
        p_max.loc[index] = new
612
        new = [element / nom for element in p_min.loc[index]]
613
        p_min.loc[index] = new
614
615
    # calculate E_nom and E per unit
616
    e_nom = pd.Series(index=p_min.index, dtype=float)
617
    for index, row in e_max.items():
618
        nom = max(max(row), abs(min(e_min.loc[index])))
619
        e_nom.loc[index] = nom
620
        new = [element / nom for element in row]
621
        e_max.loc[index] = new
622
        new = [element / nom for element in e_min.loc[index]]
623
        e_min.loc[index] = new
624
625
    # add DSM-buses to "original" buses
626
    dsm_buses = gpd.GeoDataFrame(index=dsm.index)
627
    dsm_buses["original_bus"] = dsm["bus"].copy()
628
    dsm_buses["scn_name"] = dsm["scn_name"].copy()
629
630
    # get original buses and add copy of relevant information
631
    target1 = config.datasets()["DSM_CTS_industry"]["targets"]["bus"]
632
    original_buses = db.select_geodataframe(
633
        f"""SELECT bus_id, v_nom, scn_name, x, y, geom FROM
634
            {target1['schema']}.{target1['table']}""",
635
        geom_col="geom",
636
        epsg=4326,
637
    )
638
639
    # copy relevant information from original buses to DSM-buses
640
    dsm_buses["index"] = dsm_buses.index
641
    originals = original_buses[
642
        original_buses["bus_id"].isin(np.unique(dsm_buses["original_bus"]))
643
    ]
644
    dsm_buses = originals.merge(
645
        dsm_buses,
646
        left_on=["bus_id", "scn_name"],
647
        right_on=["original_bus", "scn_name"],
648
    )
649
    dsm_buses.index = dsm_buses["index"]
650
    dsm_buses.drop(["bus_id", "index"], axis=1, inplace=True)
651
652
    # new bus_ids for DSM-buses
653
    max_id = original_buses["bus_id"].max()
654
    if np.isnan(max_id):
655
        max_id = 0
656
    dsm_id = max_id + 1
657
    bus_id = pd.Series(index=dsm_buses.index, dtype=int)
658
659
    # Get number of DSM buses for both scenarios
660
    rows_per_scenario = (
661
        dsm_buses.groupby("scn_name").count().original_bus.to_dict()
662
    )
663
664
    # Assignment of DSM ids
665
    bus_id.iloc[: rows_per_scenario.get("eGon2035", 0)] = range(
666
        dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0)
667
    )
668
669
    bus_id.iloc[
670
        rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get(
671
            "eGon2035", 0
672
        )
673
        + rows_per_scenario.get("eGon100RE", 0)
674
    ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0))
675
676
    dsm_buses["bus_id"] = bus_id
677
678
    # add links from "orignal" buses to DSM-buses
679
680
    dsm_links = pd.DataFrame(index=dsm_buses.index)
681
    dsm_links["original_bus"] = dsm_buses["original_bus"].copy()
682
    dsm_links["dsm_bus"] = dsm_buses["bus_id"].copy()
683
    dsm_links["scn_name"] = dsm_buses["scn_name"].copy()
684
685
    # set link_id
686
    target2 = config.datasets()["DSM_CTS_industry"]["targets"]["link"]
687
    sql = f"""SELECT link_id FROM {target2['schema']}.{target2['table']}"""
688
    max_id = pd.read_sql_query(sql, con)
689
    max_id = max_id["link_id"].max()
690
    if np.isnan(max_id):
691
        max_id = 0
692
    dsm_id = max_id + 1
693
    link_id = pd.Series(index=dsm_buses.index, dtype=int)
694
695
    # Assignment of link ids
696
    link_id.iloc[: rows_per_scenario.get("eGon2035", 0)] = range(
697
        dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0)
698
    )
699
700
    link_id.iloc[
701
        rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get(
702
            "eGon2035", 0
703
        )
704
        + rows_per_scenario.get("eGon100RE", 0)
705
    ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0))
706
707
    dsm_links["link_id"] = link_id
708
709
    # add calculated timeseries to df to be returned
710
    dsm_links["p_nom"] = p_nom
711
    dsm_links["p_min"] = p_min
712
    dsm_links["p_max"] = p_max
713
714
    # add DSM-stores
715
716
    dsm_stores = pd.DataFrame(index=dsm_buses.index)
717
    dsm_stores["bus"] = dsm_buses["bus_id"].copy()
718
    dsm_stores["scn_name"] = dsm_buses["scn_name"].copy()
719
    dsm_stores["original_bus"] = dsm_buses["original_bus"].copy()
720
721
    # set store_id
722
    target3 = config.datasets()["DSM_CTS_industry"]["targets"]["store"]
723
    sql = f"""SELECT store_id FROM {target3['schema']}.{target3['table']}"""
724
    max_id = pd.read_sql_query(sql, con)
725
    max_id = max_id["store_id"].max()
726
    if np.isnan(max_id):
727
        max_id = 0
728
    dsm_id = max_id + 1
729
    store_id = pd.Series(index=dsm_buses.index, dtype=int)
730
731
    # Assignment of store ids
732
    store_id.iloc[: rows_per_scenario.get("eGon2035", 0)] = range(
733
        dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0)
734
    )
735
736
    store_id.iloc[
737
        rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get(
738
            "eGon2035", 0
739
        )
740
        + rows_per_scenario.get("eGon100RE", 0)
741
    ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0))
742
743
    dsm_stores["store_id"] = store_id
744
745
    # add calculated timeseries to df to be returned
746
    dsm_stores["e_nom"] = e_nom
747
    dsm_stores["e_min"] = e_min
748
    dsm_stores["e_max"] = e_max
749
750
    return dsm_buses, dsm_links, dsm_stores
751
752
753
def aggregate_components(df_dsm_buses, df_dsm_links, df_dsm_stores):
754
    # aggregate buses
755
756
    grouper = [df_dsm_buses.original_bus, df_dsm_buses.scn_name]
757
758
    df_dsm_buses = df_dsm_buses.groupby(grouper).first()
759
760
    df_dsm_buses.reset_index(inplace=True)
761
    df_dsm_buses.sort_values("scn_name", inplace=True)
762
763
    # aggregate links
764
765
    df_dsm_links["p_max"] = df_dsm_links["p_max"].apply(lambda x: np.array(x))
766
    df_dsm_links["p_min"] = df_dsm_links["p_min"].apply(lambda x: np.array(x))
767
768
    grouper = [df_dsm_links.original_bus, df_dsm_links.scn_name]
769
    p_nom = df_dsm_links.groupby(grouper)["p_nom"].sum()
770
    p_max = df_dsm_links.groupby(grouper)["p_max"].apply(np.sum)
771
    p_min = df_dsm_links.groupby(grouper)["p_min"].apply(np.sum)
772
773
    df_dsm_links = df_dsm_links.groupby(grouper).first()
774
    df_dsm_links.p_nom = p_nom
775
    df_dsm_links.p_max = p_max
776
    df_dsm_links.p_min = p_min
777
778
    df_dsm_links["p_max"] = df_dsm_links["p_max"].apply(lambda x: list(x))
779
    df_dsm_links["p_min"] = df_dsm_links["p_min"].apply(lambda x: list(x))
780
781
    df_dsm_links.reset_index(inplace=True)
782
    df_dsm_links.sort_values("scn_name", inplace=True)
783
784
    # aggregate stores
785
786
    df_dsm_stores["e_max"] = df_dsm_stores["e_max"].apply(
787
        lambda x: np.array(x)
788
    )
789
    df_dsm_stores["e_min"] = df_dsm_stores["e_min"].apply(
790
        lambda x: np.array(x)
791
    )
792
793
    grouper = [df_dsm_stores.original_bus, df_dsm_stores.scn_name]
794
    e_nom = df_dsm_stores.groupby(grouper)["e_nom"].sum()
795
    e_max = df_dsm_stores.groupby(grouper)["e_max"].apply(np.sum)
796
    e_min = df_dsm_stores.groupby(grouper)["e_min"].apply(np.sum)
797
798
    df_dsm_stores = df_dsm_stores.groupby(grouper).first()
799
    df_dsm_stores.e_nom = e_nom
800
    df_dsm_stores.e_max = e_max
801
    df_dsm_stores.e_min = e_min
802
803
    df_dsm_stores["e_max"] = df_dsm_stores["e_max"].apply(lambda x: list(x))
804
    df_dsm_stores["e_min"] = df_dsm_stores["e_min"].apply(lambda x: list(x))
805
806
    df_dsm_stores.reset_index(inplace=True)
807
    df_dsm_stores.sort_values("scn_name", inplace=True)
808
809
    # select new bus_ids for aggregated buses and add to links and stores
810
    bus_id = db.next_etrago_id("Bus") + df_dsm_buses.index
811
812
    df_dsm_buses["bus_id"] = bus_id
813
    df_dsm_links["dsm_bus"] = bus_id
814
    df_dsm_stores["bus"] = bus_id
815
816
    # select new link_ids for aggregated links
817
    link_id = db.next_etrago_id("Link") + df_dsm_links.index
818
819
    df_dsm_links["link_id"] = link_id
820
821
    # select new store_ids to aggregated stores
822
823
    store_id = db.next_etrago_id("Store") + df_dsm_stores.index
824
825
    df_dsm_stores["store_id"] = store_id
826
827
    return df_dsm_buses, df_dsm_links, df_dsm_stores
828
829
830
def data_export(dsm_buses, dsm_links, dsm_stores, carrier):
831
    """
832
    Export new components to database.
833
834
    Parameters
835
    ----------
836
    dsm_buses: DataFrame
837
        Buses representing locations of DSM-potential
838
    dsm_links: DataFrame
839
        Links connecting DSM-buses and DSM-stores
840
    dsm_stores: DataFrame
841
        Stores representing DSM-potential
842
    carrier: str
843
        Remark to be filled in column 'carrier' identifying DSM-potential
844
    """
845
846
    targets = config.datasets()["DSM_CTS_industry"]["targets"]
847
848
    # dsm_buses
849
850
    insert_buses = gpd.GeoDataFrame(
851
        index=dsm_buses.index,
852
        data=dsm_buses["geom"],
853
        geometry="geom",
854
        crs=dsm_buses.crs,
855
    )
856
    insert_buses["scn_name"] = dsm_buses["scn_name"]
857
    insert_buses["bus_id"] = dsm_buses["bus_id"]
858
    insert_buses["v_nom"] = dsm_buses["v_nom"]
859
    insert_buses["carrier"] = carrier
860
    insert_buses["x"] = dsm_buses["x"]
861
    insert_buses["y"] = dsm_buses["y"]
862
863
    # insert into database
864
    insert_buses.to_postgis(
865
        targets["bus"]["table"],
866
        con=db.engine(),
867
        schema=targets["bus"]["schema"],
868
        if_exists="append",
869
        index=False,
870
        dtype={"geom": "geometry"},
871
    )
872
873
    # dsm_links
874
875
    insert_links = pd.DataFrame(index=dsm_links.index)
876
    insert_links["scn_name"] = dsm_links["scn_name"]
877
    insert_links["link_id"] = dsm_links["link_id"]
878
    insert_links["bus0"] = dsm_links["original_bus"]
879
    insert_links["bus1"] = dsm_links["dsm_bus"]
880
    insert_links["carrier"] = carrier
881
    insert_links["p_nom"] = dsm_links["p_nom"]
882
883
    # insert into database
884
    insert_links.to_sql(
885
        targets["link"]["table"],
886
        con=db.engine(),
887
        schema=targets["link"]["schema"],
888
        if_exists="append",
889
        index=False,
890
    )
891
892
    insert_links_timeseries = pd.DataFrame(index=dsm_links.index)
893
    insert_links_timeseries["scn_name"] = dsm_links["scn_name"]
894
    insert_links_timeseries["link_id"] = dsm_links["link_id"]
895
    insert_links_timeseries["p_min_pu"] = dsm_links["p_min"]
896
    insert_links_timeseries["p_max_pu"] = dsm_links["p_max"]
897
    insert_links_timeseries["temp_id"] = 1
898
899
    # insert into database
900
    insert_links_timeseries.to_sql(
901
        targets["link_timeseries"]["table"],
902
        con=db.engine(),
903
        schema=targets["link_timeseries"]["schema"],
904
        if_exists="append",
905
        index=False,
906
    )
907
908
    # dsm_stores
909
910
    insert_stores = pd.DataFrame(index=dsm_stores.index)
911
    insert_stores["scn_name"] = dsm_stores["scn_name"]
912
    insert_stores["store_id"] = dsm_stores["store_id"]
913
    insert_stores["bus"] = dsm_stores["bus"]
914
    insert_stores["carrier"] = carrier
915
    insert_stores["e_nom"] = dsm_stores["e_nom"]
916
917
    # insert into database
918
    insert_stores.to_sql(
919
        targets["store"]["table"],
920
        con=db.engine(),
921
        schema=targets["store"]["schema"],
922
        if_exists="append",
923
        index=False,
924
    )
925
926
    insert_stores_timeseries = pd.DataFrame(index=dsm_stores.index)
927
    insert_stores_timeseries["scn_name"] = dsm_stores["scn_name"]
928
    insert_stores_timeseries["store_id"] = dsm_stores["store_id"]
929
    insert_stores_timeseries["e_min_pu"] = dsm_stores["e_min"]
930
    insert_stores_timeseries["e_max_pu"] = dsm_stores["e_max"]
931
    insert_stores_timeseries["temp_id"] = 1
932
933
    # insert into database
934
    insert_stores_timeseries.to_sql(
935
        targets["store_timeseries"]["table"],
936
        con=db.engine(),
937
        schema=targets["store_timeseries"]["schema"],
938
        if_exists="append",
939
        index=False,
940
    )
941
942
943
def delete_dsm_entries(carrier):
944
    """
945
    Deletes DSM-components from database if they already exist before creating
946
    new ones.
947
948
    Parameters
949
        ----------
950
     carrier: str
951
        Remark in column 'carrier' identifying DSM-potential
952
    """
953
954
    targets = config.datasets()["DSM_CTS_industry"]["targets"]
955
956
    # buses
957
958
    sql = f"""DELETE FROM {targets["bus"]["schema"]}.{targets["bus"]["table"]} b
959
     WHERE (b.carrier LIKE '{carrier}');"""
960
    db.execute_sql(sql)
961
962
    # links
963
964
    sql = f"""
965
        DELETE FROM {targets["link_timeseries"]["schema"]}.
966
        {targets["link_timeseries"]["table"]} t
967
        WHERE t.link_id IN
968
        (
969
            SELECT l.link_id FROM {targets["link"]["schema"]}.
970
            {targets["link"]["table"]} l
971
            WHERE l.carrier LIKE '{carrier}'
972
        );
973
        """
974
975
    db.execute_sql(sql)
976
977
    sql = f"""
978
        DELETE FROM {targets["link"]["schema"]}.
979
        {targets["link"]["table"]} l
980
        WHERE (l.carrier LIKE '{carrier}');
981
        """
982
983
    db.execute_sql(sql)
984
985
    # stores
986
987
    sql = f"""
988
        DELETE FROM {targets["store_timeseries"]["schema"]}.
989
        {targets["store_timeseries"]["table"]} t
990
        WHERE t.store_id IN
991
        (
992
            SELECT s.store_id FROM {targets["store"]["schema"]}.
993
            {targets["store"]["table"]} s
994
            WHERE s.carrier LIKE '{carrier}'
995
        );
996
        """
997
998
    db.execute_sql(sql)
999
1000
    sql = f"""
1001
        DELETE FROM {targets["store"]["schema"]}.{targets["store"]["table"]} s
1002
        WHERE (s.carrier LIKE '{carrier}');
1003
        """
1004
1005
    db.execute_sql(sql)
1006
1007
1008
def dsm_cts_ind(
1009
    con=db.engine(),
1010
    cts_cool_vent_ac_share=0.22,
1011
    ind_vent_cool_share=0.039,
1012
    ind_vent_share=0.017,
1013
):
1014
    """
1015
    Execute methodology to create and implement components for DSM considering
1016
    a) CTS per osm-area: combined potentials of cooling, ventilation and air
1017
      conditioning
1018
    b) Industry per osm-are: combined potentials of cooling and ventilation
1019
    c) Industrial Sites: potentials of ventilation in sites of
1020
      "Wirtschaftszweig" (WZ) 23
1021
    d) Industrial Sites: potentials of sites specified by subsectors
1022
      identified by Schmidt (https://zenodo.org/record/3613767#.YTsGwVtCRhG):
1023
      Paper, Recycled Paper, Pulp, Cement
1024
1025
    Modelled using the methods by Heitkoetter et. al.:
1026
    https://doi.org/10.1016/j.adapen.2020.100001
1027
1028
    Parameters
1029
    ----------
1030
    con :
1031
        Connection to database
1032
    cts_cool_vent_ac_share: float
1033
        Share of cooling, ventilation and AC in CTS demand
1034
    ind_vent_cool_share: float
1035
        Share of cooling and ventilation in industry demand
1036
    ind_vent_share: float
1037
        Share of ventilation in industry demand in sites of WZ 23
1038
1039
    """
1040
1041
    # CTS per osm-area: cooling, ventilation and air conditioning
1042
1043
    print(" ")
1044
    print("CTS per osm-area: cooling, ventilation and air conditioning")
1045
    print(" ")
1046
1047
    dsm = cts_data_import(cts_cool_vent_ac_share)
1048
1049
    # calculate combined potentials of cooling, ventilation and air
1050
    # conditioning in CTS using combined parameters by Heitkoetter et. al.
1051
    p_max, p_min, e_max, e_min = calculate_potentials(
1052
        s_flex=S_FLEX_CTS,
1053
        s_util=S_UTIL_CTS,
1054
        s_inc=S_INC_CTS,
1055
        s_dec=S_DEC_CTS,
1056
        delta_t=DELTA_T_CTS,
1057
        dsm=dsm,
1058
    )
1059
1060
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1061
        con, p_max, p_min, e_max, e_min, dsm
1062
    )
1063
1064
    df_dsm_buses = dsm_buses.copy()
1065
    df_dsm_links = dsm_links.copy()
1066
    df_dsm_stores = dsm_stores.copy()
1067
1068
    # industry per osm-area: cooling and ventilation
1069
1070
    print(" ")
1071
    print("industry per osm-area: cooling and ventilation")
1072
    print(" ")
1073
1074
    dsm = ind_osm_data_import(ind_vent_cool_share)
1075
1076
    # calculate combined potentials of cooling and ventilation in industrial
1077
    # sector using combined parameters by Heitkoetter et. al.
1078
    p_max, p_min, e_max, e_min = calculate_potentials(
1079
        s_flex=S_FLEX_OSM,
1080
        s_util=S_UTIL_OSM,
1081
        s_inc=S_INC_OSM,
1082
        s_dec=S_DEC_OSM,
1083
        delta_t=DELTA_T_OSM,
1084
        dsm=dsm,
1085
    )
1086
1087
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1088
        con, p_max, p_min, e_max, e_min, dsm
1089
    )
1090
1091
    df_dsm_buses = gpd.GeoDataFrame(
1092
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1093
        crs="EPSG:4326",
1094
    )
1095
    df_dsm_links = pd.DataFrame(
1096
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1097
    )
1098
    df_dsm_stores = pd.DataFrame(
1099
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1100
    )
1101
1102
    # industry sites
1103
1104
    # industry sites: different applications
1105
1106
    dsm = ind_sites_data_import()
1107
1108
    print(" ")
1109
    print("industry sites: paper")
1110
    print(" ")
1111
1112
    dsm_paper = gpd.GeoDataFrame(
1113
        dsm[
1114
            dsm["application"].isin(
1115
                [
1116
                    "Graphic Paper",
1117
                    "Packing Paper and Board",
1118
                    "Hygiene Paper",
1119
                    "Technical/Special Paper and Board",
1120
                ]
1121
            )
1122
        ]
1123
    )
1124
1125
    # calculate potentials of industrial sites with paper-applications
1126
    # using parameters by Heitkoetter et al.
1127
    p_max, p_min, e_max, e_min = calculate_potentials(
1128
        s_flex=S_FLEX_PAPER,
1129
        s_util=S_UTIL_PAPER,
1130
        s_inc=S_INC_PAPER,
1131
        s_dec=S_DEC_PAPER,
1132
        delta_t=DELTA_T_PAPER,
1133
        dsm=dsm_paper,
1134
    )
1135
1136
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1137
        con, p_max, p_min, e_max, e_min, dsm_paper
1138
    )
1139
1140
    df_dsm_buses = gpd.GeoDataFrame(
1141
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1142
        crs="EPSG:4326",
1143
    )
1144
    df_dsm_links = pd.DataFrame(
1145
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1146
    )
1147
    df_dsm_stores = pd.DataFrame(
1148
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1149
    )
1150
1151
    print(" ")
1152
    print("industry sites: recycled paper")
1153
    print(" ")
1154
1155
    # calculate potentials of industrial sites with recycled paper-applications
1156
    # using parameters by Heitkoetter et. al.
1157
    dsm_recycled_paper = gpd.GeoDataFrame(
1158
        dsm[dsm["application"] == "Recycled Paper"]
1159
    )
1160
1161
    p_max, p_min, e_max, e_min = calculate_potentials(
1162
        s_flex=S_FLEX_RECYCLED_PAPER,
1163
        s_util=S_UTIL_RECYCLED_PAPER,
1164
        s_inc=S_INC_RECYCLED_PAPER,
1165
        s_dec=S_DEC_RECYCLED_PAPER,
1166
        delta_t=DELTA_T_RECYCLED_PAPER,
1167
        dsm=dsm_recycled_paper,
1168
    )
1169
1170
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1171
        con, p_max, p_min, e_max, e_min, dsm_recycled_paper
1172
    )
1173
1174
    df_dsm_buses = gpd.GeoDataFrame(
1175
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1176
        crs="EPSG:4326",
1177
    )
1178
    df_dsm_links = pd.DataFrame(
1179
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1180
    )
1181
    df_dsm_stores = pd.DataFrame(
1182
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1183
    )
1184
1185
    print(" ")
1186
    print("industry sites: pulp")
1187
    print(" ")
1188
1189
    dsm_pulp = gpd.GeoDataFrame(dsm[dsm["application"] == "Mechanical Pulp"])
1190
1191
    # calculate potentials of industrial sites with pulp-applications
1192
    # using parameters by Heitkoetter et al.
1193
    p_max, p_min, e_max, e_min = calculate_potentials(
1194
        s_flex=S_FLEX_PULP,
1195
        s_util=S_UTIL_PULP,
1196
        s_inc=S_INC_PULP,
1197
        s_dec=S_DEC_PULP,
1198
        delta_t=DELTA_T_PULP,
1199
        dsm=dsm_pulp,
1200
    )
1201
1202
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1203
        con, p_max, p_min, e_max, e_min, dsm_pulp
1204
    )
1205
1206
    df_dsm_buses = gpd.GeoDataFrame(
1207
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1208
        crs="EPSG:4326",
1209
    )
1210
    df_dsm_links = pd.DataFrame(
1211
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1212
    )
1213
    df_dsm_stores = pd.DataFrame(
1214
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1215
    )
1216
1217
    # industry sites: cement
1218
1219
    print(" ")
1220
    print("industry sites: cement")
1221
    print(" ")
1222
1223
    dsm_cement = gpd.GeoDataFrame(dsm[dsm["application"] == "Cement Mill"])
1224
1225
    # calculate potentials of industrial sites with cement-applications
1226
    # using parameters by Heitkoetter et al.
1227
    p_max, p_min, e_max, e_min = calculate_potentials(
1228
        s_flex=S_FLEX_CEMENT,
1229
        s_util=S_UTIL_CEMENT,
1230
        s_inc=S_INC_CEMENT,
1231
        s_dec=S_DEC_CEMENT,
1232
        delta_t=DELTA_T_CEMENT,
1233
        dsm=dsm_cement,
1234
    )
1235
1236
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1237
        con, p_max, p_min, e_max, e_min, dsm_cement
1238
    )
1239
1240
    df_dsm_buses = gpd.GeoDataFrame(
1241
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1242
        crs="EPSG:4326",
1243
    )
1244
    df_dsm_links = pd.DataFrame(
1245
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1246
    )
1247
    df_dsm_stores = pd.DataFrame(
1248
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1249
    )
1250
1251
    # industry sites: ventilation in WZ23
1252
1253
    print(" ")
1254
    print("industry sites: ventilation in WZ23")
1255
    print(" ")
1256
1257
    dsm = ind_sites_vent_data_import(ind_vent_share, wz=WZ)
1258
1259
    # drop entries of Cement Mills whose DSM-potentials have already been
1260
    # modelled
1261
    cement = np.unique(dsm_cement["bus"].values)
1262
    index_names = np.array(dsm[dsm["bus"].isin(cement)].index)
1263
    dsm.drop(index_names, inplace=True)
1264
1265
    # calculate potentials of ventialtion in industrial sites of WZ 23
1266
    # using parameters by Heitkoetter et al.
1267
    p_max, p_min, e_max, e_min = calculate_potentials(
1268
        s_flex=S_FLEX_WZ,
1269
        s_util=S_UTIL_WZ,
1270
        s_inc=S_INC_WZ,
1271
        s_dec=S_DEC_WZ,
1272
        delta_t=DELTA_T_WZ,
1273
        dsm=dsm,
1274
    )
1275
1276
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1277
        con, p_max, p_min, e_max, e_min, dsm
1278
    )
1279
1280
    df_dsm_buses = gpd.GeoDataFrame(
1281
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1282
        crs="EPSG:4326",
1283
    )
1284
    df_dsm_links = pd.DataFrame(
1285
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1286
    )
1287
    df_dsm_stores = pd.DataFrame(
1288
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1289
    )
1290
1291
    # aggregate DSM components per substation
1292
    dsm_buses, dsm_links, dsm_stores = aggregate_components(
1293
        df_dsm_buses, df_dsm_links, df_dsm_stores
1294
    )
1295
1296
    # export aggregated DSM components to database
1297
1298
    delete_dsm_entries("dsm-cts")
1299
    delete_dsm_entries("dsm-ind-osm")
1300
    delete_dsm_entries("dsm-ind-sites")
1301
    delete_dsm_entries("dsm")
1302
1303
    data_export(dsm_buses, dsm_links, dsm_stores, carrier="dsm")
1304
1305
1306
def get_p_nom_e_nom(df: pd.DataFrame):
1307
    p_nom = [
1308
        max(max(val), max(abs(v) for v in df.p_min_pu.at[idx]))
1309
        for idx, val in df.p_max_pu.items()
1310
    ]
1311
1312
    e_nom = [
1313
        max(max(val), max(abs(v) for v in df.e_min_pu.at[idx]))
1314
        for idx, val in df.e_max_pu.items()
1315
    ]
1316
1317
    return df.assign(p_nom=p_nom, e_nom=e_nom)
1318
1319
1320
def calc_per_unit(df):
1321
    df = get_p_nom_e_nom(df)
1322
1323
    for col in ["p_max_pu", "p_min_pu"]:
1324
        rslt = []
1325
1326
        for idx, lst in df[col].items():
1327
            p_nom = df.p_nom.at[idx]
1328
1329
            rslt.append([v / p_nom for v in lst])
1330
1331
        df[col] = rslt
1332
1333
    for col in ["e_max_pu", "e_min_pu"]:
1334
        rslt = []
1335
1336
        for idx, lst in df[col].items():
1337
            e_nom = df.e_nom.at[idx]
1338
1339
            rslt.append([v / e_nom for v in lst])
1340
1341
        df[col] = rslt
1342
1343
    return df
1344
1345
1346
def create_table(df, table, engine=CON):
1347
    """Create table"""
1348
    table.__table__.drop(bind=engine, checkfirst=True)
1349
    table.__table__.create(bind=engine, checkfirst=True)
1350
1351
    df.to_sql(
1352
        name=table.__table__.name,
1353
        schema=table.__table__.schema,
1354
        con=engine,
1355
        if_exists="append",
1356
        index=False,
1357
    )
1358
1359
1360
def dsm_cts_ind_individual(
1361
    cts_cool_vent_ac_share=CTS_COOL_VENT_AC_SHARE,
1362
    ind_vent_cool_share=IND_VENT_COOL_SHARE,
1363
    ind_vent_share=IND_VENT_SHARE,
1364
):
1365
    """
1366
    Execute methodology to create and implement components for DSM considering
1367
    a) CTS per osm-area: combined potentials of cooling, ventilation and air
1368
      conditioning
1369
    b) Industry per osm-are: combined potentials of cooling and ventilation
1370
    c) Industrial Sites: potentials of ventilation in sites of
1371
      "Wirtschaftszweig" (WZ) 23
1372
    d) Industrial Sites: potentials of sites specified by subsectors
1373
      identified by Schmidt (https://zenodo.org/record/3613767#.YTsGwVtCRhG):
1374
      Paper, Recycled Paper, Pulp, Cement
1375
1376
    Modelled using the methods by Heitkoetter et. al.:
1377
    https://doi.org/10.1016/j.adapen.2020.100001
1378
1379
    Parameters
1380
    ----------
1381
    cts_cool_vent_ac_share: float
1382
        Share of cooling, ventilation and AC in CTS demand
1383
    ind_vent_cool_share: float
1384
        Share of cooling and ventilation in industry demand
1385
    ind_vent_share: float
1386
        Share of ventilation in industry demand in sites of WZ 23
1387
1388
    """
1389
1390
    # CTS per osm-area: cooling, ventilation and air conditioning
1391
1392
    print(" ")
1393
    print("CTS per osm-area: cooling, ventilation and air conditioning")
1394
    print(" ")
1395
1396
    dsm = cts_data_import(cts_cool_vent_ac_share)
1397
1398
    # calculate combined potentials of cooling, ventilation and air
1399
    # conditioning in CTS using combined parameters by Heitkoetter et. al.
1400
    vals = calculate_potentials(
1401
        s_flex=S_FLEX_CTS,
1402
        s_util=S_UTIL_CTS,
1403
        s_inc=S_INC_CTS,
1404
        s_dec=S_DEC_CTS,
1405
        delta_t=DELTA_T_CTS,
1406
        dsm=dsm,
1407
    )
1408
1409
    base_columns = [
1410
        "bus",
1411
        "scn_name",
1412
        "p_set",
1413
        "p_max_pu",
1414
        "p_min_pu",
1415
        "e_max_pu",
1416
        "e_min_pu",
1417
    ]
1418
1419
    cts_df = pd.concat([dsm, *vals], axis=1, ignore_index=True)
1420
    cts_df.columns = base_columns
1421
    cts_df = calc_per_unit(cts_df)
1422
1423
    print(" ")
1424
    print("industry per osm-area: cooling and ventilation")
1425
    print(" ")
1426
1427
    dsm = ind_osm_data_import_individual(ind_vent_cool_share)
1428
1429
    # calculate combined potentials of cooling and ventilation in industrial
1430
    # sector using combined parameters by Heitkoetter et al.
1431
    vals = calculate_potentials(
1432
        s_flex=S_FLEX_OSM,
1433
        s_util=S_UTIL_OSM,
1434
        s_inc=S_INC_OSM,
1435
        s_dec=S_DEC_OSM,
1436
        delta_t=DELTA_T_OSM,
1437
        dsm=dsm,
1438
    )
1439
1440
    columns = ["osm_id"] + base_columns
1441
1442
    osm_df = pd.concat([dsm, *vals], axis=1, ignore_index=True)
1443
    osm_df.columns = columns
1444
    osm_df = calc_per_unit(osm_df)
1445
1446
    # industry sites
1447
1448
    # industry sites: different applications
1449
1450
    dsm = ind_sites_data_import()
1451
1452
    print(" ")
1453
    print("industry sites: paper")
1454
    print(" ")
1455
1456
    dsm_paper = gpd.GeoDataFrame(
1457
        dsm[
1458
            dsm["application"].isin(
1459
                [
1460
                    "Graphic Paper",
1461
                    "Packing Paper and Board",
1462
                    "Hygiene Paper",
1463
                    "Technical/Special Paper and Board",
1464
                ]
1465
            )
1466
        ]
1467
    )
1468
1469
    # calculate potentials of industrial sites with paper-applications
1470
    # using parameters by Heitkoetter et al.
1471
    vals = calculate_potentials(
1472
        s_flex=S_FLEX_PAPER,
1473
        s_util=S_UTIL_PAPER,
1474
        s_inc=S_INC_PAPER,
1475
        s_dec=S_DEC_PAPER,
1476
        delta_t=DELTA_T_PAPER,
1477
        dsm=dsm_paper,
1478
    )
1479
1480
    columns = ["application", "industrial_sites_id"] + base_columns
1481
1482
    paper_df = pd.concat([dsm_paper, *vals], axis=1, ignore_index=True)
1483
    paper_df.columns = columns
1484
    paper_df = calc_per_unit(paper_df)
1485
1486
    print(" ")
1487
    print("industry sites: recycled paper")
1488
    print(" ")
1489
1490
    # calculate potentials of industrial sites with recycled paper-applications
1491
    # using parameters by Heitkoetter et. al.
1492
    dsm_recycled_paper = gpd.GeoDataFrame(
1493
        dsm[dsm["application"] == "Recycled Paper"]
1494
    )
1495
1496
    vals = calculate_potentials(
1497
        s_flex=S_FLEX_RECYCLED_PAPER,
1498
        s_util=S_UTIL_RECYCLED_PAPER,
1499
        s_inc=S_INC_RECYCLED_PAPER,
1500
        s_dec=S_DEC_RECYCLED_PAPER,
1501
        delta_t=DELTA_T_RECYCLED_PAPER,
1502
        dsm=dsm_recycled_paper,
1503
    )
1504
1505
    recycled_paper_df = pd.concat(
1506
        [dsm_recycled_paper, *vals], axis=1, ignore_index=True
1507
    )
1508
    recycled_paper_df.columns = columns
1509
    recycled_paper_df = calc_per_unit(recycled_paper_df)
1510
1511
    print(" ")
1512
    print("industry sites: pulp")
1513
    print(" ")
1514
1515
    dsm_pulp = gpd.GeoDataFrame(dsm[dsm["application"] == "Mechanical Pulp"])
1516
1517
    # calculate potentials of industrial sites with pulp-applications
1518
    # using parameters by Heitkoetter et al.
1519
    vals = calculate_potentials(
1520
        s_flex=S_FLEX_PULP,
1521
        s_util=S_UTIL_PULP,
1522
        s_inc=S_INC_PULP,
1523
        s_dec=S_DEC_PULP,
1524
        delta_t=DELTA_T_PULP,
1525
        dsm=dsm_pulp,
1526
    )
1527
1528
    pulp_df = pd.concat([dsm_pulp, *vals], axis=1, ignore_index=True)
1529
    pulp_df.columns = columns
1530
    pulp_df = calc_per_unit(pulp_df)
1531
1532
    # industry sites: cement
1533
1534
    print(" ")
1535
    print("industry sites: cement")
1536
    print(" ")
1537
1538
    dsm_cement = gpd.GeoDataFrame(dsm[dsm["application"] == "Cement Mill"])
1539
1540
    # calculate potentials of industrial sites with cement-applications
1541
    # using parameters by Heitkoetter et al.
1542
    vals = calculate_potentials(
1543
        s_flex=S_FLEX_CEMENT,
1544
        s_util=S_UTIL_CEMENT,
1545
        s_inc=S_INC_CEMENT,
1546
        s_dec=S_DEC_CEMENT,
1547
        delta_t=DELTA_T_CEMENT,
1548
        dsm=dsm_cement,
1549
    )
1550
1551
    cement_df = pd.concat([dsm_cement, *vals], axis=1, ignore_index=True)
1552
    cement_df.columns = columns
1553
    cement_df = calc_per_unit(cement_df)
1554
1555
    ind_df = pd.concat(
1556
        [paper_df, recycled_paper_df, pulp_df, cement_df], ignore_index=True
1557
    )
1558
1559
    # industry sites: ventilation in WZ23
1560
1561
    print(" ")
1562
    print("industry sites: ventilation in WZ23")
1563
    print(" ")
1564
1565
    dsm = ind_sites_vent_data_import_individual(ind_vent_share, wz=WZ)
1566
1567
    # drop entries of Cement Mills whose DSM-potentials have already been
1568
    # modelled
1569
    cement = np.unique(dsm_cement["bus"].values)
1570
    index_names = np.array(dsm[dsm["bus"].isin(cement)].index)
1571
    dsm.drop(index_names, inplace=True)
1572
1573
    # calculate potentials of ventialtion in industrial sites of WZ 23
1574
    # using parameters by Heitkoetter et al.
1575
    vals = calculate_potentials(
1576
        s_flex=S_FLEX_WZ,
1577
        s_util=S_UTIL_WZ,
1578
        s_inc=S_INC_WZ,
1579
        s_dec=S_DEC_WZ,
1580
        delta_t=DELTA_T_WZ,
1581
        dsm=dsm,
1582
    )
1583
1584
    columns = ["site_id"] + base_columns
1585
1586
    ind_sites_df = pd.concat([dsm, *vals], axis=1, ignore_index=True)
1587
    ind_sites_df.columns = columns
1588
    ind_sites_df = calc_per_unit(ind_sites_df)
1589
1590
    # create tables
1591
    create_table(
1592
        df=cts_df, table=EgonEtragoElectricityCtsDsmTimeseries, engine=CON
1593
    )
1594
    create_table(
1595
        df=osm_df,
1596
        table=EgonOsmIndLoadCurvesIndividualDsmTimeseries,
1597
        engine=CON,
1598
    )
1599
    create_table(
1600
        df=ind_df,
1601
        table=EgonDemandregioSitesIndElectricityDsmTimeseries,
1602
        engine=CON,
1603
    )
1604
    create_table(
1605
        df=ind_sites_df,
1606
        table=EgonSitesIndLoadCurvesIndividualDsmTimeseries,
1607
        engine=CON,
1608
    )
1609
1610
1611
def dsm_cts_ind_processing():
1612
    dsm_cts_ind()
1613
1614
    dsm_cts_ind_individual()
1615