Passed
Pull Request — dev (#1181)
by
unknown
05:34
created

insert_storage_tyndp()   B

Complexity

Conditions 3

Size

Total Lines 92
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 47
dl 0
loc 92
rs 8.7345
c 0
b 0
f 0
cc 3
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
from os import path
5
from pathlib import Path
6
import datetime
7
import logging
8
import os.path
9
import zipfile
10
11
from shapely.geometry import LineString
12
from sqlalchemy.orm import sessionmaker
13
import entsoe
14
import requests
15
16
import geopandas as gpd
17
import pandas as pd
18
19
from egon.data import config, db, logger
20
from egon.data.db import session_scope
21
from egon.data.datasets import Dataset, wrapped_partial
22
from egon.data.datasets.fill_etrago_gen import add_marginal_costs
23
from egon.data.datasets.fix_ehv_subnetworks import select_bus_id
24
from egon.data.datasets.scenario_parameters import get_sector_parameters
25
import egon.data.datasets.etrago_setup as etrago
26
import egon.data.datasets.scenario_parameters.parameters as scenario_parameters
27
from egon.data.datasets.pypsaeur import prepared_network
28
29
30
def get_cross_border_buses(scenario, sources):
31
    """Returns buses from osmTGmod which are outside of Germany.
32
33
    Parameters
34
    ----------
35
    sources : dict
36
        List of sources
37
38
    Returns
39
    -------
40
    geopandas.GeoDataFrame
41
        Electricity buses outside of Germany
42
43
    """
44
    return db.select_geodataframe(
45
        f"""
46
        SELECT *
47
        FROM {sources['electricity_buses']['schema']}.
48
            {sources['electricity_buses']['table']}
49
        WHERE
50
        NOT ST_INTERSECTS (
51
            geom,
52
            (SELECT ST_Transform(ST_Buffer(geometry, 5), 4326) FROM
53
             {sources['german_borders']['schema']}.
54
            {sources['german_borders']['table']}))
55
        AND (bus_id IN (
56
            SELECT bus0 FROM
57
            {sources['lines']['schema']}.{sources['lines']['table']})
58
            OR bus_id IN (
59
            SELECT bus1 FROM
60
            {sources['lines']['schema']}.{sources['lines']['table']}))
61
        AND scn_name = '{scenario}';
62
        """,
63
        epsg=4326,
64
    )
65
66
67
def get_cross_border_lines(scenario, sources):
68
    """Returns lines from osmTGmod which end or start outside of Germany.
69
70
    Parameters
71
    ----------
72
    sources : dict
73
        List of sources
74
75
    Returns
76
    -------
77
    geopandas.GeoDataFrame
78
        AC-lines outside of Germany
79
80
    """
81
    return db.select_geodataframe(
82
        f"""
83
    SELECT *
84
    FROM {sources['lines']['schema']}.{sources['lines']['table']} a
85
    WHERE
86
    ST_INTERSECTS (
87
        a.topo,
88
        (SELECT ST_Transform(ST_boundary(geometry), 4326)
89
         FROM {sources['german_borders']['schema']}.
90
        {sources['german_borders']['table']}))
91
    AND scn_name = '{scenario}';
92
    """,
93
        epsg=4326,
94
    )
95
96
97
def central_buses_pypsaeur(sources, scenario):
98
    """Returns buses in the middle of foreign countries based on prepared pypsa-eur network
99
100
    Parameters
101
    ----------
102
    sources : dict
103
        List of sources
104
105
    Returns
106
    -------
107
    pandas.DataFrame
108
        Buses in the center of foreign countries
109
110
    """
111
112
    wanted_countries = [
113
        "AT",
114
        "CH",
115
        "CZ",
116
        "PL",
117
        "SE",
118
        "NO",
119
        "DK",
120
        "GB",
121
        "NL",
122
        "BE",
123
        "FR",
124
        "LU",
125
    ]
126
    network = prepared_network()
127
128
    df = network.buses[
129
        (network.buses.carrier == "AC")
130
        & (network.buses.country.isin(wanted_countries))
131
    ]
132
133
    return df
134
135
136
def buses(scenario, sources, targets):
137
    """Insert central buses in foreign countries per scenario
138
139
    Parameters
140
    ----------
141
    sources : dict
142
        List of dataset sources
143
    targets : dict
144
        List of dataset targets
145
146
    Returns
147
    -------
148
    central_buses : geoapndas.GeoDataFrame
149
        Buses in the center of foreign countries
150
151
    """
152
    sql_delete = f"""
153
        DELETE FROM {sources['electricity_buses']['schema']}.
154
            {sources['electricity_buses']['table']}
155
        WHERE country != 'DE' AND scn_name = '{scenario}'
156
        AND carrier = 'AC'
157
        AND bus_id NOT IN (
158
            SELECT bus_i
159
            FROM  {sources['osmtgmod_bus']['schema']}.
160
            {sources['osmtgmod_bus']['table']})
161
        """
162
163
    # Delete existing buses
164
    db.execute_sql(sql_delete)
165
166
    central_buses = central_buses_pypsaeur(sources, scenario)
167
168
    next_bus_id = db.next_etrago_id("bus") + 1
169
170
    central_buses["bus_id"] = central_buses.reset_index().index + next_bus_id
171
172
    next_bus_id += len(central_buses)
173
174
    # if in test mode, add bus in center of Germany
175
    if config.settings()["egon-data"]["--dataset-boundary"] != "Everything":
176
        central_buses = pd.concat(
177
            [
178
                central_buses,
179
                pd.DataFrame(
180
                    index=[central_buses.bus_id.max() + 1],
181
                    data={
182
                        "scn_name": scenario,
183
                        "bus_id": next_bus_id,
184
                        "x": 10.4234469,
185
                        "y": 51.0834196,
186
                        "country": "DE",
187
                        "carrier": "AC",
188
                        "v_nom": 380.0,
189
                    },
190
                ),
191
            ],
192
            ignore_index=True,
193
        )
194
        next_bus_id += 1
195
196
    # Add buses for other voltage levels
197
    foreign_buses = get_cross_border_buses(scenario, sources)
198
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
199
        foreign_buses = foreign_buses[foreign_buses.country != "DE"]
200
    vnom_per_country = foreign_buses.groupby("country").v_nom.unique().copy()
201
    for cntr in vnom_per_country.index:
202
        print(cntr)
203 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...
204
            central_buses = pd.concat(
205
                [
206
                    central_buses,
207
                    pd.DataFrame(
208
                        index=[next_bus_id],
209
                        data={
210
                            "scn_name": scenario,
211
                            "bus_id": next_bus_id,
212
                            "x": central_buses[
213
                                central_buses.country == cntr
214
                            ].x.unique()[0],
215
                            "y": central_buses[
216
                                central_buses.country == cntr
217
                            ].y.unique()[0],
218
                            "country": cntr,
219
                            "carrier": "AC",
220
                            "v_nom": 110.0,
221
                        },
222
                    ),
223
                ],
224
                ignore_index=True,
225
            )
226
            next_bus_id += 1
227
228 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...
229
            central_buses = pd.concat(
230
                [
231
                    central_buses,
232
                    pd.DataFrame(
233
                        index=[next_bus_id],
234
                        data={
235
                            "scn_name": scenario,
236
                            "bus_id": next_bus_id,
237
                            "x": central_buses[
238
                                central_buses.country == cntr
239
                            ].x.unique()[0],
240
                            "y": central_buses[
241
                                central_buses.country == cntr
242
                            ].y.unique()[0],
243
                            "country": cntr,
244
                            "carrier": "AC",
245
                            "v_nom": 220.0,
246
                        },
247
                    ),
248
                ],
249
                ignore_index=True,
250
            )
251
            next_bus_id += 1
252
253
    # Add geometry column
254
    central_buses = gpd.GeoDataFrame(
255
        central_buses,
256
        geometry=gpd.points_from_xy(central_buses.x, central_buses.y),
257
        crs="EPSG:4326",
258
    )
259
    central_buses["geom"] = central_buses.geometry.copy()
260
    central_buses = central_buses.set_geometry("geom").drop(
261
        "geometry", axis="columns"
262
    )
263
    central_buses.scn_name = scenario
264
265
    central_buses.drop(
266
        ["control", "generator", "location", "unit", "sub_network"],
267
        axis="columns",
268
        inplace=True,
269
        errors="ignore"
270
    )
271
272
    # Insert all central buses for eGon2035
273
    if scenario in [
274
        "eGon2035",
275
        "status2019",
276
        "status2023",
277
    ]:  # TODO: status2023 this is hardcoded shit
278
        central_buses.to_postgis(
279
            targets["buses"]["table"],
280
            schema=targets["buses"]["schema"],
281
            if_exists="append",
282
            con=db.engine(),
283
            index=False,
284
        )
285
    # Insert only buses for eGon100RE that are not coming from pypsa-eur-sec
286
    # (buses with another voltage_level or inside Germany in test mode)
287
    else:
288
        central_buses[central_buses.carrier=="AC"].to_postgis(
289
            targets["buses"]["table"],
290
            schema=targets["buses"]["schema"],
291
            if_exists="append",
292
            con=db.engine(),
293
            index=False,
294
        )
295
296
    return central_buses
297
298
299
def lines_between_foreign_countries(scenario, sorces, targets, central_buses):
300
    # import network from pypsa-eur
301
    network = prepared_network()
302
303
    gdf_buses = gpd.GeoDataFrame(
304
        network.buses,
305
        geometry=gpd.points_from_xy(network.buses.x, network.buses.y),
306
    )
307
308
    central_buses_pypsaeur = gpd.sjoin(
309
        gdf_buses[gdf_buses.carrier == "AC"], central_buses
310
    )
311
312
    central_buses_pypsaeur = central_buses_pypsaeur[
313
        central_buses_pypsaeur.v_nom_right == 380
314
    ]
315
316
    lines_to_add = network.lines[
317
        (network.lines.bus0.isin(central_buses_pypsaeur.index))
318
        & (network.lines.bus1.isin(central_buses_pypsaeur.index))
319
    ]
320
321
    lines_to_add.loc[:, "lifetime"] = get_sector_parameters(
322
        "electricity", scenario
323
    )["lifetime"]["ac_ehv_overhead_line"]
324
    lines_to_add.loc[:, "line_id"] = (
325
        lines_to_add.reset_index().index.astype(int)
326
        + db.next_etrago_id("line")
327
        + 1
328
    )
329
330
    links_to_add = network.links[
331
        (network.links.bus0.isin(central_buses_pypsaeur.index))
332
        & (network.links.bus1.isin(central_buses_pypsaeur.index))
333
    ]
334
335
    links_to_add.loc[:, "lifetime"] = get_sector_parameters(
336
        "electricity", scenario
337
    )["lifetime"]["dc_overhead_line"]
338
    links_to_add.loc[:, "link_id"] = (
339
        links_to_add.reset_index().index.astype(int)
340
        + db.next_etrago_id("link")
341
        + 1
342
    )
343
344
    for df in [lines_to_add, links_to_add]:
345
        df.loc[:, "scn_name"] = scenario
346
        gdf = gpd.GeoDataFrame(df)
347
        gdf["geom_bus0"] = gdf_buses.geometry[df.bus0].values
348
        gdf["geom_bus1"] = gdf_buses.geometry[df.bus1].values
349
        gdf["geometry"] = gdf.apply(
350
            lambda x: LineString([x["geom_bus0"], x["geom_bus1"]]),
351
            axis=1,
352
        )
353
354
        gdf = gdf.set_geometry("geometry")
355
        gdf = gdf.set_crs(4326)
356
357
        gdf = gdf.rename_geometry("topo")
358
359
        gdf.loc[:, "bus0"] = central_buses_pypsaeur.bus_id.loc[df.bus0].values
360
        gdf.loc[:, "bus1"] = central_buses_pypsaeur.bus_id.loc[df.bus1].values
361
362
        gdf.drop(["geom_bus0", "geom_bus1"], inplace=True, axis="columns")
363
        if "link_id" in df.columns:
364
            table_name = "link"
365
            gdf.drop(
366
                [
367
                    "tags",
368
                    "under_construction",
369
                    "underground",
370
                    "underwater_fraction",
371
                    "bus2",
372
                    "efficiency2",
373
                    "length_original",
374
                    "bus4",
375
                    "efficiency4",
376
                    "reversed",
377
                    "ramp_limit_up",
378
                    "ramp_limit_down",
379
                    "p_nom_opt",
380
                    "bus3",
381
                    "efficiency3",
382
                ],
383
                axis="columns",
384
                inplace=True,
385
            )
386
        else:
387
            table_name = "line"
388
            gdf.drop(
389
                [
390
                    "i_nom",
391
                    "sub_network",
392
                    "x_pu",
393
                    "r_pu",
394
                    "g_pu",
395
                    "b_pu",
396
                    "x_pu_eff",
397
                    "r_pu_eff",
398
                    "s_nom_opt",
399
                ],
400
                axis="columns",
401
                inplace=True,
402
            )
403
404
        gdf = gdf.set_index(f"{table_name}_id")
405
        gdf.to_postgis(
406
            f"egon_etrago_{table_name}",
407
            db.engine(),
408
            schema="grid",
409
            if_exists="append",
410
            index=True,
411
            index_label=f"{table_name}_id",
412
        )
413
414
415
def cross_border_lines(scenario, sources, targets, central_buses):
416
    """Adds lines which connect border-crossing lines from osmtgmod
417
    to the central buses in the corresponding neigbouring country
418
419
    Parameters
420
    ----------
421
    sources : dict
422
        List of dataset sources
423
    targets : dict
424
        List of dataset targets
425
    central_buses : geopandas.GeoDataFrame
426
        Buses in the center of foreign countries
427
428
    Returns
429
    -------
430
    new_lines : geopandas.GeoDataFrame
431
        Lines that connect cross-border lines to central bus per country
432
433
    """
434
    # Delete existing data
435
    db.execute_sql(
436
        f"""
437
        DELETE FROM {targets['lines']['schema']}.
438
        {targets['lines']['table']}
439
        WHERE scn_name = '{scenario}'
440
        AND line_id NOT IN (
441
            SELECT branch_id
442
            FROM  {sources['osmtgmod_branch']['schema']}.
443
            {sources['osmtgmod_branch']['table']}
444
              WHERE result_id = 1 and (link_type = 'line' or
445
                                       link_type = 'cable'))
446
        AND bus0 IN (
447
            SELECT bus_i
448
            FROM  {sources['osmtgmod_bus']['schema']}.
449
            {sources['osmtgmod_bus']['table']})
450
        AND bus1 NOT IN (
451
            SELECT bus_i
452
            FROM  {sources['osmtgmod_bus']['schema']}.
453
            {sources['osmtgmod_bus']['table']})
454
        """
455
    )
456
457
    # Calculate cross-border busses and lines from osmtgmod
458
    foreign_buses = get_cross_border_buses(scenario, sources)
459
    foreign_buses.dropna(subset="country", inplace=True)
460
461
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
462
        foreign_buses = foreign_buses[foreign_buses.country != "DE"]
463
    lines = get_cross_border_lines(scenario, sources)
464
465
    # Select bus outside of Germany from border-crossing lines
466
    lines.loc[
467
        lines[lines.bus0.isin(foreign_buses.bus_id)].index, "foreign_bus"
468
    ] = lines.loc[lines[lines.bus0.isin(foreign_buses.bus_id)].index, "bus0"]
469
    lines.loc[
470
        lines[lines.bus1.isin(foreign_buses.bus_id)].index, "foreign_bus"
471
    ] = lines.loc[lines[lines.bus1.isin(foreign_buses.bus_id)].index, "bus1"]
472
473
    # Drop lines with start and endpoint in Germany
474
    lines = lines[lines.foreign_bus.notnull()]
475
    lines.loc[:, "foreign_bus"] = lines.loc[:, "foreign_bus"].astype(int)
476
477
    # Copy all parameters from border-crossing lines
478
    new_lines = lines.copy().set_crs(4326)
479
480
    # Set bus0 as foreign_bus from osmtgmod
481
    new_lines.bus0 = new_lines.foreign_bus.copy()
482
    new_lines.bus0 = new_lines.bus0.astype(int)
483
484
    # Add country tag and set index
485
    new_lines["country"] = (
486
        foreign_buses.set_index("bus_id")
487
        .loc[lines.foreign_bus, "country"]
488
        .values
489
    )
490
491
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
492
        new_lines = new_lines[~new_lines.country.isnull()]
493
    new_lines.line_id = range(
494
        db.next_etrago_id("line"), db.next_etrago_id("line") + len(new_lines)
495
    )
496
497
    # Set bus in center of foreign countries as bus1
498
    for i, row in new_lines.iterrows():
499
        print(row)
500
        new_lines.loc[i, "bus1"] = central_buses.bus_id[
501
            (central_buses.country == row.country)
502
            & (central_buses.v_nom == row.v_nom)
503
        ].values[0]
504
505
    # Create geometry for new lines
506
    new_lines["geom_bus0"] = (
507
        foreign_buses.set_index("bus_id").geom[new_lines.bus0].values
508
    )
509
    new_lines["geom_bus1"] = (
510
        central_buses.set_index("bus_id").geom[new_lines.bus1].values
511
    )
512
    new_lines["topo"] = new_lines.apply(
513
        lambda x: LineString([x["geom_bus0"], x["geom_bus1"]]), axis=1
514
    )
515
516
    # Set topo as geometry column
517
    new_lines = new_lines.set_geometry("topo").set_crs(4326)
518
    # Calcultae length of lines based on topology
519
    old_length = new_lines["length"].copy()
520
    new_lines["length"] = new_lines.to_crs(3035).length / 1000
521
522
    if (new_lines["length"] == 0).any():
523
        print("WARNING! THERE ARE LINES WITH LENGTH = 0")
524
        condition = new_lines["length"] != 0
525
        new_lines["length"] = new_lines["length"].where(condition, 1)
526
        
527
528
    # Set electrical parameters based on lines from osmtgmod
529
    for parameter in ["x", "r"]:
530
        new_lines[parameter] = (
531
            new_lines[parameter] / old_length * new_lines["length"]
532
        )
533
    for parameter in ["b", "g"]:
534
        new_lines[parameter] = (
535
            new_lines[parameter] * old_length / new_lines["length"]
536
        )
537
538
539
    # Drop intermediate columns
540
    new_lines.drop(
541
        ["foreign_bus", "country", "geom_bus0", "geom_bus1", "geom"],
542
        axis="columns",
543
        inplace=True,
544
    )
545
546
    new_lines = new_lines[new_lines.bus0 != new_lines.bus1]
547
548
    new_lines["cables"] = new_lines["cables"].apply(int)
549
550
    # Insert lines to the database
551
    new_lines.to_postgis(
552
        targets["lines"]["table"],
553
        schema=targets["lines"]["schema"],
554
        if_exists="append",
555
        con=db.engine(),
556
        index=False,
557
    )
558
559
    return new_lines
560
561
562
def choose_transformer(s_nom):
563
    """Select transformer and parameters from existing data in the grid model
564
565
    It is assumed that transformers in the foreign countries are not limiting
566
    the electricity flow, so the capacitiy s_nom is set to the minimum sum
567
    of attached AC-lines.
568
    The electrical parameters are set according to already inserted
569
    transformers in the grid model for Germany.
570
571
    Parameters
572
    ----------
573
    s_nom : float
574
        Minimal sum of nominal power of lines at one side
575
576
    Returns
577
    -------
578
    int
579
        Selected transformer nominal power
580
    float
581
        Selected transformer nominal impedance
582
583
    """
584
585
    if s_nom <= 600:
586
        return 600, 0.0002
587
    elif (s_nom > 600) & (s_nom <= 1200):
588
        return 1200, 0.0001
589
    elif (s_nom > 1200) & (s_nom <= 1600):
590
        return 1600, 0.000075
591
    elif (s_nom > 1600) & (s_nom <= 2100):
592
        return 2100, 0.00006667
593
    elif (s_nom > 2100) & (s_nom <= 2600):
594
        return 2600, 0.0000461538
595
    elif (s_nom > 2600) & (s_nom <= 4800):
596
        return 4800, 0.000025
597
    elif (s_nom > 4800) & (s_nom <= 6000):
598
        return 6000, 0.0000225
599
    elif (s_nom > 6000) & (s_nom <= 7200):
600
        return 7200, 0.0000194444
601
    elif (s_nom > 7200) & (s_nom <= 8000):
602
        return 8000, 0.000016875
603
    elif (s_nom > 8000) & (s_nom <= 9000):
604
        return 9000, 0.000015
605
    elif (s_nom > 9000) & (s_nom <= 13000):
606
        return 13000, 0.0000103846
607
    elif (s_nom > 13000) & (s_nom <= 20000):
608
        return 20000, 0.00000675
609
    elif (s_nom > 20000) & (s_nom <= 33000):
610
        return 33000, 0.00000409091
611
612
613
def central_transformer(scenario, sources, targets, central_buses, new_lines):
614
    """Connect central foreign buses with different voltage levels
615
616
    Parameters
617
    ----------
618
    sources : dict
619
        List of dataset sources
620
    targets : dict
621
        List of dataset targets
622
    central_buses : geopandas.GeoDataFrame
623
        Buses in the center of foreign countries
624
    new_lines : geopandas.GeoDataFrame
625
        Lines that connect cross-border lines to central bus per country
626
627
    Returns
628
    -------
629
    None.
630
631
    """
632
    # Delete existing transformers in foreign countries
633
    db.execute_sql(
634
        f"""
635
        DELETE FROM {targets['transformers']['schema']}.
636
        {targets['transformers']['table']}
637
        WHERE scn_name = '{scenario}'
638
        AND trafo_id NOT IN (
639
            SELECT branch_id
640
            FROM {sources['osmtgmod_branch']['schema']}.
641
            {sources['osmtgmod_branch']['table']}
642
              WHERE result_id = 1 and link_type = 'transformer')
643
        """
644
    )
645
646
    # Initalize the dataframe for transformers
647
    trafo = gpd.GeoDataFrame(
648
        columns=["trafo_id", "bus0", "bus1", "s_nom"], dtype=int
649
    )
650
    trafo_id = db.next_etrago_id("transformer")
651
652
    # Add one transformer per central foreign bus with v_nom != 380
653
    for i, row in central_buses[central_buses.v_nom != 380].iterrows():
654
        s_nom_0 = new_lines[new_lines.bus0 == row.bus_id].s_nom.sum()
655
        s_nom_1 = new_lines[new_lines.bus1 == row.bus_id].s_nom.sum()
656
        if s_nom_0 == 0.0:
657
            s_nom = s_nom_1
658
        elif s_nom_1 == 0.0:
659
            s_nom = s_nom_0
660
        else:
661
            s_nom = min([s_nom_0, s_nom_1])
662
663
        s_nom, x = choose_transformer(s_nom)
664
665
        trafo = pd.concat(
666
            [
667
                trafo,
668
                pd.DataFrame(
669
                    index=[trafo.index.max() + 1],
670
                    data={
671
                        "trafo_id": trafo_id,
672
                        "bus0": row.bus_id,
673
                        "bus1": central_buses[
674
                            (central_buses.v_nom == 380)
675
                            & (central_buses.country == row.country)
676
                        ].bus_id.values[0],
677
                        "s_nom": s_nom,
678
                        "x": x,
679
                    },
680
                ),
681
            ],
682
            ignore_index=True,
683
        )
684
        trafo_id += 1
685
686
    # Set data type
687
    trafo = trafo.astype({"trafo_id": "int", "bus0": "int", "bus1": "int"})
688
    trafo["scn_name"] = scenario
689
690
    # Insert transformers to the database
691
    trafo.to_sql(
692
        targets["transformers"]["table"],
693
        schema=targets["transformers"]["schema"],
694
        if_exists="append",
695
        con=db.engine(),
696
        index=False,
697
    )
698
699
700
def foreign_dc_lines(scenario, sources, targets, central_buses):
701
    """Insert DC lines to foreign countries manually
702
703
    Parameters
704
    ----------
705
    sources : dict
706
        List of dataset sources
707
    targets : dict
708
        List of dataset targets
709
    central_buses : geopandas.GeoDataFrame
710
        Buses in the center of foreign countries
711
712
    Returns
713
    -------
714
    None.
715
716
    """
717
    # Delete existing dc lines to foreign countries
718
    db.execute_sql(
719
        f"""
720
        DELETE FROM {targets['links']['schema']}.
721
        {targets['links']['table']}
722
        WHERE scn_name = '{scenario}'
723
        AND carrier = 'DC'
724
        AND bus0 IN (
725
            SELECT bus_id
726
            FROM {sources['electricity_buses']['schema']}.
727
            {sources['electricity_buses']['table']}
728
              WHERE scn_name = '{scenario}'
729
              AND carrier = 'AC'
730
              AND country = 'DE')
731
        AND bus1 IN (
732
            SELECT bus_id
733
            FROM {sources['electricity_buses']['schema']}.
734
            {sources['electricity_buses']['table']}
735
              WHERE scn_name = '{scenario}'
736
              AND carrier = 'AC'
737
              AND country != 'DE')
738
        """
739
    )
740
    capital_cost = get_sector_parameters("electricity", scenario)[
741
        "capital_cost"
742
    ]
743
744
    # Add DC line from Lübeck to Sweden
745
    converter_luebeck = select_bus_id(
746
        10.802358024202768,
747
        53.897547401787,
748
        380,
749
        scenario,
750
        "AC",
751
        find_closest=True,
752
    )
753
754
    foreign_links = pd.DataFrame(
755
        index=[0],
756
        data={
757
            "link_id": db.next_etrago_id("link"),
758
            "bus0": converter_luebeck,
759
            "bus1": central_buses[
760
                (central_buses.country == "SE") & (central_buses.v_nom == 380)
761
            ].iloc[0]
762
            .squeeze()
763
            .bus_id,
764
            "p_nom": 600,
765
            "length": 262,
766
        },
767
    )
768
769
    # When not in test-mode, add DC line from Bentwisch to Denmark
770
    if config.settings()["egon-data"]["--dataset-boundary"] == "Everything":
771
        converter_bentwisch = select_bus_id(
772
            12.213671694775988,
773
            54.09974494662279,
774
            380,
775
            scenario,
776
            "AC",
777
            find_closest=True,
778
        )
779
780
        foreign_links = pd.concat(
781
            [
782
                foreign_links,
783
                pd.DataFrame(
784
                    index=[1],
785
                    data={
786
                        "link_id": db.next_etrago_id("link") + 1,
787
                        "bus0": converter_bentwisch,
788
                        "bus1": central_buses[
789
                            (central_buses.country == "DK")
790
                            & (central_buses.v_nom == 380)
791
                            & (central_buses.x > 10)
792
                        ].iloc[0]
793
                        .squeeze()
794
                        .bus_id,
795
                        "p_nom": 600,
796
                        "length": 170,
797
                    },
798
                ),
799
            ]
800
        )
801
802
    # Set parameters for all DC lines
803
    foreign_links["capital_cost"] = (
804
        capital_cost["dc_cable"] * foreign_links.length
805
        + 2 * capital_cost["dc_inverter"]
806
    )
807
    foreign_links["p_min_pu"] = -1
808
    foreign_links["p_nom_extendable"] = True
809
    foreign_links["p_nom_min"] = foreign_links["p_nom"]
810
    foreign_links["scn_name"] = scenario
811
    foreign_links["carrier"] = "DC"
812
    foreign_links["efficiency"] = 1
813
814
    # Add topology
815
    foreign_links = etrago.link_geom_from_buses(foreign_links, scenario)
816
817
    # Insert DC lines to the database
818
    foreign_links.to_postgis(
819
        targets["links"]["table"],
820
        schema=targets["links"]["schema"],
821
        if_exists="append",
822
        con=db.engine(),
823
        index=False,
824
    )
825
826
827
def grid():
828
    """Insert electrical grid compoenents for neighbouring countries
829
830
    Returns
831
    -------
832
    None.
833
834
    """
835
    # Select sources and targets from dataset configuration
836
    sources = config.datasets()["electrical_neighbours"]["sources"]
837
    targets = config.datasets()["electrical_neighbours"]["targets"]
838
839
    for scenario in config.settings()["egon-data"]["--scenarios"]:
840
        central_buses = buses(scenario, sources, targets)
841
842
        foreign_lines = cross_border_lines(
843
            scenario, sources, targets, central_buses
844
        )
845
846
        central_transformer(
847
            scenario, sources, targets, central_buses, foreign_lines
848
        )
849
850
        foreign_dc_lines(scenario, sources, targets, central_buses)
851
852
        if scenario != "eGon100RE":
853
            lines_between_foreign_countries(
854
                scenario, sources, targets, central_buses
855
            )
856
857
858
def map_carriers_tyndp():
859
    """Map carriers from TYNDP-data to carriers used in eGon
860
    Returns
861
    -------
862
    dict
863
        Carrier from TYNDP and eGon
864
    """
865
    return {
866
        "Battery": "battery",
867
        "DSR": "demand_side_response",
868
        "Gas CCGT new": "gas",
869
        "Gas CCGT old 2": "gas",
870
        "Gas CCGT present 1": "gas",
871
        "Gas CCGT present 2": "gas",
872
        "Gas conventional old 1": "gas",
873
        "Gas conventional old 2": "gas",
874
        "Gas OCGT new": "gas",
875
        "Gas OCGT old": "gas",
876
        "Gas CCGT old 1": "gas",
877
        "Gas CCGT old 2 Bio": "biogas",
878
        "Gas conventional old 2 Bio": "biogas",
879
        "Hard coal new": "coal",
880
        "Hard coal old 1": "coal",
881
        "Hard coal old 2": "coal",
882
        "Hard coal old 2 Bio": "coal",
883
        "Heavy oil old 1": "oil",
884
        "Heavy oil old 1 Bio": "oil",
885
        "Heavy oil old 2": "oil",
886
        "Light oil": "oil",
887
        "Lignite new": "lignite",
888
        "Lignite old 1": "lignite",
889
        "Lignite old 2": "lignite",
890
        "Lignite old 1 Bio": "lignite",
891
        "Lignite old 2 Bio": "lignite",
892
        "Nuclear": "nuclear",
893
        "Offshore Wind": "wind_offshore",
894
        "Onshore Wind": "wind_onshore",
895
        "Other non-RES": "others",
896
        "Other RES": "others",
897
        "P2G": "power_to_gas",
898
        "PS Closed": "pumped_hydro",
899
        "PS Open": "reservoir",
900
        "Reservoir": "reservoir",
901
        "Run-of-River": "run_of_river",
902
        "Solar PV": "solar",
903
        "Solar Thermal": "others",
904
        "Waste": "Other RES",
905
    }
906
907
908 View Code Duplication
def get_foreign_bus_id(scenario):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
909
    """Calculte the etrago bus id from Nodes of TYNDP based on the geometry
910
911
    Returns
912
    -------
913
    pandas.Series
914
        List of mapped node_ids from TYNDP and etragos bus_id
915
916
    """
917
918
    sources = config.datasets()["electrical_neighbours"]["sources"]
919
920
    bus_id = db.select_geodataframe(
921
        f"""SELECT bus_id, ST_Buffer(geom, 1) as geom, country
922
        FROM grid.egon_etrago_bus
923
        WHERE scn_name = '{scenario}'
924
        AND carrier = 'AC'
925
        AND v_nom = 380.
926
        AND country != 'DE'
927
        AND bus_id NOT IN (
928
            SELECT bus_i
929
            FROM osmtgmod_results.bus_data)
930
        """,
931
        epsg=3035,
932
    )
933
934
    # insert installed capacities
935
    file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}")
