create_individual_heat_per_mv_grid()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 75
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 7
dl 0
loc 75
rs 10
c 0
b 0
f 0
cc 1
nop 2

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
from datetime import date, datetime
2
from pathlib import Path
3
import json
4
import os
5
import time
6
import warnings
7
8
from sqlalchemy import ARRAY, Column, Float, Integer, String, Text
9
from sqlalchemy.ext.declarative import declarative_base
10
import geopandas as gpd
11
import numpy as np
12
import pandas as pd
13
14
from egon.data import config, db
15
import egon.data.datasets.era5 as era
16
17
try:
18
    from disaggregator import temporal
19
except ImportError as e:
20
    pass
21
22
from math import ceil
23
24
from egon.data.datasets import Dataset
25
from egon.data.datasets.heat_demand_timeseries.daily import (
26
    daily_demand_shares_per_climate_zone,
27
    map_climate_zones_to_zensus,
28
)
29
from egon.data.datasets.heat_demand_timeseries.idp_pool import create, select
30
from egon.data.datasets.heat_demand_timeseries.service_sector import (
31
    CTS_demand_scale,
32
)
33
from egon.data.metadata import (
34
    context,
35
    license_egon_data_odbl,
36
    meta_metadata,
37
    sources,
38
)
39
40
Base = declarative_base()
41
42
43
class EgonTimeseriesDistrictHeating(Base):
44
    __tablename__ = "egon_timeseries_district_heating"
45
    __table_args__ = {"schema": "demand"}
46
    area_id = Column(Integer, primary_key=True)
47
    scenario = Column(Text, primary_key=True)
48
    dist_aggregated_mw = Column(ARRAY(Float(53)))
49
50
51
class EgonEtragoTimeseriesIndividualHeating(Base):
52
    __tablename__ = "egon_etrago_timeseries_individual_heating"
53
    __table_args__ = {"schema": "demand"}
54
    bus_id = Column(Integer, primary_key=True)
55
    scenario = Column(Text, primary_key=True)
56
    dist_aggregated_mw = Column(ARRAY(Float(53)))
57
58
59
class EgonIndividualHeatingPeakLoads(Base):
60
    __tablename__ = "egon_individual_heating_peak_loads"
61
    __table_args__ = {"schema": "demand"}
62
    building_id = Column(Integer, primary_key=True)
63
    scenario = Column(Text, primary_key=True)
64
    w_th = Column(Float(53))
65
66
67
class EgonEtragoHeatCts(Base):
68
    __tablename__ = "egon_etrago_heat_cts"
69
    __table_args__ = {"schema": "demand"}
70
71
    bus_id = Column(Integer, primary_key=True)
72
    scn_name = Column(String, primary_key=True)
73
    p_set = Column(ARRAY(Float))
74
75
76
def create_timeseries_for_building(building_id, scenario):
77
    """Generates final heat demand timeseries for a specific building
78
79
    Parameters
80
    ----------
81
    building_id : int
82
        Index of the selected building
83
    scenario : str
84
        Name of the selected scenario.
85
86
    Returns
87
    -------
88
    pandas.DataFrame
89
        Hourly heat demand timeseries in MW for the selected building
90
91
    """
92
93
    return db.select_dataframe(
94
        f"""
95
        SELECT building_demand * UNNEST(idp) as demand
96
        FROM
97
        (
98
        SELECT
99
            demand.demand
100
            / building.count
101
            * daily_demand.daily_demand_share as building_demand,
102
            daily_demand.day_of_year
103
        FROM
104
105
        (SELECT demand FROM
106
        demand.egon_peta_heat
107
        WHERE scenario = '{scenario}'
108
        AND sector = 'residential'
109
        AND zensus_population_id IN(
110
        SELECT zensus_population_id FROM
111
        demand.egon_heat_timeseries_selected_profiles
112
        WHERE building_id  = {building_id})) as demand,
113
114
        (SELECT COUNT(building_id)
115
        FROM demand.egon_heat_timeseries_selected_profiles
116
        WHERE zensus_population_id IN(
117
        SELECT zensus_population_id FROM
118
        demand.egon_heat_timeseries_selected_profiles
119
        WHERE building_id  = {building_id})) as building,
120
121
        (SELECT daily_demand_share, day_of_year FROM
122
        demand.egon_daily_heat_demand_per_climate_zone
123
        WHERE climate_zone = (
124
            SELECT climate_zone FROM boundaries.egon_map_zensus_climate_zones
125
            WHERE zensus_population_id =
126
            (
127
                SELECT zensus_population_id
128
                FROM demand.egon_heat_timeseries_selected_profiles
129
                WHERE building_id = {building_id}
130
            )
131
        )) as daily_demand) as daily_demand
132
133
        JOIN (SELECT b.idp, ordinality as day
134
        FROM demand.egon_heat_timeseries_selected_profiles a,
135
        UNNEST (a.selected_idp_profiles) WITH ORDINALITY as selected_idp
136
        JOIN demand.egon_heat_idp_pool b
137
        ON selected_idp = b.index
138
        WHERE a.building_id = {building_id}) as demand_profile
139
        ON demand_profile.day = daily_demand.day_of_year
140
        """
141
    )
142
143
144
def create_district_heating_profile(scenario, area_id):
145
    """Create a heat demand profile for a district heating grid.
146
147
    The created heat demand profile includes the demands of households
148
    and the service sector.
149
150
    Parameters
151
    ----------
152
    scenario : str
153
        The name of the selected scenario.
154
    area_id : int
155
        The index of the selected district heating grid.
156
157
    Returns
158
    -------
159
    pd.DataFrame
160
        An hourly heat demand timeseries in MW for the selected district
161
        heating grid.
162
163
    """
