Passed
Pull Request — dev (#1034)
by Stephan
01:28
created

data.datasets.electrical_neighbours.tyndp_demand()   B

Complexity

Conditions 6

Size

Total Lines 120
Code Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 71
dl 0
loc 120
rs 7.0157
c 0
b 0
f 0
cc 6
nop 1

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 electrical neighbours
2
"""
3
4
import zipfile
5
6
from shapely.geometry import LineString
7
from sqlalchemy.orm import sessionmaker
8
import geopandas as gpd
9
import pandas as pd
10
11
from egon.data import config, db
12
from egon.data.datasets import Dataset
13
from egon.data.datasets.fill_etrago_gen import add_marginal_costs
14
from egon.data.datasets.scenario_parameters import get_sector_parameters
15
import egon.data.datasets.etrago_setup as etrago
16
import egon.data.datasets.scenario_parameters.parameters as scenario_parameters
17
18
19
class ElectricalNeighbours(Dataset):
20
    def __init__(self, dependencies):
21
        super().__init__(
22
            name="ElectricalNeighbours",
23
            version="0.0.7",
24
            dependencies=dependencies,
25
            tasks=(grid, {tyndp_generation, tyndp_demand}),
26
        )
27
28
29
def get_cross_border_buses(scenario, sources):
30
    """Returns buses from osmTGmod which are outside of Germany.
31
32
    Parameters
33
    ----------
34
    sources : dict
35
        List of sources
36
37
    Returns
38
    -------
39
    geopandas.GeoDataFrame
40
        Electricity buses outside of Germany
41
42
    """
43
    return db.select_geodataframe(
44
        f"""
45
        SELECT *
46
        FROM {sources['electricity_buses']['schema']}.
47
            {sources['electricity_buses']['table']}
48
        WHERE
49
        NOT ST_INTERSECTS (
50
            geom,
51
            (SELECT ST_Transform(ST_Buffer(geometry, 5), 4326) FROM
52
             {sources['german_borders']['schema']}.
53
            {sources['german_borders']['table']}))
54
        AND (bus_id IN (
55
            SELECT bus0 FROM
56
            {sources['lines']['schema']}.{sources['lines']['table']})
57
            OR bus_id IN (
58
            SELECT bus1 FROM
59
            {sources['lines']['schema']}.{sources['lines']['table']}))
60
        AND scn_name = '{scenario}';
61
        """,
62
        epsg=4326,
63
    )
64
65
66
def get_cross_border_lines(scenario, sources):
67
    """Returns lines from osmTGmod which end or start outside of Germany.
68
69
    Parameters
70
    ----------
71
    sources : dict
72
        List of sources
73
74
    Returns
75
    -------
76
    geopandas.GeoDataFrame
77
        AC-lines outside of Germany
78
79
    """
80
    return db.select_geodataframe(
81
        f"""
82
    SELECT *
83
    FROM {sources['lines']['schema']}.{sources['lines']['table']} a
84
    WHERE
85
    ST_INTERSECTS (
86
        a.topo,
87
        (SELECT ST_Transform(ST_boundary(geometry), 4326)
88
         FROM {sources['german_borders']['schema']}.
89
        {sources['german_borders']['table']}))
90
    AND scn_name = '{scenario}';
91
    """,
92
        epsg=4326,
93
    )
94
95
96
def central_buses_egon100(sources):
97
    """Returns buses in the middle of foreign countries based on eGon100RE
98
99
    Parameters
100
    ----------
101
    sources : dict
102
        List of sources
103
104
    Returns
105
    -------
106
    pandas.DataFrame
107
        Buses in the center of foreign countries
108
109
    """
110
    return db.select_dataframe(
111
        f"""
112
        SELECT *
113
        FROM {sources['electricity_buses']['schema']}.
114
            {sources['electricity_buses']['table']}
115
        WHERE country != 'DE'
116
        AND scn_name = 'eGon100RE'
117
        AND bus_id NOT IN (
118
            SELECT bus_i
119
            FROM {sources['osmtgmod_bus']['schema']}.
120
            {sources['osmtgmod_bus']['table']})
121
        AND carrier = 'AC'
122
        """
123
    )
124
125
126
def buses(scenario, sources, targets):
127
    """Insert central buses in foreign countries per scenario
128
129
    Parameters
130
    ----------
131
    sources : dict
132
        List of dataset sources
133
    targets : dict
134
        List of dataset targets
135
136
    Returns
137
    -------
138
    central_buses : geoapndas.GeoDataFrame
139
        Buses in the center of foreign countries
140
141
    """
142
    sql_delete = f"""
143
        DELETE FROM {sources['electricity_buses']['schema']}.
144
            {sources['electricity_buses']['table']}
145
        WHERE country != 'DE' AND scn_name = '{scenario}'
146
        AND carrier = 'AC'
147
        AND bus_id NOT IN (
148
            SELECT bus_i
149
            FROM  {sources['osmtgmod_bus']['schema']}.
150
            {sources['osmtgmod_bus']['table']})
151
        """
152
    # Drop only buses with v_nom != 380 for eGon100RE
153
    # to keep buses from pypsa-eur-sec
154
    if scenario == "eGon100RE":
155
        sql_delete += "AND v_nom < 380"
156
157
    # Delete existing buses
158
    db.execute_sql(sql_delete)
159
160
    central_buses = central_buses_egon100(sources)
161
162
    next_bus_id = db.next_etrago_id("bus") + 1
163
164
    # if in test mode, add bus in center of Germany
165
    if config.settings()["egon-data"]["--dataset-boundary"] != "Everything":
166
        central_buses = central_buses.append(
167
            {
168
                "scn_name": scenario,
169
                "bus_id": next_bus_id,
170
                "x": 10.4234469,
171
                "y": 51.0834196,
172
                "country": "DE",
173
                "carrier": "AC",
174
                "v_nom": 380.0,
175
            },
176
            ignore_index=True,
177
        )
178
        next_bus_id += 1
179
180
    # Add buses for other voltage levels
181
    foreign_buses = get_cross_border_buses(scenario, sources)
182
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
183
        foreign_buses = foreign_buses[foreign_buses.country != "DE"]
184
    vnom_per_country = foreign_buses.groupby("country").v_nom.unique().copy()
185
    for cntr in vnom_per_country.index:
186
        print(cntr)
187 View Code Duplication
        if 110.0 in vnom_per_country[cntr]:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
188
            central_buses = central_buses.append(
189
                {
190
                    "scn_name": scenario,
191
                    "bus_id": next_bus_id,
192
                    "x": central_buses[
193
                        central_buses.country == cntr
194
                    ].x.unique()[0],
195
                    "y": central_buses[
196
                        central_buses.country == cntr
197
                    ].y.unique()[0],
198
                    "country": cntr,
199
                    "carrier": "AC",
200
                    "v_nom": 110.0,
201
                },
202
                ignore_index=True,
203
            )
204
            next_bus_id += 1
205 View Code Duplication
        if 220.0 in vnom_per_country[cntr]:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
206
            central_buses = central_buses.append(
207
                {
208
                    "scn_name": scenario,
209
                    "bus_id": next_bus_id,
210
                    "x": central_buses[
211
                        central_buses.country == cntr
212
                    ].x.unique()[0],
213
                    "y": central_buses[
214
                        central_buses.country == cntr
215
                    ].y.unique()[0],
216
                    "country": cntr,
217
                    "carrier": "AC",
218
                    "v_nom": 220.0,
219
                },
220
                ignore_index=True,
221
            )
222
            next_bus_id += 1
223
224
    # Add geometry column
225
    central_buses = gpd.GeoDataFrame(
226
        central_buses,
227
        geometry=gpd.points_from_xy(central_buses.x, central_buses.y),
228
        crs="EPSG:4326",
229
    )
230
    central_buses["geom"] = central_buses.geometry.copy()
231
    central_buses = central_buses.set_geometry("geom").drop(
232
        "geometry", axis="columns"
233
    )
234
    central_buses.scn_name = scenario
235
236
    # Insert all central buses for eGon2035
237
    if scenario == "eGon2035":
238
        central_buses.to_postgis(
239
            targets["buses"]["table"],
240
            schema=targets["buses"]["schema"],
241
            if_exists="append",
242
            con=db.engine(),
243
            index=False,
244
        )
245
    # Insert only buses for eGon100RE that are not coming from pypsa-eur-sec
246
    # (buses with another voltage_level or inside Germany in test mode)
247
    else:
248
        central_buses[
249
            (central_buses.v_nom != 380) | (central_buses.country == "DE")
250
        ].to_postgis(
251
            targets["buses"]["table"],
252
            schema=targets["buses"]["schema"],
253
            if_exists="append",
254
            con=db.engine(),
255
            index=False,
256
        )
257
258
    return central_buses
259
260
261
def cross_border_lines(scenario, sources, targets, central_buses):
262
    """Adds lines which connect border-crossing lines from osmtgmod
263
    to the central buses in the corresponding neigbouring country
264
265
    Parameters
266
    ----------
267
    sources : dict
268
        List of dataset sources
269
    targets : dict
270
        List of dataset targets
271
    central_buses : geopandas.GeoDataFrame
272
        Buses in the center of foreign countries
273
274
    Returns
275
    -------
276
    new_lines : geopandas.GeoDataFrame
277
        Lines that connect cross-border lines to central bus per country
278
279
    """
280
    # Delete existing data
281
    db.execute_sql(
282
        f"""
283
        DELETE FROM {targets['lines']['schema']}.
284
        {targets['lines']['table']}
285
        WHERE scn_name = '{scenario}'
286
        AND line_id NOT IN (
287
            SELECT branch_id
288
            FROM  {sources['osmtgmod_branch']['schema']}.
289
            {sources['osmtgmod_branch']['table']}
290
              WHERE result_id = 1 and (link_type = 'line' or
291
                                       link_type = 'cable'))
292
        AND bus0 IN (
293
            SELECT bus_i
294
            FROM  {sources['osmtgmod_bus']['schema']}.
295
            {sources['osmtgmod_bus']['table']})
296
        AND bus1 NOT IN (
297
            SELECT bus_i
298
            FROM  {sources['osmtgmod_bus']['schema']}.
299
            {sources['osmtgmod_bus']['table']})
300
        """
301
    )
302
303
    # Calculate cross-border busses and lines from osmtgmod
304
    foreign_buses = get_cross_border_buses(scenario, sources)
305
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
306
        foreign_buses = foreign_buses[foreign_buses.country != "DE"]
307
    lines = get_cross_border_lines(scenario, sources)
308
309
    # Select bus outside of Germany from border-crossing lines
310
    lines.loc[
311
        lines[lines.bus0.isin(foreign_buses.bus_id)].index, "foreign_bus"
312
    ] = lines.loc[lines[lines.bus0.isin(foreign_buses.bus_id)].index, "bus0"]
313
    lines.loc[
314
        lines[lines.bus1.isin(foreign_buses.bus_id)].index, "foreign_bus"
315
    ] = lines.loc[lines[lines.bus1.isin(foreign_buses.bus_id)].index, "bus1"]
316
317
    # Drop lines with start and endpoint in Germany
318
    lines = lines[lines.foreign_bus.notnull()]
319
    lines.loc[:, "foreign_bus"] = lines.loc[:, "foreign_bus"].astype(int)
320
321
    # Copy all parameters from border-crossing lines
322
    new_lines = lines.copy()
323
324
    # Set bus0 as foreign_bus from osmtgmod
325
    new_lines.bus0 = new_lines.foreign_bus.copy()
326
327
    # Add country tag and set index
328
    new_lines["country"] = (
329
        foreign_buses.set_index("bus_id")
330
        .loc[lines.foreign_bus, "country"]
331
        .values
332
    )
333
334
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
335
        new_lines = new_lines[~new_lines.country.isnull()]
336
    new_lines.line_id = range(
337
        db.next_etrago_id("line"), db.next_etrago_id("line") + len(new_lines)
338
    )
339
340
    # Set bus in center of foreogn countries as bus1
341
    for i, row in new_lines.iterrows():
342
        print(row)
343
        new_lines.loc[i, "bus1"] = central_buses.bus_id[
344
            (central_buses.country == row.country)
345
            & (central_buses.v_nom == row.v_nom)
346
        ].values[0]
347
348
    # Create geometry for new lines
349
    new_lines["geom_bus0"] = (
350
        foreign_buses.set_index("bus_id").geom[new_lines.bus0].values
351
    )
352
    new_lines["geom_bus1"] = (
353
        central_buses.set_index("bus_id").geom[new_lines.bus1].values
354
    )
355
    new_lines["topo"] = new_lines.apply(
356
        lambda x: LineString([x["geom_bus0"], x["geom_bus1"]]), axis=1
357
    )
358
359
    # Set topo as geometry column
360
    new_lines = new_lines.set_geometry("topo")
361
362
    # Calcultae length of lines based on topology
363
    old_length = new_lines["length"].copy()
364
    new_lines["length"] = new_lines.to_crs(3035).length / 1000
365
366
    # Set electrical parameters based on lines from osmtgmod
367
    for parameter in ["x", "r"]:
368
        new_lines[parameter] = (
369
            new_lines[parameter] / old_length * new_lines["length"]
370
        )
371
    for parameter in ["b", "g"]:
372
        new_lines[parameter] = (
373
            new_lines[parameter] * old_length / new_lines["length"]
374
        )
375
376
    # Drop intermediate columns
377
    new_lines.drop(
378
        ["foreign_bus", "country", "geom_bus0", "geom_bus1", "geom"],
379
        axis="columns",
380
        inplace=True,
381
    )
382
383
    new_lines = new_lines[new_lines.bus0 != new_lines.bus1]
384
385
    # Set scn_name
386
387
    # Insert lines to the database
388
    new_lines.to_postgis(
389
        targets["lines"]["table"],
390
        schema=targets["lines"]["schema"],
391
        if_exists="append",
392
        con=db.engine(),
393
        index=False,
394
    )
395
396
    return new_lines
397
398
399
def choose_transformer(s_nom):
400
    """Select transformer and parameters from existing data in the grid model
401
402
    It is assumed that transformers in the foreign countries are not limiting
403
    the electricity flow, so the capacitiy s_nom is set to the minimum sum
404
    of attached AC-lines.
405
    The electrical parameters are set according to already inserted
406
    transformers in the grid model for Germany.
407
408
    Parameters
409
    ----------
410
    s_nom : float
411
        Minimal sum of nominal power of lines at one side
412
413
    Returns
414
    -------
415
    int
416
        Selected transformer nominal power
417
    float
418
        Selected transformer nominal impedance
419
420
    """
421
422
    if s_nom <= 600:
423
        return 600, 0.0002
424
    elif (s_nom > 600) & (s_nom <= 1200):
425
        return 1200, 0.0001
426
    elif (s_nom > 1200) & (s_nom <= 1600):
427
        return 1600, 0.000075
428
    elif (s_nom > 1600) & (s_nom <= 2100):
429
        return 2100, 0.00006667
430
    elif (s_nom > 2100) & (s_nom <= 2600):
431
        return 2600, 0.0000461538
432
    elif (s_nom > 2600) & (s_nom <= 4800):
433
        return 4800, 0.000025
434
    elif (s_nom > 4800) & (s_nom <= 6000):
435
        return 6000, 0.0000225
436
    elif (s_nom > 6000) & (s_nom <= 7200):
437
        return 7200, 0.0000194444
438
    elif (s_nom > 7200) & (s_nom <= 8000):
439
        return 8000, 0.000016875
440
    elif (s_nom > 8000) & (s_nom <= 9000):
441
        return 9000, 0.000015
442
    elif (s_nom > 9000) & (s_nom <= 13000):
443
        return 13000, 0.0000103846
444
    elif (s_nom > 13000) & (s_nom <= 20000):
445
        return 20000, 0.00000675
446
    elif (s_nom > 20000) & (s_nom <= 33000):
447
        return 33000, 0.00000409091
448
449
450
def central_transformer(scenario, sources, targets, central_buses, new_lines):
451
    """Connect central foreign buses with different voltage levels
452
453
    Parameters
454
    ----------
455
    sources : dict
456
        List of dataset sources
457
    targets : dict
458
        List of dataset targets
459
    central_buses : geopandas.GeoDataFrame
460
        Buses in the center of foreign countries
461
    new_lines : geopandas.GeoDataFrame
462
        Lines that connect cross-border lines to central bus per country
463
464
    Returns
465
    -------
466
    None.
467
468
    """
469
    # Delete existing transformers in foreign countries
470
    db.execute_sql(
471
        f"""
472
        DELETE FROM {targets['transformers']['schema']}.
473
        {targets['transformers']['table']}
474
        WHERE scn_name = '{scenario}'
475
        AND trafo_id NOT IN (
476
            SELECT branch_id
477
            FROM {sources['osmtgmod_branch']['schema']}.
478
            {sources['osmtgmod_branch']['table']}
479
              WHERE result_id = 1 and link_type = 'transformer')
480
        """
481
    )
482
483
    # Initalize the dataframe for transformers
484
    trafo = gpd.GeoDataFrame(
485
        columns=["trafo_id", "bus0", "bus1", "s_nom"], dtype=int
486
    )
487
    trafo_id = db.next_etrago_id("transformer")
488
489
    # Add one transformer per central foreign bus with v_nom != 380
490
    for i, row in central_buses[central_buses.v_nom != 380].iterrows():
491
492
        s_nom_0 = new_lines[new_lines.bus0 == row.bus_id].s_nom.sum()
493
        s_nom_1 = new_lines[new_lines.bus1 == row.bus_id].s_nom.sum()
494
        if s_nom_0 == 0.0:
495
            s_nom = s_nom_1
496
        elif s_nom_1 == 0.0:
497
            s_nom = s_nom_0
498
        else:
499
            s_nom = min([s_nom_0, s_nom_1])
500
501
        s_nom, x = choose_transformer(s_nom)
502
503
        trafo = trafo.append(
504
            {
505
                "trafo_id": trafo_id,
506
                "bus0": row.bus_id,
507
                "bus1": central_buses[
508
                    (central_buses.v_nom == 380)
509
                    & (central_buses.country == row.country)
510
                ].bus_id.values[0],
511
                "s_nom": s_nom,
512
                "x": x,
513
            },
514
            ignore_index=True,
515
        )
516
        trafo_id += 1
517
518
    # Set data type
519
    trafo = trafo.astype({"trafo_id": "int", "bus0": "int", "bus1": "int"})
520
    trafo["scn_name"] = scenario
521
522
    # Insert transformers to the database
523
    trafo.to_sql(
524
        targets["transformers"]["table"],
525
        schema=targets["transformers"]["schema"],
526
        if_exists="append",
527
        con=db.engine(),
528
        index=False,
529
    )
530
531
532
def foreign_dc_lines(scenario, sources, targets, central_buses):
533
    """Insert DC lines to foreign countries manually
534
535
    Parameters
536
    ----------
537
    sources : dict
538
        List of dataset sources
539
    targets : dict
540
        List of dataset targets
541
    central_buses : geopandas.GeoDataFrame
542
        Buses in the center of foreign countries
543
544
    Returns
545
    -------
546
    None.
547
548
    """
549
    # Delete existing dc lines to foreign countries
550
    db.execute_sql(
551
        f"""
552
        DELETE FROM {targets['links']['schema']}.
553
        {targets['links']['table']}
554
        WHERE scn_name = '{scenario}'
555
        AND carrier = 'DC'
556
        AND bus0 IN (
557
            SELECT bus_id
558
            FROM {sources['electricity_buses']['schema']}.
559
            {sources['electricity_buses']['table']}
560
              WHERE scn_name = '{scenario}'
561
              AND carrier = 'AC'
562
              AND country = 'DE')
563
        AND bus1 IN (
564
            SELECT bus_id
565
            FROM {sources['electricity_buses']['schema']}.
566
            {sources['electricity_buses']['table']}
567
              WHERE scn_name = '{scenario}'
568
              AND carrier = 'AC'
569
              AND country != 'DE')
570
        """
571
    )
572
    capital_cost = get_sector_parameters("electricity", "eGon2035")[
573
        "capital_cost"
574
    ]
575
576
    # Add DC line from Lübeck to Sweden
577
    converter_luebeck = db.select_dataframe(
578
        f"""
579
        SELECT bus_id FROM
580
        {sources['electricity_buses']['schema']}.
581
        {sources['electricity_buses']['table']}
582
        WHERE x = 10.802358024202768
583
        AND y = 53.897547401787
584
        AND v_nom = 380
585
        AND scn_name = '{scenario}'
586
        AND carrier = 'AC'
587
        """
588
    ).squeeze()
589
590
    foreign_links = pd.DataFrame(
591
        index=[0],
592
        data={
593
            "link_id": db.next_etrago_id("link"),
594
            "bus0": converter_luebeck,
595
            "bus1": central_buses[
596
                (central_buses.country == "SE") & (central_buses.v_nom == 380)
597
            ]
598
            .squeeze()
599
            .bus_id,
600
            "p_nom": 600,
601
            "length": 262,
602
        },
603
    )
604
605
    # When not in test-mode, add DC line from Bentwisch to Denmark
606
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
607
        converter_bentwisch = db.select_dataframe(
608
            f"""
609
            SELECT bus_id FROM
610
            {sources['electricity_buses']['schema']}.
611
            {sources['electricity_buses']['table']}
612
            WHERE x = 12.213671694775988
613
            AND y = 54.09974494662279
614
            AND v_nom = 380
615
            AND scn_name = '{scenario}'
616
            AND carrier = 'AC'
617
            """
618
        ).squeeze()
619
620
        foreign_links = foreign_links.append(
621
            pd.DataFrame(
622
                index=[1],
623
                data={
624
                    "link_id": db.next_etrago_id("link") + 1,
625
                    "bus0": converter_bentwisch,
626
                    "bus1": central_buses[
627
                        (central_buses.country == "DK")
628
                        & (central_buses.v_nom == 380)
629
                        & (central_buses.x > 10)
630
                    ]
631
                    .squeeze()
632
                    .bus_id,
633
                    "p_nom": 600,
634
                    "length": 170,
635
                },
636
            )
637
        )
638
639
    # Set parameters for all DC lines
640
    foreign_links["capital_cost"] = (
641
        capital_cost["dc_cable"] * foreign_links.length
642
        + 2 * capital_cost["dc_inverter"]
643
    )
644
    foreign_links["p_min_pu"] = -1
645
    foreign_links["p_nom_extendable"] = True
646
    foreign_links["p_nom_min"] = foreign_links["p_nom"]
647
    foreign_links["scn_name"] = scenario
648
    foreign_links["carrier"] = "DC"
649
    foreign_links["efficiency"] = 1
650
651
    # Add topology
652
    foreign_links = etrago.link_geom_from_buses(foreign_links, scenario)
653
654
    # Insert DC lines to the database
655
    foreign_links.to_postgis(
656
        targets["links"]["table"],
657
        schema=targets["links"]["schema"],
658
        if_exists="append",
659
        con=db.engine(),
660
        index=False,
661
    )
662
663
664
def grid():
665
    """Insert electrical grid compoenents for neighbouring countries
666
667
    Returns
668
    -------
669
    None.
670
671
    """
672
    # Select sources and targets from dataset configuration
673
    sources = config.datasets()["electrical_neighbours"]["sources"]
674
    targets = config.datasets()["electrical_neighbours"]["targets"]
675
676
    for scenario in ["eGon2035"]:
677
678
        central_buses = buses(scenario, sources, targets)
679
680
        foreign_lines = cross_border_lines(
681
            scenario, sources, targets, central_buses
682
        )
683
684
        central_transformer(
685
            scenario, sources, targets, central_buses, foreign_lines
686
        )
687
688
        foreign_dc_lines(scenario, sources, targets, central_buses)
689
690
691
def map_carriers_tyndp():
692
    """Map carriers from TYNDP-data to carriers used in eGon
693
    Returns
694
    -------
695
    dict
696
        Carrier from TYNDP and eGon
697
    """
698
    return {
699
        "Battery": "battery",
700
        "DSR": "demand_side_response",
701
        "Gas CCGT new": "gas",
702
        "Gas CCGT old 2": "gas",
703
        "Gas CCGT present 1": "gas",
704
        "Gas CCGT present 2": "gas",
705
        "Gas conventional old 1": "gas",
706
        "Gas conventional old 2": "gas",
707
        "Gas OCGT new": "gas",
708
        "Gas OCGT old": "gas",
709
        "Gas CCGT old 1": "gas",
710
        "Gas CCGT old 2 Bio": "biogas",
711
        "Gas conventional old 2 Bio": "biogas",
712
        "Hard coal new": "coal",
713
        "Hard coal old 1": "coal",
714
        "Hard coal old 2": "coal",
715
        "Hard coal old 2 Bio": "coal",
716
        "Heavy oil old 1": "oil",
717
        "Heavy oil old 1 Bio": "oil",
718
        "Heavy oil old 2": "oil",
719
        "Light oil": "oil",
720
        "Lignite new": "lignite",
721
        "Lignite old 1": "lignite",
722
        "Lignite old 2": "lignite",
723
        "Lignite old 1 Bio": "lignite",
724
        "Lignite old 2 Bio": "lignite",
725
        "Nuclear": "nuclear",
726
        "Offshore Wind": "wind_offshore",
727
        "Onshore Wind": "wind_onshore",
728
        "Other non-RES": "others",
729
        "Other RES": "others",
730
        "P2G": "power_to_gas",
731
        "PS Closed": "pumped_hydro",
732
        "PS Open": "reservoir",
733
        "Reservoir": "reservoir",
734
        "Run-of-River": "run_of_river",
735
        "Solar PV": "solar",
736
        "Solar Thermal": "others",
737
        "Waste": "Other RES",
738
    }
739
740
741 View Code Duplication
def get_foreign_bus_id():
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
742
    """Calculte the etrago bus id from Nodes of TYNDP based on the geometry
743
744
    Returns
745
    -------
746
    pandas.Series
747
        List of mapped node_ids from TYNDP and etragos bus_id
748
749
    """
750
751
    sources = config.datasets()["electrical_neighbours"]["sources"]
752
753
    bus_id = db.select_geodataframe(
754
        """SELECT bus_id, ST_Buffer(geom, 1) as geom, country
755
        FROM grid.egon_etrago_bus
756
        WHERE scn_name = 'eGon2035'
757
        AND carrier = 'AC'
758
        AND v_nom = 380.
759
        AND country != 'DE'
760
        AND bus_id NOT IN (
761
            SELECT bus_i
762
            FROM osmtgmod_results.bus_data)
763
        """,
764
        epsg=3035,
765
    )
766
767
    # insert installed capacities
768
    file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}")
769
770
    # Select buses in neighbouring countries as geodataframe
771
    buses = pd.read_excel(
772
        file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(),
773
        sheet_name="Nodes - Dict",
774
    ).query("longitude==longitude")
775
    buses = gpd.GeoDataFrame(
776
        buses,
777
        crs=4326,
778
        geometry=gpd.points_from_xy(buses.longitude, buses.latitude),
779
    ).to_crs(3035)
780
781
    buses["bus_id"] = 0
782
783
    # Select bus_id from etrago with shortest distance to TYNDP node
784
    for i, row in buses.iterrows():
785
        distance = bus_id.set_index("bus_id").geom.distance(row.geometry)
786
        buses.loc[i, "bus_id"] = distance[
787
            distance == distance.min()
788
        ].index.values[0]
789
790
    return buses.set_index("node_id").bus_id
791
792
793
@db.session_scoped
794
def calc_capacities(session=None):
795
    """Calculates installed capacities from TYNDP data
796
797
    Returns
798
    -------
799
    pandas.DataFrame
800
        Installed capacities per foreign node and energy carrier
801
802
    """
803
804
    sources = config.datasets()["electrical_neighbours"]["sources"]
805
806
    countries = [
807
        "AT",
808
        "BE",
809
        "CH",
810
        "CZ",
811
        "DK",
812
        "FR",
813
        "NL",
814
        "NO",
815
        "SE",
816
        "PL",
817
        "UK",
818
    ]
819
820
    # insert installed capacities
821
    file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}")
822
    df = pd.read_excel(
823
        file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(),
824
        sheet_name="Capacity",
825
    )
826
827
    # differneces between different climate years are very small (<1MW)
828
    # choose 1984 because it is the mean value
829
    df_2030 = (
830
        df.rename({"Climate Year": "Climate_Year"}, axis="columns")
831
        .query(
832
            'Scenario == "Distributed Energy" & Year == 2030 & '
833
            "Climate_Year == 1984"
834
        )
835
        .set_index(["Node/Line", "Generator_ID"])
836
    )
837
838
    df_2040 = (
839
        df.rename({"Climate Year": "Climate_Year"}, axis="columns")
840
        .query(
841
            'Scenario == "Distributed Energy" & Year == 2040 & '
842
            "Climate_Year == 1984"
843
        )
844
        .set_index(["Node/Line", "Generator_ID"])
845
    )
846
847
    # interpolate linear between 2030 and 2040 for 2035 accordning to
848
    # scenario report of TSO's and the approval by BNetzA
849
    df_2035 = pd.DataFrame(index=df_2030.index)
850
    df_2035["cap_2030"] = df_2030.Value
851
    df_2035["cap_2040"] = df_2040.Value
852
    df_2035.fillna(0.0, inplace=True)
853
    df_2035["cap_2035"] = (
854
        df_2035["cap_2030"] + (df_2035["cap_2040"] - df_2035["cap_2030"]) / 2
855
    )
856
    df_2035 = df_2035.reset_index()
857
    df_2035["carrier"] = df_2035.Generator_ID.map(map_carriers_tyndp())
858
859
    # group capacities by new carriers
860
    grouped_capacities = (
861
        df_2035.groupby(["carrier", "Node/Line"]).cap_2035.sum().reset_index()
862
    )
863
864
    # choose capacities for considered countries
865
    return grouped_capacities[
866
        grouped_capacities["Node/Line"].str[:2].isin(countries)
867
    ]
868
869
870
def insert_generators(capacities):
871
    """Insert generators for foreign countries based on TYNDP-data
872
873
    Parameters
874
    ----------
875
    capacities : pandas.DataFrame
876
        Installed capacities per foreign node and energy carrier
877
878
    Returns
879
    -------
880
    None.
881
882
    """
883
    targets = config.datasets()["electrical_neighbours"]["targets"]
884
    map_buses = get_map_buses()
885
886
    # Delete existing data
887
    db.execute_sql(
888
        f"""
889
        DELETE FROM
890
        {targets['generators']['schema']}.{targets['generators']['table']}
891
        WHERE bus IN (
892
            SELECT bus_id FROM
893
            {targets['buses']['schema']}.{targets['buses']['table']}
894
            WHERE country != 'DE'
895
            AND scn_name = 'eGon2035')
896
        AND scn_name = 'eGon2035'
897
        AND carrier != 'CH4'
898
        """
899
    )
900
901
    db.execute_sql(
902
        f"""
903
        DELETE FROM
904
        {targets['generators_timeseries']['schema']}.
905
        {targets['generators_timeseries']['table']}
906
        WHERE generator_id NOT IN (
907
            SELECT generator_id FROM
908
            {targets['generators']['schema']}.{targets['generators']['table']}
909
        )
910
        AND scn_name = 'eGon2035'
911
        """
912
    )
913
914
    # Select generators from TYNDP capacities
915
    gen = capacities[
916
        capacities.carrier.isin(
917
            [
918
                "others",
919
                "wind_offshore",
920
                "wind_onshore",
921
                "solar",
922
                "reservoir",
923
                "run_of_river",
924
                "lignite",
925
                "coal",
926
                "oil",
927
                "nuclear",
928
            ]
929
        )
930
    ]
931
932
    # Set bus_id
933
    gen.loc[
934
        gen[gen["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
935
    ] = gen.loc[
936
        gen[gen["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
937
    ].map(
938
        map_buses
939
    )
940
941
    gen.loc[:, "bus"] = (
942
        get_foreign_bus_id().loc[gen.loc[:, "Node/Line"]].values
943
    )
944
945
    # Add scenario column
946
    gen["scenario"] = "eGon2035"
947
948
    # Add marginal costs
949
    gen = add_marginal_costs(gen)
950
951
    # insert generators data
952
    session = sessionmaker(bind=db.engine())()
953
    for i, row in gen.iterrows():
954
        entry = etrago.EgonPfHvGenerator(
955
            scn_name=row.scenario,
956
            generator_id=int(db.next_etrago_id("generator")),
957
            bus=row.bus,
958
            carrier=row.carrier,
959
            p_nom=row.cap_2035,
960
            marginal_cost=row.marginal_cost,
961
        )
962
963
        session.add(entry)
964
        session.commit()
965
    session.close()
966
967
    # assign generators time-series data
968
    renew_carriers_2035 = ["wind_onshore", "wind_offshore", "solar"]
969
970
    sql = f"""SELECT * FROM
971
    {targets['generators_timeseries']['schema']}.
972
    {targets['generators_timeseries']['table']}
973
    WHERE scn_name = 'eGon100RE'
974
    """
975
    series_egon100 = pd.read_sql_query(sql, db.engine())
976
977
    sql = f""" SELECT * FROM
978
    {targets['generators']['schema']}.{targets['generators']['table']}
979
    WHERE bus IN (
980
        SELECT bus_id FROM
981
                {targets['buses']['schema']}.{targets['buses']['table']}
982
                WHERE country != 'DE'
983
                AND scn_name = 'eGon2035')
984
        AND scn_name = 'eGon2035'
985
    """
986
    gen_2035 = pd.read_sql_query(sql, db.engine())
987
    gen_2035 = gen_2035[gen_2035.carrier.isin(renew_carriers_2035)]
988
989
    sql = f""" SELECT * FROM
990
    {targets['generators']['schema']}.{targets['generators']['table']}
991
    WHERE bus IN (
992
        SELECT bus_id FROM
993
                {targets['buses']['schema']}.{targets['buses']['table']}
994
                WHERE country != 'DE'
995
                AND scn_name = 'eGon100RE')
996
        AND scn_name = 'eGon100RE'
997
    """
998
    gen_100 = pd.read_sql_query(sql, db.engine())
999
    gen_100 = gen_100[gen_100["carrier"].isin(renew_carriers_2035)]
1000
1001
    # egon_2035_to_100 map the timeseries used in the scenario eGon100RE
1002
    # to the same bus and carrier for the scenario egon2035
1003
    egon_2035_to_100 = {}
1004
    for i, gen in gen_2035.iterrows():
1005
        gen_id_100 = gen_100[
1006
            (gen_100["bus"] == gen["bus"])
1007
            & (gen_100["carrier"] == gen["carrier"])
1008
        ]["generator_id"].values[0]
1009
1010
        egon_2035_to_100[gen["generator_id"]] = gen_id_100
1011
1012
    # insert generators_timeseries data
1013
    session = sessionmaker(bind=db.engine())()
1014
1015
    for gen_id in gen_2035.generator_id:
1016
        serie = series_egon100[
1017
            series_egon100.generator_id == egon_2035_to_100[gen_id]
1018
        ]["p_max_pu"].values[0]
1019
        entry = etrago.EgonPfHvGeneratorTimeseries(
1020
            scn_name="eGon2035", generator_id=gen_id, temp_id=1, p_max_pu=serie
1021
        )
1022
1023
        session.add(entry)
1024
        session.commit()
1025
    session.close()
1026
1027
1028
@db.session_scoped
1029
def insert_storage(capacities, session=None):
1030
    """Insert storage units for foreign countries based on TYNDP-data
1031
1032
    Parameters
1033
    ----------
1034
    capacities : pandas.DataFrame
1035
        Installed capacities per foreign node and energy carrier
1036
1037
1038
    Returns
1039
    -------
1040
    None.
1041
1042
    """
1043
    targets = config.datasets()["electrical_neighbours"]["targets"]
1044
    map_buses = get_map_buses()
1045
1046
    # Delete existing data
1047
    db.execute_sql(
1048
        f"""
1049
        DELETE FROM {targets['storage']['schema']}.{targets['storage']['table']}
1050
        WHERE bus IN (
1051
            SELECT bus_id FROM
1052
            {targets['buses']['schema']}.{targets['buses']['table']}
1053
            WHERE country != 'DE'
1054
            AND scn_name = 'eGon2035')
1055
        AND scn_name = 'eGon2035'
1056
        """
1057
    )
1058
1059
    # Add missing information suitable for eTraGo selected from
1060
    # scenario_parameter table
1061
    parameters_pumped_hydro = scenario_parameters.electricity("eGon2035")[
1062
        "efficiency"
1063
    ]["pumped_hydro"]
1064
1065
    parameters_battery = scenario_parameters.electricity("eGon2035")[
1066
        "efficiency"
1067
    ]["battery"]
1068
1069
    # Select storage capacities from TYNDP-data
1070
    store = capacities[capacities.carrier.isin(["battery", "pumped_hydro"])]
1071
1072
    # Set bus_id
1073
    store.loc[
1074
        store[store["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1075
    ] = store.loc[
1076
        store[store["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1077
    ].map(
1078
        map_buses
1079
    )
1080
1081
    store.loc[:, "bus"] = (
1082
        get_foreign_bus_id().loc[store.loc[:, "Node/Line"]].values
1083
    )
1084
1085
    # Add columns for additional parameters to df
1086
    (
1087
        store["dispatch"],
1088
        store["store"],
1089
        store["standing_loss"],
1090
        store["max_hours"],
1091
    ) = (None, None, None, None)
1092
1093
    # Insert carrier specific parameters
1094
1095
    parameters = ["dispatch", "store", "standing_loss", "max_hours"]
1096
1097
    for x in parameters:
1098
        store.loc[store["carrier"] == "battery", x] = parameters_battery[x]
1099
        store.loc[
1100
            store["carrier"] == "pumped_hydro", x
1101
        ] = parameters_pumped_hydro[x]
1102
1103
    # insert data
1104
    for i, row in store.iterrows():
1105
        entry = etrago.EgonPfHvStorage(
1106
            scn_name="eGon2035",
1107
            storage_id=int(db.next_etrago_id("storage")),
1108
            bus=row.bus,
1109
            max_hours=row.max_hours,
1110
            efficiency_store=row.store,
1111
            efficiency_dispatch=row.dispatch,
1112
            standing_loss=row.standing_loss,
1113
            carrier=row.carrier,
1114
            p_nom=row.cap_2035,
1115
        )
1116
1117
        session.add(entry)
1118
        session.commit()
1119
1120
1121
def get_map_buses():
1122
    """Returns a dictonary of foreign regions which are aggregated to another
1123
1124
    Returns
1125
    -------
1126
    Combination of aggregated regions
1127
1128
1129
    """
1130
    return {
1131
        "DK00": "DKW1",
1132
        "DKKF": "DKE1",
1133
        "FR15": "FR00",
1134
        "NON1": "NOM1",
1135
        "NOS0": "NOM1",
1136
        "NOS1": "NOM1",
1137
        "PLE0": "PL00",
1138
        "PLI0": "PL00",
1139
        "SE00": "SE02",
1140
        "SE01": "SE02",
1141
        "SE03": "SE02",
1142
        "SE04": "SE02",
1143
        "RU": "RU00",
1144
    }
1145
1146
1147
def tyndp_generation():
1148
    """Insert data from TYNDP 2020 accordning to NEP 2021
1149
    Scenario 'Distributed Energy', linear interpolate between 2030 and 2040
1150
1151
    Returns
1152
    -------
1153
    None.
1154
    """
1155
1156
    capacities = calc_capacities()
1157
1158
    insert_generators(capacities)
1159
1160
    insert_storage(capacities)
1161
1162
1163
@db.session_scoped
1164
def tyndp_demand(session=None):
1165
    """Copy load timeseries data from TYNDP 2020.
1166
    According to NEP 2021, the data for 2030 and 2040 is interpolated linearly.
1167
1168
    Returns
1169
    -------
1170
    None.
1171
1172
    """
1173
    map_buses = get_map_buses()
1174
1175
    sources = config.datasets()["electrical_neighbours"]["sources"]
1176
    targets = config.datasets()["electrical_neighbours"]["targets"]
1177
1178
    # Delete existing data
1179
    db.execute_sql(
1180
        f"""
1181
        DELETE FROM {targets['loads']['schema']}.
1182
        {targets['loads']['table']}
1183
        WHERE
1184
        scn_name = 'eGon2035'
1185
        AND carrier = 'AC'
1186
        AND bus NOT IN (
1187
            SELECT bus_i
1188
            FROM  {sources['osmtgmod_bus']['schema']}.
1189
            {sources['osmtgmod_bus']['table']})
1190
        """
1191
    )
1192
1193
    nodes = [
1194
        "AT00",
1195
        "BE00",
1196
        "CH00",
1197
        "CZ00",
1198
        "DKE1",
1199
        "DKW1",
1200
        "FR00",
1201
        "NL00",
1202
        "LUB1",
1203
        "LUF1",
1204
        "LUG1",
1205
        "NOM1",
1206
        "NON1",
1207
        "NOS0",
1208
        "SE01",
1209
        "SE02",
1210
        "SE03",
1211
        "SE04",
1212
        "PL00",
1213
        "UK00",
1214
        "UKNI",
1215
    ]
1216
    # Assign etrago bus_id to TYNDP nodes
1217
    buses = pd.DataFrame({"nodes": nodes})
1218
    buses.loc[
1219
        buses[buses.nodes.isin(map_buses.keys())].index, "nodes"
1220
    ] = buses[buses.nodes.isin(map_buses.keys())].nodes.map(map_buses)
1221
    buses.loc[:, "bus"] = (
1222
        get_foreign_bus_id().loc[buses.loc[:, "nodes"]].values
1223
    )
1224
    buses.set_index("nodes", inplace=True)
1225
    buses = buses[~buses.index.duplicated(keep="first")]
1226
1227
    # Read in data from TYNDP for 2030 and 2040
1228
    dataset_2030 = pd.read_excel(
1229
        f"tyndp/{sources['tyndp_demand_2030']}", sheet_name=nodes, skiprows=10
1230
    )
1231
1232
    dataset_2040 = pd.read_excel(
1233
        f"tyndp/{sources['tyndp_demand_2040']}", sheet_name=None, skiprows=10
1234
    )
1235
1236
    # Transform map_buses to pandas.Series and select only used values
1237
    map_series = pd.Series(map_buses)
1238
    map_series = map_series[map_series.index.isin(nodes)]
1239
1240
    # Calculate and insert demand timeseries per etrago bus_id
1241
    for bus in buses.index:
1242
        nodes = [bus]
1243
1244
        if bus in map_series.values:
1245
            nodes.extend(list(map_series[map_series == bus].index.values))
1246
1247
        load_id = db.next_etrago_id("load")
1248
1249
        # Some etrago bus_ids represent multiple TYNDP nodes,
1250
        # in this cases the loads are summed
1251
        data_2030 = pd.Series(index=range(8760), data=0.0)
1252
        for node in nodes:
1253
            data_2030 = dataset_2030[node][2011] + data_2030
1254
1255
        try:
1256
            data_2040 = pd.Series(index=range(8760), data=0.0)
1257
1258
            for node in nodes:
1259
                data_2040 = dataset_2040[node][2011] + data_2040
1260
        except:
1261
            data_2040 = data_2030
1262
1263
        # According to the NEP, data for 2030 and 2040 is linear interpolated
1264
        data_2035 = ((data_2030 + data_2040) / 2)[:8760]
1265
1266
        entry = etrago.EgonPfHvLoad(
1267
            scn_name="eGon2035",
1268
            load_id=int(load_id),
1269
            carrier="AC",
1270
            bus=int(buses.bus[bus]),
1271
        )
1272
1273
        entry_ts = etrago.EgonPfHvLoadTimeseries(
1274
            scn_name="eGon2035",
1275
            load_id=int(load_id),
1276
            temp_id=1,
1277
            p_set=list(data_2035.values),
1278
        )
1279
1280
        session.add(entry)
1281
        session.add(entry_ts)
1282
        session.commit()
1283