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

data.datasets.storages.Storages.__init__()   B

Complexity

Conditions 1

Size

Total Lines 83
Code Lines 69

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 69
dl 0
loc 83
rs 8.0145
c 0
b 0
f 0
cc 1
nop 2

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

1
"""The central module containing all code dealing with power plant data.
2
"""
3
4
from pathlib import Path
5
6
from geoalchemy2 import Geometry
7
from sqlalchemy import BigInteger, Column, Float, Integer, Sequence, String
8
from sqlalchemy.dialects.postgresql import JSONB
9
from sqlalchemy.ext.declarative import declarative_base
10
from sqlalchemy.orm import sessionmaker
11
import geopandas as gpd
12
import pandas as pd
13
14
from egon.data import config, db
15
from egon.data.datasets import Dataset
16
from egon.data.datasets.electrical_neighbours import entsoe_to_bus_etrago
17
from egon.data.datasets.mastr import (
18
    WORKING_DIR_MASTR_NEW,
19
    WORKING_DIR_MASTR_OLD,
20
)
21
from egon.data.datasets.mv_grid_districts import Vg250GemClean
22
from egon.data.datasets.power_plants import assign_bus_id, assign_voltage_level
23
from egon.data.datasets.scenario_parameters import get_sector_parameters
24
from egon.data.datasets.storages.home_batteries import (
25
    allocate_home_batteries_to_buildings,
26
)
27
from egon.data.validation.rules.custom.sanity import HomeBatteriesAggregation
28
from egon.data.datasets.storages.pumped_hydro import (
29
    apply_voltage_level_thresholds,
30
    get_location,
31
    match_storage_units,
32
    select_mastr_pumped_hydro,
33
    select_nep_pumped_hydro,
34
)
35
from egon.data.db import session_scope
36
37
from egon_validation import(
38
    RowCountValidation,
39
    DataTypeValidation,
40
    NotNullAndNotNaNValidation,
41
    WholeTableNotNullAndNotNaNValidation,
42
    ValueSetValidation,
43
    SRIDUniqueNonZero
44
)
45
46
Base = declarative_base()
47
48
49 View Code Duplication
class EgonStorages(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
50
    __tablename__ = "egon_storages"
51
    __table_args__ = {"schema": "supply"}
52
    id = Column(BigInteger, Sequence("storage_seq"), primary_key=True)
53
    sources = Column(JSONB)
54
    source_id = Column(JSONB)
55
    carrier = Column(String)
56
    el_capacity = Column(Float)
57
    bus_id = Column(Integer)
58
    voltage_level = Column(Integer)
59
    scenario = Column(String)
60
    geom = Column(Geometry("POINT", 4326))
61
62
63
class Storages(Dataset):
64
    """
65
    Allocates storage units such as pumped hydro and home batteries
66
67
    This data set creates interim tables to store information on storage units.
68
    In addition the target value for the installed capacity of pumped hydro
69
    storage units are spatially allocated using information of existing plants
70
    from the official registry Markstammdatenregister. After allocating the
71
    plants missing information such as the voltage level and the correct grid
72
    connection point are added.
73
    This data set also allocates the target value of home batteries spatially
74
    on different aggregation levels. In a first step function
75
    :py:func:`allocate_pv_home_batteries_to_grids` spatially distributes the
76
    installed battery capacities to all mv grid districts based on their
77
    installed pv rooftop capacity.
78
    Function :py:func:`allocate_home_batteries_to_buildings` further
79
    distributes the home battery storage systems to buildings with pv
80
    rooftop systems.
81
82
    *Dependencies*
83
      * :py:func:`download_mastr_data <egon.data.datasets.mastr.download_mastr_data>`
84
      * :py:func:`define_mv_grid_districts <egon.data.datasets.mv_grid_districts.define_mv_grid_districts>`
85
      * :py:class: `PowerPlants <egon.data.datasets.power_plants.PowerPlants>`
86
      * :py:class:`ScenarioCapacities <egon.data.datasets.scenario_capacities.ScenarioCapacities>`
87
      * :py:class:`ScenarioParameters <egon.data.datasets.scenario_parameters.ScenarioParameters>`
88
      * :py:class:`Vg250MvGridDistricts <egon.data.datasets.vg250_mv_grid_districts.Vg250MvGridDistricts>`
89
90
    *Resulting tables*
91
      * :py:class:`supply.egon_storages <egon.data.datasets.storages.EgonStorages>`
92
93
    """
94
95
    #:
96
    name: str = "Storages"
97
    #:
98
    version: str = "0.0.8"
99
100
    def __init__(self, dependencies):
101
        super().__init__(
102
            name=self.name,
103
            version=self.version,
104
            dependencies=dependencies,
105
            tasks=(
106
                create_tables,
107
                allocate_pumped_hydro_scn,
108
                allocate_other_storage_units,
109
                allocate_pv_home_batteries_to_grids,
110
                allocate_home_batteries_to_buildings,
111
            ),
112
            validation={
113
                "sanity_home_batteries_aggregation": [
114
                    HomeBatteriesAggregation(
115
                        table="supply.egon_home_batteries",
116
                        rule_id="SANITY_HOME_BATTERIES_AGGREGATION_EGON2035",
117
                        scenario="eGon2035"
118
                    ),
119
                    HomeBatteriesAggregation(
120
                        table="supply.egon_home_batteries",
121
                        rule_id="SANITY_HOME_BATTERIES_AGGREGATION_EGON100RE",
122
                        scenario="eGon100RE"
123
                    ),
124
                    RowCountValidation(
125
                        table="supply.egon_storages",
126
                        rule_id="TEST_ROW_COUNT.egon_storages",
127
                        expected_count={"Schleswig-Holstein": 290, "Everything": 7748}
128
                    ),
129
                    DataTypeValidation(
130
                        table="supply.egon_storages",
131
                        rule_id="TEST_DATA_MULTIPLE_TYPES.egon_storages",
132
                        column_types={
133
                            "id": "bigint",
134
                            "sources": "jsonb",
135
                            "source_id": "jsonb",
136
                            "carrier": "character varying",
137
                            "el_capacity": "double precision",
138
                            "bus_id": "integer",
139
                            "voltage_level": "integer",
140
                            "scenario": "character varying",
141
                            "geom": "geometry"
142
                        }
143
                    ),
144
                    NotNullAndNotNaNValidation(
145
                        table="supply.egon_storages",
146
                        rule_id="TEST_NOT_NAN.egon_storages",
147
                        columns=[
148
                            "id",
149
                            "sources",
150
                            "source_id",
151
                            "carrier",
152
                            "el_capacity",
153
                            "bus_id",
154
                            "voltage_level",
155
                            "scenario",
156
                            "geom"
157
                        ]
158
                    ),
159
                    WholeTableNotNullAndNotNaNValidation(
160
                        table="supply.egon_storages",
161
                        rule_id="TEST_WHOLE_TABLE_NOT_NAN.egon_storages"
162
                    ),
163
                    ValueSetValidation(
164
                        table="supply.egon_storages",
165
                        rule_id="VALUE_SET_VALIDATION_SCENARIO.egon_storages",
166
                        column="scenario",
167
                        expected_values=["eGon2035", "eGon100RE"]
168
                    ),
169
                    ValueSetValidation(
170
                        table="supply.egon_storages",
171
                        rule_id="VALUE_SET_VALIDATION_CARRIER.egon_storages",
172
                        column="carrier",
173
                        expected_values=["home_battery", "pumped_hydro"]
174
                    ),
175
                    SRIDUniqueNonZero(
176
                        table="supply.egon_storages",
177
                        rule_id="SRIDUniqueNonZero.egon_storages.geom",
178
                        column="geom"
179
                    ),
180
                ]
181
            },
182
            on_validation_failure="continue"
183
        )
184
185
186
def create_tables():
187
    """Create tables for power plant data
188
    Returns
189
    -------
190
    None.
191
    """
192
193
    cfg = config.datasets()["storages"]
194
    db.execute_sql(f"CREATE SCHEMA IF NOT EXISTS {cfg['target']['schema']};")
195
    engine = db.engine()
196
    db.execute_sql(
197
        f"""DROP TABLE IF EXISTS
198
        {cfg['target']['schema']}.{cfg['target']['table']}"""
199
    )
200
201
    db.execute_sql("""DROP SEQUENCE IF EXISTS pp_seq""")
202
    EgonStorages.__table__.create(bind=engine, checkfirst=True)
203
204
205
def allocate_pumped_hydro(scn, export=True):
206
    """Allocates pumped_hydro plants for eGon2035 and scenario2019 scenarios
207
    and either exports results to data base or returns as a dataframe
208
209
    Parameters
210
    ----------
211
    export : bool
212
        Choose if allocated pumped hydro plants should be exported to the data
213
        base. The default is True.
214
        If export=False a data frame will be returned
215
216
    Returns
217
    -------
218
    power_plants : pandas.DataFrame
219
        List of pumped hydro plants in 'eGon2035' and 'scenario2019' scenarios
220
    """
221
222
    carrier = "pumped_hydro"
223
224
    cfg = config.datasets()["power_plants"]
225
226
    nep = select_nep_pumped_hydro(scn=scn)
227
    mastr = select_mastr_pumped_hydro()
228
229
    # Assign voltage level to MaStR
230
    mastr["voltage_level"] = assign_voltage_level(
231
        mastr.rename({"el_capacity": "Nettonennleistung"}, axis=1),
232
        cfg,
233
        WORKING_DIR_MASTR_OLD,
234
    )
235
236
    # Initalize DataFrame for matching power plants
237
    matched = gpd.GeoDataFrame(
238
        columns=[
239
            "carrier",
240
            "el_capacity",
241
            "scenario",
242
            "geometry",
243
            "MaStRNummer",
244
            "source",
245
            "voltage_level",
246
        ]
247
    )
248
249
    # Match pumped_hydro units from NEP list
250
    # using PLZ and capacity
251
    matched, mastr, nep = match_storage_units(
252
        nep,
253
        mastr,
254
        matched,
255
        buffer_capacity=0.1,
256
        consider_carrier=False,
257
        scn=scn,
258
    )
259
260
    # Match plants from NEP list using plz,
261
    # neglecting the capacity
262
    matched, mastr, nep = match_storage_units(
263
        nep,
264
        mastr,
265
        matched,
266
        consider_location="plz",
267
        consider_carrier=False,
268
        consider_capacity=False,
269
        scn=scn,
270
    )
271
272
    # Match plants from NEP list using city,
273
    # neglecting the capacity
274
    matched, mastr, nep = match_storage_units(
275
        nep,
276
        mastr,
277
        matched,
278
        consider_location="city",
279
        consider_carrier=False,
280
        consider_capacity=False,
281
        scn=scn,
282
    )
283
284
    # Match remaining plants from NEP using the federal state
285
    matched, mastr, nep = match_storage_units(
286
        nep,
287
        mastr,
288
        matched,
289
        buffer_capacity=0.1,
290
        consider_location="federal_state",
291
        consider_carrier=False,
292
        scn=scn,
293
    )
294
295
    # Match remaining plants from NEP using the federal state
296
    matched, mastr, nep = match_storage_units(
297
        nep,
298
        mastr,
299
        matched,
300
        buffer_capacity=0.7,
301
        consider_location="federal_state",
302
        consider_carrier=False,
303
        scn=scn,
304
    )
305
306
    print(f"{matched.el_capacity.sum()} MW of {carrier} matched")
307
    print(f"{nep.elec_capacity.sum()} MW of {carrier} not matched")
308
309
    if nep.elec_capacity.sum() > 0:
310
        # Get location using geolocator and city information
311
        located, unmatched = get_location(nep)
312
313
        # Bring both dataframes together
314
        matched = pd.concat(
315
            [
316
                matched,
317
                located[
318
                    [
319
                        "carrier",
320
                        "el_capacity",
321
                        "scenario",
322
                        "geometry",
323
                        "source",
324
                        "MaStRNummer",
325
                    ]
326
                ],
327
            ],
328
            ignore_index=True,
329
        )
330
331
    # Set CRS
332
    matched.crs = "EPSG:4326"
333
334
    # Assign voltage level
335
    matched = apply_voltage_level_thresholds(matched)
336
337
    # Assign bus_id
338
    # Load grid district polygons
339
    mv_grid_districts = db.select_geodataframe(
340
        f"""
341
    SELECT * FROM {cfg['sources']['egon_mv_grid_district']}
342
    """,
343
        epsg=4326,
344
    )
345
346
    ehv_grid_districts = db.select_geodataframe(
347
        f"""
348
    SELECT * FROM {cfg['sources']['ehv_voronoi']}
349
    """,
350
        epsg=4326,
351
    )
352
353
    # Perform spatial joins for plants in ehv and hv level seperately
354
    power_plants_hv = gpd.sjoin(
355
        matched[matched.voltage_level >= 3],
356
        mv_grid_districts[["bus_id", "geom"]],
357
        how="left",
358
    ).drop(columns=["index_right"])
359
    power_plants_ehv = gpd.sjoin(
360
        matched[matched.voltage_level < 3],
361
        ehv_grid_districts[["bus_id", "geom"]],
362
        how="left",
363
    ).drop(columns=["index_right"])
364
365
    # Combine both dataframes
366
    power_plants = pd.concat([power_plants_hv, power_plants_ehv])
367
368
    # Delete existing units in the target table
369
    db.execute_sql(
370
        f""" DELETE FROM {cfg ['sources']['storages']}
371
        WHERE carrier IN ('pumped_hydro')
372
        AND scenario='{scn}';"""
373
    )
374
375
    # If export = True export pumped_hydro plants to data base
376
377
    if export:
378
        # Insert into target table
379
        with session_scope() as session:
380
            for i, row in power_plants.iterrows():
381
                entry = EgonStorages(
382
                    sources={"el_capacity": row.source},
383
                    source_id={"MastrNummer": row.MaStRNummer},
384
                    carrier=row.carrier,
385
                    el_capacity=row.el_capacity,
386
                    voltage_level=row.voltage_level,
387
                    bus_id=row.bus_id,
388
                    scenario=row.scenario,
389
                    geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})",
