Passed
Pull Request — dev (#1008)
by
unknown
01:39
created

data.datasets.storages.create_tables()   A

Complexity

Conditions 1

Size

Total Lines 17
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nop 0
dl 0
loc 17
rs 10
c 0
b 0
f 0
1
"""The central module containing all code dealing with power plant data.
2
"""
3
from pathlib import Path
4
5
from geoalchemy2 import Geometry
6
from sqlalchemy import BigInteger, Column, Float, Integer, Sequence, String
7
from sqlalchemy.dialects.postgresql import JSONB
8
from sqlalchemy.ext.declarative import declarative_base
9
from sqlalchemy.orm import sessionmaker
10
import geopandas as gpd
11
import pandas as pd
12
13
from egon.data import config, db
14
from egon.data.datasets import Dataset
15
from egon.data.datasets.power_plants import assign_voltage_level
16
from egon.data.datasets.storages.home_batteries import (
17
    allocate_home_batteries_to_buildings,
18
)
19
from egon.data.datasets.storages.pumped_hydro import (
20
    apply_voltage_level_thresholds,
21
    get_location,
22
    match_storage_units,
23
    select_mastr_pumped_hydro,
24
    select_nep_pumped_hydro,
25
)
26
27
Base = declarative_base()
28
29
30
class EgonHomeBatteries(Base):
31
    __tablename__ = "egon_home_batteries"
32
    __table_args__ = {"schema": "supply"}
33
34
    index = Column(Integer, primary_key=True, index=True)
35
    scenario = Column(String)
36
    bus_id = Column(Integer)
37
    building_id = Column(Integer)
38
    p_nom = Column(Float)
39
    capacity = Column(Float)
40
41
42 View Code Duplication
class EgonStorages(Base):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
43
    __tablename__ = "egon_storages"
44
    __table_args__ = {"schema": "supply"}
45
    id = Column(BigInteger, Sequence("storage_seq"), primary_key=True)
46
    sources = Column(JSONB)
47
    source_id = Column(JSONB)
48
    carrier = Column(String)
49
    el_capacity = Column(Float)
50
    bus_id = Column(Integer)
51
    voltage_level = Column(Integer)
52
    scenario = Column(String)
53
    geom = Column(Geometry("POINT", 4326))
54
55
56
class Storages(Dataset):
57
    def __init__(self, dependencies):
58
        super().__init__(
59
            name="Storages",
60
            version="0.0.3dev",
61
            dependencies=dependencies,
62
            tasks=(
63
                create_tables,
64
                allocate_pumped_hydro_eGon2035,
65
                allocate_pumped_hydro_eGon100RE,
66
                allocate_pv_home_batteries_to_grids,
67
                allocate_home_batteries_to_buildings,
68
            ),
69
        )
70
71
72
def create_tables():
73
    """Create tables for power plant data
74
    Returns
75
    -------
76
    None.
77
    """
78
79
    cfg = config.datasets()["storages"]
80
    db.execute_sql(f"CREATE SCHEMA IF NOT EXISTS {cfg['target']['schema']};")
81
    engine = db.engine()
82
    db.execute_sql(
83
        f"""DROP TABLE IF EXISTS
84
        {cfg['target']['schema']}.{cfg['target']['table']}"""
85
    )
86
87
    db.execute_sql("""DROP SEQUENCE IF EXISTS pp_seq""")
88
    EgonStorages.__table__.create(bind=engine, checkfirst=True)
89
90
91
def allocate_pumped_hydro_eGon2035(export=True):
92
    """Allocates pumped_hydro plants for eGon2035 scenario and either exports
93
    results to data base or returns as a dataframe
94
95
    Parameters
96
    ----------
97
    export : bool
98
        Choose if allocated pumped hydro plants should be exported to the data
99
        base. The default is True.
100
        If export=False a data frame will be returned
101
102
    Returns
103
    -------
104
    power_plants : pandas.DataFrame
105
        List of pumped hydro plants in 'eGon2035' scenario
106
    """
107
108
    carrier = "pumped_hydro"
109
110
    cfg = config.datasets()["power_plants"]
111
112
    nep = select_nep_pumped_hydro()
113
    mastr = select_mastr_pumped_hydro()
114
115
    # Assign voltage level to MaStR
116
    mastr["voltage_level"] = assign_voltage_level(
117
        mastr.rename({"el_capacity": "Nettonennleistung"}, axis=1), cfg
118
    )
119
120
    # Initalize DataFrame for matching power plants
121
    matched = gpd.GeoDataFrame(
122
        columns=[
123
            "carrier",
124
            "el_capacity",
125
            "scenario",
126
            "geometry",
127
            "MaStRNummer",
128
            "source",
129
            "voltage_level",
130
        ]
131
    )
132
133
    # Match pumped_hydro units from NEP list
134
    # using PLZ and capacity
135
    matched, mastr, nep = match_storage_units(
136
        nep, mastr, matched, buffer_capacity=0.1, consider_carrier=False
137
    )
138
139
    # Match plants from NEP list using plz,
140
    # neglecting the capacity
141
    matched, mastr, nep = match_storage_units(
142
        nep,
143
        mastr,
144
        matched,
145
        consider_location="plz",
146
        consider_carrier=False,
147
        consider_capacity=False,
148
    )
149
150
    # Match plants from NEP list using city,
151
    # neglecting the capacity
152
    matched, mastr, nep = match_storage_units(
153
        nep,
154
        mastr,
155
        matched,
156
        consider_location="city",
157
        consider_carrier=False,
158
        consider_capacity=False,
159
    )
160
161
    # Match remaining plants from NEP using the federal state
162
    matched, mastr, nep = match_storage_units(
163
        nep,
164
        mastr,
165
        matched,
166
        buffer_capacity=0.1,
167
        consider_location="federal_state",
168
        consider_carrier=False,
169
    )
170
171
    # Match remaining plants from NEP using the federal state
172
    matched, mastr, nep = match_storage_units(
173
        nep,
174
        mastr,
175
        matched,
176
        buffer_capacity=0.7,
177
        consider_location="federal_state",
178
        consider_carrier=False,
179
    )
180
181
    print(f"{matched.el_capacity.sum()} MW of {carrier} matched")
182
    print(f"{nep.c2035_capacity.sum()} MW of {carrier} not matched")
183
184
    if nep.c2035_capacity.sum() > 0:
185
186
        # Get location using geolocator and city information
187
        located, unmatched = get_location(nep)
188
189
        # Bring both dataframes together
190
        matched = matched.append(
191
            located[
192
                [
193
                    "carrier",
194
                    "el_capacity",
195
                    "scenario",
196
                    "geometry",
197
                    "source",
198
                    "MaStRNummer",
199
                ]
200
            ],
201
            ignore_index=True,
202
        )
203
204
    # Set CRS
205
    matched.crs = "EPSG:4326"
206
207
    # Assign voltage level
208
    matched = apply_voltage_level_thresholds(matched)
209
210
    # Assign bus_id
211
    # Load grid district polygons
212
    mv_grid_districts = db.select_geodataframe(
213
        f"""
214
    SELECT * FROM {cfg['sources']['egon_mv_grid_district']}
215
    """,
216
        epsg=4326,
217
    )
218
219
    ehv_grid_districts = db.select_geodataframe(
220
        f"""
221
    SELECT * FROM {cfg['sources']['ehv_voronoi']}
222
    """,
223
        epsg=4326,
224
    )
225
226
    # Perform spatial joins for plants in ehv and hv level seperately
227
    power_plants_hv = gpd.sjoin(
228
        matched[matched.voltage_level >= 3],
229
        mv_grid_districts[["bus_id", "geom"]],
230
        how="left",
231
    ).drop(columns=["index_right"])
232
    power_plants_ehv = gpd.sjoin(
233
        matched[matched.voltage_level < 3],
234
        ehv_grid_districts[["bus_id", "geom"]],
235
        how="left",
236
    ).drop(columns=["index_right"])
237
238
    # Combine both dataframes
239
    power_plants = pd.concat([power_plants_hv, power_plants_ehv])
240
241
    # Delete existing units in the target table
242
    db.execute_sql(
243
        f""" DELETE FROM {cfg ['target']['schema']}.{cfg ['target']['table']}
244
        WHERE carrier IN ('pumped_hydro')
245
        AND scenario='eGon2035';"""
246
    )
247
248
    # If export = True export pumped_hydro plants to data base
249
250
    if export:
251
        # Insert into target table
252
        session = sessionmaker(bind=db.engine())()
253
        for i, row in power_plants.iterrows():
254
            entry = EgonStorages(
255
                sources={"el_capacity": row.source},
256
                source_id={"MastrNummer": row.MaStRNummer},
257
                carrier=row.carrier,
258
                el_capacity=row.el_capacity,
259
                voltage_level=row.voltage_level,
260
                bus_id=row.bus_id,
261
                scenario=row.scenario,
262
                geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})",
