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

allocate_pv_home_batteries()   A

Complexity

Conditions 1

Size

Total Lines 4
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

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