936
937
    # Select buses in neighbouring countries as geodataframe
938
    buses = pd.read_excel(
939
        file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(),
940
        sheet_name="Nodes - Dict",
941
    ).query("longitude==longitude")
942
    buses = gpd.GeoDataFrame(
943
        buses,
944
        crs=4326,
945
        geometry=gpd.points_from_xy(buses.longitude, buses.latitude),
946
    ).to_crs(3035)
947
948
    buses["bus_id"] = 0
949
950
    # Select bus_id from etrago with shortest distance to TYNDP node
951
    for i, row in buses.iterrows():
952
        distance = bus_id.set_index("bus_id").geom.distance(row.geometry)
953
        buses.loc[i, "bus_id"] = distance[
954
            distance == distance.min()
955
        ].index.values[0]
956
957
    return buses.set_index("node_id").bus_id
958
959
960
def calc_capacities():
961
    """Calculates installed capacities from TYNDP data
962
963
    Returns
964
    -------
965
    pandas.DataFrame
966
        Installed capacities per foreign node and energy carrier
967
968
    """
969
970
    sources = config.datasets()["electrical_neighbours"]["sources"]
971
972
    countries = [
973
        "AT",
974
        "BE",
975
        "CH",
976
        "CZ",
977
        "DK",
978
        "FR",
979
        "NL",
980
        "NO",
981
        "SE",
982
        "PL",
983
        "UK",
984
    ]