164
165
    start_time = datetime.now()
166
167
    df = db.select_dataframe(
168
        f"""
169
170
        SELECT SUM(building_demand_per_hour) as demand_profile, hour_of_year
171
        FROM
172
173
        (
174
        SELECT demand.demand  *
175
        c.daily_demand_share * hourly_demand as building_demand_per_hour,
176
        ordinality + 24* (c.day_of_year-1) as hour_of_year,
177
        demand_profile.building_id,
178
        c.day_of_year,
179
        ordinality
180
181
        FROM
182
183
        (SELECT zensus_population_id, demand FROM
184
        demand.egon_peta_heat
185
        WHERE scenario = '{scenario}'
186
        AND sector = 'residential'
187
        AND zensus_population_id IN(
188
        SELECT zensus_population_id FROM
189
        demand.egon_map_zensus_district_heating_areas
190
        WHERE scenario = '{scenario}'
191
        AND area_id = {area_id}
192
        )) as demand
193
194
        JOIN boundaries.egon_map_zensus_climate_zones b
195
        ON demand.zensus_population_id = b.zensus_population_id
196
197
        JOIN demand.egon_daily_heat_demand_per_climate_zone c
198
        ON c.climate_zone = b.climate_zone
199
200
        JOIN (
201
        SELECT e.idp, ordinality as day, zensus_population_id, building_id
202
        FROM demand.egon_heat_timeseries_selected_profiles d,
203
        UNNEST (d.selected_idp_profiles) WITH ORDINALITY as selected_idp
204
        JOIN demand.egon_heat_idp_pool e
205
        ON selected_idp = e.index
206
        WHERE zensus_population_id IN (
207
        SELECT zensus_population_id FROM
208
        demand.egon_map_zensus_district_heating_areas
209
        WHERE scenario = '{scenario}'
210
        AND area_id = {area_id}
211
        ))  demand_profile
212
        ON (demand_profile.day = c.day_of_year AND
213
            demand_profile.zensus_population_id = b.zensus_population_id)
214
215
        JOIN (SELECT COUNT(building_id), zensus_population_id
216
        FROM demand.egon_heat_timeseries_selected_profiles
217
        WHERE zensus_population_id IN(
218
        SELECT zensus_population_id FROM
219
        demand.egon_heat_timeseries_selected_profiles
220
       WHERE zensus_population_id IN (
221
       SELECT zensus_population_id FROM
222
       demand.egon_map_zensus_district_heating_areas
223
       WHERE scenario = '{scenario}'
224
       AND area_id = {area_id}
225
       ))
226
        GROUP BY zensus_population_id) building
227
        ON building.zensus_population_id = b.zensus_population_id,
228
229
        UNNEST(demand_profile.idp) WITH ORDINALITY as hourly_demand
230
        )   result
231
232
233
        GROUP BY hour_of_year
234
235
        """
236
    )
237
238
    print(
239
        f"Time to create time series for district heating grid {scenario}"
240
        f" {area_id}:\n{datetime.now() - start_time}"
241
    )
242
243
    return df
244
245
246
def create_district_heating_profile_python_like(scenario="eGon2035"):
247
    """Creates profiles for all district heating grids in one scenario.
248
    Similar to create_district_heating_profile but faster and needs more RAM.
249
    The results are directly written into the database.
250
251
    Parameters
252
    ----------
253
    scenario : str
254
        Name of the selected scenario.
255
256
    Returns
257
    -------
258
    None.
259
260
    """
261
262
    start_time = datetime.now()
263
264
    idp_df = db.select_dataframe(
265
        """
266
        SELECT index, idp FROM demand.egon_heat_idp_pool
267
        """,
268
        index_col="index",
269
    )
270
271
    district_heating_grids = db.select_dataframe(
272
        f"""
273
        SELECT area_id
274
        FROM demand.egon_district_heating_areas
275
        WHERE scenario = '{scenario}'
276
        """
277
    )
278
279
    annual_demand = db.select_dataframe(
280
        f"""
281
        SELECT
282
            a.zensus_population_id,
283
            demand / c.count as per_building,
284
            area_id,
285
            demand as demand_total
286
        FROM
287
        demand.egon_peta_heat a
288
        INNER JOIN (
289
            SELECT * FROM demand.egon_map_zensus_district_heating_areas
290
            WHERE scenario = '{scenario}'
291
        ) b ON a.zensus_population_id = b.zensus_population_id
292
293
        JOIN (SELECT COUNT(building_id), zensus_population_id
294
        FROM demand.egon_heat_timeseries_selected_profiles
295
        WHERE zensus_population_id IN(
296
        SELECT zensus_population_id FROM
297
        demand.egon_heat_timeseries_selected_profiles
298
        WHERE zensus_population_id IN (
299
        SELECT zensus_population_id FROM
300
        boundaries.egon_map_zensus_grid_districts
301
       ))
302
        GROUP BY zensus_population_id)c
303
        ON a.zensus_population_id = c.zensus_population_id
304
305
        WHERE a.scenario = '{scenario}'
306
        AND a.sector = 'residential'
307
308
        """,
309
        index_col="zensus_population_id",
310
    )
311
312
    # Assign zensus cells that are part of two district heating grids to
313
    # one of them
314
    annual_demand = annual_demand[
315
        ~annual_demand.index.duplicated(keep="first")
316
    ]
317
318
    daily_demand_shares = db.select_dataframe(
319
        """
320
        SELECT climate_zone, day_of_year as day, daily_demand_share FROM
321
        demand.egon_daily_heat_demand_per_climate_zone
322
        """
323
    )
324
325
    CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
326
        aggregation_level="district"
327
    )
328
329
    print(datetime.now() - start_time)
330
331
    start_time = datetime.now()
