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

HeatTimeSeries.__init__()   A

Complexity

Conditions 1

Size

Total Lines 15
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 13
dl 0
loc 15
rs 9.75
c 0
b 0
f 0
cc 1
nop 2
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 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
    # TODO: use session_scope!
327
    from sqlalchemy.orm import sessionmaker
328
329
    session = sessionmaker(bind=db.engine())()
330
331
    print(datetime.now() - start_time)
332
333
    start_time = datetime.now()
334
    for area in district_heating_grids.area_id.unique():
335
        selected_profiles = db.select_dataframe(
336
            f"""
337
            SELECT a.zensus_population_id, building_id, c.climate_zone,
338
            selected_idp, ordinality as day, b.area_id
339
            FROM demand.egon_heat_timeseries_selected_profiles a
340
            INNER JOIN boundaries.egon_map_zensus_climate_zones c
341
            ON a.zensus_population_id = c.zensus_population_id
342
            INNER JOIN (
343
                SELECT * FROM demand.egon_map_zensus_district_heating_areas
344
                WHERE scenario = '{scenario}'
345
                AND area_id = '{area}'
346
            ) b ON a.zensus_population_id = b.zensus_population_id        ,
347
348
            UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp
349
350
            """
351
        )
352
353
        if not selected_profiles.empty:
354
            df = pd.merge(
355
                selected_profiles,
356
                daily_demand_shares,
357
                on=["day", "climate_zone"],
358
            )
359
360
            slice_df = pd.merge(
361
                df[df.area_id == area],
362
                idp_df,
363
                left_on="selected_idp",
364
                right_on="index",
365
            )
366
367
            for hour in range(24):
368
                slice_df[hour] = (
369
                    slice_df.idp.str[hour]
370
                    .mul(slice_df.daily_demand_share)
371
                    .mul(
372
                        annual_demand.loc[
373
                            slice_df.zensus_population_id.values,
374
                            "per_building",
375
                        ].values
376
                    )
377
                )
378
379
            diff = (
380
                slice_df.groupby("day").sum()[range(24)].sum().sum()
381
                - annual_demand[
382
                    annual_demand.area_id == area
383
                ].demand_total.sum()
384
            ) / (
385
                annual_demand[annual_demand.area_id == area].demand_total.sum()
386
            )
387
388
            assert (
389
                abs(diff) < 0.03
390
            ), f"""Deviation of residential heat demand time
391
            series for district heating grid {str(area)} is {diff}"""
392
393
            hh = np.concatenate(
394
                slice_df.groupby("day").sum()[range(24)].values
395
            ).ravel()
396
397
        cts = CTS_demand_dist[
398
            (CTS_demand_dist.scenario == scenario)
399
            & (CTS_demand_dist.index == area)
400
        ].drop("scenario", axis="columns")
401
402
        if (not selected_profiles.empty) and not cts.empty:
403
            entry = EgonTimeseriesDistrictHeating(
404
                area_id=int(area),
405
                scenario=scenario,
406
                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...
407
            )
408
        elif (not selected_profiles.empty) and cts.empty:
409
            entry = EgonTimeseriesDistrictHeating(
410
                area_id=int(area),
411
                scenario=scenario,
412
                dist_aggregated_mw=(hh).tolist(),
413
            )
414
        elif not cts.empty:
415
            entry = EgonTimeseriesDistrictHeating(
416
                area_id=int(area),
417
                scenario=scenario,
418
                dist_aggregated_mw=(cts.values[0]).tolist(),
419
            )
420
421
        session.add(entry)
0 ignored issues
show
introduced by
The variable entry does not seem to be defined for all execution paths.
Loading history...
422
    session.commit()
423
424
    print(
425
        f"Time to create time series for district heating scenario {scenario}"
426
    )
427
    print(datetime.now() - start_time)
428
429
430
def create_individual_heat_per_mv_grid(scenario="eGon2035", mv_grid_id=1564):
431
    start_time = datetime.now()
