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

assign_electrical_bus()   C

Complexity

Conditions 5

Size

Total Lines 197
Code Lines 86

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 86
dl 0
loc 197
rs 6.9915
c 0
b 0
f 0
cc 5
nop 4

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
"""The central module containing all code dealing with power to heat
2
"""
3
from shapely.geometry import LineString
4
import geopandas as gpd
5
import pandas as pd
6
7
from egon.data import config, db
8
from egon.data.datasets.scenario_parameters import get_sector_parameters
9
10
11
def insert_individual_power_to_heat(scenario):
12
    """Insert power to heat into database
13
14
    Parameters
15
    ----------
16
    scenario : str, optional
17
        Name of the scenario.
18
19
    Returns
20
    -------
21
    None.
22
23
    """
24
25
    sources = config.datasets()["etrago_heat"]["sources"]
26
    targets = config.datasets()["etrago_heat"]["targets"]
27
28
    # Delete existing entries
29
    db.execute_sql(
30
        f"""
31
        DELETE FROM {targets['heat_link_timeseries']['schema']}.
32
        {targets['heat_link_timeseries']['table']}
33
        WHERE link_id IN (
34
            SELECT link_id FROM {targets['heat_links']['schema']}.
35
        {targets['heat_links']['table']}
36
        WHERE carrier IN ('individual_heat_pump', 'rural_heat_pump',
37
                          'rural_resisitive_heater')
38
        AND scn_name = '{scenario}')
39
        AND scn_name = '{scenario}'
40
        """
41
    )
42
    db.execute_sql(
43
        f"""
44
        DELETE FROM {targets['heat_links']['schema']}.
45
        {targets['heat_links']['table']}
46
        WHERE carrier IN ('individual_heat_pump', 'rural_heat_pump',
47
                          'rural_resisitive_heater')
48
        AND bus0 IN
49
        (SELECT bus_id
50
         FROM {targets['heat_buses']['schema']}.
51
         {targets['heat_buses']['table']}
52
         WHERE scn_name = '{scenario}'
53
         AND country = 'DE')
54
        AND bus1 IN
55
        (SELECT bus_id
56
         FROM {targets['heat_buses']['schema']}.
57
         {targets['heat_buses']['table']}
58
         WHERE scn_name = '{scenario}'
59
         AND country = 'DE')
60
        """
61
    )
62
63
    # Select heat pumps for individual heating
64
    heat_pumps = db.select_dataframe(
65
        f"""
66
        SELECT mv_grid_id as power_bus,
67
        a.carrier, capacity, b.bus_id as heat_bus, d.feedin as cop
68
        FROM {sources['individual_heating_supply']['schema']}.
69
            {sources['individual_heating_supply']['table']} a
70
        JOIN {targets['heat_buses']['schema']}.
71
        {targets['heat_buses']['table']} b
72
        ON ST_Intersects(
73
            ST_Buffer(ST_Transform(ST_Centroid(a.geometry), 4326), 0.00000001),
74
            geom)
75
        JOIN {sources['weather_cells']['schema']}.
76
            {sources['weather_cells']['table']} c
77
        ON ST_Intersects(
78
            b.geom, c.geom)
79
        JOIN {sources['feedin_timeseries']['schema']}.
80
            {sources['feedin_timeseries']['table']} d
81
        ON c.w_id = d.w_id
82
        WHERE scenario = '{scenario}'
83
        AND scn_name  = '{scenario}'
84
        AND a.carrier = 'heat_pump'
85
        AND b.carrier = 'rural_heat'
86
        AND d.carrier = 'heat_pump_cop'
87
        """
88
    )
89
90
    # Assign voltage level
91
    heat_pumps["voltage_level"] = 7
92
93
    # Set marginal_cost
94
    heat_pumps["marginal_cost"] = get_sector_parameters("heat", scenario)[
95
        "marginal_cost"
96
    ]["rural_heat_pump"]
97
98
    # Insert heatpumps
99
    insert_power_to_heat_per_level(
100
        heat_pumps,
101
        carrier="rural_heat_pump",
102
        multiple_per_mv_grid=False,
103
        scenario=scenario,
104
    )