390
                )
391
                session.add(entry)
392
            session.commit()
393
394
    else:
395
        return power_plants
396
397
398
def allocate_storage_units_sq(scn_name, storage_types):
399
    """
400
    Allocate storage units by mastr data only. Capacities outside
401
    germany are assigned to foreign buses.
402
403
    Parameters
404
    ----------
405
    scn_name: str
406
        Scenario name
407
    storage_types: list
408
        contains all the required storage units carriers to be imported
409
410
    Returns
411
    -------
412
413
    """
414
    sources = config.datasets()["power_plants"]["sources"]
415
    scn_parameters = get_sector_parameters("global", scn_name)
416
    scenario_date_max = str(scn_parameters["weather_year"]) + "-12-31 23:59:00"
417
418
    map_storage = {
419
        "battery": "Batterie",
420
        "pumped_hydro": "Pumpspeicher",
421
        "compressed_air": "Druckluft",
422
        "flywheel": "Schwungrad",
423
        "other": "Sonstige",
424
    }
425
426
    for storage_type in storage_types:
427
        # Read-in data from MaStR
428
        mastr_ph = pd.read_csv(
429
            WORKING_DIR_MASTR_NEW / sources["mastr_storage"],
430
            delimiter=",",
431
            usecols=[
432
                "Nettonennleistung",
433
                "EinheitMastrNummer",
434
                "Kraftwerksnummer",
435
                "Technologie",
436
                "Postleitzahl",
437
                "Laengengrad",
438
                "Breitengrad",
439
                "EinheitBetriebsstatus",
440
                "LokationMastrNummer",
441
                "Ort",
442
                "Bundesland",
443
                "DatumEndgueltigeStilllegung",
444
                "Inbetriebnahmedatum",
445
            ],
446
            dtype={"Postleitzahl": str},
447
        )
