ind_sites_vent_data_import()   A
last analyzed

Complexity

Conditions 2

Size

Total Lines 37
Code Lines 11

Duplication

Lines 35
Ratio 94.59 %

Importance

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