432
    df = db.select_dataframe(
433
        f"""
434
435
        SELECT SUM(building_demand_per_hour) as demand_profile, hour_of_year
436
        FROM
437
438
        (
439
        SELECT demand.demand  *
440
        c.daily_demand_share * hourly_demand as building_demand_per_hour,
441
        ordinality + 24* (c.day_of_year-1) as hour_of_year,
442
        demand_profile.building_id,
443
        c.day_of_year,
444
        ordinality
445
446
        FROM
447
448
        (SELECT zensus_population_id, demand FROM
449
        demand.egon_peta_heat
450
        WHERE scenario = '{scenario}'
451
        AND sector = 'residential'
452
        AND zensus_population_id IN (
453
        SELECT zensus_population_id FROM
454
        boundaries.egon_map_zensus_grid_districts
455
        WHERE bus_id = {mv_grid_id}
456
        )) as demand
457
458
        JOIN boundaries.egon_map_zensus_climate_zones b
459
        ON demand.zensus_population_id = b.zensus_population_id
460
461
        JOIN demand.egon_daily_heat_demand_per_climate_zone c
462
        ON c.climate_zone = b.climate_zone
463
464
        JOIN (
465
        SELECT
466
            e.idp, ordinality as day, zensus_population_id, building_id
467
        FROM demand.egon_heat_timeseries_selected_profiles d,
468
        UNNEST (d.selected_idp_profiles) WITH ORDINALITY as selected_idp
469
        JOIN demand.egon_heat_idp_pool e
470
        ON selected_idp = e.index
471
        WHERE zensus_population_id IN (
472
        SELECT zensus_population_id FROM
473
        boundaries.egon_map_zensus_grid_districts
474
        WHERE bus_id = {mv_grid_id}
475
        ))  demand_profile
476
        ON (demand_profile.day = c.day_of_year AND
477
            demand_profile.zensus_population_id = b.zensus_population_id)
478
479
        JOIN (SELECT COUNT(building_id), zensus_population_id
480
        FROM demand.egon_heat_timeseries_selected_profiles
481
        WHERE zensus_population_id IN(
482
        SELECT zensus_population_id FROM
483
        demand.egon_heat_timeseries_selected_profiles
484
        WHERE zensus_population_id IN (
485
        SELECT zensus_population_id FROM
486
        boundaries.egon_map_zensus_grid_districts
487
        WHERE bus_id = {mv_grid_id}
488
       ))
489
        GROUP BY zensus_population_id) building
490
        ON building.zensus_population_id = b.zensus_population_id,
491
492
        UNNEST(demand_profile.idp) WITH ORDINALITY as hourly_demand
493
        )   result
494
495
496
        GROUP BY hour_of_year
497
498
        """
499
    )
500
501
    print(f"Time to create time series for mv grid {scenario} {mv_grid_id}:")
502
    print(datetime.now() - start_time)
503
504
    return df
505
506
507
def calulate_peak_load(df, scenario):
508
    # peat load in W_th
509
    data = (
510
        df.groupby("building_id")
511
        .max()[range(24)]
512
        .max(axis=1)
513
        .mul(1000000)
514
        .astype(int)
515
        .reset_index()
516
    )
517
518
    data["scenario"] = scenario
519
520
    data.rename({0: "w_th"}, axis="columns", inplace=True)
521
522
    data.to_sql(
523
        EgonIndividualHeatingPeakLoads.__table__.name,
524
        schema=EgonIndividualHeatingPeakLoads.__table__.schema,
525
        con=db.engine(),
526
        if_exists="append",
527
        index=False,
528
    )
529
530
531
def create_individual_heating_peak_loads(scenario="eGon2035"):
532
    engine = db.engine()
533
534
    EgonIndividualHeatingPeakLoads.__table__.drop(bind=engine, checkfirst=True)
535
536
    EgonIndividualHeatingPeakLoads.__table__.create(
537
        bind=engine, checkfirst=True
538
    )
539
540
    start_time = datetime.now()
541
542
    idp_df = db.select_dataframe(
543
        """
544
        SELECT index, idp FROM demand.egon_heat_idp_pool
545
        """,
546
        index_col="index",
547
    )