448
449
        # Rename columns
450
        mastr_ph = mastr_ph.rename(
451
            columns={
452
                "Kraftwerksnummer": "bnetza_id",
453
                "Technologie": "carrier",
454
                "Postleitzahl": "plz",
455
                "Ort": "city",
456
                "Bundesland": "federal_state",
457
                "Nettonennleistung": "el_capacity",
458
                "DatumEndgueltigeStilllegung": "decommissioning_date",
459
            }
460
        )
461
462
        # Select only the required type of storage
463
        mastr_ph = mastr_ph.loc[mastr_ph.carrier == map_storage[storage_type]]
464
465
        # Select only storage units in operation
466
        mastr_ph.loc[
467
            mastr_ph["decommissioning_date"] > scenario_date_max,
468
            "EinheitBetriebsstatus",
469
        ] = "InBetrieb"
470
        mastr_ph = mastr_ph.loc[
471
            mastr_ph.EinheitBetriebsstatus.isin(
472
                ["InBetrieb", "VoruebergehendStillgelegt"]
473
            )
474
        ]
475
476
        # Select only storage units installed before scenario_date_max
477
        mastr_ph = mastr_ph[
478
            pd.to_datetime(mastr_ph["Inbetriebnahmedatum"]) < scenario_date_max
479
        ]