105
106
    # Deal with rural resistive heaters
107
    # Select resisitve heaters for individual heating
108
    resistive_heaters = db.select_dataframe(
109
        f"""
110
        SELECT mv_grid_id as power_bus,
111
        a.carrier, capacity, b.bus_id as heat_bus
112
        FROM {sources['individual_heating_supply']['schema']}.
113
            {sources['individual_heating_supply']['table']} a
114
        JOIN {targets['heat_buses']['schema']}.
115
        {targets['heat_buses']['table']} b
116
        ON ST_Intersects(
117
            ST_Buffer(ST_Transform(ST_Centroid(a.geometry), 4326), 0.00000001),
118
            geom)
119
        WHERE scenario = '{scenario}'
120
        AND scn_name  = '{scenario}'
121
        AND a.carrier = 'resistive_heater'
122
        AND b.carrier = 'rural_heat'
123
        """
124
    )
125
126
    if resistive_heaters.empty:
127
        print(f"No rural resistive heaters in scenario {scenario}.")
128
    else:
129
        # Assign voltage level
130
        resistive_heaters["voltage_level"] = 7
131
132
        # Set marginal_cost
133
        resistive_heaters["marginal_cost"] = 0
134
135
        # Insert heatpumps
136
        insert_power_to_heat_per_level(
137
            resistive_heaters,
138
            carrier="rural_resistive_heater",
139
            multiple_per_mv_grid=False,
140
            scenario=scenario,
141
        )
142
143
144
def insert_central_power_to_heat(scenario):
145
    """Insert power to heat in district heating areas into database
146
147
    Parameters
148
    ----------
149
    scenario : str
150
        Name of the scenario.
151
152
    Returns
153
    -------
154
    None.
155
156
    """
157
158
    sources = config.datasets()["etrago_heat"]["sources"]
159
    targets = config.datasets()["etrago_heat"]["targets"]
160
161
    # Delete existing entries
162
    db.execute_sql(
163
        f"""
164
        DELETE FROM {targets['heat_link_timeseries']['schema']}.
165
        {targets['heat_link_timeseries']['table']}
166
        WHERE link_id IN (
167
            SELECT link_id FROM {targets['heat_links']['schema']}.
168
        {targets['heat_links']['table']}
169
        WHERE carrier = 'central_heat_pump'
170
        AND scn_name = '{scenario}')
171
        AND scn_name = '{scenario}'
172
        """
173
    )
174
175
    db.execute_sql(
176
        f"""
177
        DELETE FROM {targets['heat_links']['schema']}.
178
        {targets['heat_links']['table']}
179
        WHERE carrier = 'central_heat_pump'
180
        AND bus0 IN
181
        (SELECT bus_id
182
         FROM {targets['heat_buses']['schema']}.
183
         {targets['heat_buses']['table']}
184
         WHERE scn_name = '{scenario}'
185
         AND country = 'DE')
186
        AND bus1 IN
187
        (SELECT bus_id
188
         FROM {targets['heat_buses']['schema']}.
189
         {targets['heat_buses']['table']}
190
         WHERE scn_name = '{scenario}'
191
         AND country = 'DE')
192
        """
193
    )
194
195
    # Select heat pumps in district heating
196
    central_heat_pumps = db.select_geodataframe(
197
        f"""
198
        SELECT a.index, a.district_heating_id, a.carrier, a.category, a.capacity, a.geometry, a.scenario, d.feedin as cop
199
        FROM {sources['district_heating_supply']['schema']}.
200
            {sources['district_heating_supply']['table']} a
201
        JOIN {sources['weather_cells']['schema']}.
202
            {sources['weather_cells']['table']} c
203
        ON ST_Intersects(
204
            ST_Transform(a.geometry, 4326), c.geom)
205
        JOIN {sources['feedin_timeseries']['schema']}.
206
            {sources['feedin_timeseries']['table']} d
207
        ON c.w_id = d.w_id
208
        WHERE scenario = '{scenario}'
209
        AND a.carrier = 'heat_pump'
210
        AND d.carrier = 'heat_pump_cop'
211
        """,
212
        geom_col="geometry",
213
        epsg=4326,
214
    )