332
    for area in district_heating_grids.area_id.unique():
333
        with db.session_scope() as session:
334
            selected_profiles = db.select_dataframe(
335
                f"""
336
                SELECT a.zensus_population_id, building_id, c.climate_zone,
337
                selected_idp, ordinality as day, b.area_id
338
                FROM demand.egon_heat_timeseries_selected_profiles a
339
                INNER JOIN boundaries.egon_map_zensus_climate_zones c
340
                ON a.zensus_population_id = c.zensus_population_id
341
                INNER JOIN (
342
                    SELECT * FROM demand.egon_map_zensus_district_heating_areas
343
                    WHERE scenario = '{scenario}'
344
                    AND area_id = '{area}'
345
                ) b ON a.zensus_population_id = b.zensus_population_id        ,
346
347
                UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp
348
349
                """
350
            )
351
352
            # Exclude profiles of zensus cells that are in two district
353
            # heating grids and added to the other one in the lines above
354
            selected_profiles = selected_profiles[
355
                selected_profiles.zensus_population_id.isin(
356
                    annual_demand[annual_demand.area_id == area].index
357
                )
358
            ]
359
360
            if not selected_profiles.empty:
361
                df = pd.merge(
362
                    selected_profiles,
363
                    daily_demand_shares,
364
                    on=["day", "climate_zone"],
365
                )
366
367
                slice_df = pd.merge(
368
                    df[df.area_id == area],
369
                    idp_df,
370
                    left_on="selected_idp",
371
                    right_on="index",
372
                )
373
374
                # Drop cells without a demand or outside of MVGD
375
                slice_df = slice_df[
376
                    slice_df.zensus_population_id.isin(annual_demand.index)
377
                ]
378
379
                for hour in range(24):
380
                    slice_df[hour] = (
381
                        slice_df.idp.str[hour]
382
                        .mul(slice_df.daily_demand_share)
383
                        .mul(
384
                            annual_demand.loc[
385
                                slice_df.zensus_population_id.values,
386
                                "per_building",
387
                            ].values
388
                        )
389
                    )
390
391
                diff = (
392
                    slice_df[range(24)].sum().sum()
393
                    - annual_demand[
394
                        annual_demand.area_id == area
395
                    ].demand_total.sum()
396
                ) / (
397
                    annual_demand[
398
                        annual_demand.area_id == area
399
                    ].demand_total.sum()
400
                )
401
402
                assert (
403
                    abs(diff) < 0.04
404
                ), f"""Deviation of residential heat demand time
405
                series for district heating grid {str(area)} is {diff}"""
406
407
                if abs(diff) > 0.03:
408
                    warnings.warn(
409
                        f"""Deviation of residential heat demand time
410
                    series for district heating grid {str(area)} is {diff}"""
411
                    )
412
413
                hh = np.concatenate(
414
                    slice_df.drop(
415
                        [
416
                            "zensus_population_id",
417
                            "building_id",
418
                            "climate_zone",
419
                            "selected_idp",
420
                            "area_id",
421
                            "daily_demand_share",
422
                            "idp",
423
                        ],
424
                        axis="columns",
425
                    )
426
                    .groupby("day")
427
                    .sum()[range(24)]
428
                    .values
429
                ).ravel()
430
431
            cts = CTS_demand_dist[
432
                (CTS_demand_dist.scenario == scenario)
433
                & (CTS_demand_dist.index == area)
434
            ].drop("scenario", axis="columns")
435
436
            if (not selected_profiles.empty) and not cts.empty:
437
                entry = EgonTimeseriesDistrictHeating(
438
                    area_id=int(area),
439
                    scenario=scenario,
440
                    dist_aggregated_mw=(hh + cts.values[0]).tolist(),
0 ignored issues
show
introduced by
The variable hh does not seem to be defined for all execution paths.
Loading history...
441
                )
442
            elif (not selected_profiles.empty) and cts.empty:
443
                entry = EgonTimeseriesDistrictHeating(
444
                    area_id=int(area),
445
                    scenario=scenario,
446
                    dist_aggregated_mw=(hh).tolist(),
447
                )
448
            elif not cts.empty:
449
                entry = EgonTimeseriesDistrictHeating(
450
                    area_id=int(area),
451
                    scenario=scenario,
452
                    dist_aggregated_mw=(cts.values[0]).tolist(),
453
                )
454
            else:
455
                entry = EgonTimeseriesDistrictHeating(
456
                    area_id=int(area),
457
                    scenario=scenario,
458
                    dist_aggregated_mw=np.repeat(0, 8760).tolist(),
459
                )
460
461
            session.add(entry)
462
        session.commit()
463
464
    print(
465
        f"Time to create time series for district heating scenario {scenario}"
466
    )
467
    print(datetime.now() - start_time)
468
469
470
def create_individual_heat_per_mv_grid(scenario="eGon2035", mv_grid_id=1564):
471
    start_time = datetime.now()