548
549
    annual_demand = db.select_dataframe(
550
        f"""
551
        SELECT a.zensus_population_id, demand/c.count as per_building, bus_id
552
        FROM demand.egon_peta_heat a
553
554
555
        JOIN (SELECT COUNT(building_id), zensus_population_id
556
        FROM demand.egon_heat_timeseries_selected_profiles
557
        WHERE zensus_population_id IN(
558
        SELECT zensus_population_id FROM
559
        demand.egon_heat_timeseries_selected_profiles
560
        WHERE zensus_population_id IN (
561
        SELECT zensus_population_id FROM
562
        boundaries.egon_map_zensus_grid_districts
563
       ))
564
        GROUP BY zensus_population_id)c
565
        ON a.zensus_population_id = c.zensus_population_id
566
567
        JOIN boundaries.egon_map_zensus_grid_districts d
568
        ON a.zensus_population_id = d.zensus_population_id
569
570
        WHERE a.scenario = '{scenario}'
571
        AND a.sector = 'residential'
572
        AND a.zensus_population_id NOT IN (
573
            SELECT zensus_population_id
574
            FROM demand.egon_map_zensus_district_heating_areas
575
            WHERE scenario = '{scenario}'
576
        )
577
578
        """,
579
        index_col="zensus_population_id",
580
    )
581
582
    daily_demand_shares = db.select_dataframe(
583
        """
584
        SELECT climate_zone, day_of_year as day, daily_demand_share FROM
585
        demand.egon_daily_heat_demand_per_climate_zone
586
        """
587
    )
588
589
    start_time = datetime.now()
590
    for grid in annual_demand.bus_id.unique():
591
        selected_profiles = db.select_dataframe(
592
            f"""
593
            SELECT a.zensus_population_id, building_id, c.climate_zone,
594
            selected_idp, ordinality as day
595
            FROM demand.egon_heat_timeseries_selected_profiles a
596
            INNER JOIN boundaries.egon_map_zensus_climate_zones c
597
            ON a.zensus_population_id = c.zensus_population_id
598
            ,
599
600
            UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp
601
602
            WHERE a.zensus_population_id NOT IN (
603
                SELECT zensus_population_id
604
                FROM demand.egon_map_zensus_district_heating_areas
605
                WHERE scenario = '{scenario}'
606
            )
607
            AND a.zensus_population_id IN (
608
                SELECT zensus_population_id
609
                FROM boundaries.egon_map_zensus_grid_districts
610
                WHERE bus_id = '{grid}'
611
            )
612
613
            """
614
        )
615
616
        df = pd.merge(
617
            selected_profiles, daily_demand_shares, on=["day", "climate_zone"]
618
        )
619
620
        slice_df = pd.merge(
621
            df, idp_df, left_on="selected_idp", right_on="index"
622
        )
623
624
        for hour in range(24):
625
            slice_df[hour] = (
626
                slice_df.idp.str[hour]
627
                .mul(slice_df.daily_demand_share)
628
                .mul(
629
                    annual_demand.loc[
630
                        slice_df.zensus_population_id.values, "per_building"
631
                    ].values
632
                )
633
            )
634
635
        calulate_peak_load(slice_df, scenario)
636
637
    print(f"Time to create peak loads per building for {scenario}")
638
    print(datetime.now() - start_time)
639
640
641
def create_individual_heating_profile_python_like(scenario="eGon2035"):
642
    start_time = datetime.now()
643
644
    idp_df = db.select_dataframe(
645
        f"""
646
        SELECT index, idp FROM demand.egon_heat_idp_pool
647
        """,
648
        index_col="index",
649
    )
650
651
    annual_demand = db.select_dataframe(
652
        f"""
653
        SELECT
654
            a.zensus_population_id,
655
            demand / c.count as per_building,
656
            demand as demand_total,
657
            bus_id
658
        FROM demand.egon_peta_heat a
659
660
661
        JOIN (SELECT COUNT(building_id), zensus_population_id
662
        FROM demand.egon_heat_timeseries_selected_profiles
663
        WHERE zensus_population_id IN(
664
        SELECT zensus_population_id FROM
665
        demand.egon_heat_timeseries_selected_profiles
666
        WHERE zensus_population_id IN (
667
        SELECT zensus_population_id FROM
668
        boundaries.egon_map_zensus_grid_districts
669
       ))
670
        GROUP BY zensus_population_id)c
671
        ON a.zensus_population_id = c.zensus_population_id
672
673
        JOIN boundaries.egon_map_zensus_grid_districts d
674
        ON a.zensus_population_id = d.zensus_population_id
675
676
        WHERE a.scenario = '{scenario}'
677
        AND a.sector = 'residential'
678
        AND a.zensus_population_id NOT IN (
679
            SELECT zensus_population_id
680
            FROM demand.egon_map_zensus_district_heating_areas
681
            WHERE scenario = '{scenario}'
682
        )
683
684
        """,
685
        index_col="zensus_population_id",
686
    )