480
481
        # Calculate power in MW
482
        mastr_ph.loc[:, "el_capacity"] *= 1e-3
483
484
        # Create geodataframe from long, lat
485
        mastr_ph = gpd.GeoDataFrame(
486
            mastr_ph,
487
            geometry=gpd.points_from_xy(
488
                mastr_ph["Laengengrad"], mastr_ph["Breitengrad"]
489
            ),
490
            crs="4326",
491
        )
492
493
        # Identify pp without geocord
494
        mastr_ph_nogeo = mastr_ph.loc[mastr_ph["Laengengrad"].isna()]
495
496
        # Remove all PP without geocord
497
        mastr_ph = mastr_ph.dropna(subset="Laengengrad")
498
499
        # Get geometry of villages/cities with same name of pp with missing geocord
500
        with session_scope() as session:
501
            query = session.query(Vg250GemClean.gen, Vg250GemClean.geometry)
502
            df_cities = gpd.read_postgis(
503
                query.statement,
504
                query.session.bind,
505
                geom_col="geometry",
506
                crs="3035",
507
            )
508
509
        # Keep only useful cities
510
        df_cities = df_cities[df_cities["gen"].isin(mastr_ph_nogeo["city"])]
511
512
        # Just take the first entry, inaccuracy is negligible as centroid is taken afterwards