215
216
    # Assign voltage level
217
    central_heat_pumps = assign_voltage_level(
218
        central_heat_pumps, carrier="heat_pump"
219
    )
220
221
    # Set marginal_cost
222
    central_heat_pumps["marginal_cost"] = get_sector_parameters(
223
        "heat", scenario
224
    )["marginal_cost"]["central_heat_pump"]
225
226
    # Insert heatpumps in mv and below
227
    # (one hvmv substation per district heating grid)
228
    insert_power_to_heat_per_level(
229
        central_heat_pumps[central_heat_pumps.voltage_level > 3],
230
        multiple_per_mv_grid=False,
231
        carrier="central_heat_pump",
232
        scenario=scenario,
233
    )
234
    # Insert heat pumps in hv grid
235
    # (as many hvmv substations as intersect with district heating grid)
236
    insert_power_to_heat_per_level(
237
        central_heat_pumps[central_heat_pumps.voltage_level < 3],
238
        multiple_per_mv_grid=True,
239
        carrier="central_heat_pump",
240
        scenario=scenario,
241
    )
242
243
    # Delete existing entries
244
    db.execute_sql(
245
        f"""
246
        DELETE FROM {targets['heat_links']['schema']}.
247
        {targets['heat_links']['table']}
248
        WHERE carrier = 'central_resistive_heater'
249
        AND bus0 IN
250
        (SELECT bus_id
251
         FROM {targets['heat_buses']['schema']}.
252
         {targets['heat_buses']['table']}
253
         WHERE scn_name = '{scenario}'
254
         AND country = 'DE')
255
        AND bus1 IN
256
        (SELECT bus_id
257
         FROM {targets['heat_buses']['schema']}.
258
         {targets['heat_buses']['table']}
259
         WHERE scn_name = '{scenario}'
260
         AND country = 'DE')
261
        """
262
    )
263
    # Select heat pumps in district heating
264
    central_resistive_heater = db.select_geodataframe(
265
        f"""
266
        SELECT district_heating_id, carrier, category, SUM(capacity) as capacity,
267
               geometry, scenario
268
        FROM {sources['district_heating_supply']['schema']}.
269
            {sources['district_heating_supply']['table']}
270
        WHERE scenario = '{scenario}'
271
        AND carrier = 'resistive_heater'
272
        GROUP BY (district_heating_id, carrier, category, geometry, scenario)
273
        """,
274
        geom_col="geometry",
275
        epsg=4326,
276
    )
277
278
    # Assign voltage level
279
    central_resistive_heater = assign_voltage_level(
280
        central_resistive_heater, carrier="resistive_heater"
281
    )
282
283
    # Set efficiency
284
    central_resistive_heater["efficiency"] = get_sector_parameters(
285
        "heat", scenario
286
    )["efficiency"]["central_resistive_heater"]
287
288
    # Insert heatpumps in mv and below
289
    # (one hvmv substation per district heating grid)
290
    if (
291
        len(
292
            central_resistive_heater[
293
                central_resistive_heater.voltage_level > 3
294
            ]
295
        )
296
        > 0
297
    ):
298
        insert_power_to_heat_per_level(
299
            central_resistive_heater[
300
                central_resistive_heater.voltage_level > 3
301
            ],
302
            multiple_per_mv_grid=False,
303
            carrier="central_resistive_heater",
304
            scenario=scenario,
305
        )
306
    # Insert heat pumps in hv grid
307
    # (as many hvmv substations as intersect with district heating grid)
308
    insert_power_to_heat_per_level(
309
        central_resistive_heater[central_resistive_heater.voltage_level < 3],
310
        multiple_per_mv_grid=True,
311
        carrier="central_resistive_heater",
312
        scenario=scenario,
313
    )
