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

data.datasets.heat_demand_timeseries   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 1244
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 44
eloc 482
dl 0
loc 1244
rs 8.8798
c 0
b 0
f 0

15 Functions

Rating   Name   Duplication   Size   Complexity  
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 create_district_heating_profile() 0 100 1
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 199 11
A calulate_peak_load() 0 21 1

1 Method

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