Passed
Pull Request — dev (#1122)
by
unknown
04:34
created

data.datasets.DSM_cts_ind   C

Complexity

Total Complexity 53

Size/Duplication

Total Lines 1789
Duplicated Lines 11.29 %

Importance

Changes 0
Metric Value
wmc 53
eloc 942
dl 202
loc 1789
rs 6.618
c 0
b 0
f 0

19 Functions

Rating   Name   Duplication   Size   Complexity  
A ind_sites_vent_data_import() 35 35 2
A ind_sites_vent_data_import_individual() 35 35 2
A delete_dsm_entries() 0 65 1
A dsm_cts_ind_processing() 0 6 1
A create_table() 0 11 1
B data_export() 0 110 1
A ind_sites_data_import() 0 21 1
C calculate_potentials() 0 91 9
A div_list() 0 2 1
A ind_osm_data_import_individual() 34 34 2
A cts_data_import() 0 40 2
A ind_osm_data_import() 32 34 2
B dsm_cts_ind() 0 296 1
A relate_to_schmidt_sites() 0 25 1
D create_dsm_components() 0 169 9
A calc_ind_site_timeseries() 0 71 2
B dsm_cts_ind_individual() 0 247 1
C aggregate_components() 0 88 11
B add_metadata_individual() 0 186 2

1 Method

Rating   Name   Duplication   Size   Complexity  
A DsmPotential.__init__() 0 6 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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