472
    df = db.select_dataframe(
473
        f"""
474
475
        SELECT SUM(building_demand_per_hour) as demand_profile, hour_of_year
476
        FROM
477
478
        (
479
        SELECT demand.demand  *
480
        c.daily_demand_share * hourly_demand as building_demand_per_hour,
481
        ordinality + 24* (c.day_of_year-1) as hour_of_year,
482
        demand_profile.building_id,
483
        c.day_of_year,
484
        ordinality
485
486
        FROM
487
488
        (SELECT zensus_population_id, demand FROM
489
        demand.egon_peta_heat
490
        WHERE scenario = '{scenario}'
491
        AND sector = 'residential'
492
        AND zensus_population_id IN (
493
        SELECT zensus_population_id FROM
494
        boundaries.egon_map_zensus_grid_districts
495
        WHERE bus_id = {mv_grid_id}
496
        )) as demand
497
498
        JOIN boundaries.egon_map_zensus_climate_zones b
499
        ON demand.zensus_population_id = b.zensus_population_id
500
501
        JOIN demand.egon_daily_heat_demand_per_climate_zone c
502
        ON c.climate_zone = b.climate_zone
503
504
        JOIN (
505
        SELECT
506
            e.idp, ordinality as day, zensus_population_id, building_id
507
        FROM demand.egon_heat_timeseries_selected_profiles d,
508
        UNNEST (d.selected_idp_profiles) WITH ORDINALITY as selected_idp
509
        JOIN demand.egon_heat_idp_pool e
510
        ON selected_idp = e.index
511
        WHERE zensus_population_id IN (
512
        SELECT zensus_population_id FROM
513
        boundaries.egon_map_zensus_grid_districts
514
        WHERE bus_id = {mv_grid_id}
515
        ))  demand_profile
516
        ON (demand_profile.day = c.day_of_year AND
517
            demand_profile.zensus_population_id = b.zensus_population_id)
518
519
        JOIN (SELECT COUNT(building_id), zensus_population_id
520
        FROM demand.egon_heat_timeseries_selected_profiles
521
        WHERE zensus_population_id IN(
522
        SELECT zensus_population_id FROM
523
        demand.egon_heat_timeseries_selected_profiles
524
        WHERE zensus_population_id IN (
525
        SELECT zensus_population_id FROM
526
        boundaries.egon_map_zensus_grid_districts
527
        WHERE bus_id = {mv_grid_id}
528
       ))
529
        GROUP BY zensus_population_id) building
530
        ON building.zensus_population_id = b.zensus_population_id,
531
532
        UNNEST(demand_profile.idp) WITH ORDINALITY as hourly_demand
533
        )   result
534
535
536
        GROUP BY hour_of_year
537
538
        """
539
    )
540
541
    print(f"Time to create time series for mv grid {scenario} {mv_grid_id}:")
542
    print(datetime.now() - start_time)
543
544
    return df
545
546
547
def calulate_peak_load(df, scenario):
548
    # peat load in W_th
549
    data = (
550
        df.groupby("building_id")
551
        .max()[range(24)]
552
        .max(axis=1)
553
        .mul(1000000)
554
        .astype(int)
555
        .reset_index()
556
    )
557
558
    data["scenario"] = scenario
559
560
    data.rename({0: "w_th"}, axis="columns", inplace=True)
561
562
    data.to_sql(
563
        EgonIndividualHeatingPeakLoads.__table__.name,
564
        schema=EgonIndividualHeatingPeakLoads.__table__.schema,
565
        con=db.engine(),
566
        if_exists="append",
567
        index=False,
568
    )
569
570
571
def create_individual_heating_peak_loads(scenario="eGon2035"):
572
    engine = db.engine()
573
574
    EgonIndividualHeatingPeakLoads.__table__.drop(bind=engine, checkfirst=True)
575
576
    EgonIndividualHeatingPeakLoads.__table__.create(
577
        bind=engine, checkfirst=True
578
    )
579
580
    start_time = datetime.now()
581
582
    idp_df = db.select_dataframe(
583
        """
584
        SELECT index, idp FROM demand.egon_heat_idp_pool
585
        """,
586
        index_col="index",
587
    )
588
589
    annual_demand = db.select_dataframe(
590
        f"""
591
        SELECT a.zensus_population_id, demand/c.count as per_building, bus_id
592
        FROM demand.egon_peta_heat a
593
594
595
        JOIN (SELECT COUNT(building_id), zensus_population_id
596
        FROM demand.egon_heat_timeseries_selected_profiles
597
        WHERE zensus_population_id IN(
598
        SELECT zensus_population_id FROM
599
        demand.egon_heat_timeseries_selected_profiles
600
        WHERE zensus_population_id IN (
601
        SELECT zensus_population_id FROM
602
        boundaries.egon_map_zensus_grid_districts
603
       ))
604
        GROUP BY zensus_population_id)c
605
        ON a.zensus_population_id = c.zensus_population_id
606
607
        JOIN boundaries.egon_map_zensus_grid_districts d
608
        ON a.zensus_population_id = d.zensus_population_id
609
610
        WHERE a.scenario = '{scenario}'
611
        AND a.sector = 'residential'
612
        AND a.zensus_population_id NOT IN (
613
            SELECT zensus_population_id
614
            FROM demand.egon_map_zensus_district_heating_areas
615
            WHERE scenario = '{scenario}'
616
        )
617
618
        """,
619
        index_col="zensus_population_id",
620
    )
621
622
    daily_demand_shares = db.select_dataframe(
623
        """
624
        SELECT climate_zone, day_of_year as day, daily_demand_share FROM
625
        demand.egon_daily_heat_demand_per_climate_zone
626
        """
627
    )
628
629
    start_time = datetime.now()
630
    for grid in annual_demand.bus_id.unique():
631
        selected_profiles = db.select_dataframe(
632
            f"""
633
            SELECT a.zensus_population_id, building_id, c.climate_zone,
634
            selected_idp, ordinality as day
635
            FROM demand.egon_heat_timeseries_selected_profiles a
636
            INNER JOIN boundaries.egon_map_zensus_climate_zones c
637
            ON a.zensus_population_id = c.zensus_population_id
638
            ,
639
640
            UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp
641
642
            WHERE a.zensus_population_id NOT IN (
643
                SELECT zensus_population_id
644
                FROM demand.egon_map_zensus_district_heating_areas
645
                WHERE scenario = '{scenario}'
646
            )
647
            AND a.zensus_population_id IN (
648
                SELECT zensus_population_id
649
                FROM boundaries.egon_map_zensus_grid_districts
650
                WHERE bus_id = '{grid}'
651
            )
652
653
            """
654
        )
