Passed
Push — dev ( 7cf077...0e9721 )
by
unknown
07:11 queued 04:45
created

data.datasets.DSM_cts_ind.create_table()   A

Complexity

Conditions 1

Size

Total Lines 11
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 9
dl 0
loc 11
rs 9.95
c 0
b 0
f 0
cc 1
nop 3
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.5"
138
139
    def __init__(self, dependencies):
140
        super().__init__(
141
            name=self.name,
142
            version=self.version,
143
            dependencies=self.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(meta_metadata()["metadataVersion"])()
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
    ----------
416
    cts_share: float
417
        Share of cooling, ventilation and AC in CTS demand
418
    """
419
420
    # import load data
421
422
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
423
        "cts_loadcurves"
424
    ]
425
426
    ts = db.select_dataframe(
427
        f"""SELECT bus_id, scn_name, p_set FROM
428
        {sources['schema']}.{sources['table']}"""
429
    )
430
431
    # identify relevant columns and prepare df to be returned
432
433
    dsm = pd.DataFrame(index=ts.index)
434
435
    dsm["bus"] = ts["bus_id"].copy()
436
    dsm["scn_name"] = ts["scn_name"].copy()
437
    dsm["p_set"] = ts["p_set"].copy()
438
439
    # calculate share of timeseries for air conditioning, cooling and
440
    # ventilation out of CTS-data
441
442
    timeseries = dsm["p_set"].copy()
443
444
    for index, liste in timeseries.items():
445
        share = [float(item) * cts_cool_vent_ac_share for item in liste]
446
        timeseries.loc[index] = share
447
448
    dsm["p_set"] = timeseries.copy()
449
450
    return dsm
451
452
453 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...
454
    """
455
    Import industry data per osm-area necessary to identify DSM-potential.
456
        ----------
457
    ind_share: float
458
        Share of considered application in industry demand
459
    """
460
461
    # import load data
462
463
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
464
        "ind_osm_loadcurves"
465
    ]
466
467
    dsm = db.select_dataframe(
468
        f"""
469
        SELECT bus, scn_name, p_set FROM
470
        {sources['schema']}.{sources['table']}
471
        """
472
    )
473
474
    # calculate share of timeseries for cooling and ventilation out of
475
    # industry-data
476
477
    timeseries = dsm["p_set"].copy()
478
479
    for index, liste in timeseries.items():
480
        share = [float(item) * ind_vent_cool_share for item in liste]
481
482
        timeseries.loc[index] = share
483
484
    dsm["p_set"] = timeseries.copy()
485
486
    return dsm
487
488
489 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...
490
    """
491
    Import industry data per osm-area necessary to identify DSM-potential.
492
        ----------
493
    ind_share: float
494
        Share of considered application in industry demand
495
    """
496
497
    # import load data
498
499
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
500
        "ind_osm_loadcurves_individual"
501
    ]
502
503
    dsm = db.select_dataframe(
504
        f"""
505
        SELECT osm_id, bus_id as bus, scn_name, p_set FROM
506
        {sources['schema']}.{sources['table']}
507
        """
508
    )
509
510
    # calculate share of timeseries for cooling and ventilation out of
511
    # industry-data
512
513
    timeseries = dsm["p_set"].copy()
514
515
    for index, liste in timeseries.items():
516
        share = [float(item) * ind_vent_cool_share for item in liste]
517
518
        timeseries.loc[index] = share
519
520
    dsm["p_set"] = timeseries.copy()
521
522
    return dsm
523
524
525 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...
526
    """
527
    Import industry sites necessary to identify DSM-potential.
528
        ----------
529
    ind_vent_share: float
530
        Share of considered application in industry demand
531
    wz: int
532
        Wirtschaftszweig to be considered within industry sites
533
    """
534
535
    # import load data
536
537
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
538
        "ind_sites_loadcurves"
539
    ]
540
541
    dsm = db.select_dataframe(
542
        f"""
543
        SELECT bus, scn_name, p_set FROM
544
        {sources['schema']}.{sources['table']}
545
        WHERE wz = {wz}
546
        """
547
    )
548
549
    # calculate share of timeseries for ventilation
550
551
    timeseries = dsm["p_set"].copy()
552
553
    for index, liste in timeseries.items():
554
        share = [float(item) * ind_vent_share for item in liste]
555
        timeseries.loc[index] = share
556
557
    dsm["p_set"] = timeseries.copy()
558
559
    return dsm
560
561
562 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...
563
    """
564
    Import industry sites necessary to identify DSM-potential.
565
        ----------
566
    ind_vent_share: float
567
        Share of considered application in industry demand
568
    wz: int
569
        Wirtschaftszweig to be considered within industry sites
570
    """
571
572
    # import load data
573
574
    sources = config.datasets()["DSM_CTS_industry"]["sources"][
575
        "ind_sites_loadcurves_individual"
576
    ]
577
578
    dsm = db.select_dataframe(
579
        f"""
580
        SELECT site_id, bus_id as bus, scn_name, p_set FROM
581
        {sources['schema']}.{sources['table']}
582
        WHERE wz = {wz}
583
        """
584
    )
585
586
    # calculate share of timeseries for ventilation
587
588
    timeseries = dsm["p_set"].copy()
589
590
    for index, liste in timeseries.items():
591
        share = [float(item) * ind_vent_share for item in liste]
592
        timeseries.loc[index] = share
593
594
    dsm["p_set"] = timeseries.copy()
595
596
    return dsm
597
598
599
def calc_ind_site_timeseries(scenario):
600
    # calculate timeseries per site
601
    # -> using code from egon.data.datasets.industry.temporal:
602
    # calc_load_curves_ind_sites
603
604
    # select demands per industrial site including the subsector information
605
    source1 = config.datasets()["DSM_CTS_industry"]["sources"][
606
        "demandregio_ind_sites"
607
    ]
608
609
    demands_ind_sites = db.select_dataframe(
610
        f"""SELECT industrial_sites_id, wz, demand
611
            FROM {source1['schema']}.{source1['table']}
612
            WHERE scenario = '{scenario}'
613
            AND demand > 0
614
            """
615
    ).set_index(["industrial_sites_id"])
616
617
    # select industrial sites as demand_areas from database
618
    source2 = config.datasets()["DSM_CTS_industry"]["sources"]["ind_sites"]
619
620
    demand_area = db.select_geodataframe(
621
        f"""SELECT id, geom, subsector FROM
622
            {source2['schema']}.{source2['table']}""",
623
        index_col="id",
624
        geom_col="geom",
625
        epsg=3035,
626
    )
627
628
    # replace entries to bring it in line with demandregio's subsector
629
    # definitions
630
    demands_ind_sites.replace(1718, 17, inplace=True)
631
    share_wz_sites = demands_ind_sites.copy()
632
633
    # create additional df on wz_share per industrial site, which is always set
634
    # to one as the industrial demand per site is subsector specific
635
    share_wz_sites.demand = 1
636
    share_wz_sites.reset_index(inplace=True)
637
638
    share_transpose = pd.DataFrame(
639
        index=share_wz_sites.industrial_sites_id.unique(),
640
        columns=share_wz_sites.wz.unique(),
641
    )
642
    share_transpose.index.rename("industrial_sites_id", inplace=True)
643
    for wz in share_transpose.columns:
644
        share_transpose[wz] = (
645
            share_wz_sites[share_wz_sites.wz == wz]
646
            .set_index("industrial_sites_id")
647
            .demand
648
        )
649
650
    # calculate load curves
651
    load_curves = calc_load_curve(share_transpose, demands_ind_sites["demand"])
652
653
    # identify bus per industrial site
654
    curves_bus = identify_bus(load_curves, demand_area)
655
    curves_bus.index = curves_bus["id"].astype(int)
656
657
    # initialize dataframe to be returned
658
659
    ts = pd.DataFrame(
660
        data=curves_bus["bus_id"], index=curves_bus["id"].astype(int)
661
    )
662
    ts["scenario_name"] = scenario
663
    curves_bus.drop({"id", "bus_id", "geom"}, axis=1, inplace=True)
664
    ts["p_set"] = curves_bus.values.tolist()
665
666
    # add subsector to relate to Schmidt's tables afterwards
667
    ts["application"] = demand_area["subsector"]
668
669
    return ts
670
671
672
def relate_to_schmidt_sites(dsm):
673
    # import industrial sites by Schmidt
674
675
    source = config.datasets()["DSM_CTS_industry"]["sources"][
676
        "ind_sites_schmidt"
677
    ]
678
679
    schmidt = db.select_dataframe(
680
        f"""SELECT application, geom FROM
681
            {source['schema']}.{source['table']}"""
682
    )
683
684
    # relate calculated timeseries (dsm) to Schmidt's industrial sites
685
686
    applications = np.unique(schmidt["application"])
687
    dsm = pd.DataFrame(dsm[dsm["application"].isin(applications)])
688
689
    # initialize dataframe to be returned
690
691
    dsm.rename(
692
        columns={"scenario_name": "scn_name", "bus_id": "bus"},
693
        inplace=True,
694
    )
695
696
    return dsm
697
698
699
def ind_sites_data_import():
700
    """
701
    Import industry sites data necessary to identify DSM-potential.
702
    """
703
    # calculate timeseries per site
704
705
    # scenario eGon2035
706
    dsm_2035 = calc_ind_site_timeseries("eGon2035")
707
    dsm_2035.reset_index(inplace=True)
708
    # scenario eGon100RE
709
    dsm_100 = calc_ind_site_timeseries("eGon100RE")
710
    dsm_100.reset_index(inplace=True)
711
    # bring df for both scenarios together
712
    dsm_100.index = range(len(dsm_2035), (len(dsm_2035) + len((dsm_100))))
713
    dsm = dsm_2035.append(dsm_100)
714
715
    # relate calculated timeseries to Schmidt's industrial sites
716
717
    dsm = relate_to_schmidt_sites(dsm)
718
719
    return dsm[["application", "id", "bus", "scn_name", "p_set"]]
720
721
722
def calculate_potentials(s_flex, s_util, s_inc, s_dec, delta_t, dsm):
723
    """
724
    Calculate DSM-potential per bus using the methods by Heitkoetter et. al.:
725
        https://doi.org/10.1016/j.adapen.2020.100001
726
    Parameters
727
        ----------
728
    s_flex: float
729
        Feasability factor to account for socio-technical restrictions
730
    s_util: float
731
        Average annual utilisation rate
732
    s_inc: float
733
        Shiftable share of installed capacity up to which load can be
734
        increased considering technical limitations
735
    s_dec: float
736
        Shiftable share of installed capacity up to which load can be
737
        decreased considering technical limitations
738
    delta_t: int
739
        Maximum shift duration in hours
740
    dsm: DataFrame
741
        List of existing buses with DSM-potential including timeseries of
742
        loads
743
    """
744
745
    # copy relevant timeseries
746
    timeseries = dsm["p_set"].copy()
747
748
    # calculate scheduled load L(t)
749
750
    scheduled_load = timeseries.copy()
751
752
    for index, liste in scheduled_load.items():
753
        share = [item * s_flex for item in liste]
754
        scheduled_load.loc[index] = share
755
756
    # calculate maximum capacity Lambda
757
758
    # calculate energy annual requirement
759
    energy_annual = pd.Series(index=timeseries.index, dtype=float)
760
    for index, liste in timeseries.items():
761
        energy_annual.loc[index] = sum(liste)
762
763
    # calculate Lambda
764
    lam = (energy_annual * s_flex) / (8760 * s_util)
765
766
    # calculation of P_max and P_min
767
768
    # P_max
769
    p_max = scheduled_load.copy()
770
    for index, liste in scheduled_load.items():
771
        lamb = lam.loc[index]
772
        p_max.loc[index] = [max(0, lamb * s_inc - item) for item in liste]
773
774
    # P_min
775
    p_min = scheduled_load.copy()
776
    for index, liste in scheduled_load.items():
777
        lamb = lam.loc[index]
778
        p_min.loc[index] = [min(0, -(item - lamb * s_dec)) for item in liste]
779
780
    # calculation of E_max and E_min
781
782
    e_max = scheduled_load.copy()
783
    e_min = scheduled_load.copy()
784
785
    for index, liste in scheduled_load.items():
786
        emin = []
787
        emax = []
788
        for i in range(len(liste)):
789
            if i + delta_t > len(liste):
790
                emax.append(
791
                    (sum(liste[i:]) + sum(liste[: delta_t - (len(liste) - i)]))
792
                )
793
            else:
794
                emax.append(sum(liste[i : i + delta_t]))
795
            if i - delta_t < 0:
796
                emin.append(
797
                    (
798
                        -1
799
                        * (
800
                            (
801
                                sum(liste[:i])
802
                                + sum(liste[len(liste) - delta_t + i :])
803
                            )
804
                        )
805
                    )
806
                )
807
            else:
808
                emin.append(-1 * sum(liste[i - delta_t : i]))
809
        e_max.loc[index] = emax
810
        e_min.loc[index] = emin
811
812
    return p_max, p_min, e_max, e_min
813
814
815
def create_dsm_components(
816
    con, p_max, p_min, e_max, e_min, dsm, export_aggregated=True
817
):
818
    """
819
    Create components representing DSM.
820
    Parameters
821
        ----------
822
    con :
823
        Connection to database
824
    p_max: DataFrame
825
        Timeseries identifying maximum load increase
826
    p_min: DataFrame
827
        Timeseries identifying maximum load decrease
828
    e_max: DataFrame
829
        Timeseries identifying maximum energy amount to be preponed
830
    e_min: DataFrame
831
        Timeseries identifying maximum energy amount to be postponed
832
    dsm: DataFrame
833
        List of existing buses with DSM-potential including timeseries of loads
834
    """
835
    if not export_aggregated:
836
        # calculate P_nom and P per unit
837
        p_nom = pd.Series(index=p_max.index, dtype=float)
838
        for index, row in p_max.items():
839
            nom = max(max(row), abs(min(p_min.loc[index])))
840
            p_nom.loc[index] = nom
841
            new = [element / nom for element in row]
842
            p_max.loc[index] = new
843
            new = [element / nom for element in p_min.loc[index]]
844
            p_min.loc[index] = new
845
846
        # calculate E_nom and E per unit
847
        e_nom = pd.Series(index=p_min.index, dtype=float)
848
        for index, row in e_max.items():
849
            nom = max(max(row), abs(min(e_min.loc[index])))
850
            e_nom.loc[index] = nom
851
            new = [element / nom for element in row]
852
            e_max.loc[index] = new
853
            new = [element / nom for element in e_min.loc[index]]
854
            e_min.loc[index] = new
855
856
    # add DSM-buses to "original" buses
857
    dsm_buses = gpd.GeoDataFrame(index=dsm.index)
858
    dsm_buses["original_bus"] = dsm["bus"].copy()
859
    dsm_buses["scn_name"] = dsm["scn_name"].copy()
860
861
    # get original buses and add copy of relevant information
862
    target1 = config.datasets()["DSM_CTS_industry"]["targets"]["bus"]
863
    original_buses = db.select_geodataframe(
864
        f"""SELECT bus_id, v_nom, scn_name, x, y, geom FROM
865
            {target1['schema']}.{target1['table']}""",
866
        geom_col="geom",
867
        epsg=4326,
868
    )
869
870
    # copy relevant information from original buses to DSM-buses
871
    dsm_buses["index"] = dsm_buses.index
872
    originals = original_buses[
873
        original_buses["bus_id"].isin(np.unique(dsm_buses["original_bus"]))
874
    ]
875
    dsm_buses = originals.merge(
876
        dsm_buses,
877
        left_on=["bus_id", "scn_name"],
878
        right_on=["original_bus", "scn_name"],
879
    )
880
    dsm_buses.index = dsm_buses["index"]
881
    dsm_buses.drop(["bus_id", "index"], axis=1, inplace=True)
882
883
    # new bus_ids for DSM-buses
884
    max_id = original_buses["bus_id"].max()
885
    if np.isnan(max_id):
886
        max_id = 0
887
    dsm_id = max_id + 1
888
    bus_id = pd.Series(index=dsm_buses.index, dtype=int)
889
890
    # Get number of DSM buses for both scenarios
891
    rows_per_scenario = (
892
        dsm_buses.groupby("scn_name").count().original_bus.to_dict()
893
    )
894
895
    # Assignment of DSM ids
896
    bus_id.iloc[: rows_per_scenario.get("eGon2035", 0)] = range(
897
        dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0)
898
    )
899
900
    bus_id.iloc[
901
        rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get(
902
            "eGon2035", 0
903
        )
904
        + rows_per_scenario.get("eGon100RE", 0)
905
    ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0))
906
907
    dsm_buses["bus_id"] = bus_id
908
909
    # add links from "orignal" buses to DSM-buses
910
911
    dsm_links = pd.DataFrame(index=dsm_buses.index)
912
    dsm_links["original_bus"] = dsm_buses["original_bus"].copy()
913
    dsm_links["dsm_bus"] = dsm_buses["bus_id"].copy()
914
    dsm_links["scn_name"] = dsm_buses["scn_name"].copy()
915
916
    # set link_id
917
    target2 = config.datasets()["DSM_CTS_industry"]["targets"]["link"]
918
    sql = f"""SELECT link_id FROM {target2['schema']}.{target2['table']}"""
919
    max_id = pd.read_sql_query(sql, con)
920
    max_id = max_id["link_id"].max()
921
    if np.isnan(max_id):
922
        max_id = 0
923
    dsm_id = max_id + 1
924
    link_id = pd.Series(index=dsm_buses.index, dtype=int)
925
926
    # Assignment of link ids
927
    link_id.iloc[: rows_per_scenario.get("eGon2035", 0)] = range(
928
        dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0)
929
    )
930
931
    link_id.iloc[
932
        rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get(
933
            "eGon2035", 0
934
        )
935
        + rows_per_scenario.get("eGon100RE", 0)
936
    ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0))
937
938
    dsm_links["link_id"] = link_id
939
940
    # add calculated timeseries to df to be returned
941
    if not export_aggregated:
942
        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 835 is False. Are you sure this can never be the case?
Loading history...
943
    dsm_links["p_min"] = p_min
944
    dsm_links["p_max"] = p_max
945
946
    # add DSM-stores
947
948
    dsm_stores = pd.DataFrame(index=dsm_buses.index)
949
    dsm_stores["bus"] = dsm_buses["bus_id"].copy()
950
    dsm_stores["scn_name"] = dsm_buses["scn_name"].copy()
951
    dsm_stores["original_bus"] = dsm_buses["original_bus"].copy()
952
953
    # set store_id
954
    target3 = config.datasets()["DSM_CTS_industry"]["targets"]["store"]
955
    sql = f"""SELECT store_id FROM {target3['schema']}.{target3['table']}"""
956
    max_id = pd.read_sql_query(sql, con)
957
    max_id = max_id["store_id"].max()
958
    if np.isnan(max_id):
959
        max_id = 0
960
    dsm_id = max_id + 1
961
    store_id = pd.Series(index=dsm_buses.index, dtype=int)
962
963
    # Assignment of store ids
964
    store_id.iloc[: rows_per_scenario.get("eGon2035", 0)] = range(
965
        dsm_id, dsm_id + rows_per_scenario.get("eGon2035", 0)
966
    )
967
968
    store_id.iloc[
969
        rows_per_scenario.get("eGon2035", 0) : rows_per_scenario.get(
970
            "eGon2035", 0
971
        )
972
        + rows_per_scenario.get("eGon100RE", 0)
973
    ] = range(dsm_id, dsm_id + rows_per_scenario.get("eGon100RE", 0))
974
975
    dsm_stores["store_id"] = store_id
976
977
    # add calculated timeseries to df to be returned
978
    if not export_aggregated:
979
        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 835 is False. Are you sure this can never be the case?
Loading history...
980
    dsm_stores["e_min"] = e_min
981
    dsm_stores["e_max"] = e_max
982
983
    return dsm_buses, dsm_links, dsm_stores
984
985
986
def aggregate_components(df_dsm_buses, df_dsm_links, df_dsm_stores):
987
    # aggregate buses
988
989
    grouper = [df_dsm_buses.original_bus, df_dsm_buses.scn_name]
990
991
    df_dsm_buses = df_dsm_buses.groupby(grouper).first()
992
993
    df_dsm_buses.reset_index(inplace=True)
994
    df_dsm_buses.sort_values("scn_name", inplace=True)
995
996
    # aggregate links
997
998
    df_dsm_links["p_max"] = df_dsm_links["p_max"].apply(lambda x: np.array(x))
999
    df_dsm_links["p_min"] = df_dsm_links["p_min"].apply(lambda x: np.array(x))
1000
1001
    grouper = [df_dsm_links.original_bus, df_dsm_links.scn_name]
1002
1003
    p_max = df_dsm_links.groupby(grouper)["p_max"].apply(np.sum)
1004
    p_min = df_dsm_links.groupby(grouper)["p_min"].apply(np.sum)
1005
1006
    df_dsm_links = df_dsm_links.groupby(grouper).first()
1007
    df_dsm_links.p_max = p_max
1008
    df_dsm_links.p_min = p_min
1009
1010
    df_dsm_links.reset_index(inplace=True)
1011
    df_dsm_links.sort_values("scn_name", inplace=True)
1012
1013
    # calculate P_nom and P per unit
1014
    for index, row in df_dsm_links.iterrows():
1015
        nom = max(max(row.p_max), abs(min(row.p_min)))
1016
        df_dsm_links.at[index, "p_nom"] = nom
1017
1018
    df_dsm_links["p_max"] = df_dsm_links["p_max"] / df_dsm_links["p_nom"]
1019
    df_dsm_links["p_min"] = df_dsm_links["p_min"] / df_dsm_links["p_nom"]
1020
1021
    df_dsm_links["p_max"] = df_dsm_links["p_max"].apply(lambda x: list(x))
1022
    df_dsm_links["p_min"] = df_dsm_links["p_min"].apply(lambda x: list(x))
1023
1024
    # aggregate stores
1025
    df_dsm_stores["e_max"] = df_dsm_stores["e_max"].apply(
1026
        lambda x: np.array(x)
1027
    )
1028
    df_dsm_stores["e_min"] = df_dsm_stores["e_min"].apply(
1029
        lambda x: np.array(x)
1030
    )
1031
1032
    grouper = [df_dsm_stores.original_bus, df_dsm_stores.scn_name]
1033
1034
    e_max = df_dsm_stores.groupby(grouper)["e_max"].apply(np.sum)
1035
    e_min = df_dsm_stores.groupby(grouper)["e_min"].apply(np.sum)
1036
1037
    df_dsm_stores = df_dsm_stores.groupby(grouper).first()
1038
    df_dsm_stores.e_max = e_max
1039
    df_dsm_stores.e_min = e_min
1040
1041
    df_dsm_stores.reset_index(inplace=True)
1042
    df_dsm_stores.sort_values("scn_name", inplace=True)
1043
1044
    # calculate E_nom and E per unit
1045
    for index, row in df_dsm_stores.iterrows():
1046
        nom = max(max(row.e_max), abs(min(row.e_min)))
1047
        df_dsm_stores.at[index, "e_nom"] = nom
1048
1049
    df_dsm_stores["e_max"] = df_dsm_stores["e_max"] / df_dsm_stores["e_nom"]
1050
    df_dsm_stores["e_min"] = df_dsm_stores["e_min"] / df_dsm_stores["e_nom"]
1051
1052
    df_dsm_stores["e_max"] = df_dsm_stores["e_max"].apply(lambda x: list(x))
1053
    df_dsm_stores["e_min"] = df_dsm_stores["e_min"].apply(lambda x: list(x))
1054
1055
    # select new bus_ids for aggregated buses and add to links and stores
1056
    bus_id = db.next_etrago_id("Bus") + df_dsm_buses.index
1057
1058
    df_dsm_buses["bus_id"] = bus_id
1059
    df_dsm_links["dsm_bus"] = bus_id
1060
    df_dsm_stores["bus"] = bus_id
1061
1062
    # select new link_ids for aggregated links
1063
    link_id = db.next_etrago_id("Link") + df_dsm_links.index
1064
1065
    df_dsm_links["link_id"] = link_id
1066
1067
    # select new store_ids to aggregated stores
1068
1069
    store_id = db.next_etrago_id("Store") + df_dsm_stores.index
1070
1071
    df_dsm_stores["store_id"] = store_id
1072
1073
    return df_dsm_buses, df_dsm_links, df_dsm_stores
1074
1075
1076
def data_export(dsm_buses, dsm_links, dsm_stores, carrier):
1077
    """
1078
    Export new components to database.
1079
1080
    Parameters
1081
    ----------
1082
    dsm_buses: DataFrame
1083
        Buses representing locations of DSM-potential
1084
    dsm_links: DataFrame
1085
        Links connecting DSM-buses and DSM-stores
1086
    dsm_stores: DataFrame
1087
        Stores representing DSM-potential
1088
    carrier: str
1089
        Remark to be filled in column 'carrier' identifying DSM-potential
1090
    """
1091
1092
    targets = config.datasets()["DSM_CTS_industry"]["targets"]
1093
1094
    # dsm_buses
1095
1096
    insert_buses = gpd.GeoDataFrame(
1097
        index=dsm_buses.index,
1098
        data=dsm_buses["geom"],
1099
        geometry="geom",
1100
        crs=dsm_buses.crs,
1101
    )
1102
    insert_buses["scn_name"] = dsm_buses["scn_name"]
1103
    insert_buses["bus_id"] = dsm_buses["bus_id"]
1104
    insert_buses["v_nom"] = dsm_buses["v_nom"]
1105
    insert_buses["carrier"] = carrier
1106
    insert_buses["x"] = dsm_buses["x"]
1107
    insert_buses["y"] = dsm_buses["y"]
1108
1109
    # insert into database
1110
    insert_buses.to_postgis(
1111
        targets["bus"]["table"],
1112
        con=db.engine(),
1113
        schema=targets["bus"]["schema"],
1114
        if_exists="append",
1115
        index=False,
1116
        dtype={"geom": "geometry"},
1117
    )
1118
1119
    # dsm_links
1120
1121
    insert_links = pd.DataFrame(index=dsm_links.index)
1122
    insert_links["scn_name"] = dsm_links["scn_name"]
1123
    insert_links["link_id"] = dsm_links["link_id"]
1124
    insert_links["bus0"] = dsm_links["original_bus"]
1125
    insert_links["bus1"] = dsm_links["dsm_bus"]
1126
    insert_links["carrier"] = carrier
1127
    insert_links["p_nom"] = dsm_links["p_nom"]
1128
1129
    # insert into database
1130
    insert_links.to_sql(
1131
        targets["link"]["table"],
1132
        con=db.engine(),
1133
        schema=targets["link"]["schema"],
1134
        if_exists="append",
1135
        index=False,
1136
    )
1137
1138
    insert_links_timeseries = pd.DataFrame(index=dsm_links.index)
1139
    insert_links_timeseries["scn_name"] = dsm_links["scn_name"]
1140
    insert_links_timeseries["link_id"] = dsm_links["link_id"]
1141
    insert_links_timeseries["p_min_pu"] = dsm_links["p_min"]
1142
    insert_links_timeseries["p_max_pu"] = dsm_links["p_max"]
1143
    insert_links_timeseries["temp_id"] = 1
1144
1145
    # insert into database
1146
    insert_links_timeseries.to_sql(
1147
        targets["link_timeseries"]["table"],
1148
        con=db.engine(),
1149
        schema=targets["link_timeseries"]["schema"],
1150
        if_exists="append",
1151
        index=False,
1152
    )
1153
1154
    # dsm_stores
1155
1156
    insert_stores = pd.DataFrame(index=dsm_stores.index)
1157
    insert_stores["scn_name"] = dsm_stores["scn_name"]
1158
    insert_stores["store_id"] = dsm_stores["store_id"]
1159
    insert_stores["bus"] = dsm_stores["bus"]
1160
    insert_stores["carrier"] = carrier
1161
    insert_stores["e_nom"] = dsm_stores["e_nom"]
1162
1163
    # insert into database
1164
    insert_stores.to_sql(
1165
        targets["store"]["table"],
1166
        con=db.engine(),
1167
        schema=targets["store"]["schema"],
1168
        if_exists="append",
1169
        index=False,
1170
    )
1171
1172
    insert_stores_timeseries = pd.DataFrame(index=dsm_stores.index)
1173
    insert_stores_timeseries["scn_name"] = dsm_stores["scn_name"]
1174
    insert_stores_timeseries["store_id"] = dsm_stores["store_id"]
1175
    insert_stores_timeseries["e_min_pu"] = dsm_stores["e_min"]
1176
    insert_stores_timeseries["e_max_pu"] = dsm_stores["e_max"]
1177
    insert_stores_timeseries["temp_id"] = 1
1178
1179
    # insert into database
1180
    insert_stores_timeseries.to_sql(
1181
        targets["store_timeseries"]["table"],
1182
        con=db.engine(),
1183
        schema=targets["store_timeseries"]["schema"],
1184
        if_exists="append",
1185
        index=False,
1186
    )
1187
1188
1189
def delete_dsm_entries(carrier):
1190
    """
1191
    Deletes DSM-components from database if they already exist before creating
1192
    new ones.
1193
1194
    Parameters
1195
        ----------
1196
     carrier: str
1197
        Remark in column 'carrier' identifying DSM-potential
1198
    """
1199
1200
    targets = config.datasets()["DSM_CTS_industry"]["targets"]
1201
1202
    # buses
1203
1204
    sql = (
1205
        f"DELETE FROM {targets['bus']['schema']}.{targets['bus']['table']} b "
1206
        f"WHERE (b.carrier LIKE '{carrier}');"
1207
    )
1208
    db.execute_sql(sql)
1209
1210
    # links
1211
1212
    sql = f"""
1213
        DELETE FROM {targets["link_timeseries"]["schema"]}.
1214
        {targets["link_timeseries"]["table"]} t
1215
        WHERE t.link_id IN
1216
        (
1217
            SELECT l.link_id FROM {targets["link"]["schema"]}.
1218
            {targets["link"]["table"]} l
1219
            WHERE l.carrier LIKE '{carrier}'
1220
        );
1221
        """
1222
1223
    db.execute_sql(sql)
1224
1225
    sql = f"""
1226
        DELETE FROM {targets["link"]["schema"]}.
1227
        {targets["link"]["table"]} l
1228
        WHERE (l.carrier LIKE '{carrier}');
1229
        """
1230
1231
    db.execute_sql(sql)
1232
1233
    # stores
1234
1235
    sql = f"""
1236
        DELETE FROM {targets["store_timeseries"]["schema"]}.
1237
        {targets["store_timeseries"]["table"]} t
1238
        WHERE t.store_id IN
1239
        (
1240
            SELECT s.store_id FROM {targets["store"]["schema"]}.
1241
            {targets["store"]["table"]} s
1242
            WHERE s.carrier LIKE '{carrier}'
1243
        );
1244
        """
1245
1246
    db.execute_sql(sql)
1247
1248
    sql = f"""
1249
        DELETE FROM {targets["store"]["schema"]}.{targets["store"]["table"]} s
1250
        WHERE (s.carrier LIKE '{carrier}');
1251
        """
1252
1253
    db.execute_sql(sql)
1254
1255
1256
def dsm_cts_ind(
1257
    con=db.engine(),
1258
    cts_cool_vent_ac_share=0.22,
1259
    ind_vent_cool_share=0.039,
1260
    ind_vent_share=0.017,
1261
):
1262
    """
1263
    Execute methodology to create and implement components for DSM considering
1264
    a) CTS per osm-area: combined potentials of cooling, ventilation and air