687
688
    daily_demand_shares = db.select_dataframe(
689
        """
690
        SELECT climate_zone, day_of_year as day, daily_demand_share FROM
691
        demand.egon_daily_heat_demand_per_climate_zone
692
        """
693
    )
694
695
    CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
696
        aggregation_level="district"
697
    )
698
699
    # TODO: use session_scope!
700
    from sqlalchemy.orm import sessionmaker
701
702
    session = sessionmaker(bind=db.engine())()
703
704
    print(
705
        f"Time to create overhead for time series for district heating scenario {scenario}"
706
    )
707
    print(datetime.now() - start_time)
708
709
    start_time = datetime.now()
710
    for grid in annual_demand.bus_id.unique():
711
712
        selected_profiles = db.select_dataframe(
713
            f"""
714
            SELECT a.zensus_population_id, building_id, c.climate_zone,
715
            selected_idp, ordinality as day
716
            FROM demand.egon_heat_timeseries_selected_profiles a
717
            INNER JOIN boundaries.egon_map_zensus_climate_zones c
718
            ON a.zensus_population_id = c.zensus_population_id
719
            ,
720
721
            UNNEST (selected_idp_profiles) WITH ORDINALITY as selected_idp
722
723
            WHERE a.zensus_population_id NOT IN (
724
                SELECT zensus_population_id FROM demand.egon_map_zensus_district_heating_areas
725
                WHERE scenario = '{scenario}'
726
            )
727
            AND a.zensus_population_id IN (
728
                SELECT zensus_population_id
729
                FROM boundaries.egon_map_zensus_grid_districts
730
                WHERE bus_id = '{grid}'
731
            )
732
733
            """
734
        )
735
736
        df = pd.merge(
737
            selected_profiles, daily_demand_shares, on=["day", "climate_zone"]
738
        )
739
740
        slice_df = pd.merge(
741
            df, idp_df, left_on="selected_idp", right_on="index"
742
        )
743
744
        for hour in range(24):
745
            slice_df[hour] = (
746
                slice_df.idp.str[hour]
747
                .mul(slice_df.daily_demand_share)
748
                .mul(
749
                    annual_demand.loc[
750
                        slice_df.zensus_population_id.values, "per_building"
751
                    ].values
752
                )
753
            )
754
755
        cts = CTS_demand_grid[
756
            (CTS_demand_grid.scenario == scenario)
757
            & (CTS_demand_grid.index == grid)
758
        ].drop("scenario", axis="columns")
759
760
        hh = np.concatenate(
761
            slice_df.groupby("day").sum()[range(24)].values
762
        ).ravel()
763
764
        diff = (
765
            slice_df.groupby("day").sum()[range(24)].sum().sum()
766
            - annual_demand[annual_demand.bus_id == grid].demand_total.sum()
767
        ) / (annual_demand[annual_demand.bus_id == grid].demand_total.sum())
768
769
        assert abs(diff) < 0.03, (
770
            "Deviation of residential heat demand time series for mv"
771
            f" grid {grid} is {diff}"
772
        )
773
774
        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...
775
            entry = EgonEtragoTimeseriesIndividualHeating(
776
                bus_id=int(grid),
777
                scenario=scenario,
778
                dist_aggregated_mw=(hh + cts.values[0]).tolist(),
779
            )
780
        elif not slice_df[hour].empty:
781
            entry = EgonEtragoTimeseriesIndividualHeating(
782
                bus_id=int(grid),
783
                scenario=scenario,
784
                dist_aggregated_mw=(hh).tolist(),
785
            )
786
        elif not cts.empty:
787
            entry = EgonEtragoTimeseriesIndividualHeating(
788
                bus_id=int(grid),
789
                scenario=scenario,
790
                dist_aggregated_mw=(cts).tolist(),
791
            )
792
793
        session.add(entry)
0 ignored issues
show
introduced by
The variable entry does not seem to be defined for all execution paths.
Loading history...
794
795
    session.commit()
796
797
    print(
798
        f"Time to create time series for district heating scenario {scenario}"
799
    )
800
    print(datetime.now() - start_time)
801
802
803
def district_heating(method="python"):
804
    engine = db.engine()
805
    EgonTimeseriesDistrictHeating.__table__.drop(bind=engine, checkfirst=True)
806
    EgonTimeseriesDistrictHeating.__table__.create(
807
        bind=engine, checkfirst=True
808
    )
809
810
    if method == "python":
811
        create_district_heating_profile_python_like("eGon2035")
812
        create_district_heating_profile_python_like("eGon100RE")
813
814
    else:
815
        CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
816
            aggregation_level="district"
817
        )
818
819
        ids = db.select_dataframe(
820
            """
821
            SELECT area_id, scenario
822
            FROM demand.egon_district_heating_areas
823
            """
824
        )
825
826
        df = pd.DataFrame(
827
            columns=["area_id", "scenario", "dist_aggregated_mw"]
828
        )
829
830
        for index, row in ids.iterrows():
831
            series = create_district_heating_profile(
832
                scenario=row.scenario, area_id=row.area_id
833
            )
834
835
            cts = (
836
                CTS_demand_dist[
837
                    (CTS_demand_dist.scenario == row.scenario)
838
                    & (CTS_demand_dist.index == row.area_id)
839
                ]
840
                .drop("scenario", axis="columns")
841
                .transpose()
842
            )
843
844
            if not cts.empty:
845
                data = (
846
                    cts[row.area_id] + series.demand_profile
847
                ).values.tolist()
848
            else:
849
                data = series.demand_profile.values.tolist()
850
851
            df = df.append(
852
                pd.Series(
853
                    data={
854
                        "area_id": row.area_id,
855
                        "scenario": row.scenario,
856
                        "dist_aggregated_mw": data,
857
                    },
858
                ),
859
                ignore_index=True,
860
            )
861
862
        df.to_sql(
863
            "egon_timeseries_district_heating",
864
            schema="demand",
865
            con=db.engine(),
866
            if_exists="append",
867
            index=False,
868
        )
869
870
871
def individual_heating_per_mv_grid_tables(method="python"):
872
    engine = db.engine()
873
    EgonEtragoTimeseriesIndividualHeating.__table__.drop(
874
        bind=engine, checkfirst=True
875
    )
876
    EgonEtragoTimeseriesIndividualHeating.__table__.create(
877
        bind=engine, checkfirst=True
878
    )
879
880
881
def individual_heating_per_mv_grid_2035(method="python"):
882
    create_individual_heating_profile_python_like("eGon2035")
883
884
885
def individual_heating_per_mv_grid_100(method="python"):
886
    create_individual_heating_profile_python_like("eGon100RE")
887
888
889
def individual_heating_per_mv_grid(method="python"):
890
    if method == "python":
891
        engine = db.engine()
892
        EgonEtragoTimeseriesIndividualHeating.__table__.drop(
893
            bind=engine, checkfirst=True
894
        )
895
        EgonEtragoTimeseriesIndividualHeating.__table__.create(
896
            bind=engine, checkfirst=True
897
        )
898
899
        create_individual_heating_profile_python_like("eGon2035")
900
        create_individual_heating_profile_python_like("eGon100RE")
901
902
    else:
903
        engine = db.engine()
904
        EgonEtragoTimeseriesIndividualHeating.__table__.drop(
905
            bind=engine, checkfirst=True
906
        )
907
        EgonEtragoTimeseriesIndividualHeating.__table__.create(
908
            bind=engine, checkfirst=True
909
        )
910
911
        CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
912
            aggregation_level="district"
913
        )
914
        df = pd.DataFrame(columns=["bus_id", "scenario", "dist_aggregated_mw"])