263
            )
264
            session.add(entry)
265
        session.commit()
266
267
    else:
268
        return power_plants
269
270
271
def allocate_pumped_hydro_eGon100RE():
272
    """Allocates pumped_hydro plants for eGon100RE scenario based on a
273
    prox-to-now method applied on allocated pumped-hydro plants in the eGon2035
274
    scenario.
275
276
    Parameters
277
    ----------
278
    None
279
280
    Returns
281
    -------
282
    None
283
    """
284
285
    carrier = "pumped_hydro"
286
    cfg = config.datasets()["power_plants"]
287
    boundary = config.settings()["egon-data"]["--dataset-boundary"]
288
289
    # Select installed capacity for pumped_hydro in eGon100RE scenario from
290
    # scenario capacities table
291
    capacity = db.select_dataframe(
292
        f"""
293
        SELECT capacity
294
        FROM {cfg['sources']['capacities']}
295
        WHERE carrier = '{carrier}'
296
        AND scenario_name = 'eGon100RE';
297
        """
298
    )
299
300
    if boundary == "Schleswig-Holstein":
301
        # Break capacity of pumped hydron plants down SH share in eGon2035
302
        capacity_phes = capacity.iat[0, 0] * 0.0176
303
304
    elif boundary == "Everything":
305
        # Select national capacity for pumped hydro