1265
      conditioning
1266
    b) Industry per osm-are: combined potentials of cooling and ventilation
1267
    c) Industrial Sites: potentials of ventilation in sites of
1268
      "Wirtschaftszweig" (WZ) 23
1269
    d) Industrial Sites: potentials of sites specified by subsectors
1270
      identified by Schmidt (https://zenodo.org/record/3613767#.YTsGwVtCRhG):
1271
      Paper, Recycled Paper, Pulp, Cement
1272
1273
    Modelled using the methods by Heitkoetter et. al.:
1274
    https://doi.org/10.1016/j.adapen.2020.100001
1275
1276
    Parameters
1277
    ----------
1278
    con :
1279
        Connection to database
1280
    cts_cool_vent_ac_share: float
1281
        Share of cooling, ventilation and AC in CTS demand
1282
    ind_vent_cool_share: float
1283
        Share of cooling and ventilation in industry demand
1284
    ind_vent_share: float
1285
        Share of ventilation in industry demand in sites of WZ 23
1286
1287
    """
1288
1289
    # CTS per osm-area: cooling, ventilation and air conditioning
1290
1291
    print(" ")
1292
    print("CTS per osm-area: cooling, ventilation and air conditioning")
1293
    print(" ")
1294
1295
    dsm = cts_data_import(cts_cool_vent_ac_share)
1296
1297
    # calculate combined potentials of cooling, ventilation and air
1298
    # conditioning in CTS using combined parameters by Heitkoetter et. al.
1299
    p_max, p_min, e_max, e_min = calculate_potentials(
1300
        s_flex=S_FLEX_CTS,
1301
        s_util=S_UTIL_CTS,
1302
        s_inc=S_INC_CTS,
1303
        s_dec=S_DEC_CTS,
1304
        delta_t=DELTA_T_CTS,
1305
        dsm=dsm,
1306
    )
1307
1308
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1309
        con, p_max, p_min, e_max, e_min, dsm
1310
    )
1311
1312
    df_dsm_buses = dsm_buses.copy()
1313
    df_dsm_links = dsm_links.copy()
1314
    df_dsm_stores = dsm_stores.copy()
1315
1316
    # industry per osm-area: cooling and ventilation
1317
1318
    print(" ")
1319
    print("industry per osm-area: cooling and ventilation")
1320
    print(" ")
1321
1322
    dsm = ind_osm_data_import(ind_vent_cool_share)
1323
1324
    # calculate combined potentials of cooling and ventilation in industrial
1325
    # sector using combined parameters by Heitkoetter et. al.
1326
    p_max, p_min, e_max, e_min = calculate_potentials(
1327
        s_flex=S_FLEX_OSM,
1328
        s_util=S_UTIL_OSM,
1329
        s_inc=S_INC_OSM,
1330
        s_dec=S_DEC_OSM,
1331
        delta_t=DELTA_T_OSM,
1332
        dsm=dsm,
1333
    )
1334
1335
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1336
        con, p_max, p_min, e_max, e_min, dsm
1337
    )
1338
1339
    df_dsm_buses = gpd.GeoDataFrame(
1340
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1341
        crs="EPSG:4326",
1342
    )
1343
    df_dsm_links = pd.DataFrame(
1344
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1345
    )
1346
    df_dsm_stores = pd.DataFrame(
1347
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1348
    )
1349
1350
    # industry sites
1351
1352
    # industry sites: different applications
1353
1354
    dsm = ind_sites_data_import()
1355
1356
    print(" ")
1357
    print("industry sites: paper")
1358
    print(" ")
1359
1360
    dsm_paper = gpd.GeoDataFrame(
1361
        dsm[
1362
            dsm["application"].isin(
1363
                [
1364
                    "Graphic Paper",
1365
                    "Packing Paper and Board",
1366
                    "Hygiene Paper",
1367
                    "Technical/Special Paper and Board",
1368
                ]
1369
            )
1370
        ]
1371
    )
1372
1373
    # calculate potentials of industrial sites with paper-applications
1374
    # using parameters by Heitkoetter et al.
1375
    p_max, p_min, e_max, e_min = calculate_potentials(
1376
        s_flex=S_FLEX_PAPER,
1377
        s_util=S_UTIL_PAPER,
1378
        s_inc=S_INC_PAPER,
1379
        s_dec=S_DEC_PAPER,
1380
        delta_t=DELTA_T_PAPER,
1381
        dsm=dsm_paper,
1382
    )
1383
1384
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1385
        con, p_max, p_min, e_max, e_min, dsm_paper
1386
    )
1387
1388
    df_dsm_buses = gpd.GeoDataFrame(
1389
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1390
        crs="EPSG:4326",
1391
    )
1392
    df_dsm_links = pd.DataFrame(
1393
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1394
    )
1395
    df_dsm_stores = pd.DataFrame(
1396
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1397
    )
1398
1399
    print(" ")
1400
    print("industry sites: recycled paper")
1401
    print(" ")
1402
1403
    # calculate potentials of industrial sites with recycled paper-applications
1404
    # using parameters by Heitkoetter et. al.
1405
    dsm_recycled_paper = gpd.GeoDataFrame(
1406
        dsm[dsm["application"] == "Recycled Paper"]
1407
    )
1408
1409
    p_max, p_min, e_max, e_min = calculate_potentials(
1410
        s_flex=S_FLEX_RECYCLED_PAPER,
1411
        s_util=S_UTIL_RECYCLED_PAPER,
1412
        s_inc=S_INC_RECYCLED_PAPER,
1413
        s_dec=S_DEC_RECYCLED_PAPER,
1414
        delta_t=DELTA_T_RECYCLED_PAPER,
1415
        dsm=dsm_recycled_paper,
1416
    )
1417
1418
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1419
        con, p_max, p_min, e_max, e_min, dsm_recycled_paper
1420
    )
1421
1422
    df_dsm_buses = gpd.GeoDataFrame(
1423
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1424
        crs="EPSG:4326",
1425
    )
1426
    df_dsm_links = pd.DataFrame(
1427
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1428
    )
1429
    df_dsm_stores = pd.DataFrame(
1430
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1431
    )
1432
1433
    print(" ")
1434
    print("industry sites: pulp")
1435
    print(" ")
1436
1437
    dsm_pulp = gpd.GeoDataFrame(dsm[dsm["application"] == "Mechanical Pulp"])
1438
1439
    # calculate potentials of industrial sites with pulp-applications
1440
    # using parameters by Heitkoetter et al.
1441
    p_max, p_min, e_max, e_min = calculate_potentials(
1442
        s_flex=S_FLEX_PULP,
1443
        s_util=S_UTIL_PULP,
1444
        s_inc=S_INC_PULP,
1445
        s_dec=S_DEC_PULP,
1446
        delta_t=DELTA_T_PULP,
1447
        dsm=dsm_pulp,
1448
    )
1449
1450
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1451
        con, p_max, p_min, e_max, e_min, dsm_pulp
1452
    )
1453
1454
    df_dsm_buses = gpd.GeoDataFrame(
1455
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1456
        crs="EPSG:4326",
1457
    )
1458
    df_dsm_links = pd.DataFrame(
1459
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1460
    )
1461
    df_dsm_stores = pd.DataFrame(
1462
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1463
    )
1464
1465
    # industry sites: cement
1466
1467
    print(" ")
1468
    print("industry sites: cement")
1469
    print(" ")
1470
1471
    dsm_cement = gpd.GeoDataFrame(dsm[dsm["application"] == "Cement Mill"])
1472
1473
    # calculate potentials of industrial sites with cement-applications
1474
    # using parameters by Heitkoetter et al.
1475
    p_max, p_min, e_max, e_min = calculate_potentials(
1476
        s_flex=S_FLEX_CEMENT,
1477
        s_util=S_UTIL_CEMENT,
1478
        s_inc=S_INC_CEMENT,
1479
        s_dec=S_DEC_CEMENT,
1480
        delta_t=DELTA_T_CEMENT,
1481
        dsm=dsm_cement,
1482
    )
1483
1484
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1485
        con, p_max, p_min, e_max, e_min, dsm_cement
1486
    )
1487
1488
    df_dsm_buses = gpd.GeoDataFrame(
1489
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1490
        crs="EPSG:4326",
1491
    )
1492
    df_dsm_links = pd.DataFrame(
1493
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1494
    )
1495
    df_dsm_stores = pd.DataFrame(
1496
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1497
    )
1498
1499
    # industry sites: ventilation in WZ23
1500
1501
    print(" ")
1502
    print("industry sites: ventilation in WZ23")
1503
    print(" ")
1504
1505
    dsm = ind_sites_vent_data_import(ind_vent_share, wz=WZ)
1506
1507
    # drop entries of Cement Mills whose DSM-potentials have already been
1508
    # modelled
1509
    cement = np.unique(dsm_cement["bus"].values)
1510
    index_names = np.array(dsm[dsm["bus"].isin(cement)].index)
1511
    dsm.drop(index_names, inplace=True)
1512
1513
    # calculate potentials of ventialtion in industrial sites of WZ 23
1514
    # using parameters by Heitkoetter et al.
1515
    p_max, p_min, e_max, e_min = calculate_potentials(
1516
        s_flex=S_FLEX_WZ,
1517
        s_util=S_UTIL_WZ,
1518
        s_inc=S_INC_WZ,
1519
        s_dec=S_DEC_WZ,
1520
        delta_t=DELTA_T_WZ,
1521
        dsm=dsm,
1522
    )
1523
1524
    dsm_buses, dsm_links, dsm_stores = create_dsm_components(
1525
        con, p_max, p_min, e_max, e_min, dsm
1526
    )
1527
1528
    df_dsm_buses = gpd.GeoDataFrame(
1529
        pd.concat([df_dsm_buses, dsm_buses], ignore_index=True),
1530
        crs="EPSG:4326",
1531
    )
1532
    df_dsm_links = pd.DataFrame(
1533
        pd.concat([df_dsm_links, dsm_links], ignore_index=True)
1534
    )
1535
    df_dsm_stores = pd.DataFrame(
1536
        pd.concat([df_dsm_stores, dsm_stores], ignore_index=True)
1537
    )
1538
1539
    # aggregate DSM components per substation
1540
    dsm_buses, dsm_links, dsm_stores = aggregate_components(
1541
        df_dsm_buses, df_dsm_links, df_dsm_stores
1542
    )
1543
1544
    # export aggregated DSM components to database
1545
1546
    delete_dsm_entries("dsm-cts")
1547
    delete_dsm_entries("dsm-ind-osm")
1548
    delete_dsm_entries("dsm-ind-sites")
1549
    delete_dsm_entries("dsm")
1550
1551
    data_export(dsm_buses, dsm_links, dsm_stores, carrier="dsm")
1552
1553
1554
def create_table(df, table, engine=CON):
1555
    """Create table"""
1556
    table.__table__.drop(bind=engine, checkfirst=True)
1557
    table.__table__.create(bind=engine, checkfirst=True)
1558
1559
    df.to_sql(
1560
        name=table.__table__.name,
1561
        schema=table.__table__.schema,
1562
        con=engine,
1563
        if_exists="append",
1564
        index=False,
1565
    )
1566
1567
1568
def div_list(lst: list, div: float):
1569
    return [v / div for v in lst]
1570
1571
1572
def dsm_cts_ind_individual(
1573
    cts_cool_vent_ac_share=CTS_COOL_VENT_AC_SHARE,
1574
    ind_vent_cool_share=IND_VENT_COOL_SHARE,
1575
    ind_vent_share=IND_VENT_SHARE,
1576
):
1577
    """
1578
    Execute methodology to create and implement components for DSM considering
1579
    a) CTS per osm-area: combined potentials of cooling, ventilation and air