915
916
        ids = db.select_dataframe(
917
            """
918
            SELECT bus_id
919
            FROM grid.egon_mv_grid_district
920
            """
921
        )
922
923
        for index, row in ids.iterrows():
924
            for scenario in ["eGon2035", "eGon100RE"]:
925
                series = create_individual_heat_per_mv_grid(
926
                    scenario, row.bus_id
927
                )
928
                cts = (
929
                    CTS_demand_grid[
930
                        (CTS_demand_grid.scenario == scenario)
931
                        & (CTS_demand_grid.index == row.bus_id)
932
                    ]
933
                    .drop("scenario", axis="columns")
934
                    .transpose()
935
                )
936
                if not cts.empty:
937
                    data = (
938
                        cts[row.bus_id] + series.demand_profile
939
                    ).values.tolist()
940
                else:
941
                    data = series.demand_profile.values.tolist()
942
943
                df = df.append(
944
                    pd.Series(
945
                        data={
946
                            "bus_id": row.bus_id,
947
                            "scenario": scenario,
948
                            "dist_aggregated_mw": data,
949
                        },
950
                    ),
951
                    ignore_index=True,
952
                )
953
954
        df.to_sql(
955
            "egon_etrago_timeseries_individual_heating",
956
            schema="demand",
957
            con=db.engine(),
958
            if_exists="append",
959
            index=False,
960
        )
961
962
963
def store_national_profiles():
964
    scenario = "eGon100RE"
965
966
    df = db.select_dataframe(
967
        f"""
968
969
        SELECT SUM(building_demand_per_hour) as "residential rural"
970
        FROM
971
972
        (
973
        SELECT demand.demand  *
974
        c.daily_demand_share * hourly_demand as building_demand_per_hour,
975
        ordinality + 24* (c.day_of_year-1) as hour_of_year,
976
        demand_profile.building_id,
977
        c.day_of_year,
978
        ordinality
979
980
        FROM
981
982
        (SELECT zensus_population_id, demand FROM
983
        demand.egon_peta_heat
984
        WHERE scenario = '{scenario}'
985
        AND sector = 'residential'
986
       ) as demand
987
988
        JOIN boundaries.egon_map_zensus_climate_zones b
989
        ON demand.zensus_population_id = b.zensus_population_id
990
991
        JOIN demand.egon_daily_heat_demand_per_climate_zone c
992
        ON c.climate_zone = b.climate_zone
993
994
        JOIN (
995
        SELECT e.idp, ordinality as day, zensus_population_id, building_id
996
        FROM demand.egon_heat_timeseries_selected_profiles d,
997
        UNNEST (d.selected_idp_profiles) WITH ORDINALITY as selected_idp
998
        JOIN demand.egon_heat_idp_pool e
999
        ON selected_idp = e.index
1000
        )  demand_profile
1001
        ON (demand_profile.day = c.day_of_year AND
1002
            demand_profile.zensus_population_id = b.zensus_population_id)
1003
1004
        JOIN (SELECT COUNT(building_id), zensus_population_id
1005
        FROM demand.egon_heat_timeseries_selected_profiles
1006
        WHERE zensus_population_id IN(
1007
        SELECT zensus_population_id FROM
1008
        demand.egon_heat_timeseries_selected_profiles
1009
        )
1010
        GROUP BY zensus_population_id) building
1011
        ON building.zensus_population_id = b.zensus_population_id,
1012
1013
        UNNEST(demand_profile.idp) WITH ORDINALITY as hourly_demand
1014
        )   result
1015
1016
1017
        GROUP BY hour_of_year
1018
1019
        """
1020
    )
1021
1022
    CTS_demand_dist, CTS_demand_grid, CTS_demand_zensus = CTS_demand_scale(
1023
        aggregation_level="district"
1024
    )
1025
1026
    df["service rural"] = (
1027
        CTS_demand_dist.loc[CTS_demand_dist.scenario == scenario]
1028
        .drop("scenario", axis=1)
1029
        .sum()
1030
    )
