Passed
Pull Request — dev (#1034)
by Stephan
02:13
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
def calc_capacities():
794
    """Calculates installed capacities from TYNDP data
795
796
    Returns
797
    -------
798
    pandas.DataFrame
799
        Installed capacities per foreign node and energy carrier
800
801
    """
802
803
    sources = config.datasets()["electrical_neighbours"]["sources"]
804
805
    countries = [
806
        "AT",
807
        "BE",
808
        "CH",
809
        "CZ",
810
        "DK",
811
        "FR",
812
        "NL",
813
        "NO",
814
        "SE",
815
        "PL",
816
        "UK",
817
    ]
818
819
    # insert installed capacities
820
    file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}")
821
    df = pd.read_excel(
822
        file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(),
823
        sheet_name="Capacity",
824
    )
825
826
    # differneces between different climate years are very small (<1MW)
827
    # choose 1984 because it is the mean value
828
    df_2030 = (
829
        df.rename({"Climate Year": "Climate_Year"}, axis="columns")
830
        .query(
831
            'Scenario == "Distributed Energy" & Year == 2030 & '
832
            "Climate_Year == 1984"
833
        )
834
        .set_index(["Node/Line", "Generator_ID"])
835
    )
836
837
    df_2040 = (
838
        df.rename({"Climate Year": "Climate_Year"}, axis="columns")
839
        .query(
840
            'Scenario == "Distributed Energy" & Year == 2040 & '
841
            "Climate_Year == 1984"
842
        )
843
        .set_index(["Node/Line", "Generator_ID"])
844
    )
845
846
    # interpolate linear between 2030 and 2040 for 2035 accordning to
847
    # scenario report of TSO's and the approval by BNetzA
848
    df_2035 = pd.DataFrame(index=df_2030.index)
849
    df_2035["cap_2030"] = df_2030.Value
850
    df_2035["cap_2040"] = df_2040.Value
851
    df_2035.fillna(0.0, inplace=True)
852
    df_2035["cap_2035"] = (
853
        df_2035["cap_2030"] + (df_2035["cap_2040"] - df_2035["cap_2030"]) / 2
854
    )
855
    df_2035 = df_2035.reset_index()
856
    df_2035["carrier"] = df_2035.Generator_ID.map(map_carriers_tyndp())
857
858
    # group capacities by new carriers
859
    grouped_capacities = (
860
        df_2035.groupby(["carrier", "Node/Line"]).cap_2035.sum().reset_index()
861
    )
862
863
    # choose capacities for considered countries
864
    return grouped_capacities[
865
        grouped_capacities["Node/Line"].str[:2].isin(countries)
866
    ]
867
868
869
def insert_generators(capacities):
870
    """Insert generators for foreign countries based on TYNDP-data
871
872
    Parameters
873
    ----------
874
    capacities : pandas.DataFrame
875
        Installed capacities per foreign node and energy carrier
876
877
    Returns
878
    -------
879
    None.
880
881
    """
882
    targets = config.datasets()["electrical_neighbours"]["targets"]
883
    map_buses = get_map_buses()
884
885
    # Delete existing data
886
    db.execute_sql(
887
        f"""
888
        DELETE FROM
889
        {targets['generators']['schema']}.{targets['generators']['table']}
890
        WHERE bus IN (
891
            SELECT bus_id FROM
892
            {targets['buses']['schema']}.{targets['buses']['table']}
893
            WHERE country != 'DE'
894
            AND scn_name = 'eGon2035')
895
        AND scn_name = 'eGon2035'
896
        AND carrier != 'CH4'
897
        """
898
    )
899
900
    db.execute_sql(
901
        f"""
902
        DELETE FROM
903
        {targets['generators_timeseries']['schema']}.
904
        {targets['generators_timeseries']['table']}
905
        WHERE generator_id NOT IN (
906
            SELECT generator_id FROM
907
            {targets['generators']['schema']}.{targets['generators']['table']}
908
        )
909
        AND scn_name = 'eGon2035'
910
        """
911
    )
912
913
    # Select generators from TYNDP capacities
914
    gen = capacities[
915
        capacities.carrier.isin(
916
            [
917
                "others",
918
                "wind_offshore",
919
                "wind_onshore",
920
                "solar",
921
                "reservoir",
922
                "run_of_river",
923
                "lignite",
924
                "coal",
925
                "oil",
926
                "nuclear",
927
            ]
928
        )
929
    ]