655
656
        df = pd.merge(
657
            selected_profiles, daily_demand_shares, on=["day", "climate_zone"]
658
        )
659
660
        slice_df = pd.merge(
661
            df, idp_df, left_on="selected_idp", right_on="index"
662
        )
663
664
        for hour in range(24):
665
            slice_df[hour] = (
666
                slice_df.idp.str[hour]
667
                .mul(slice_df.daily_demand_share)
668
                .mul(
669
                    annual_demand.loc[
670
                        slice_df.zensus_population_id.values, "per_building"
671
                    ].values
672
                )
673
            )
674
675
        calulate_peak_load(slice_df, scenario)
676
677
    print(f"Time to create peak loads per building for {scenario}")
678
    print(datetime.now() - start_time)
679
680
681
def create_individual_heating_profile_python_like(scenario="eGon2035"):
682
    start_time = datetime.now()
683
684
    idp_df = db.select_dataframe(
685
        f"""
686
        SELECT index, idp FROM demand.egon_heat_idp_pool
687
        """,
688
        index_col="index",
689
    )
690
691
    annual_demand = db.select_dataframe(
692
        f"""
693
        SELECT
694
            a.zensus_population_id,
695
            demand / c.count as per_building,
696
            demand as demand_total,
697
            bus_id
698
        FROM demand.egon_peta_heat a
699
700
701
        JOIN (SELECT COUNT(building_id), zensus_population_id
702
        FROM demand.egon_heat_timeseries_selected_profiles
703
        WHERE zensus_population_id IN(
704
        SELECT zensus_population_id FROM
705
        demand.egon_heat_timeseries_selected_profiles
706
        WHERE zensus_population_id IN (
707
        SELECT zensus_population_id FROM
708
        boundaries.egon_map_zensus_grid_districts
709
       ))
710
        GROUP BY zensus_population_id)c
711
        ON a.zensus_population_id = c.zensus_population_id
712
713
        JOIN boundaries.egon_map_zensus_grid_districts d
714
        ON a.zensus_population_id = d.zensus_population_id
715
716
        WHERE a.scenario = '{scenario}'
717
        AND a.sector = 'residential'
718
        AND a.zensus_population_id NOT IN (
719
            SELECT zensus_population_id
720
            FROM demand.egon_map_zensus_district_heating_areas
721
            WHERE scenario = '{scenario}'
722
        )
723
724
        """,
725
        index_col="zensus_population_id",
726
    )
727
728
    daily_demand_shares = db.select_dataframe(
729
        """
730
        SELECT climate_zone, day_of_year as day, daily_demand_share FROM
731
        demand.egon_daily_heat_demand_per_climate_zone
732
        """
733
    )
734
735
    CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
736
        aggregation_level="district"
737
    )
738
739
    # TODO: use session_scope!
740
    from sqlalchemy.orm import sessionmaker
741
742
    session = sessionmaker(bind=db.engine())()
743
744
    print(
745
        f"Time to create overhead for time series for district heating scenario {scenario}"
746
    )
747
    print(datetime.now() - start_time)
748
749
    start_time = datetime.now()
750
    for grid in annual_demand.bus_id.unique():
751
        selected_profiles = db.select_dataframe(
752
            f"""
753
            SELECT a.zensus_population_id, building_id, c.climate_zone,
754
            selected_idp, ordinality as day
755
            FROM demand.egon_heat_timeseries_selected_profiles a
756
            INNER JOIN boundaries.egon_map_zensus_climate_zones c
757
            ON a.zensus_population_id = c.zensus_population_id
758
            ,
759
760
            UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp
761
762
            WHERE a.zensus_population_id NOT IN (
763
                SELECT zensus_population_id FROM demand.egon_map_zensus_district_heating_areas
764
                WHERE scenario = '{scenario}'
765
            )
766
            AND a.zensus_population_id IN (
767
                SELECT zensus_population_id
768
                FROM boundaries.egon_map_zensus_grid_districts
769
                WHERE bus_id = '{grid}'
770
            )
771
772
            """
773
        )
774
775
        df = pd.merge(
776
            selected_profiles, daily_demand_shares, on=["day", "climate_zone"]
777
        )
778
779
        slice_df = pd.merge(
780
            df, idp_df, left_on="selected_idp", right_on="index"
781
        )
782
783
        for hour in range(24):
784
            slice_df[hour] = (
785
                slice_df.idp.str[hour]
786
                .mul(slice_df.daily_demand_share)
787
                .mul(
788
                    annual_demand.loc[
789
                        slice_df.zensus_population_id.values, "per_building"
790
                    ].values
791
                )
792
            )
793
794
        cts = CTS_demand_grid[
795
            (CTS_demand_grid.scenario == scenario)
796
            & (CTS_demand_grid.index == grid)
797
        ].drop("scenario", axis="columns")
798
799
        hh = np.concatenate(
800
            slice_df.groupby("day").sum()[range(24)].values
801
        ).ravel()
802
803
        diff = (
804
            slice_df.groupby("day").sum()[range(24)].sum().sum()
805
            - annual_demand[annual_demand.bus_id == grid].demand_total.sum()
806
        ) / (annual_demand[annual_demand.bus_id == grid].demand_total.sum())
807
808
        assert abs(diff) < 0.03, (
809
            "Deviation of residential heat demand time series for mv"
810
            f" grid {grid} is {diff}"
811
        )
812
813
        if not (slice_df[hour].empty or cts.empty):