1031
1032
    df["urban central"] = db.select_dataframe(
1033
        f"""
1034
        SELECT sum(demand) as "urban central"
1035
1036
        FROM demand.egon_timeseries_district_heating,
1037
        UNNEST (dist_aggregated_mw) WITH ORDINALITY as demand
1038
1039
        WHERE scenario = '{scenario}'
1040
1041
        GROUP BY ordinality
1042
1043
        """
1044
    )
1045
1046
    folder = Path(".") / "input-pypsa-eur-sec"
1047
    # Create the folder, if it does not exists already
1048
    if not os.path.exists(folder):
1049
        os.mkdir(folder)
1050
1051
    df.to_csv(folder / f"heat_demand_timeseries_DE_{scenario}.csv")
1052
1053
1054
def export_etrago_cts_heat_profiles():
1055
    """Export heat cts load profiles at mv substation level
1056
    to etrago-table in the database
1057
1058
    Returns
1059
    -------
1060
    None.
1061
1062
    """
1063
1064
    # Calculate cts heat profiles at substation
1065
    _, CTS_grid, _ = CTS_demand_scale("district")
1066
1067
    # Change format
1068
    data = CTS_grid.drop(columns="scenario")
1069
    df_etrago_cts_heat_profiles = pd.DataFrame(
1070
        index=data.index, columns=["scn_name", "p_set"]
1071
    )
1072
    df_etrago_cts_heat_profiles.p_set = data.values.tolist()
1073
    df_etrago_cts_heat_profiles.scn_name = CTS_grid["scenario"]
1074
    df_etrago_cts_heat_profiles.reset_index(inplace=True)
1075
1076
    # Drop and recreate Table if exists
1077
    EgonEtragoHeatCts.__table__.drop(bind=db.engine(), checkfirst=True)
1078
    EgonEtragoHeatCts.__table__.create(bind=db.engine(), checkfirst=True)
1079
1080
    # Write heat ts into db
1081
    with db.session_scope() as session:
1082
        session.bulk_insert_mappings(
1083
            EgonEtragoHeatCts,
1084
            df_etrago_cts_heat_profiles.to_dict(orient="records"),
1085
        )
1086
1087
1088
def metadata():
1089
    fields = [
1090
        {
1091
            "description": "Index of corresponding district heating area",
1092
            "name": "area_id",
1093
            "type": "integer",
1094
            "unit": "none",
1095
        },
1096
        {
1097
            "description": "Name of scenario",
1098
            "name": "scenario",
1099
            "type": "str",
1100
            "unit": "none",
1101
        },
1102
        {
1103
            "description": "Heat demand time series",
1104
            "name": "dist_aggregated_mw",
1105
            "type": "array of floats",
1106
            "unit": "MW",
1107
        },
1108
    ]
1109
1110
    meta_district = {
1111
        "name": "demand.egon_timeseries_district_heating",
1112
        "title": "eGon heat demand time series for district heating grids",
1113
        "id": "WILL_BE_SET_AT_PUBLICATION",
1114
        "description": "Heat demand time series for district heating grids",
1115
        "language": ["EN"],
1116
        "publicationDate": date.today().isoformat(),
1117
        "context": context(),
1118
        "spatial": {
1119
            "location": None,
1120
            "extent": "Germany",
1121
            "resolution": None,
1122
        },
1123
        "sources": [
1124
            sources()["era5"],
1125
            sources()["vg250"],
1126
            sources()["egon-data"],
1127
            sources()["egon-data_bundle"],
1128
            sources()["peta"],
1129
        ],
1130
        "licenses": [license_egon_data_odbl()],
1131
        "contributors": [
1132
            {
1133
                "title": "Clara Büttner",
1134
                "email": "http://github.com/ClaraBuettner",
1135
                "date": time.strftime("%Y-%m-%d"),
1136
                "object": None,
1137
                "comment": "Imported data",
1138
            },
1139
        ],
1140
        "resources": [
1141
            {
1142
                "profile": "tabular-data-resource",
1143
                "name": "demand.egon_timeseries_district_heating",
1144
                "path": None,
1145
                "format": "PostgreSQL",
1146
                "encoding": "UTF-8",
1147
                "schema": {
1148
                    "fields": fields,
1149
                    "primaryKey": ["index"],
1150
                    "foreignKeys": [],
1151
                },
1152
                "dialect": {"delimiter": None, "decimalSeparator": "."},
1153
            }
1154
        ],
1155
        "metaMetadata": meta_metadata(),
1156
    }