513
        df_cities = df_cities.drop_duplicates("gen", keep="first")
514
515
        # Use the centroid instead of polygon of region
516
        df_cities.loc[:, "geometry"] = df_cities["geometry"].centroid
517
518
        # Change coordinate system
519
        df_cities.to_crs("4326", inplace=True)
520
521
        # Add centroid geometry to pp without geometry
522
        mastr_ph_nogeo = pd.merge(
523
            left=df_cities,
524
            right=mastr_ph_nogeo,
525
            right_on="city",
526
            left_on="gen",
527
            suffixes=("", "_no-geo"),
528
            how="inner",
529
        ).drop("gen", axis=1)
530
531
        mastr_ph = pd.concat([mastr_ph, mastr_ph_nogeo], axis=0)
532
533
        # aggregate capacity per location
534
        agg_cap = mastr_ph.groupby("geometry")["el_capacity"].sum()
535
536
        # list mastr number by location
537
        agg_mastr = mastr_ph.groupby("geometry")["EinheitMastrNummer"].apply(
538
            list
539
        )
540
541
        # remove duplicates by location
542
        mastr_ph = mastr_ph.drop_duplicates(
543
            subset="geometry", keep="first"
544
        ).drop(["el_capacity", "EinheitMastrNummer"], axis=1)