306
        capacity_phes = capacity.iat[0, 0]
307
308
    else:
309
        raise ValueError(f"'{boundary}' is not a valid dataset boundary.")
310
311
    # Get allocation of pumped_hydro plants in eGon2035 scenario as the
312
    # reference for the distribution in eGon100RE scenario
313
    allocation = allocate_pumped_hydro_eGon2035(export=False)
314
315
    scaling_factor = capacity_phes / allocation.el_capacity.sum()
316
317
    power_plants = allocation.copy()
318
    power_plants["scenario"] = "eGon100RE"
319
    power_plants["el_capacity"] = allocation.el_capacity * scaling_factor
320
321
    # Insert into target table
322
    session = sessionmaker(bind=db.engine())()
323
    for i, row in power_plants.iterrows():
324
        entry = EgonStorages(
325
            sources={"el_capacity": row.source},
326
            source_id={"MastrNummer": row.MaStRNummer},
327
            carrier=row.carrier,
328
            el_capacity=row.el_capacity,
329
            voltage_level=row.voltage_level,
330
            bus_id=row.bus_id,
331
            scenario=row.scenario,
332
            geom=f"SRID=4326;POINT({row.geometry.x} {row.geometry.y})",
333
        )
334
        session.add(entry)
335
    session.commit()
336
337
338
def home_batteries_per_scenario(scenario):
339
    """Allocates home batteries which define a lower boundary for extendable
340
    battery storage units. The overall installed capacity is taken from NEP
341
    for eGon2035 scenario. The spatial distribution of installed battery
342
    capacities is based on the installed pv rooftop capacity.
343
344
    Parameters
345
    ----------
346
    None
347
348
    Returns
349
    -------
350
    None
351
    """
352
353
    cfg = config.datasets()["storages"]
354
    dataset = config.settings()["egon-data"]["--dataset-boundary"]
355
356
    if scenario == "eGon2035":
357
358
        target_file = (
359
            Path(".")
360
            / "data_bundle_egon_data"
361
            / "nep2035_version2021"
362
            / cfg["sources"]["nep_capacities"]
363
        )
364
365
        capacities_nep = pd.read_excel(
366
            target_file,
367
            sheet_name="1.Entwurf_NEP2035_V2021",
368
            index_col="Unnamed: 0",
369
        )
370
371
        target = capacities_nep.Summe["PV-Batteriespeicher"]
372
373
    else:
374
        target = db.select_dataframe(
375
            f"""
376
            SELECT capacity
377
            FROM {cfg['sources']['capacities']}
378
            WHERE scenario_name = '{scenario}'
379
            AND carrier = 'battery';
380
            """
381
        ).capacity[0]
382
383
    pv_rooftop = db.select_dataframe(
384
        f"""
385
        SELECT bus, p_nom, generator_id
386
        FROM {cfg['sources']['generators']}
387
        WHERE scn_name = '{scenario}'
388
        AND carrier = 'solar_rooftop'
389
        AND bus IN
390
            (SELECT bus_id FROM {cfg['sources']['bus']}
391
               WHERE scn_name = '{scenario}' AND country = 'DE' );
392
        """
393
    )
394
395
    if dataset == "Schleswig-Holstein":
396
        target = target / 16
397
398
    battery = pv_rooftop
399
    battery["p_nom_min"] = target * battery["p_nom"] / battery["p_nom"].sum()
400
    battery = battery.drop(columns=["p_nom"])
401
402
    battery["carrier"] = "home_battery"
403
    battery["scenario"] = scenario
404
405
    if scenario == "eGon2035":
406
        source = "NEP"
407
408
    else:
409
        source = "p-e-s"
410
411
    battery[
412
        "source"
413
    ] = f"{source} capacity allocated based in installed PV rooftop capacity"
414
415
    # Insert into target table
416
    session = sessionmaker(bind=db.engine())()
417
    for i, row in battery.iterrows():
418
        entry = EgonStorages(
419
            sources={"el_capacity": row.source},
420
            source_id={"generator_id": row.generator_id},
421
            carrier=row.carrier,
422
            el_capacity=row.p_nom_min,
423
            bus_id=row.bus,
424
            scenario=row.scenario,
425
        )
426
        session.add(entry)
427
    session.commit()
428
429
430
def allocate_pv_home_batteries_to_grids():
431
432
    home_batteries_per_scenario("eGon2035")
433
    home_batteries_per_scenario("eGon100RE")
434