1157
1158
    # Add metadata as a comment to the table
1159
    db.submit_comment(
1160
        "'" + json.dumps(meta_district) + "'",
1161
        EgonTimeseriesDistrictHeating.__table__.schema,
1162
        EgonTimeseriesDistrictHeating.__table__.name,
1163
    )
1164
1165
1166
1167
class HeatTimeSeries(Dataset):
1168
    """
1169
    Chooses heat demand profiles for each residential and CTS building
1170
1171
    This dataset creates heat demand profiles in an hourly resoultion.
1172
    Time series for CTS buildings are created using the SLP-gas method implemented
1173
    in the demandregio disagregator with the function :py:func:`export_etrago_cts_heat_profiles`
1174
    and stored in the database.
1175
    Time series for residential buildings are created based on a variety of synthetical created
1176
    individual demand profiles that are part of :py:class:`DataBundle <egon.data.datasets.data_bundle.DataBundle>`.
1177
    This method is desribed within the functions and in this publication:
1178
        C. Büttner, J. Amme, J. Endres, A. Malla, B. Schachler, I. Cußmann,
1179
        Open modeling of electricity and heat demand curves for all
1180
        residential buildings in Germany, Energy Informatics 5 (1) (2022) 21.
1181
        doi:10.1186/s42162-022-00201-y.
1182
1183
1184
    *Dependencies*
1185
      * :py:class:`DataBundle <egon.data.datasets.data_bundle.DataBundle>`
1186
      * :py:class:`DemandRegio <egon.data.datasets.demandregio.DemandRegio>`
1187
      * :py:class:`HeatDemandImport <egon.data.datasets.heat_demand.HeatDemandImport>`
1188
      * :py:class:`DistrictHeatingAreas <egon.data.datasets.district_heating_areas.DistrictHeatingAreas>`
1189
      * :py:class:`Vg250 <egon.data.datasets.vg250.Vg250>`
1190
      * :py:class:`ZensusMvGridDistricts <egon.data.datasets.zensus_mv_grid_districts.ZensusMvGridDistricts>`
1191
      * :py:func:`hh_demand_buildings_setup <egon.data.datasets.electricity_demand_timeseries.hh_buildings.map_houseprofiles_to_buildings>`
1192
      * :py:class:`WeatherData <egon.data.datasets.era5.WeatherData>`
1193
1194
1195
    *Resulting tables*
1196
      * :py:class:`demand.egon_timeseries_district_heating <egon.data.datasets.heat_demand_timeseries.EgonTimeseriesDistrictHeating>` is created and filled
1197
      * :py:class:`demand.egon_etrago_heat_cts <egon.data.datasets.heat_demand_timeseries.EgonEtragoHeatCts>` is created and filled
1198
      * :py:class:`demand.egon_heat_timeseries_selected_profiles <egon.data.datasets.heat_demand_timeseries.idp_pool.EgonHeatTimeseries>` is created and filled
1199
      * :py:class:`demand.egon_daily_heat_demand_per_climate_zone <egon.data.datasets.heat_demand_timeseries.daily.EgonDailyHeatDemandPerClimateZone>`
1200
        is created and filled
1201
      * :py:class:`boundaries.egon_map_zensus_climate_zones <egon.data.datasets.heat_demand_timeseries.daily.EgonMapZensusClimateZones>` is created and filled
1202
1203
    """
1204
1205
    #:
1206
    name: str = "HeatTimeSeries"
1207
    #:
1208
    version: str = "0.0.8"
1209
1210
    def __init__(self, dependencies):
1211
        super().__init__(
1212
            name=self.name,
1213
            version=self.version,
1214
            dependencies=dependencies,
1215
            tasks=(
1216
                {
1217
                    export_etrago_cts_heat_profiles,
1218
                    map_climate_zones_to_zensus,
1219
                    daily_demand_shares_per_climate_zone,
1220
                    create,
1221
                },
1222
                select,
1223
                district_heating,
1224
                metadata,
1225
                # store_national_profiles,
1226
            ),
1227
        )
1228