985
986
    # insert installed capacities
987
    file = zipfile.ZipFile(f"tyndp/{sources['tyndp_capacities']}")
988
    df = pd.read_excel(
989
        file.open("TYNDP-2020-Scenario-Datafile.xlsx").read(),
990
        sheet_name="Capacity",
991
    )
992
993
    # differneces between different climate years are very small (<1MW)
994
    # choose 1984 because it is the mean value
995
    df_2030 = (
996
        df.rename({"Climate Year": "Climate_Year"}, axis="columns")
997
        .query(
998
            'Scenario == "Distributed Energy" & Year == 2030 & '
999
            "Climate_Year == 1984"
1000
        )
1001
        .set_index(["Node/Line", "Generator_ID"])
1002
    )
1003
1004
    df_2040 = (
1005
        df.rename({"Climate Year": "Climate_Year"}, axis="columns")
1006
        .query(
1007
            'Scenario == "Distributed Energy" & Year == 2040 & '
1008
            "Climate_Year == 1984"
1009
        )
1010
        .set_index(["Node/Line", "Generator_ID"])
1011
    )
1012
1013
    # interpolate linear between 2030 and 2040 for 2035 accordning to
1014
    # scenario report of TSO's and the approval by BNetzA
1015
    df_2035 = pd.DataFrame(index=df_2030.index)