314
315
316
def insert_power_to_heat_per_level(
317
    heat_pumps,
318
    multiple_per_mv_grid,
319
    carrier,
320
    scenario,
321
):
322
    """Insert power to heat plants per grid level
323
324
    Parameters
325
    ----------
326
    heat_pumps : pandas.DataFrame
327
        Heat pumps in selected grid level
328
    multiple_per_mv_grid : boolean
329
        Choose if one district heating areas is supplied by one hvmv substation
330
    scenario : str
331
        Name of the scenario.
332
333
    Returns
334
    -------
335
    None.
336
337
    """
338
    sources = config.datasets()["etrago_heat"]["sources"]
339
    targets = config.datasets()["etrago_heat"]["targets"]
340
341
    if "central" in carrier:
342
        # Calculate heat pumps per electrical bus
343
        gdf = assign_electrical_bus(
344
            heat_pumps, carrier, scenario, multiple_per_mv_grid
345
        )
346
347
    else:
348
        gdf = heat_pumps.copy()
349
350
    # Select geometry of buses
351
    geom_buses = db.select_geodataframe(
352
        f"""
353
        SELECT bus_id, geom FROM {targets['heat_buses']['schema']}.
354
        {targets['heat_buses']['table']}
355
        WHERE scn_name = '{scenario}'
356
        """,
357
        index_col="bus_id",
358
        epsg=4326,
359
    )
360
361
    # Create topology of heat pumps
362
    gdf["geom_power"] = geom_buses.geom[gdf.power_bus].values
363
    gdf["geom_heat"] = geom_buses.loc[gdf.heat_bus, "geom"].reset_index().geom
364
    gdf["geometry"] = gdf.apply(
365
        lambda x: LineString([x["geom_power"], x["geom_heat"]]), axis=1
366
    )
367
368
    # Choose next unused link id
369
    next_link_id = db.next_etrago_id("link")
370
371
    # Initilize dataframe of links
372
    links = (
373
        gpd.GeoDataFrame(
374
            index=range(len(gdf)),
375
            columns=[
376
                "scn_name",
377
                "bus0",
378
                "bus1",
379
                "carrier",
380
                "link_id",
381
                "p_nom",
382
                "topo",
383
            ],
384
            data={"scn_name": scenario, "carrier": carrier},
385
        )
386
        .set_geometry("topo")
387
        .set_crs(epsg=4326)
388
    )
389
390
    # Insert values into dataframe
391
    links.bus0 = gdf.power_bus.values.astype(int)
392
    links.bus1 = gdf.heat_bus.values
393
    links.p_nom = gdf.capacity.values
394
    links.topo = gdf.geometry.values
395
    links.link_id = range(next_link_id, next_link_id + len(links))
396
397
    # Insert data into database
398
    links.to_postgis(
399
        targets["heat_links"]["table"],
400
        schema=targets["heat_links"]["schema"],
401
        if_exists="append",
402
        con=db.engine(),
403
    )
404
405
    if "cop" in gdf.columns:
406
407
        # Create dataframe for time-dependent data
408
        links_timeseries = pd.DataFrame(
409
            index=links.index,
410
            data={
411
                "link_id": links.link_id,
412
                "efficiency": gdf.cop,
413
                "scn_name": scenario,
414
                "temp_id": 1,
415
            },
416
        )
417
418
        # Insert time-dependent data to database
419
        links_timeseries.to_sql(
420
            targets["heat_link_timeseries"]["table"],
421
            schema=targets["heat_link_timeseries"]["schema"],
422
            if_exists="append",
423
            con=db.engine(),
424
            index=False,
425
        )
426
427
428
def assign_voltage_level(heat_pumps, carrier="heat_pump"):
429
    """Assign voltage level to heat pumps
430
431
    Parameters
432
    ----------
433
    heat_pumps : pandas.DataFrame
434
        Heat pumps without voltage level
435
436
    Returns
437
    -------
438
    heat_pumps : pandas.DataFrame
439
        Heat pumps including voltage level
440
441
    """
442
443
    # set voltage level for heat pumps according to category
444
    heat_pumps["voltage_level"] = 0
