Passed
Pull Request — dev (#1170)
by
unknown
05:05
created

data.datasets.heat_demand_timeseries   B

Complexity

Total Complexity 43

Size/Duplication

Total Lines 1236
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 43
eloc 477
dl 0
loc 1236
rs 8.96
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
A create_district_heating_profile() 0 100 1
A create_timeseries_for_building() 0 19 1
A store_national_profiles() 0 89 2
A create_individual_heat_per_mv_grid() 0 75 1
B district_heating() 0 67 5
A individual_heating_per_mv_grid_100() 0 2 1
C create_individual_heating_profile_python_like() 0 159 7
B metadata() 0 75 1
A export_etrago_cts_heat_profiles() 0 31 2
A create_individual_heating_peak_loads() 0 108 3
B individual_heating_per_mv_grid() 0 71 5
A individual_heating_per_mv_grid_tables() 0 7 1
A individual_heating_per_mv_grid_2035() 0 2 1
D create_district_heating_profile_python_like() 0 193 10
A calulate_peak_load() 0 21 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A HeatTimeSeries.__init__() 0 15 1

How to fix   Complexity   

Complexity

Complex classes like data.datasets.heat_demand_timeseries often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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