545
546
        # Adjust capacity
547
        mastr_ph = pd.merge(
548
            left=mastr_ph,
549
            right=agg_cap,
550
            left_on="geometry",
551
            right_on="geometry",
552
        )
553
554
        # Adjust capacity
555
        mastr_ph = pd.merge(
556
            left=mastr_ph,
557
            right=agg_mastr,
558
            left_on="geometry",
559
            right_on="geometry",
560
        )
561
562
        # Drop small pp <= 30 kW
563
        mastr_ph = mastr_ph.loc[mastr_ph["el_capacity"] > 0.03]
564
565
        # Apply voltage level by capacity
566
        mastr_ph = apply_voltage_level_thresholds(mastr_ph)
567
        mastr_ph["voltage_level"] = mastr_ph["voltage_level"].astype(int)
568
569
        # Capacity located outside germany -> will be assigned to foreign buses
570
        mastr_ph_foreign = mastr_ph.loc[mastr_ph["federal_state"].isna()]
571
572
        # Keep only capacities within germany
573
        mastr_ph = mastr_ph.dropna(subset="federal_state")
574
575
        # Asign buses within germany
576
        mastr_ph = assign_bus_id(
577
            mastr_ph, cfg=config.datasets()["power_plants"], drop_missing=True
578
        )
579
        mastr_ph["bus_id"] = mastr_ph["bus_id"].astype(int)
580
581
        # Get foreign central buses
582
        sql = f"""
583
        SELECT * FROM grid.egon_etrago_bus
584
        WHERE scn_name = '{scn_name}'
585
        and country != 'DE'
586
        """
587
        df_foreign_buses = db.select_geodataframe(
588
            sql, geom_col="geom", epsg="4326"
589
        )
590
        central_bus = entsoe_to_bus_etrago(scn_name).to_frame()
591
        central_bus["geom"] = (
592
            df_foreign_buses.set_index("bus_id")
593
            .loc[central_bus[0], "geom"]
594
            .values
595
        )
596
        df_foreign_buses = df_foreign_buses[
597
            df_foreign_buses["geom"].isin(central_bus["geom"])
598
        ]
599
600
        if len(mastr_ph_foreign) > 0:
601
            # Assign closest bus at voltage level to foreign pp
602
            nearest_neighbors = []
603
            for vl, v_nom in {1: 380, 2: 220, 3: 110}.items():
604
                ph = mastr_ph_foreign.loc[
605
                    mastr_ph_foreign["voltage_level"] == vl
606
                ]
607
                if ph.empty:
608
                    continue
609
                bus = df_foreign_buses.loc[
610
                    df_foreign_buses["v_nom"] == v_nom,
611
                    ["v_nom", "country", "bus_id", "geom"],
612
                ]
613
                results = gpd.sjoin_nearest(
614
                    left_df=ph,
615
                    right_df=bus,
616
                    how="left",
617
                    distance_col="distance",
618
                )
619
                nearest_neighbors.append(results)
620
            mastr_ph_foreign = pd.concat(nearest_neighbors)
621
622
            # Merge foreign pp
623
            mastr_ph = pd.concat([mastr_ph, mastr_ph_foreign])
624
625
        # Reduce to necessary columns