0 ignored issues
show
introduced by
The variable hour does not seem to be defined for all execution paths.
Loading history...
814
            entry = EgonEtragoTimeseriesIndividualHeating(
815
                bus_id=int(grid),
816
                scenario=scenario,
817
                dist_aggregated_mw=(hh + cts.values[0]).tolist(),
818
            )
819
        elif not slice_df[hour].empty:
820
            entry = EgonEtragoTimeseriesIndividualHeating(
821
                bus_id=int(grid),
822
                scenario=scenario,
823
                dist_aggregated_mw=(hh).tolist(),
824
            )
825
        elif not cts.empty:
826
            entry = EgonEtragoTimeseriesIndividualHeating(
827
                bus_id=int(grid),
828
                scenario=scenario,
829
                dist_aggregated_mw=(cts).tolist(),
830
            )
831
832
        session.add(entry)
0 ignored issues
show
introduced by
The variable entry does not seem to be defined for all execution paths.
Loading history...
833
834
    session.commit()
835
836
    print(
837
        f"Time to create time series for district heating scenario {scenario}"
838
    )
839
    print(datetime.now() - start_time)
840
841
842
def district_heating(method="python"):
843
    engine = db.engine()
844
    EgonTimeseriesDistrictHeating.__table__.drop(bind=engine, checkfirst=True)
845
    EgonTimeseriesDistrictHeating.__table__.create(
846
        bind=engine, checkfirst=True
847
    )
848
849
    if method == "python":
850
        for scenario in config.settings()["egon-data"]["--scenarios"]:
851
            create_district_heating_profile_python_like(scenario)
852
853
    else:
854
        CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
855
            aggregation_level="district"
856
        )
857
858
        ids = db.select_dataframe(
859
            """
860
            SELECT area_id, scenario
861
            FROM demand.egon_district_heating_areas
862
            """
863
        )
864
865
        df = pd.DataFrame(
866
            columns=["area_id", "scenario", "dist_aggregated_mw"]
867
        )
868
869
        for index, row in ids.iterrows():
870
            series = create_district_heating_profile(
871
                scenario=row.scenario, area_id=row.area_id
872
            )
873
874
            cts = (
875
                CTS_demand_dist[
876
                    (CTS_demand_dist.scenario == row.scenario)
877
                    & (CTS_demand_dist.index == row.area_id)
878
                ]
879
                .drop("scenario", axis="columns")
880
                .transpose()
881
            )
882
883
            if not cts.empty:
884
                data = (
885
                    cts[row.area_id] + series.demand_profile
886
                ).values.tolist()
887
            else:
888
                data = series.demand_profile.values.tolist()
889
890
            df = df.append(
891
                pd.Series(
892
                    data={
893
                        "area_id": row.area_id,
894
                        "scenario": row.scenario,
895
                        "dist_aggregated_mw": data,
896
                    },
897
                ),
898
                ignore_index=True,
899
            )
900
901
        df.to_sql(
902
            "egon_timeseries_district_heating",
903
            schema="demand",
904
            con=db.engine(),
905
            if_exists="append",
906
            index=False,
907
        )
908
909
910
def individual_heating_per_mv_grid_tables(method="python"):
911
    engine = db.engine()
912
    EgonEtragoTimeseriesIndividualHeating.__table__.drop(
913
        bind=engine, checkfirst=True
914
    )
915
    EgonEtragoTimeseriesIndividualHeating.__table__.create(
916
        bind=engine, checkfirst=True
917
    )
918
919
920
def individual_heating_per_mv_grid_2035(method="python"):
921
    create_individual_heating_profile_python_like("eGon2035")
922
923
924
def individual_heating_per_mv_grid_100(method="python"):
925
    create_individual_heating_profile_python_like("eGon100RE")
926
927
928
def individual_heating_per_mv_grid(method="python"):
929
    if method == "python":
930
        engine = db.engine()
931
        EgonEtragoTimeseriesIndividualHeating.__table__.drop(
932
            bind=engine, checkfirst=True
933
        )
934
        EgonEtragoTimeseriesIndividualHeating.__table__.create(
935
            bind=engine, checkfirst=True
936
        )
937
938
        create_individual_heating_profile_python_like("eGon2035")
939
        create_individual_heating_profile_python_like("eGon100RE")
940
941
    else:
942
        engine = db.engine()
943
        EgonEtragoTimeseriesIndividualHeating.__table__.drop(
944
            bind=engine, checkfirst=True
945
        )
946
        EgonEtragoTimeseriesIndividualHeating.__table__.create(
947
            bind=engine, checkfirst=True
948
        )
949
950
        CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
951
            aggregation_level="district"
952
        )
953
        df = pd.DataFrame(columns=["bus_id", "scenario", "dist_aggregated_mw"])
954
955
        ids = db.select_dataframe(
956
            """
957
            SELECT bus_id
958
            FROM grid.egon_mv_grid_district
959
            """
960
        )
961
962
        for index, row in ids.iterrows():
963
            for scenario in ["eGon2035", "eGon100RE"]:
964
                series = create_individual_heat_per_mv_grid(
965
                    scenario, row.bus_id
966
                )
967
                cts = (
968
                    CTS_demand_grid[
969
                        (CTS_demand_grid.scenario == scenario)
970
                        & (CTS_demand_grid.index == row.bus_id)
971
                    ]
972
                    .drop("scenario", axis="columns")
973
                    .transpose()
974
                )
975
                if not cts.empty:
976
                    data = (
977
                        cts[row.bus_id] + series.demand_profile
978
                    ).values.tolist()
979
                else:
980
                    data = series.demand_profile.values.tolist()
981
982
                df = df.append(
983
                    pd.Series(
984
                        data={
985
                            "bus_id": row.bus_id,
986
                            "scenario": scenario,
987
                            "dist_aggregated_mw": data,
988
                        },
989
                    ),
990
                    ignore_index=True,
991
                )
