Passed
Pull Request — dev (#1375)
by
unknown
02:18
created

individual_heating_per_mv_grid()   B

Complexity

Conditions 5

Size

Total Lines 71
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 47
dl 0
loc 71
rs 8.2678
c 0
b 0
f 0
cc 5
nop 1

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