626
        mastr_ph = mastr_ph[
627
            [
628
                "el_capacity",
629
                "voltage_level",
630
                "bus_id",
631
                "geometry",
632
                "EinheitMastrNummer",
633
            ]
634
        ]
635
636
        # Rename and format columns
637
        mastr_ph["carrier"] = storage_type
638
        mastr_ph = mastr_ph.rename(
639
            columns={"EinheitMastrNummer": "source_id", "geometry": "geom"}
640
        )
641
        mastr_ph["source_id"] = mastr_ph["source_id"].apply(
642
            lambda x: {"MastrNummer": ", ".join(x)}
643
        )
644
        mastr_ph = mastr_ph.set_geometry("geom")
645
        mastr_ph["geom"] = mastr_ph["geom"].apply(lambda x: x.wkb_hex)
646
        mastr_ph["scenario"] = scn_name
647
        mastr_ph["sources"] = [
648
            {"el_capacity": "MaStR aggregated by location"}
649
        ] * mastr_ph.shape[0]
650
651
        # Delete existing units in the target table
652
        db.execute_sql(
653
            f""" DELETE FROM supply.egon_storages
654
            WHERE carrier = '{storage_type}'
655
            AND scenario = '{scn_name}'
656
            AND sources ->> 'el_capacity' = 'MaStR aggregated by location';"""
657
        )
658
659
        with db.session_scope() as session:
660
            session.bulk_insert_mappings(
661
                EgonStorages,
662
                mastr_ph.to_dict(orient="records"),
663
            )
664
665
666
def allocate_pumped_hydro_eGon100RE():
667
    """Allocates pumped_hydro plants for eGon100RE scenario based on a
668
    prox-to-now method applied on allocated pumped-hydro plants in the eGon2035
669
    scenario.
670
671
    Parameters
672
    ----------
673
    None
674
675
    Returns
676
    -------
677
    None
678
    """
679
680
    carrier = "pumped_hydro"
681
    cfg = config.datasets()["power_plants"]
682
    boundary = config.settings()["egon-data"]["--dataset-boundary"]
683
684
    # Select installed capacity for pumped_hydro in eGon100RE scenario from
685
    # scenario capacities table
686
    capacity = db.select_dataframe(
687
        f"""
688
        SELECT capacity
689
        FROM {cfg['sources']['capacities']}
690
        WHERE carrier = '{carrier}'
691
        AND scenario_name = 'eGon100RE';
692
        """
693
    )
694
695
    if boundary == "Schleswig-Holstein":
696
        # Break capacity of pumped hydron plants down SH share in eGon2035
697
        capacity_phes = capacity.iat[0, 0] * 0.0176
698
699
    elif boundary == "Everything":
700
        # Select national capacity for pumped hydro
701
        capacity_phes = capacity.iat[0, 0]
702
703
    else:
704
        raise ValueError(f"'{boundary}' is not a valid dataset boundary.")
705
706
    # Get allocation of pumped_hydro plants in eGon2035 scenario as the
707
    # reference for the distribution in eGon100RE scenario
708
    allocation = allocate_pumped_hydro(scn="status2019", export=False)
709
710
    scaling_factor = capacity_phes / allocation.el_capacity.sum()
711
712
    power_plants = allocation.copy()
713
    power_plants["scenario"] = "eGon100RE"
714
    power_plants["el_capacity"] = allocation.el_capacity * scaling_factor
715
716
    # Insert into target table
717
    session = sessionmaker(bind=db.engine())()
718
    for i, row in power_plants.iterrows():
719
        entry = EgonStorages(
720
            sources={"el_capacity": row.source},
721
            source_id={"MastrNummer": row.MaStRNummer},
722
            carrier=row.carrier,
723
            el_capacity=row.el_capacity,
724
            voltage_level=row.voltage_level,
725
            bus_id=row.bus_id,
726
            scenario=row.scenario,
727
            geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})",
728
        )
729
        session.add(entry)
730
    session.commit()