445
446
    heat_pumps.loc[
447
        heat_pumps[
448
            (heat_pumps.carrier == carrier) & (heat_pumps.category == "small")
449
        ].index,
450
        "voltage_level",
451
    ] = 7
452
453
    heat_pumps.loc[
454
        heat_pumps[
455
            (heat_pumps.carrier == carrier) & (heat_pumps.category == "medium")
456
        ].index,
457
        "voltage_level",
458
    ] = 5
459
460
    heat_pumps.loc[
461
        heat_pumps[
462
            (heat_pumps.carrier == carrier) & (heat_pumps.category == "large")
463
        ].index,
464
        "voltage_level",
465
    ] = 1
466
467
    # if capacity > 5.5 MW, heatpump is installed in HV
468
    heat_pumps.loc[
469
        heat_pumps[
470
            (heat_pumps.carrier == carrier) & (heat_pumps.capacity > 5.5)
471
        ].index,
472
        "voltage_level",
473
    ] = 1
474
475
    return heat_pumps
476
477
478
def assign_electrical_bus(
479
    heat_pumps, carrier, scenario, multiple_per_mv_grid=False
480
):
481
    """Calculates heat pumps per electrical bus
482
483
    Parameters
484
    ----------
485
    heat_pumps : pandas.DataFrame
486
        Heat pumps including voltage level
487
    multiple_per_mv_grid : boolean, optional
488
        Choose if a district heating area can by supplied by multiple
489
        hvmv substaions/mv grids. The default is False.
490
491
    Returns
492
    -------
493
    gdf : pandas.DataFrame
494
        Heat pumps per electrical bus
495
496
    """
497
498
    sources = config.datasets()["etrago_heat"]["sources"]
499
    targets = config.datasets()["etrago_heat"]["targets"]
500
501
    # Map heat buses to district heating id and area_id
502
    heat_buses = db.select_dataframe(
503
        f"""
504
        SELECT bus_id, area_id, id FROM
505
        {targets['heat_buses']['schema']}.
506
        {targets['heat_buses']['table']}
507
        JOIN {sources['district_heating_areas']['schema']}.
508
            {sources['district_heating_areas']['table']}
509
        ON ST_Intersects(
510
        ST_Transform(ST_Buffer(
511
        ST_Centroid(geom_polygon), 0.0000001), 4326), geom)
512
        WHERE carrier = 'central_heat'
513
        AND scenario='{scenario}'
514
        AND scn_name = '{scenario}'
515
        """,
516
        index_col="id",
517
    )
518
519
    heat_pumps["power_bus"] = ""
520
521
    # Select mv grid distrcits
522
    mv_grid_district = db.select_geodataframe(
523
        f"""
524
        SELECT bus_id, geom FROM
525
        {sources['egon_mv_grid_district']['schema']}.
526
        {sources['egon_mv_grid_district']['table']}
527
        """,
528
        epsg=4326,
529
    )
530
531
    # Map zensus cells to district heating areas
532
    map_zensus_dh = db.select_geodataframe(
533
        f"""
534
        SELECT area_id, a.zensus_population_id,
535
        geom_point as geom, sum(a.demand) as demand
536
        FROM {sources['map_district_heating_areas']['schema']}.
537
            {sources['map_district_heating_areas']['table']} b
538
        JOIN {sources['heat_demand']['schema']}.
539
            {sources['heat_demand']['table']} a
540
        ON b.zensus_population_id = a.zensus_population_id
541
        JOIN society.destatis_zensus_population_per_ha
542
        ON society.destatis_zensus_population_per_ha.id =
543
        a.zensus_population_id
544
        WHERE a.scenario = '{scenario}'
545
        AND b.scenario = '{scenario}'
546
        GROUP BY (area_id, a.zensus_population_id, geom_point)
547
        """,
548
        epsg=4326,
549
    )
550
551
    # Select area_id per heat pump
552
    heat_pumps["area_id"] = heat_buses.area_id[
553
        heat_pumps.district_heating_id.values
554
    ].values
555
556
    heat_buses.set_index("area_id", inplace=True)