1016
    df_2035["cap_2030"] = df_2030.Value
1017
    df_2035["cap_2040"] = df_2040.Value
1018
    df_2035.fillna(0.0, inplace=True)
1019
    df_2035["cap_2035"] = (
1020
        df_2035["cap_2030"] + (df_2035["cap_2040"] - df_2035["cap_2030"]) / 2
1021
    )
1022
    df_2035 = df_2035.reset_index()
1023
    df_2035["carrier"] = df_2035.Generator_ID.map(map_carriers_tyndp())
1024
1025
    # group capacities by new carriers
1026
    grouped_capacities = (
1027
        df_2035.groupby(["carrier", "Node/Line"]).cap_2035.sum().reset_index()
1028
    )
1029
1030
    # choose capacities for considered countries
1031
    return grouped_capacities[
1032
        grouped_capacities["Node/Line"].str[:2].isin(countries)
1033
    ]
1034
1035
1036
def insert_generators_tyndp(capacities):
1037
    """Insert generators for foreign countries based on TYNDP-data
1038
1039
    Parameters
1040
    ----------
1041
    capacities : pandas.DataFrame
1042
        Installed capacities per foreign node and energy carrier
1043
1044
    Returns
1045
    -------
1046
    None.
1047
1048
    """
1049
    targets = config.datasets()["electrical_neighbours"]["targets"]
1050
    map_buses = get_map_buses()
1051
1052
    # Delete existing data
1053
    db.execute_sql(
1054
        f"""
1055
        DELETE FROM
1056
        {targets['generators']['schema']}.{targets['generators']['table']}
1057
        WHERE bus IN (
1058
            SELECT bus_id FROM
1059
            {targets['buses']['schema']}.{targets['buses']['table']}
1060
            WHERE country != 'DE'
1061
            AND scn_name = 'eGon2035')
1062
        AND scn_name = 'eGon2035'
1063
        AND carrier != 'CH4'
1064
        """
1065
    )
1066
1067
    db.execute_sql(
1068
        f"""
1069
        DELETE FROM
1070
        {targets['generators_timeseries']['schema']}.
1071
        {targets['generators_timeseries']['table']}
1072
        WHERE generator_id NOT IN (
1073
            SELECT generator_id FROM
1074
            {targets['generators']['schema']}.{targets['generators']['table']}
1075
        )
1076
        AND scn_name = 'eGon2035'
1077
        """
1078
    )
1079
1080
    # Select generators from TYNDP capacities
1081
    gen = capacities[
1082
        capacities.carrier.isin(
1083
            [
1084
                "others",
1085
                "wind_offshore",
1086
                "wind_onshore",
1087
                "solar",
1088
                "reservoir",
1089
                "run_of_river",
1090
                "lignite",
1091
                "coal",
1092
                "oil",
1093
                "nuclear",
1094
            ]
1095
        )
1096
    ]
1097
1098
    # Set bus_id
