Passed
Pull Request — dev (#568)
by
unknown
01:46
created

data.datasets.heat_demand_timeseries   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 1168
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 41
eloc 466
dl 0
loc 1168
rs 9.1199
c 0
b 0
f 0

15 Functions

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