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