992
993
        df.to_sql(
994
            "egon_etrago_timeseries_individual_heating",
995
            schema="demand",
996
            con=db.engine(),
997
            if_exists="append",
998
            index=False,
999
        )
1000
1001
1002
def store_national_profiles():
1003
    scenario = "eGon100RE"
1004
1005
    df = db.select_dataframe(
1006
        f"""
1007
1008
        SELECT SUM(building_demand_per_hour) as "residential rural"
1009
        FROM
1010
1011
        (
1012
        SELECT demand.demand  / building.count *
1013
        c.daily_demand_share * hourly_demand as building_demand_per_hour,
1014
        ordinality + 24* (c.day_of_year-1) as hour_of_year,
1015
        demand_profile.building_id,
1016
        c.day_of_year,
1017
        ordinality
1018
1019
        FROM
1020
1021
        (SELECT zensus_population_id, demand FROM
1022
        demand.egon_peta_heat
1023
        WHERE scenario = '{scenario}'
1024
        AND sector = 'residential'
1025
       ) as demand
1026
1027
        JOIN boundaries.egon_map_zensus_climate_zones b
1028
        ON demand.zensus_population_id = b.zensus_population_id
1029
1030
        JOIN demand.egon_daily_heat_demand_per_climate_zone c
1031
        ON c.climate_zone = b.climate_zone
1032
1033
        JOIN (
1034
        SELECT e.idp, ordinality as day, zensus_population_id, building_id
1035
        FROM demand.egon_heat_timeseries_selected_profiles d,
1036
        UNNEST (d.selected_idp_profiles) WITH ORDINALITY as selected_idp
1037
        JOIN demand.egon_heat_idp_pool e
1038
        ON selected_idp = e.index
1039
        )  demand_profile
1040
        ON (demand_profile.day = c.day_of_year AND
1041
            demand_profile.zensus_population_id = b.zensus_population_id)
1042
1043
        JOIN (SELECT COUNT(building_id), zensus_population_id
1044
        FROM demand.egon_heat_timeseries_selected_profiles
1045
        WHERE zensus_population_id IN(
1046
        SELECT zensus_population_id FROM
1047
        demand.egon_heat_timeseries_selected_profiles
1048
        )
1049
        GROUP BY zensus_population_id) building
1050
        ON building.zensus_population_id = b.zensus_population_id,
1051
1052
        UNNEST(demand_profile.idp) WITH ORDINALITY as hourly_demand
1053
        )   result
1054
1055
1056
        GROUP BY hour_of_year
1057
1058
        """
1059
    )
1060
1061
    CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
1062
        aggregation_level="district"
1063
    )
1064
1065
    df["service rural"] = (
1066
        CTS_demand_dist.loc[CTS_demand_dist.scenario == scenario]
1067
        .drop("scenario", axis=1)
1068
        .sum()
1069
    )
1070
1071
    df["urban central"] = db.select_dataframe(
1072
        f"""
1073
        SELECT sum(nullif(demand, 'NaN')) as "urban central"
1074
1075
        FROM demand.egon_timeseries_district_heating,
1076
        UNNEST (dist_aggregated_mw) WITH ORDINALITY as demand
1077
1078
        WHERE scenario = '{scenario}'
1079
1080
        GROUP BY ordinality
1081
1082
        """
1083
    )
1084
1085
    folder = Path(".") / "input-pypsa-eur-sec"
1086
    # Create the folder, if it does not exists already
1087
    if not os.path.exists(folder):
1088
        os.mkdir(folder)
1089
1090
    df.to_csv(folder / f"heat_demand_timeseries_DE_{scenario}.csv")
1091
1092
1093
def export_etrago_cts_heat_profiles():
1094
    """Export heat cts load profiles at mv substation level
1095
    to etrago-table in the database
1096
1097
    Returns
1098
    -------
1099
    None.
1100
1101
    """
1102
1103
    # Calculate cts heat profiles at substation
1104
    _, CTS_grid, _ = CTS_demand_scale("district")
1105
1106
    # Change format
1107
    data = CTS_grid.drop(columns="scenario")
1108
    df_etrago_cts_heat_profiles = pd.DataFrame(
1109
        index=data.index, columns=["scn_name", "p_set"]
1110
    )
1111
    df_etrago_cts_heat_profiles.p_set = data.values.tolist()
1112
    df_etrago_cts_heat_profiles.scn_name = CTS_grid["scenario"]
1113
    df_etrago_cts_heat_profiles.reset_index(inplace=True)
1114
1115
    # Drop and recreate Table if exists
1116
    EgonEtragoHeatCts.__table__.drop(bind=db.engine(), checkfirst=True)
1117
    EgonEtragoHeatCts.__table__.create(bind=db.engine(), checkfirst=True)
1118
1119
    # Write heat ts into db
1120
    with db.session_scope() as session:
1121
        session.bulk_insert_mappings(
1122
            EgonEtragoHeatCts,
1123
            df_etrago_cts_heat_profiles.to_dict(orient="records"),
1124
        )
1125
1126
1127
def metadata():
1128
    fields = [
1129
        {
1130
            "description": "Index of corresponding district heating area",
1131
            "name": "area_id",
1132
            "type": "integer",
1133
            "unit": "none",
1134
        },
1135
        {
1136
            "description": "Name of scenario",
1137
            "name": "scenario",
1138
            "type": "str",
1139
            "unit": "none",
1140
        },
1141
        {
1142
            "description": "Heat demand time series",
1143
            "name": "dist_aggregated_mw",
1144
            "type": "array of floats",
1145
            "unit": "MW",
1146
        },
1147
    ]