1580
      conditioning
1581
    b) Industry per osm-are: combined potentials of cooling and ventilation
1582
    c) Industrial Sites: potentials of ventilation in sites of
1583
      "Wirtschaftszweig" (WZ) 23
1584
    d) Industrial Sites: potentials of sites specified by subsectors
1585
      identified by Schmidt (https://zenodo.org/record/3613767#.YTsGwVtCRhG):
1586
      Paper, Recycled Paper, Pulp, Cement
1587
1588
    Modelled using the methods by Heitkoetter et. al.:
1589
    https://doi.org/10.1016/j.adapen.2020.100001
1590
1591
    Parameters
1592
    ----------
1593
    cts_cool_vent_ac_share: float
1594
        Share of cooling, ventilation and AC in CTS demand
1595
    ind_vent_cool_share: float
1596
        Share of cooling and ventilation in industry demand
1597
    ind_vent_share: float
1598
        Share of ventilation in industry demand in sites of WZ 23
1599
1600
    """
1601
1602
    # CTS per osm-area: cooling, ventilation and air conditioning
1603
1604
    print(" ")
1605
    print("CTS per osm-area: cooling, ventilation and air conditioning")
1606
    print(" ")
1607
1608
    dsm = cts_data_import(cts_cool_vent_ac_share)
1609
1610
    # calculate combined potentials of cooling, ventilation and air
1611
    # conditioning in CTS using combined parameters by Heitkoetter et. al.
1612
    vals = calculate_potentials(
1613
        s_flex=S_FLEX_CTS,
1614
        s_util=S_UTIL_CTS,
1615
        s_inc=S_INC_CTS,
1616
        s_dec=S_DEC_CTS,
1617
        delta_t=DELTA_T_CTS,
1618
        dsm=dsm,
1619
    )
1620
1621
    dsm = dsm.assign(
1622
        p_set=dsm.p_set.apply(div_list, div=cts_cool_vent_ac_share)
1623
    )
1624
1625
    base_columns = [
1626
        "bus",
1627
        "scn_name",
1628
        "p_set",
1629
        "p_max",
1630
        "p_min",
1631
        "e_max",
1632
        "e_min",
1633
    ]
1634
1635
    cts_df = pd.concat([dsm, *vals], axis=1, ignore_index=True)
1636
    cts_df.columns = base_columns
1637
1638
    print(" ")
1639
    print("industry per osm-area: cooling and ventilation")
1640
    print(" ")
1641
1642
    dsm = ind_osm_data_import_individual(ind_vent_cool_share)
1643
1644
    # calculate combined potentials of cooling and ventilation in industrial
1645
    # sector using combined parameters by Heitkoetter et al.
1646
    vals = calculate_potentials(
1647
        s_flex=S_FLEX_OSM,
1648
        s_util=S_UTIL_OSM,
1649
        s_inc=S_INC_OSM,
1650
        s_dec=S_DEC_OSM,
1651
        delta_t=DELTA_T_OSM,
1652
        dsm=dsm,
1653
    )
1654
1655
    dsm = dsm.assign(p_set=dsm.p_set.apply(div_list, div=ind_vent_cool_share))
1656
1657
    columns = ["osm_id"] + base_columns
1658
1659
    osm_df = pd.concat([dsm, *vals], axis=1, ignore_index=True)
1660
    osm_df.columns = columns
1661
1662
    # industry sites
1663
1664
    # industry sites: different applications
1665
1666
    dsm = ind_sites_data_import()
1667
1668
    print(" ")
1669
    print("industry sites: paper")
1670
    print(" ")
1671
1672
    dsm_paper = gpd.GeoDataFrame(
1673
        dsm[
1674
            dsm["application"].isin(
1675
                [
1676
                    "Graphic Paper",
1677
                    "Packing Paper and Board",
1678
                    "Hygiene Paper",
1679
                    "Technical/Special Paper and Board",
1680
                ]
1681
            )
1682
        ]
1683
    )
1684
1685
    # calculate potentials of industrial sites with paper-applications
1686
    # using parameters by Heitkoetter et al.
1687
    vals = calculate_potentials(
1688
        s_flex=S_FLEX_PAPER,
1689
        s_util=S_UTIL_PAPER,
1690
        s_inc=S_INC_PAPER,
1691
        s_dec=S_DEC_PAPER,
1692
        delta_t=DELTA_T_PAPER,
1693
        dsm=dsm_paper,
1694
    )
1695
1696
    columns = ["application", "industrial_sites_id"] + base_columns
1697
1698
    paper_df = pd.concat([dsm_paper, *vals], axis=1, ignore_index=True)
1699
    paper_df.columns = columns
1700
1701
    print(" ")
1702
    print("industry sites: recycled paper")
1703
    print(" ")
1704
1705
    # calculate potentials of industrial sites with recycled paper-applications
1706
    # using parameters by Heitkoetter et. al.
1707
    dsm_recycled_paper = gpd.GeoDataFrame(
1708
        dsm[dsm["application"] == "Recycled Paper"]
1709
    )
1710
1711
    vals = calculate_potentials(
1712
        s_flex=S_FLEX_RECYCLED_PAPER,
1713
        s_util=S_UTIL_RECYCLED_PAPER,
1714
        s_inc=S_INC_RECYCLED_PAPER,
1715
        s_dec=S_DEC_RECYCLED_PAPER,
1716
        delta_t=DELTA_T_RECYCLED_PAPER,
1717
        dsm=dsm_recycled_paper,
1718
    )
1719
1720
    recycled_paper_df = pd.concat(
1721
        [dsm_recycled_paper, *vals], axis=1, ignore_index=True
1722
    )
1723
    recycled_paper_df.columns = columns
1724
1725
    print(" ")
1726
    print("industry sites: pulp")
1727
    print(" ")
1728
1729
    dsm_pulp = gpd.GeoDataFrame(dsm[dsm["application"] == "Mechanical Pulp"])
1730
1731
    # calculate potentials of industrial sites with pulp-applications
1732
    # using parameters by Heitkoetter et al.
1733
    vals = calculate_potentials(
1734
        s_flex=S_FLEX_PULP,
1735
        s_util=S_UTIL_PULP,
1736
        s_inc=S_INC_PULP,
1737
        s_dec=S_DEC_PULP,
1738
        delta_t=DELTA_T_PULP,
1739
        dsm=dsm_pulp,
1740
    )
1741
1742
    pulp_df = pd.concat([dsm_pulp, *vals], axis=1, ignore_index=True)
1743
    pulp_df.columns = columns
1744
1745
    # industry sites: cement
1746
1747
    print(" ")
1748
    print("industry sites: cement")
1749
    print(" ")
1750
1751
    dsm_cement = gpd.GeoDataFrame(dsm[dsm["application"] == "Cement Mill"])
1752
1753
    # calculate potentials of industrial sites with cement-applications
1754
    # using parameters by Heitkoetter et al.
1755
    vals = calculate_potentials(
1756
        s_flex=S_FLEX_CEMENT,
1757
        s_util=S_UTIL_CEMENT,
1758
        s_inc=S_INC_CEMENT,
1759
        s_dec=S_DEC_CEMENT,
1760
        delta_t=DELTA_T_CEMENT,
1761
        dsm=dsm_cement,
1762
    )
1763
1764
    cement_df = pd.concat([dsm_cement, *vals], axis=1, ignore_index=True)
1765
    cement_df.columns = columns
1766
1767
    ind_df = pd.concat(
1768
        [paper_df, recycled_paper_df, pulp_df, cement_df], ignore_index=True
1769
    )
1770
1771
    # industry sites: ventilation in WZ23
1772
1773
    print(" ")
1774
    print("industry sites: ventilation in WZ23")
1775
    print(" ")
1776
1777
    dsm = ind_sites_vent_data_import_individual(ind_vent_share, wz=WZ)
1778
1779
    # drop entries of Cement Mills whose DSM-potentials have already been
1780
    # modelled
1781
    cement = np.unique(dsm_cement["bus"].values)
1782
    index_names = np.array(dsm[dsm["bus"].isin(cement)].index)
1783
    dsm.drop(index_names, inplace=True)
1784
1785
    # calculate potentials of ventialtion in industrial sites of WZ 23
1786
    # using parameters by Heitkoetter et al.
1787
    vals = calculate_potentials(
1788
        s_flex=S_FLEX_WZ,
1789
        s_util=S_UTIL_WZ,
1790
        s_inc=S_INC_WZ,
1791
        s_dec=S_DEC_WZ,
1792
        delta_t=DELTA_T_WZ,
1793
        dsm=dsm,
1794
    )
1795
1796
    columns = ["site_id"] + base_columns
1797
1798
    ind_sites_df = pd.concat([dsm, *vals], axis=1, ignore_index=True)
1799
    ind_sites_df.columns = columns
1800
1801
    # create tables
1802
    create_table(
1803
        df=cts_df, table=EgonEtragoElectricityCtsDsmTimeseries, engine=CON
1804
    )
1805
    create_table(
1806
        df=osm_df,
1807
        table=EgonOsmIndLoadCurvesIndividualDsmTimeseries,
1808
        engine=CON,
1809
    )
1810
    create_table(
1811
        df=ind_df,
1812
        table=EgonDemandregioSitesIndElectricityDsmTimeseries,
1813
        engine=CON,
1814
    )
1815
    create_table(
1816
        df=ind_sites_df,
1817
        table=EgonSitesIndLoadCurvesIndividualDsmTimeseries,
1818
        engine=CON,
1819
    )
1820
1821
1822
def dsm_cts_ind_processing():
1823
    dsm_cts_ind()
1824
1825
    dsm_cts_ind_individual()
1826
1827
    add_metadata_individual()
1828