731
732
733
def home_batteries_per_scenario(scenario):
734
    """Allocates home batteries which define a lower boundary for extendable
735
    battery storage units. The overall installed capacity is taken from NEP
736
    for eGon2035 scenario. The spatial distribution of installed battery
737
    capacities is based on the installed pv rooftop capacity.
738
739
    Parameters
740
    ----------
741
    None
742
743
    Returns
744
    -------
745
    None
746
    """
747
748
    cfg = config.datasets()["storages"]
749
    dataset = config.settings()["egon-data"]["--dataset-boundary"]
750
751
    if scenario == "eGon2035":
752
        target_file = (
753
            Path(".")
754
            / "data_bundle_egon_data"
755
            / "nep2035_version2021"
756
            / cfg["sources"]["nep_capacities"]
757
        )
758
759
        capacities_nep = pd.read_excel(
760
            target_file,
761
            sheet_name="1.Entwurf_NEP2035_V2021",
762
            index_col="Unnamed: 0",
763
        )
764
765
        # Select target value in MW
766
        target = capacities_nep.Summe["PV-Batteriespeicher"] * 1000
767
768
    else:
769
        target = db.select_dataframe(
770
            f"""
771
            SELECT capacity
772
            FROM {cfg['sources']['capacities']}
773
            WHERE scenario_name = '{scenario}'
774
            AND carrier = 'battery';
775
            """
776
        ).capacity[0]
777
778
    pv_rooftop = db.select_dataframe(
779
        f"""
780
        SELECT bus, p_nom, generator_id
781
        FROM {cfg['sources']['generators']}
782
        WHERE scn_name = '{scenario}'
783
        AND carrier = 'solar_rooftop'
784
        AND bus IN
785
            (SELECT bus_id FROM {cfg['sources']['bus']}
786
               WHERE scn_name = '{scenario}' AND country = 'DE' );
787
        """
788
    )
789
790
    if dataset == "Schleswig-Holstein":
791
        target = target / 16
792
793
    battery = pv_rooftop
794
    battery["p_nom_min"] = target * battery["p_nom"] / battery["p_nom"].sum()
795
    battery = battery.drop(columns=["p_nom"])
796
797
    battery["carrier"] = "home_battery"
798
    battery["scenario"] = scenario
799
800
    if (scenario == "eGon2035") | ("status" in scenario):
801
        source = "NEP"
802
803
    else:
804
        source = "p-e-s"
805
806
    battery["source"] = (
807
        f"{source} capacity allocated based in installed PV rooftop capacity"
808
    )
809
810
    # Insert into target table
811
    session = sessionmaker(bind=db.engine())()
812
    for i, row in battery.iterrows():
813
        entry = EgonStorages(
814
            sources={"el_capacity": row.source},
815
            source_id={"generator_id": row.generator_id},
816
            carrier=row.carrier,
817
            el_capacity=row.p_nom_min,
818
            bus_id=row.bus,
819
            scenario=row.scenario,
820
        )
821
        session.add(entry)
822
    session.commit()
823
824
825
def allocate_pv_home_batteries_to_grids():
826
    for scn in config.settings()["egon-data"]["--scenarios"]:
827
        home_batteries_per_scenario(scn)
828
829
830
def allocate_pumped_hydro_scn():
831
    for scn in config.settings()["egon-data"]["--scenarios"]:
832
        if scn == "eGon2035":
833
            allocate_pumped_hydro(scn="eGon2035")
834
        elif scn == "eGon100RE":
835
            allocate_pumped_hydro_eGon100RE()
836
        elif "status" in scn:
837
            allocate_storage_units_sq(
838
                scn_name=scn, storage_types=["pumped_hydro"]
839
            )
840
841
842
def allocate_other_storage_units():
843
    for scn in config.settings()["egon-data"]["--scenarios"]:
844
        if "status" in scn:
845
            allocate_storage_units_sq(scn_name=scn, storage_types=["battery"])
846