930
931
    # Set bus_id
932
    gen.loc[
933
        gen[gen["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
934
    ] = gen.loc[
935
        gen[gen["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
936
    ].map(
937
        map_buses
938
    )
939
940
    gen.loc[:, "bus"] = (
941
        get_foreign_bus_id().loc[gen.loc[:, "Node/Line"]].values
942
    )
943
944
    # Add scenario column
945
    gen["scenario"] = "eGon2035"
946
947
    # Add marginal costs
948
    gen = add_marginal_costs(gen)
949
950
    # insert generators data
951
    session = sessionmaker(bind=db.engine())()
952
    for i, row in gen.iterrows():
953
        entry = etrago.EgonPfHvGenerator(
954
            scn_name=row.scenario,
955
            generator_id=int(db.next_etrago_id("generator")),
956
            bus=row.bus,
957
            carrier=row.carrier,
958
            p_nom=row.cap_2035,
959
            marginal_cost=row.marginal_cost,
960
        )
961
962
        session.add(entry)
963
        session.commit()
964
    session.close()
965
966
    # assign generators time-series data
967
    renew_carriers_2035 = ["wind_onshore", "wind_offshore", "solar"]
968
969
    sql = f"""SELECT * FROM
970
    {targets['generators_timeseries']['schema']}.
971
    {targets['generators_timeseries']['table']}
972
    WHERE scn_name = 'eGon100RE'
973
    """
974
    series_egon100 = pd.read_sql_query(sql, db.engine())
975
976
    sql = f""" SELECT * FROM
977
    {targets['generators']['schema']}.{targets['generators']['table']}
978
    WHERE bus IN (
979
        SELECT bus_id FROM
980
                {targets['buses']['schema']}.{targets['buses']['table']}
981
                WHERE country != 'DE'
982
                AND scn_name = 'eGon2035')
983
        AND scn_name = 'eGon2035'
984
    """
985
    gen_2035 = pd.read_sql_query(sql, db.engine())
986
    gen_2035 = gen_2035[gen_2035.carrier.isin(renew_carriers_2035)]
987
988
    sql = f""" SELECT * FROM
989
    {targets['generators']['schema']}.{targets['generators']['table']}
990
    WHERE bus IN (
991
        SELECT bus_id FROM
992
                {targets['buses']['schema']}.{targets['buses']['table']}
993
                WHERE country != 'DE'
994
                AND scn_name = 'eGon100RE')
995
        AND scn_name = 'eGon100RE'
996
    """
997
    gen_100 = pd.read_sql_query(sql, db.engine())
998
    gen_100 = gen_100[gen_100["carrier"].isin(renew_carriers_2035)]
999
1000
    # egon_2035_to_100 map the timeseries used in the scenario eGon100RE
1001
    # to the same bus and carrier for the scenario egon2035
1002
    egon_2035_to_100 = {}
1003
    for i, gen in gen_2035.iterrows():
1004
        gen_id_100 = gen_100[
1005
            (gen_100["bus"] == gen["bus"])
1006
            & (gen_100["carrier"] == gen["carrier"])
1007
        ]["generator_id"].values[0]
1008
1009
        egon_2035_to_100[gen["generator_id"]] = gen_id_100
1010
1011
    # insert generators_timeseries data
1012
    session = sessionmaker(bind=db.engine())()
1013
1014
    for gen_id in gen_2035.generator_id:
1015
        serie = series_egon100[
1016
            series_egon100.generator_id == egon_2035_to_100[gen_id]
1017
        ]["p_max_pu"].values[0]
1018
        entry = etrago.EgonPfHvGeneratorTimeseries(
1019
            scn_name="eGon2035", generator_id=gen_id, temp_id=1, p_max_pu=serie
1020
        )
1021
1022
        session.add(entry)
1023
        session.commit()
1024
    session.close()
1025
1026
1027
@db.session_scoped
1028
def insert_storage(capacities, session=None):
1029
    """Insert storage units for foreign countries based on TYNDP-data
1030
1031
    Parameters
1032
    ----------
1033
    capacities : pandas.DataFrame
1034
        Installed capacities per foreign node and energy carrier
1035
1036
1037
    Returns
1038
    -------
1039
    None.
1040
1041
    """
1042
    targets = config.datasets()["electrical_neighbours"]["targets"]
1043
    map_buses = get_map_buses()
1044
1045
    # Delete existing data
1046
    db.execute_sql(
1047
        f"""
1048
        DELETE FROM {targets['storage']['schema']}.{targets['storage']['table']}
1049
        WHERE bus IN (
1050
            SELECT bus_id FROM
1051
            {targets['buses']['schema']}.{targets['buses']['table']}
1052
            WHERE country != 'DE'
1053
            AND scn_name = 'eGon2035')
1054
        AND scn_name = 'eGon2035'
1055
        """
1056
    )
1057
1058
    # Add missing information suitable for eTraGo selected from
1059
    # scenario_parameter table
1060
    parameters_pumped_hydro = scenario_parameters.electricity("eGon2035")[
1061
        "efficiency"
1062
    ]["pumped_hydro"]
1063
1064
    parameters_battery = scenario_parameters.electricity("eGon2035")[
1065
        "efficiency"
1066
    ]["battery"]
1067
1068
    # Select storage capacities from TYNDP-data
1069
    store = capacities[capacities.carrier.isin(["battery", "pumped_hydro"])]
1070
1071
    # Set bus_id
1072
    store.loc[
1073
        store[store["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1074
    ] = store.loc[
1075
        store[store["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1076
    ].map(
1077
        map_buses
1078
    )
1079
1080
    store.loc[:, "bus"] = (
1081
        get_foreign_bus_id().loc[store.loc[:, "Node/Line"]].values
1082
    )
1083
1084
    # Add columns for additional parameters to df
1085
    (
1086
        store["dispatch"],
1087
        store["store"],
1088
        store["standing_loss"],
1089
        store["max_hours"],
1090
    ) = (None, None, None, None)
1091
1092
    # Insert carrier specific parameters
1093
1094
    parameters = ["dispatch", "store", "standing_loss", "max_hours"]
1095
1096
    for x in parameters:
1097
        store.loc[store["carrier"] == "battery", x] = parameters_battery[x]
1098
        store.loc[
1099
            store["carrier"] == "pumped_hydro", x
1100
        ] = parameters_pumped_hydro[x]
1101
1102
    # insert data
1103
    for i, row in store.iterrows():
1104
        entry = etrago.EgonPfHvStorage(
1105
            scn_name="eGon2035",
1106
            storage_id=int(db.next_etrago_id("storage")),
1107
            bus=row.bus,
1108
            max_hours=row.max_hours,
1109
            efficiency_store=row.store,
1110
            efficiency_dispatch=row.dispatch,
1111
            standing_loss=row.standing_loss,
1112
            carrier=row.carrier,
1113
            p_nom=row.cap_2035,
1114
        )
1115
1116
        session.add(entry)
1117
        session.commit()
1118
1119
1120
def get_map_buses():
1121
    """Returns a dictonary of foreign regions which are aggregated to another
1122
1123
    Returns
1124
    -------
1125
    Combination of aggregated regions
1126
1127
1128
    """
1129
    return {
1130
        "DK00": "DKW1",
1131
        "DKKF": "DKE1",
1132
        "FR15": "FR00",
1133
        "NON1": "NOM1",
1134
        "NOS0": "NOM1",
1135
        "NOS1": "NOM1",
1136
        "PLE0": "PL00",
1137
        "PLI0": "PL00",
1138
        "SE00": "SE02",
1139
        "SE01": "SE02",
1140
        "SE03": "SE02",
1141
        "SE04": "SE02",
1142
        "RU": "RU00",
1143
    }
1144
1145
1146
def tyndp_generation():
1147
    """Insert data from TYNDP 2020 accordning to NEP 2021
1148
    Scenario 'Distributed Energy', linear interpolate between 2030 and 2040
1149
1150
    Returns
1151
    -------
1152
    None.
1153
    """
1154
1155
    capacities = calc_capacities()
1156
1157
    insert_generators(capacities)
1158
1159
    insert_storage(capacities)
1160
1161
1162
@db.session_scoped
1163
def tyndp_demand(session=None):
1164
    """Copy load timeseries data from TYNDP 2020.
1165
    According to NEP 2021, the data for 2030 and 2040 is interpolated linearly.
1166
1167
    Returns
1168
    -------
1169
    None.
1170
1171
    """
1172
    map_buses = get_map_buses()
1173
1174
    sources = config.datasets()["electrical_neighbours"]["sources"]
1175
    targets = config.datasets()["electrical_neighbours"]["targets"]
1176
1177
    # Delete existing data
1178
    db.execute_sql(
1179
        f"""
1180
        DELETE FROM {targets['loads']['schema']}.
1181
        {targets['loads']['table']}
1182
        WHERE
1183
        scn_name = 'eGon2035'
1184
        AND carrier = 'AC'
1185
        AND bus NOT IN (
1186
            SELECT bus_i
1187
            FROM  {sources['osmtgmod_bus']['schema']}.
1188
            {sources['osmtgmod_bus']['table']})
1189
        """
1190
    )
1191
1192
    nodes = [
1193
        "AT00",
1194
        "BE00",
1195
        "CH00",
1196
        "CZ00",
1197
        "DKE1",
1198
        "DKW1",
1199
        "FR00",
1200
        "NL00",
1201
        "LUB1",
1202
        "LUF1",
1203
        "LUG1",
1204
        "NOM1",
1205
        "NON1",
1206
        "NOS0",
1207
        "SE01",
1208
        "SE02",
1209
        "SE03",
1210
        "SE04",
1211
        "PL00",
1212
        "UK00",
1213
        "UKNI",
1214
    ]
1215
    # Assign etrago bus_id to TYNDP nodes
1216
    buses = pd.DataFrame({"nodes": nodes})
1217
    buses.loc[
1218
        buses[buses.nodes.isin(map_buses.keys())].index, "nodes"
1219
    ] = buses[buses.nodes.isin(map_buses.keys())].nodes.map(map_buses)
1220
    buses.loc[:, "bus"] = (
1221
        get_foreign_bus_id().loc[buses.loc[:, "nodes"]].values
1222
    )
1223
    buses.set_index("nodes", inplace=True)
1224
    buses = buses[~buses.index.duplicated(keep="first")]
1225
1226
    # Read in data from TYNDP for 2030 and 2040
1227
    dataset_2030 = pd.read_excel(
1228
        f"tyndp/{sources['tyndp_demand_2030']}", sheet_name=nodes, skiprows=10
1229
    )
1230
1231
    dataset_2040 = pd.read_excel(
1232
        f"tyndp/{sources['tyndp_demand_2040']}", sheet_name=None, skiprows=10
1233
    )
1234
1235
    # Transform map_buses to pandas.Series and select only used values
1236
    map_series = pd.Series(map_buses)
1237
    map_series = map_series[map_series.index.isin(nodes)]
1238
1239
    # Calculate and insert demand timeseries per etrago bus_id
1240
    for bus in buses.index:
1241
        nodes = [bus]
1242
1243
        if bus in map_series.values:
1244
            nodes.extend(list(map_series[map_series == bus].index.values))
1245
1246
        load_id = db.next_etrago_id("load")
1247
1248
        # Some etrago bus_ids represent multiple TYNDP nodes,
1249
        # in this cases the loads are summed
1250
        data_2030 = pd.Series(index=range(8760), data=0.0)
1251
        for node in nodes:
1252
            data_2030 = dataset_2030[node][2011] + data_2030
1253
1254
        try:
1255
            data_2040 = pd.Series(index=range(8760), data=0.0)
1256
1257
            for node in nodes:
1258
                data_2040 = dataset_2040[node][2011] + data_2040
1259
        except:
1260
            data_2040 = data_2030
1261
1262
        # According to the NEP, data for 2030 and 2040 is linear interpolated
1263
        data_2035 = ((data_2030 + data_2040) / 2)[:8760]
1264
1265
        entry = etrago.EgonPfHvLoad(
1266
            scn_name="eGon2035",
1267
            load_id=int(load_id),
1268
            carrier="AC",
1269
            bus=int(buses.bus[bus]),
1270
        )
1271
1272
        entry_ts = etrago.EgonPfHvLoadTimeseries(
1273
            scn_name="eGon2035",
1274
            load_id=int(load_id),
1275
            temp_id=1,
1276
            p_set=list(data_2035.values),
1277
        )
1278
1279
        session.add(entry)
1280
        session.add(entry_ts)
1281
        session.commit()
1282