1099
    gen.loc[
1100
        gen[gen["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1101
    ] = gen.loc[
1102
        gen[gen["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1103
    ].map(
1104
        map_buses
1105
    )
1106
1107
    gen.loc[:, "bus"] = (
1108
        get_foreign_bus_id(scenario="eGon2035")
1109
        .loc[gen.loc[:, "Node/Line"]]
1110
        .values
1111
    )
1112
1113
    # Add scenario column
1114
    gen["scenario"] = "eGon2035"
1115
1116
    # Add marginal costs
1117
    gen = add_marginal_costs(gen)
1118
1119
    # insert generators data
1120
    session = sessionmaker(bind=db.engine())()
1121
    for i, row in gen.iterrows():
1122
        entry = etrago.EgonPfHvGenerator(
1123
            scn_name=row.scenario,
1124
            generator_id=int(db.next_etrago_id("generator")),
1125
            bus=row.bus,
1126
            carrier=row.carrier,
1127
            p_nom=row.cap_2035,
1128
            marginal_cost=row.marginal_cost,
1129
        )
1130
1131
        session.add(entry)
1132
        session.commit()
1133
1134
    # assign generators time-series data
1135
1136
    renewable_timeseries_pypsaeur("eGon2035")
1137
1138
1139
def insert_storage_tyndp(capacities):
1140
    """Insert storage units for foreign countries based on TYNDP-data
1141
1142
    Parameters
1143
    ----------
1144
    capacities : pandas.DataFrame
1145
        Installed capacities per foreign node and energy carrier
1146
1147
1148
    Returns
1149
    -------
1150
    None.
1151
1152
    """
1153
    targets = config.datasets()["electrical_neighbours"]["targets"]
1154
    map_buses = get_map_buses()
1155
1156
    # Delete existing data
1157
    db.execute_sql(
1158
        f"""
1159
        DELETE FROM {targets['storage']['schema']}.{targets['storage']['table']}
1160
        WHERE bus IN (
1161
            SELECT bus_id FROM
1162
            {targets['buses']['schema']}.{targets['buses']['table']}
1163
            WHERE country != 'DE'
1164
            AND scn_name = 'eGon2035')
1165
        AND scn_name = 'eGon2035'
1166
        """
1167
    )
1168
1169
    # Add missing information suitable for eTraGo selected from scenario_parameter table
1170
    parameters_pumped_hydro = scenario_parameters.electricity("eGon2035")[
1171
        "efficiency"
1172
    ]["pumped_hydro"]
1173
1174
    parameters_battery = scenario_parameters.electricity("eGon2035")[
1175
        "efficiency"
1176
    ]["battery"]
1177
1178
    # Select storage capacities from TYNDP-data
1179
    store = capacities[capacities.carrier.isin(["battery", "pumped_hydro"])]
1180
1181
    # Set bus_id
1182
    store.loc[
1183
        store[store["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1184
    ] = store.loc[
1185
        store[store["Node/Line"].isin(map_buses.keys())].index, "Node/Line"
1186
    ].map(
1187
        map_buses
1188
    )
1189
1190
    store.loc[:, "bus"] = (
1191
        get_foreign_bus_id(scenario="eGon2035")
1192
        .loc[store.loc[:, "Node/Line"]]
1193
        .values
1194
    )
1195
1196
    # Add columns for additional parameters to df
1197
    (
1198
        store["dispatch"],
1199
        store["store"],
1200
        store["standing_loss"],
1201
        store["max_hours"],
1202
    ) = (None, None, None, None)
1203
1204
    # Insert carrier specific parameters
1205
1206
    parameters = ["dispatch", "store", "standing_loss", "max_hours"]
1207
1208
    for x in parameters:
1209
        store.loc[store["carrier"] == "battery", x] = parameters_battery[x]
1210
        store.loc[store["carrier"] == "pumped_hydro", x] = (
1211
            parameters_pumped_hydro[x]
1212
        )
1213
1214
    # insert data
1215
    session = sessionmaker(bind=db.engine())()
1216
    for i, row in store.iterrows():
1217
        entry = etrago.EgonPfHvStorage(
1218
            scn_name="eGon2035",
1219
            storage_id=int(db.next_etrago_id("storage")),
1220
            bus=row.bus,
1221
            max_hours=row.max_hours,
1222
            efficiency_store=row.store,
1223
            efficiency_dispatch=row.dispatch,
1224
            standing_loss=row.standing_loss,
1225
            carrier=row.carrier,
1226
            p_nom=row.cap_2035,
1227
        )
1228
1229
        session.add(entry)
1230
        session.commit()
1231
1232
1233
def get_map_buses():
1234
    """Returns a dictonary of foreign regions which are aggregated to another
1235
1236
    Returns
1237
    -------
1238
    Combination of aggregated regions
1239
1240
1241
    """
1242
    return {
1243
        "DK00": "DKW1",
1244
        "DKKF": "DKE1",
1245
        "FR15": "FR00",
1246
        "NON1": "NOM1",
1247
        "NOS0": "NOM1",
1248
        "NOS1": "NOM1",
1249
        "PLE0": "PL00",
1250
        "PLI0": "PL00",
1251
        "SE00": "SE02",
1252
        "SE01": "SE02",
1253
        "SE03": "SE02",
1254
        "SE04": "SE02",
1255
        "RU": "RU00",
1256
    }
1257
1258
1259
def tyndp_generation():
1260
    """Insert data from TYNDP 2020 accordning to NEP 2021
1261
    Scenario 'Distributed Energy', linear interpolate between 2030 and 2040
1262
1263
    Returns
1264
    -------
1265
    None.
1266
    """
1267
1268
    capacities = calc_capacities()
1269
1270
    insert_generators_tyndp(capacities)
1271
1272
    insert_storage_tyndp(capacities)
1273
1274
1275
def tyndp_demand():
1276
    """Copy load timeseries data from TYNDP 2020.
1277
    According to NEP 2021, the data for 2030 and 2040 is interpolated linearly.
1278
1279
    Returns
1280
    -------
1281
    None.
1282
1283
    """
1284
    map_buses = get_map_buses()
1285
1286
    sources = config.datasets()["electrical_neighbours"]["sources"]
1287
    targets = config.datasets()["electrical_neighbours"]["targets"]
1288
1289
    # Delete existing data
1290
    db.execute_sql(
1291
        f"""
1292
        DELETE FROM {targets['loads']['schema']}.
1293
        {targets['loads']['table']}
1294
        WHERE
1295
        scn_name = 'eGon2035'
1296
        AND carrier = 'AC'
1297
        AND bus NOT IN (
1298
            SELECT bus_i
1299
            FROM  {sources['osmtgmod_bus']['schema']}.
1300
            {sources['osmtgmod_bus']['table']})
1301
        """
1302
    )
1303
1304
    # Connect to database
1305
    engine = db.engine()
1306
    session = sessionmaker(bind=engine)()
1307
1308
    nodes = [
1309
        "AT00",
1310
        "BE00",
1311
        "CH00",
1312
        "CZ00",
1313
        "DKE1",
1314
        "DKW1",
1315
        "FR00",
1316
        "NL00",
1317
        "LUB1",
1318
        "LUF1",
1319
        "LUG1",
1320
        "NOM1",
1321
        "NON1",
1322
        "NOS0",
1323
        "SE01",
1324
        "SE02",
1325
        "SE03",
1326
        "SE04",
1327
        "PL00",
1328
        "UK00",
1329
        "UKNI",
1330
    ]
1331
    # Assign etrago bus_id to TYNDP nodes
1332
    buses = pd.DataFrame({"nodes": nodes})
1333
    buses.loc[buses[buses.nodes.isin(map_buses.keys())].index, "nodes"] = (
1334
        buses[buses.nodes.isin(map_buses.keys())].nodes.map(map_buses)
1335
    )
1336
    buses.loc[:, "bus"] = (
1337
        get_foreign_bus_id(scenario="eGon2035")
1338
        .loc[buses.loc[:, "nodes"]]
1339
        .values
1340
    )
1341
    buses.set_index("nodes", inplace=True)
1342
    buses = buses[~buses.index.duplicated(keep="first")]
1343
1344
    # Read in data from TYNDP for 2030 and 2040
1345
    dataset_2030 = pd.read_excel(
1346
        f"tyndp/{sources['tyndp_demand_2030']}", sheet_name=nodes, skiprows=10
1347
    )
1348
1349
    dataset_2040 = pd.read_excel(
1350
        f"tyndp/{sources['tyndp_demand_2040']}", sheet_name=None, skiprows=10
1351
    )
1352
1353
    # Transform map_buses to pandas.Series and select only used values
1354
    map_series = pd.Series(map_buses)
1355
    map_series = map_series[map_series.index.isin(nodes)]
1356
1357
    # Calculate and insert demand timeseries per etrago bus_id
1358
    for bus in buses.index:
1359
        nodes = [bus]
1360
1361
        if bus in map_series.values:
1362
            nodes.extend(list(map_series[map_series == bus].index.values))
1363
1364
        load_id = db.next_etrago_id("load")
1365
1366
        # Some etrago bus_ids represent multiple TYNDP nodes,
1367
        # in this cases the loads are summed
1368
        data_2030 = pd.Series(index=range(8760), data=0.0)
1369
        for node in nodes:
1370
            data_2030 = dataset_2030[node][2011] + data_2030
1371
1372
        try:
1373
            data_2040 = pd.Series(index=range(8760), data=0.0)
1374
1375
            for node in nodes:
1376
                data_2040 = dataset_2040[node][2011] + data_2040
1377
        except:
1378
            data_2040 = data_2030
1379
1380
        # According to the NEP, data for 2030 and 2040 is linear interpolated
1381
        data_2035 = ((data_2030 + data_2040) / 2)[:8760]
1382
1383
        entry = etrago.EgonPfHvLoad(
1384
            scn_name="eGon2035",
1385
            load_id=int(load_id),
1386
            carrier="AC",
1387
            bus=int(buses.bus[bus]),
1388
        )
1389
1390
        entry_ts = etrago.EgonPfHvLoadTimeseries(
1391
            scn_name="eGon2035",
1392
            load_id=int(load_id),
1393
            temp_id=1,
1394
            p_set=list(data_2035.values),
1395
        )
1396
1397
        session.add(entry)
1398
        session.add(entry_ts)
1399
        session.commit()
1400
1401
1402
def get_entsoe_token():
1403
    """Check for token in home dir. If not exists, check in working dir"""
1404
    token_path = path.join(path.expanduser("~"), ".entsoe-token")
1405
    if not os.path.isfile(token_path):
1406
        logger.info(
1407
            f"Token file not found at {token_path}. Will check in working directory."
1408
        )
1409
        token_path = Path(".entsoe-token")
1410
        if os.path.isfile(token_path):
1411
            logger.info(f"Token found at {token_path}")
1412
    entsoe_token = open(token_path, "r").read(36)
1413
    if entsoe_token is None:
1414
        raise FileNotFoundError("No entsoe-token found.")
1415
    return entsoe_token
1416
1417
1418
def entsoe_historic_generation_capacities(
1419
    year_start="20190101", year_end="20200101"
1420
):
1421
    entsoe_token = get_entsoe_token()
1422
    client = entsoe.EntsoePandasClient(api_key=entsoe_token)
1423
1424
    start = pd.Timestamp(year_start, tz="Europe/Brussels")
1425
    end = pd.Timestamp(year_end, tz="Europe/Brussels")
1426
    start_gb = pd.Timestamp(year_start, tz="Europe/London")
1427
    end_gb = pd.Timestamp(year_end, tz="Europe/London")
1428
    countries = [
1429
        "LU",
1430
        "AT",
1431
        "FR",
1432
        "NL",
1433
        "CZ",
1434
        "DK_1",
1435
        "DK_2",
1436
        "PL",
1437
        "CH",
1438
        "NO",
1439
        "BE",
1440
        "SE",
1441
        "GB",
1442
    ]
1443
    # No GB data after Brexit
1444
    if int(year_start[:4]) > 2021:
1445
        logger.warning(
1446
            "No GB data after Brexit. GB is dropped from entsoe query!"
1447
        )
1448
        countries = [c for c in countries if c != "GB"]
1449
    # todo: define wanted countries
1450
1451
    not_retrieved = []
1452
    dfs = []
1453
    for country in countries:
1454
        if country == "GB":
1455
            kwargs = dict(start=start_gb, end=end_gb)
1456
        else:
1457
            kwargs = dict(start=start, end=end)
1458
        try:
1459
            country_data = client.query_installed_generation_capacity(
1460
                country, **kwargs
1461
            )
1462
            dfs.append(country_data)
1463
        except (entsoe.exceptions.NoMatchingDataError, requests.HTTPError):
1464
            logger.warning(
1465
                f"Data for country: {country} could not be retrieved."
1466
            )
1467
            not_retrieved.append(country)
1468
            pass
1469
1470
    if dfs:
1471
        df = pd.concat(dfs)
1472
        df["country"] = [c for c in countries if c not in not_retrieved]
1473
        df.set_index("country", inplace=True)
1474
        if int(year_start[:4]) == 2023:
1475
            # https://www.bmreports.com/bmrs/?q=foregeneration/capacityaggregated
1476
            # could probably somehow be automised
1477
            # https://www.elexonportal.co.uk/category/view/178
1478
            # in MW
1479
            installed_capacity_gb = pd.Series(
1480
                {
1481
                    "Biomass": 4438,
1482
                    "Fossil Gas": 37047,
1483
                    "Fossil Hard coal": 1491,
1484
                    "Hydro Pumped Storage": 5603,
1485
                    "Hydro Run-of-river and poundage": 2063,
1486
                    "Nuclear": 4950,
1487
                    "Other": 3313,
1488
                    "Other renewable": 1462,
1489
                    "Solar": 14518,
1490
                    "Wind Offshore": 13038,
1491
                    "Wind Onshore": 13907,
1492
                },
1493
                name="GB",
1494
            )
1495
            df = pd.concat([df.T, installed_capacity_gb], axis=1).T
1496
            logger.info("Manually added generation capacities for GB 2023.")
1497
            not_retrieved = [c for c in not_retrieved if c != "GB"]
1498
        df.fillna(0, inplace=True)
1499
    else:
1500
        df = pd.DataFrame()
1501
    return df, not_retrieved
1502
1503
1504
def entsoe_historic_demand(year_start="20190101", year_end="20200101"):
1505
    entsoe_token = get_entsoe_token()
1506
    client = entsoe.EntsoePandasClient(api_key=entsoe_token)
1507
1508
    start = pd.Timestamp(year_start, tz="Europe/Brussels")
1509
    end = pd.Timestamp(year_end, tz="Europe/Brussels")
1510
    start_gb = start.tz_convert("Europe/London")
1511
    end_gb = end.tz_convert("Europe/London")
1512
1513
    countries = [
1514
        "LU",
1515
        "AT",
1516
        "FR",
1517
        "NL",
1518
        "CZ",
1519
        "DK_1",
1520
        "DK_2",
1521
        "PL",
1522
        "CH",
1523
        "NO",
1524
        "BE",
1525
        "SE",
1526
        "GB",
1527
    ]
1528
1529
    # todo: define wanted countries
1530
1531
    not_retrieved = []
1532
    dfs = []
1533
1534
    for country in countries:
1535
        if country == "GB":
1536
            kwargs = dict(start=start_gb, end=end_gb)
1537
        else:
1538
            kwargs = dict(start=start, end=end)
1539
        try:
1540
            country_data = (
1541
                client.query_load(country, **kwargs)
1542
                .resample("H")["Actual Load"]
1543
                .mean()
1544
            )
1545
            if country == "GB":
1546
                country_data.index = country_data.index.tz_convert(
1547
                    "Europe/Brussels"
1548
                )
1549
            dfs.append(country_data)
1550
        except (entsoe.exceptions.NoMatchingDataError, requests.HTTPError):
1551
            not_retrieved.append(country)
1552
            logger.warning(
1553
                f"Data for country: {country} could not be retrieved."
1554
            )
1555
            pass
1556
1557
    if dfs:
1558
        df = pd.concat(dfs, axis=1)
1559
        df.columns = [c for c in countries if c not in not_retrieved]
1560
        df.index = pd.date_range(year_start, periods=8760, freq="H")
1561
    else:
1562
        df = pd.DataFrame()
1563
    return df, not_retrieved
1564
1565
1566
def map_carriers_entsoe():
1567
    """Map carriers from entsoe-data to carriers used in eGon
1568
    Returns
1569
    -------
1570
    dict
1571
        Carrier from entsoe to eGon
1572
    """
1573
    return {
1574
        "Biomass": "biomass",
1575
        "Fossil Brown coal/Lignite": "lignite",
1576
        "Fossil Coal-derived gas": "coal",
1577
        "Fossil Gas": "OCGT",
1578
        "Fossil Hard coal": "coal",
1579
        "Fossil Oil": "oil",
1580
        "Fossil Oil shale": "oil",
1581
        "Fossil Peat": "others",
1582
        "Geothermal": "geo_thermal",
1583
        "Hydro Pumped Storage": "Hydro Pumped Storage",
1584
        "Hydro Run-of-river and poundage": "run_of_river",
1585
        "Hydro Water Reservoir": "reservoir",
1586
        "Marine": "others",
1587
        "Nuclear": "nuclear",
1588
        "Other": "others",
1589
        "Other renewable": "others",
1590
        "Solar": "solar",
1591
        "Waste": "others",
1592
        "Wind Offshore": "wind_offshore",
1593
        "Wind Onshore": "wind_onshore",
1594
    }
1595
1596
1597
def entsoe_to_bus_etrago(scenario="status2019"):
1598
    map_entsoe = pd.Series(
1599
        {
1600
            "LU": "LU00",
1601
            "AT": "AT00",
1602
            "FR": "FR00",
1603
            "NL": "NL00",
1604
            "DK_1": "DK00",
1605
            "DK_2": "DKE1",
1606
            "PL": "PL00",
1607
            "CH": "CH00",
1608
            "NO": "NO00",
1609
            "BE": "BE00",
1610
            "SE": "SE00",
1611
            "GB": "UK00",
1612
            "CZ": "CZ00",
1613
        }
1614
    )
1615
1616
    for_bus = get_foreign_bus_id(scenario=scenario)
1617
1618
    return map_entsoe.map(for_bus)
1619
1620
1621
def save_entsoe_data(df: pd.DataFrame, file_path: Path):
1622
    os.makedirs(file_path.parent, exist_ok=True)
1623
    if not df.empty:
1624
        df.to_csv(file_path, index_label="Index")
1625
        logger.info(
1626
            f"Saved entsoe data for {file_path.stem} "
1627
            f"to {file_path.parent} for countries: {df.index}"
1628
        )
1629
1630
1631
def fill_by_backup_data_from_former_runs(df_sq, file_path, not_retrieved):
1632
    """
1633
    Fills missing data from former runs
1634
    Parameters
1635
    ----------
1636
    df_sq: pd.DataFrame
1637
    file_path: str, Path
1638
    not_retrieved: list
1639
1640
    Returns
1641
    -------
1642
    df_sq, not_retrieved
1643
1644
    """
1645
    sq_backup = pd.read_csv(file_path, index_col="Index")
1646
    # check for missing columns in backup (former runs)
1647
    c_backup = [c for c in sq_backup.columns if c in not_retrieved]
1648
    # remove columns, if found in backup
1649
    not_retrieved = [c for c in not_retrieved if c not in c_backup]
1650
    if c_backup:
1651
        df_sq = pd.concat([df_sq, sq_backup.loc[:, c_backup]], axis=1)
1652
        logger.info(f"Appended data from former runs for {c_backup}")
1653
    return df_sq, not_retrieved
1654
1655
1656
def insert_storage_units_sq(scn_name="status2019"):
1657
    """
1658
    Insert storage_units for foreign countries based on ENTSO-E data
1659
1660
    Parameters
1661
    ----------
1662
    scn_name : str
1663
        Scenario to which the foreign storage units will be assigned.
1664
        The default is "status2019".
1665
1666
    Returns
1667
    -------
1668
    None.
1669
1670
    """
1671
    if "status" in scn_name:
1672
        year = int(scn_name.split("status")[-1])
1673
        year_start_end = {
1674
            "year_start": f"{year}0101",
1675
            "year_end": f"{year+1}0101",
1676
        }
1677
    else:
1678
        raise ValueError("No valid scenario name!")
1679
1680
    df_gen_sq, not_retrieved = entsoe_historic_generation_capacities(
1681
        **year_start_end
1682
    )
1683
1684 View Code Duplication
    if not_retrieved:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1685
        logger.warning("Generation data from entsoe could not be retrieved.")
1686
        # check for generation backup from former runs
1687
        file_path = Path(
1688
            "./", "entsoe_data", f"gen_entsoe_{scn_name}.csv"
1689
        ).resolve()
1690
        if os.path.isfile(file_path):
1691
            df_gen_sq, not_retrieved = fill_by_backup_data_from_former_runs(
1692
                df_gen_sq, file_path, not_retrieved
1693
            )
1694
        save_entsoe_data(df_gen_sq, file_path=file_path)
1695
1696
    if not_retrieved:
1697
        logger.warning(
1698
            f"Backup data of 2019 is used instead for {not_retrieved}"
1699
        )
1700
        df_gen_sq_backup = pd.read_csv(
1701
            "data_bundle_egon_data/entsoe/gen_entsoe.csv", index_col="Index"
1702
        )
1703
        df_gen_sq = pd.concat(
1704
            [df_gen_sq, df_gen_sq_backup.loc[not_retrieved]], axis=1
1705
        )
1706
1707
    sto_sq = df_gen_sq.loc[:, df_gen_sq.columns == "Hydro Pumped Storage"]
1708
    sto_sq.rename(columns={"Hydro Pumped Storage": "p_nom"}, inplace=True)
1709
1710
    targets = config.datasets()["electrical_neighbours"]["targets"]
1711
1712
    # Delete existing data
1713
    db.execute_sql(
1714
        f"""
1715
        DELETE FROM {targets['storage']['schema']}.{targets['storage']['table']}
1716
        WHERE bus IN (
1717
            SELECT bus_id FROM
1718
            {targets['buses']['schema']}.{targets['buses']['table']}
1719
            WHERE country != 'DE'
1720
            AND scn_name = '{scn_name}')
1721
        AND scn_name = '{scn_name}'
1722
        """
1723
    )
1724
1725
    # Add missing information suitable for eTraGo selected from scenario_parameter table
1726
    parameters_pumped_hydro = get_sector_parameters(
1727
        sector="electricity", scenario=scn_name
1728
    )["efficiency"]["pumped_hydro"]
1729
1730
    # Set bus_id
1731
    entsoe_to_bus = entsoe_to_bus_etrago(scn_name=scn_name)
1732
    sto_sq["bus"] = sto_sq.index.map(entsoe_to_bus)
1733
1734
    # Insert carrier specific parameters
1735
    sto_sq["carrier"] = "pumped_hydro"
1736
    sto_sq["scn_name"] = scn_name
1737
    sto_sq["dispatch"] = parameters_pumped_hydro["dispatch"]
1738
    sto_sq["store"] = parameters_pumped_hydro["store"]
1739
    sto_sq["standing_loss"] = parameters_pumped_hydro["standing_loss"]
1740
    sto_sq["max_hours"] = parameters_pumped_hydro["max_hours"]
1741
    sto_sq["cyclic_state_of_charge"] = parameters_pumped_hydro[
1742
        "cyclic_state_of_charge"
1743
    ]
1744
1745
    next_id = int(db.next_etrago_id("storage"))
1746
    sto_sq["storage_id"] = range(next_id, next_id + len(sto_sq))
1747
1748
    # Delete entrances without any installed capacity
1749
    sto_sq = sto_sq[sto_sq["p_nom"] > 0]
1750
1751
    # insert data pumped_hydro storage
1752
1753
    with session_scope() as session:
1754
        for i, row in sto_sq.iterrows():
1755
            entry = etrago.EgonPfHvStorage(
1756
                scn_name=scn_name,
1757
                storage_id=row.storage_id,
1758
                bus=row.bus,
1759
                max_hours=row.max_hours,
1760
                efficiency_store=row.store,
1761
                efficiency_dispatch=row.dispatch,
1762
                standing_loss=row.standing_loss,
1763
                carrier=row.carrier,
1764
                p_nom=row.p_nom,
1765
                cyclic_state_of_charge=row.cyclic_state_of_charge,
1766
            )
1767
            session.add(entry)
1768
            session.commit()
1769
1770
    # big scale batteries
1771
    # info based on EASE data. https://ease-storage.eu/publication/emmes-7-0-march-2023/
1772
    # batteries smaller than 100MW are neglected
1773
1774
    # TODO: include capacities between 2020 and 2023
1775
    bat_per_country = {
1776
        "LU": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1777
        "AT": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1778
        "FR": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1779
        "NL": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1780
        "DK_1": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1781
        "DK_2": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1782
        "PL": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1783
        "CH": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1784
        "NO": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1785
        "BE": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1786
        "SE": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1787
        "GB": [723.8, 952.3, 1380.9, 2333.3, 3928.5],
1788
        "CZ": [0, pd.NA, pd.NA, pd.NA, pd.NA],
1789
    }
1790
    bat_sq = pd.DataFrame(bat_per_country).T.set_axis(
1791
        ["2019", "2020", "2021", "2022", "2023"], axis=1
1792
    )
1793
1794
    # Select year of interest
1795
    bat_sq = bat_sq[[str(year)]]
1796
    bat_sq.rename(columns={str(year): "p_nom"}, inplace= True)
1797
1798
    # Add missing information suitable for eTraGo selected from scenario_parameter table
1799
    parameters_batteries = get_sector_parameters(
1800
        sector="electricity", scenario=scn_name
1801
    )["efficiency"]["battery"]
1802
1803
    # Set bus_id
1804
    entsoe_to_bus = entsoe_to_bus_etrago()
1805
    bat_sq["bus"] = bat_sq.index.map(entsoe_to_bus)
1806
1807
    # Insert carrier specific parameters
1808
    bat_sq["carrier"] = "battery"
1809
    bat_sq["scn_name"] = scn_name
1810
    bat_sq["dispatch"] = parameters_batteries["dispatch"]
1811
    bat_sq["store"] = parameters_batteries["store"]
1812
    bat_sq["standing_loss"] = parameters_batteries["standing_loss"]
1813
    bat_sq["max_hours"] = parameters_batteries["max_hours"]
1814
    bat_sq["cyclic_state_of_charge"] = parameters_batteries[
1815
        "cyclic_state_of_charge"
1816
    ]
1817
1818
    next_id = int(db.next_etrago_id("storage"))
1819
    bat_sq["storage_id"] = range(next_id, next_id + len(bat_sq))
1820
1821
    # Delete entrances without any installed capacity
1822
    bat_sq = bat_sq[bat_sq["p_nom"] > 0]
1823
1824
    # insert data pumped_hydro storage
1825
    with db.session_scope() as session:
1826
        for i, row in bat_sq.iterrows():
1827
            entry = etrago.EgonPfHvStorage(
1828
                scn_name=scn_name,
1829
                storage_id=row.storage_id,
1830
                bus=row.bus,
1831
                max_hours=row.max_hours,
1832
                efficiency_store=row.store,
1833
                efficiency_dispatch=row.dispatch,
1834
                standing_loss=row.standing_loss,
1835
                carrier=row.carrier,
1836
                p_nom=row.p_nom,
1837
                cyclic_state_of_charge=row.cyclic_state_of_charge,
1838
            )
1839
            session.add(entry)
1840
            session.commit()
1841
1842
1843
def insert_generators_sq(scn_name="status2019"):
1844
    """
1845
    Insert generators for foreign countries based on ENTSO-E data
1846
1847
    Parameters
1848
    ----------
1849
    gen_sq : pandas dataframe
1850
        df with all the foreign generators produced by the function
1851
        entsoe_historic_generation_capacities
1852
    scn_name : str
1853
        The default is "status2019".
1854
1855
    Returns
1856
    -------
1857
    None.
1858
1859
    """
1860
    if "status" in scn_name:
1861
        year = int(scn_name.split("status")[-1])
1862
        year_start_end = {
1863
            "year_start": f"{year}0101",
1864
            "year_end": f"{year+1}0101",
1865
        }
1866
    else:
1867
        raise ValueError("No valid scenario name!")
1868
1869
    df_gen_sq, not_retrieved = entsoe_historic_generation_capacities(
1870
        **year_start_end
1871
    )
1872
1873 View Code Duplication
    if not_retrieved:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
1874
        logger.warning("Generation data from entsoe could not be retrieved.")
1875
        # check for generation backup from former runs
1876
        file_path = Path(
1877
            "./", "entsoe_data", f"gen_entsoe_{scn_name}.csv"
1878
        ).resolve()
1879
        if os.path.isfile(file_path):
1880
            df_gen_sq, not_retrieved = fill_by_backup_data_from_former_runs(
1881
                df_gen_sq, file_path, not_retrieved
1882
            )
1883
        save_entsoe_data(df_gen_sq, file_path=file_path)
1884
1885
    if not_retrieved:
1886
        logger.warning(
1887
            f"Backup data of 2019 is used instead for {not_retrieved}"
1888
        )
1889
        df_gen_sq_backup = pd.read_csv(
1890
            "data_bundle_egon_data/entsoe/gen_entsoe.csv", index_col="Index"
1891
        )
1892
        df_gen_sq = pd.concat(
1893
            [df_gen_sq, df_gen_sq_backup.loc[not_retrieved]], axis=1
1894
        )
1895
1896
    targets = config.datasets()["electrical_neighbours"]["targets"]
1897
    # Delete existing data
1898
    db.execute_sql(
1899
        f"""
1900
        DELETE FROM
1901
        {targets['generators']['schema']}.{targets['generators']['table']}
1902
        WHERE bus IN (
1903
            SELECT bus_id FROM
1904
            {targets['buses']['schema']}.{targets['buses']['table']}
1905
            WHERE country != 'DE'
1906
            AND scn_name = '{scn_name}')
1907
        AND scn_name = '{scn_name}'
1908
        AND carrier != 'CH4'
1909
        """
1910
    )
1911
1912
    db.execute_sql(
1913
        f"""
1914
        DELETE FROM
1915
        {targets['generators_timeseries']['schema']}.
1916
        {targets['generators_timeseries']['table']}
1917
        WHERE generator_id NOT IN (
1918
            SELECT generator_id FROM
1919
            {targets['generators']['schema']}.{targets['generators']['table']}
1920
        )
1921
        AND scn_name = '{scn_name}'
1922
        """
1923
    )
1924
    entsoe_to_bus = entsoe_to_bus_etrago(scn_name)
1925
    carrier_entsoe = map_carriers_entsoe()
1926
    df_gen_sq = df_gen_sq.groupby(axis=1, by=carrier_entsoe).sum()
1927
1928
    # Filter generators modeled as storage and geothermal
1929
    df_gen_sq = df_gen_sq.loc[
1930
        :, ~df_gen_sq.columns.isin(["Hydro Pumped Storage", "geo_thermal"])
1931
    ]
1932
1933
    list_gen_sq = pd.DataFrame(
1934
        dtype=int, columns=["carrier", "country", "capacity"]
1935
    )
1936
    for carrier in df_gen_sq.columns:
1937
        gen_carry = df_gen_sq[carrier]
1938
        for country, cap in gen_carry.items():
1939
            gen = pd.DataFrame(
1940
                {"carrier": carrier, "country": country, "capacity": cap},
1941
                index=[1],
1942
            )
1943
            # print(gen)
1944
            list_gen_sq = pd.concat([list_gen_sq, gen], ignore_index=True)
1945
1946
    list_gen_sq = list_gen_sq[list_gen_sq.capacity > 0]
1947
    list_gen_sq["scenario"] = scn_name
1948
1949
    # Add marginal costs
1950
    list_gen_sq = add_marginal_costs(list_gen_sq)
1951
1952
    # Find foreign bus to assign the generator
1953
    list_gen_sq["bus"] = list_gen_sq.country.map(entsoe_to_bus)
1954
1955
    # insert generators data
1956
    session = sessionmaker(bind=db.engine())()
1957
    for i, row in list_gen_sq.iterrows():
1958
        entry = etrago.EgonPfHvGenerator(
1959
            scn_name=row.scenario,
1960
            generator_id=int(db.next_etrago_id("generator")),
1961
            bus=row.bus,
1962
            carrier=row.carrier,
1963
            p_nom=row.capacity,
1964
            marginal_cost=row.marginal_cost,
1965
        )
1966
1967
        session.add(entry)
1968
        session.commit()
1969
1970
    renewable_timeseries_pypsaeur(scn_name)
1971
1972
1973
def renewable_timeseries_pypsaeur(scn_name):
1974
    # select generators from database to get index values
1975
    foreign_re_generators = db.select_dataframe(
1976
        f"""
1977
        SELECT generator_id, a.carrier, country, x, y
1978
        FROM grid.egon_etrago_generator a
1979
        JOIN  grid.egon_etrago_bus b
1980
        ON a.bus = b.bus_id
1981
        WHERE a.scn_name = '{scn_name}'
1982
        AND  b.scn_name = '{scn_name}'
1983
        AND b.carrier = 'AC'
1984
        AND b.country != 'DE'
1985
        AND a.carrier IN ('wind_onshore', 'wind_offshore', 'solar')
1986
        """
1987
    )
1988
1989
    # Import prepared network from pypsa-eur
1990
    network = prepared_network()
1991
1992
    # Select fluctuating renewable generators
1993
    generators_pypsa_eur = network.generators.loc[
1994
        network.generators[
1995
            network.generators.carrier.isin(["onwind", "offwind-ac", "solar"])
1996
        ].index,
1997
        ["bus", "carrier"],
1998
    ]
1999
2000
    # Align carrier names for wind turbines
2001
    generators_pypsa_eur.loc[
2002
        generators_pypsa_eur[generators_pypsa_eur.carrier == "onwind"].index,
2003
        "carrier",
2004
    ] = "wind_onshore"
2005
    generators_pypsa_eur.loc[
2006
        generators_pypsa_eur[
2007
            generators_pypsa_eur.carrier == "offwind-ac"
2008
        ].index,
2009
        "carrier",
2010
    ] = "wind_offshore"
2011
2012
    # Set coordinates from bus table
2013
    generators_pypsa_eur["x"] = network.buses.loc[
2014
        generators_pypsa_eur.bus.values, "x"
2015
    ].values
2016
    generators_pypsa_eur["y"] = network.buses.loc[
2017
        generators_pypsa_eur.bus.values, "y"
2018
    ].values
2019
2020
    # Get p_max_pu time series from pypsa-eur
2021
    generators_pypsa_eur["p_max_pu"] = network.generators_t.p_max_pu[
2022
        generators_pypsa_eur.index
2023
    ].T.values.tolist()
2024
2025
    session = sessionmaker(bind=db.engine())()
2026
2027
    # Insert p_max_pu timeseries based on geometry and carrier
2028
    for gen in foreign_re_generators.index:
2029
        entry = etrago.EgonPfHvGeneratorTimeseries(
2030
            scn_name=scn_name,
2031
            generator_id=foreign_re_generators.loc[gen, "generator_id"],
2032
            temp_id=1,
2033
            p_max_pu=generators_pypsa_eur[
2034
                (
2035
                    (
2036
                        generators_pypsa_eur.x
2037
                        - foreign_re_generators.loc[gen, "x"]
2038
                    ).abs()
2039
                    < 0.01
2040
                )
2041
                & (
2042
                    (
2043
                        generators_pypsa_eur.y
2044
                        - foreign_re_generators.loc[gen, "y"]
2045
                    ).abs()
2046
                    < 0.01
2047
                )
2048
                & (
2049
                    generators_pypsa_eur.carrier
2050
                    == foreign_re_generators.loc[gen, "carrier"]
2051
                )
2052
            ].p_max_pu.iloc[0],
2053
        )
2054
2055
        session.add(entry)
2056
        session.commit()
2057
2058
2059
def insert_loads_sq(scn_name="status2019"):
2060
    """
2061
    Copy load timeseries data from entso-e.
2062
2063
    Returns
2064
    -------
2065
    None.
2066
2067
    """
2068
    sources = config.datasets()["electrical_neighbours"]["sources"]
2069
    targets = config.datasets()["electrical_neighbours"]["targets"]
2070
2071
    if scn_name == "status2019":
2072
        year_start_end = {"year_start": "20190101", "year_end": "20200101"}
2073
    elif scn_name == "status2023":
2074
        year_start_end = {"year_start": "20230101", "year_end": "20240101"}
2075
    else:
2076
        raise ValueError("No valid scenario name!")
2077
2078
    df_load_sq, not_retrieved = entsoe_historic_demand(**year_start_end)
2079
2080
    if not_retrieved:
2081
        logger.warning("Demand data from entsoe could not be retrieved.")
2082
        # check for generation backup from former runs
2083
        file_path = Path(
2084
            "./", "entsoe_data", f"load_entsoe_{scn_name}.csv"
2085
        ).resolve()
2086
        if os.path.isfile(file_path):
2087
            df_load_sq, not_retrieved = fill_by_backup_data_from_former_runs(
2088
                df_load_sq, file_path, not_retrieved
2089
            )
2090
        save_entsoe_data(df_load_sq, file_path=file_path)
2091
2092
    if not_retrieved:
2093
        logger.warning(
2094
            f"Backup data of 2019 is used instead for {not_retrieved}"
2095
        )
2096
        df_load_sq_backup = pd.read_csv(
2097
            "data_bundle_egon_data/entsoe/load_entsoe.csv", index_col="Index"
2098
        )
2099
        df_load_sq_backup.index = df_load_sq.index
2100
        df_load_sq = pd.concat(
2101
            [df_load_sq, df_load_sq_backup.loc[:, not_retrieved]], axis=1
2102
        )
2103
2104
    # Delete existing data
2105
    db.execute_sql(
2106
        f"""
2107
        DELETE FROM {targets['load_timeseries']['schema']}.
2108
        {targets['load_timeseries']['table']}
2109
        WHERE
2110
        scn_name = '{scn_name}'
2111
        AND load_id IN (
2112
        SELECT load_id FROM {targets['loads']['schema']}.
2113
        {targets['loads']['table']}
2114
        WHERE
2115
        scn_name = '{scn_name}'
2116
        AND carrier = 'AC'
2117
        AND bus NOT IN (
2118
            SELECT bus_i
2119
            FROM  {sources['osmtgmod_bus']['schema']}.
2120
            {sources['osmtgmod_bus']['table']}))
2121
        """
2122
    )
2123
2124
    db.execute_sql(
2125
        f"""
2126
        DELETE FROM {targets['loads']['schema']}.
2127
        {targets['loads']['table']}
2128
        WHERE
2129
        scn_name = '{scn_name}'
2130
        AND carrier = 'AC'
2131
        AND bus NOT IN (
2132
            SELECT bus_i
2133
            FROM  {sources['osmtgmod_bus']['schema']}.
2134
            {sources['osmtgmod_bus']['table']})
2135
        """
2136
    )
2137
2138
    # get the corresponding bus per foreign country
2139
    entsoe_to_bus = entsoe_to_bus_etrago(scn_name)
2140
2141
    # Calculate and insert demand timeseries per etrago bus_id
2142
    with session_scope() as session:
2143
        for country in df_load_sq.columns:
2144
            load_id = db.next_etrago_id("load")
2145
2146
            entry = etrago.EgonPfHvLoad(
2147
                scn_name=scn_name,
2148
                load_id=int(load_id),
2149
                carrier="AC",
2150
                bus=int(entsoe_to_bus[country]),
2151
            )
2152
2153
            entry_ts = etrago.EgonPfHvLoadTimeseries(
2154
                scn_name=scn_name,
2155
                load_id=int(load_id),
2156
                temp_id=1,
2157
                p_set=list(df_load_sq[country]),
2158
            )
2159
2160
            session.add(entry)
2161
            session.add(entry_ts)
2162
            session.commit()
2163
2164
2165
tasks = (grid,)
2166
2167
insert_per_scenario = set()
2168
2169
for scn_name in config.settings()["egon-data"]["--scenarios"]:
2170
2171
    if scn_name == "eGon2035":
2172
        insert_per_scenario.update([tyndp_generation, tyndp_demand])
2173
2174
    if "status" in scn_name:
2175
        postfix = f"_{scn_name.split('status')[-1]}"
2176
        insert_per_scenario.update(
2177
            [
2178
                wrapped_partial(
2179
                    insert_generators_sq, scn_name=scn_name, postfix=postfix
2180
                ),
2181
                wrapped_partial(
2182
                    insert_loads_sq, scn_name=scn_name, postfix=postfix
2183
                ),
2184
                wrapped_partial(
2185
                    insert_storage_units_sq, scn_name=scn_name, postfix=postfix
2186
                ),
2187
            ]
2188
        )
2189
2190
tasks = tasks + (insert_per_scenario,)
2191
2192
2193
class ElectricalNeighbours(Dataset):
2194
    """
2195
    Add lines, loads, generation and storage for electrical neighbours
2196
2197
    This dataset creates data for modelling the considered foreign countries and writes
2198
    that data into the database tables that can be read by the eTraGo tool.
2199
    Neighbouring countries are modelled in a lower spatial resolution, in general one node per
2200
    country is considered.
2201
    Defined load timeseries as well as generatrion and storage capacities are connected to these nodes.
2202
    The nodes are connected by AC and DC transmission lines with the German grid and other neighbouring countries
2203
    considering the grid topology from ENTSO-E.
2204
2205
2206
    *Dependencies*
2207
      * :py:class:`Tyndp <egon.data.datasets.tyndp.Tyndp>`
2208
      * :py:class:`PypsaEurSec <egon.data.datasets.pypsaeursec.PypsaEurSec>`
2209
2210
2211
    *Resulting tables*
2212
      * :py:class:`grid.egon_etrago_bus <egon.data.datasets.etrago_setup.EgonPfHvBus>` is extended
2213
      * :py:class:`grid.egon_etrago_link <egon.data.datasets.etrago_setup.EgonPfHvLink>` is extended
2214
      * :py:class:`grid.egon_etrago_line <egon.data.datasets.etrago_setup.EgonPfHvLine>` is extended
2215
      * :py:class:`grid.egon_etrago_load <egon.data.datasets.etrago_setup.EgonPfHvLoad>` is extended
2216
      * :py:class:`grid.egon_etrago_load_timeseries <egon.data.datasets.etrago_setup.EgonPfHvLoadTimeseries>` is extended
2217
      * :py:class:`grid.egon_etrago_storage <egon.data.datasets.etrago_setup.EgonPfHvStorageUnit>` is extended
2218
      * :py:class:`grid.egon_etrago_generator <egon.data.datasets.etrago_setup.EgonPfHvGenerator>` is extended
2219
      * :py:class:`grid.egon_etrago_generator_timeseries <egon.data.datasets.etrago_setup.EgonPfHvGeneratorTimeseries>` is extended
2220
      * :py:class:`grid.egon_etrago_transformer <egon.data.datasets.etrago_setup.EgonPfHvTransformer>` is extended
2221
      
2222
    """
2223
2224
    #:
2225
    name: str = "ElectricalNeighbours"
2226
    #:
2227
    version: str = "0.0.11"
2228
2229
    def __init__(self, dependencies):
2230
        super().__init__(
2231
            name=self.name,
2232
            version=self.version,
2233
            dependencies=dependencies,
2234
            tasks=tasks,
2235
        )
2236