557
558
    # Select only cells in choosen district heating areas
559
    cells = map_zensus_dh[map_zensus_dh.area_id.isin(heat_pumps.area_id)]
560
561
    # Assign power bus per zensus cell
562
    cells["power_bus"] = gpd.sjoin(
563
        cells, mv_grid_district, how="inner", op="intersects"
564
    ).bus_id
565
566
    # Calclate district heating demand per substaion
567
    demand_per_substation = pd.DataFrame(
568
        cells.groupby(["area_id", "power_bus"]).demand.sum()
569
    )
570
571
    heat_pumps.set_index("area_id", inplace=True)
572
573
    # If district heating areas are supplied by multiple hvmv-substations,
574
    # create one heatpump per electrical bus.
575
    # The installed capacity is assigned regarding the share of heat demand.
576
    if multiple_per_mv_grid:
577
578
        power_to_heat = demand_per_substation.reset_index()
579
580
        power_to_heat["carrier"] = carrier
581
582
        power_to_heat.loc[:, "voltage_level"] = heat_pumps.voltage_level[
583
            power_to_heat.area_id
584
        ].values
585
586
        if "heat_pump" in carrier:
587
588
            power_to_heat.loc[:, "cop"] = heat_pumps.cop[
589
                power_to_heat.area_id
590
            ].values
591
592
        power_to_heat["share_demand"] = (
593
            power_to_heat.groupby("area_id")
594
            .demand.apply(lambda grp: grp / grp.sum())
595
            .values
596
        )
597
598
        power_to_heat["capacity"] = power_to_heat["share_demand"].mul(
599
            heat_pumps.capacity[power_to_heat.area_id].values
600
        )
601
602
        power_to_heat = power_to_heat[power_to_heat.voltage_level.notnull()]
603
604
        gdf = gpd.GeoDataFrame(
605
            power_to_heat,
606
            index=power_to_heat.index,
607
            geometry=heat_pumps.geometry[power_to_heat.area_id].values,
608
        )
609
610
    # If district heating areas are supplied by one hvmv-substations,
611
    # the hvmv substation which has the most heat demand is choosen.
612
    else:
613
614
        substation_max_demand = (
615
            demand_per_substation.reset_index()
616
            .set_index("power_bus")
617
            .groupby("area_id")
618
            .demand.max()
619
        )
620
621
        selected_substations = (
622
            demand_per_substation[
623
                demand_per_substation.demand.isin(substation_max_demand)
624
            ]
625
            .reset_index()
626
            .set_index("area_id")
627
        )
628
629
        selected_substations.rename(
630
            {"demand": "demand_selected_substation"}, axis=1, inplace=True
631
        )
632
633
        selected_substations["share_demand"] = (
634
            cells.groupby(["area_id", "power_bus"])
635
            .demand.sum()
636
            .reset_index()
637
            .groupby("area_id")
638
            .demand.max()
639
            / cells.groupby(["area_id", "power_bus"])
640
            .demand.sum()
641
            .reset_index()
642
            .groupby("area_id")
643
            .demand.sum()
644
        )
645
646
        power_to_heat = selected_substations
647
648
        power_to_heat["carrier"] = carrier
649
650
        power_to_heat.loc[:, "voltage_level"] = heat_pumps.voltage_level
651
652
        if "heat_pump" in carrier:
653
654
            power_to_heat.loc[:, "cop"] = heat_pumps.cop
655
656
        power_to_heat["capacity"] = heat_pumps.capacity[
657
            power_to_heat.index
658
        ].values
659
660
        power_to_heat = power_to_heat[power_to_heat.voltage_level.notnull()]
661
662
        gdf = gpd.GeoDataFrame(
663
            power_to_heat,
664
            index=power_to_heat.index,
665
            geometry=heat_pumps.geometry,
666
        )
667
668
    gdf.reset_index(inplace=True)
669
670
    gdf["heat_bus"] = (
671
        heat_buses.loc[gdf.area_id, "bus_id"].reset_index().bus_id
672
    )
673
674
    return gdf
675