1148
1149
    meta_district = {
1150
        "name": "demand.egon_timeseries_district_heating",
1151
        "title": "eGon heat demand time series for district heating grids",
1152
        "id": "WILL_BE_SET_AT_PUBLICATION",
1153
        "description": "Heat demand time series for district heating grids",
1154
        "language": ["EN"],
1155
        "publicationDate": date.today().isoformat(),
1156
        "context": context(),
1157
        "spatial": {
1158
            "location": None,
1159
            "extent": "Germany",
1160
            "resolution": None,
1161
        },
1162
        "sources": [
1163
            sources()["era5"],
1164
            sources()["vg250"],
1165
            sources()["egon-data"],
1166
            sources()["egon-data_bundle"],
1167
            sources()["peta"],
1168
        ],
1169
        "licenses": [license_egon_data_odbl()],
1170
        "contributors": [
1171
            {
1172
                "title": "Clara Büttner",
1173
                "email": "http://github.com/ClaraBuettner",
1174
                "date": time.strftime("%Y-%m-%d"),
1175
                "object": None,
1176
                "comment": "Imported data",
1177
            },
1178
        ],
1179
        "resources": [
1180
            {
1181
                "profile": "tabular-data-resource",
1182
                "name": "demand.egon_timeseries_district_heating",
1183
                "path": None,
1184
                "format": "PostgreSQL",
1185
                "encoding": "UTF-8",
1186
                "schema": {
1187
                    "fields": fields,
1188
                    "primaryKey": ["index"],
1189
                    "foreignKeys": [],
1190
                },
1191
                "dialect": {"delimiter": None, "decimalSeparator": "."},
1192
            }
1193
        ],
1194
        "metaMetadata": meta_metadata(),
1195
    }
1196
1197
    # Add metadata as a comment to the table
1198
    db.submit_comment(
1199
        "'" + json.dumps(meta_district) + "'",
1200
        EgonTimeseriesDistrictHeating.__table__.schema,
1201
        EgonTimeseriesDistrictHeating.__table__.name,
1202
    )
1203
1204
1205
class HeatTimeSeries(Dataset):
1206
    """
1207
    Chooses heat demand profiles for each residential and CTS building
1208
1209
    This dataset creates heat demand profiles in an hourly resoultion.
1210
    Time series for CTS buildings are created using the SLP-gas method implemented
1211
    in the demandregio disagregator with the function :py:func:`export_etrago_cts_heat_profiles`
1212
    and stored in the database.
1213
    Time series for residential buildings are created based on a variety of synthetical created
1214
    individual demand profiles that are part of :py:class:`DataBundle <egon.data.datasets.data_bundle.DataBundle>`.
1215
    This method is desribed within the functions and in this publication:
1216
1217
    C. Büttner, J. Amme, J. Endres, A. Malla, B. Schachler, I. Cußmann,
1218
    Open modeling of electricity and heat demand curves for all
1219
    residential buildings in Germany, Energy Informatics 5 (1) (2022) 21.
1220
    doi:10.1186/s42162-022-00201-y.
1221
1222
1223
    *Dependencies*
1224
      * :py:class:`DataBundle <egon.data.datasets.data_bundle.DataBundle>`
1225
      * :py:class:`DemandRegio <egon.data.datasets.demandregio.DemandRegio>`
1226
      * :py:class:`HeatDemandImport <egon.data.datasets.heat_demand.HeatDemandImport>`
1227
      * :py:class:`DistrictHeatingAreas <egon.data.datasets.district_heating_areas.DistrictHeatingAreas>`
1228
      * :py:class:`Vg250 <egon.data.datasets.vg250.Vg250>`
1229
      * :py:class:`ZensusMvGridDistricts <egon.data.datasets.zensus_mv_grid_districts.ZensusMvGridDistricts>`
1230
      * :py:func:`hh_demand_buildings_setup <egon.data.datasets.electricity_demand_timeseries.hh_buildings.map_houseprofiles_to_buildings>`
1231
      * :py:class:`WeatherData <egon.data.datasets.era5.WeatherData>`
1232
1233
1234
    *Resulting tables*
1235
      * :py:class:`demand.egon_timeseries_district_heating <egon.data.datasets.heat_demand_timeseries.EgonTimeseriesDistrictHeating>` is created and filled
1236
      * :py:class:`demand.egon_etrago_heat_cts <egon.data.datasets.heat_demand_timeseries.EgonEtragoHeatCts>` is created and filled
1237
      * :py:class:`demand.egon_heat_timeseries_selected_profiles <egon.data.datasets.heat_demand_timeseries.idp_pool.EgonHeatTimeseries>` is created and filled
1238
      * :py:class:`demand.egon_daily_heat_demand_per_climate_zone <egon.data.datasets.heat_demand_timeseries.daily.EgonDailyHeatDemandPerClimateZone>`
1239
        is created and filled
1240
      * :py:class:`boundaries.egon_map_zensus_climate_zones <egon.data.datasets.heat_demand_timeseries.daily.EgonMapZensusClimateZones>` is created and filled
1241
1242
    """
1243
1244
    #:
1245
    name: str = "HeatTimeSeries"
1246
    #:
1247
    version: str = "0.0.12"
1248
1249
    def __init__(self, dependencies):
1250
        super().__init__(
1251
            name=self.name,
1252
            version=self.version,
1253
            dependencies=dependencies,
1254
            tasks=(
1255
                {
1256
                    export_etrago_cts_heat_profiles,
1257
                    map_climate_zones_to_zensus,
1258
                    daily_demand_shares_per_climate_zone,
1259
                    create,
1260
                },
1261
                select,
1262
                district_heating,
1263
                metadata,
1264
                store_national_profiles,
1265
            ),
1266
        )
1267