scenario_builder.powerplants   A
last analyzed

Complexity

Total Complexity 39

Size/Duplication

Total Lines 599
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 283
dl 0
loc 599
rs 9.28
c 0
b 0
f 0
wmc 39

10 Functions

Rating   Name   Duplication   Size   Complexity  
B get_deflex_pp_by_year() 0 62 5
A process_pp_table() 0 11 2
A pp_reegis2deflex() 0 30 2
C chp_table() 0 116 7
A scenario_chp() 0 46 1
B substract_chp_capacity_and_limit_from_pp() 0 76 7
A add_additional_values() 0 31 4
A add_pp_limit() 0 35 4
A scenario_powerplants() 0 20 1
B create_powerplants() 0 76 6
1
"""Create a basic scenario from the internal data structure.
2
3
SPDX-FileCopyrightText: 2016-2021 Uwe Krien <[email protected]>
4
5
SPDX-License-Identifier: MIT
6
"""
7
8
import logging
9
import os
10
from warnings import warn
11
12
import pandas as pd
13
from reegis import bmwi
14
from reegis import config as cfg
15
from reegis import energy_balance
16
from reegis import geometries as reegis_geometries
17
from reegis import powerplants
18
19
from scenario_builder import data
20
from scenario_builder import demand
21
22
# Todo: Revise and test.
23
24
25
def pp_reegis2deflex(regions, name, filename_in=None, filename_out=None):
26
    """
27
    Add federal states and deflex regions to powerplant table from reegis. As
28
    the process takes a while the result is stored for further usage.
29
30
    Returns
31
    -------
32
    str : The full path where the result file is stored.
33
34
    """
35
    if filename_out is None:
36
        filename_out = os.path.join(
37
            cfg.get("paths", "powerplants"),
38
            cfg.get("powerplants", "deflex_pp"),
39
        ).format(map=name)
40
41
    # Add deflex regions to powerplants
42
    pp = powerplants.add_regions_to_powerplants(
43
        regions, name, dump=False, filename=filename_in
44
    )
45
46
    # Add federal states to powerplants
47
    federal_states = reegis_geometries.get_federal_states_polygon()
48
    pp = powerplants.add_regions_to_powerplants(
49
        federal_states, "federal_states", pp=pp, dump=False
50
    )
51
52
    # store the results for further usage of deflex
53
    pp.to_hdf(filename_out, "pp")
54
    return filename_out
55
56
57
# def remove_onshore_technology_from_offshore_regions(df):
58
#     """ This filter should be improved. It is slow and has to be adapted
59
#     manually. Anyhow it seems to work this way."""
60
#
61
#     logging.info("Removing onshore technology from offshore regions.")
62
#     logging.info("The code is not efficient. So it may take a while.")
63
#
64
#     offshore_regions=(
65
#         cfg.get_dict_list('offshore_regions_set')[cfg.get('init', 'map')])
66
#
67
#     coast_regions={'de02': {'MV': 'DE01',
68
#                               'SH': 'DE01',
69
#                               'NI': 'DE01 '},
70
#                      'de17': {'MV': 'DE13',
71
#                               'SH': 'DE01',
72
#                               'NI': 'DE03'},
73
#                      'de21': {'MV': 'DE01',
74
#                               'SH': 'DE13',
75
#                               'NI': 'DE14'},
76
#                      'de22': {'MV': 'DE01',
77
#                               'SH': 'DE13',
78
#                               'NI': 'DE14'}}
79
#     try:
80
#         dc=coast_regions[cfg.get('init', 'map')]
81
#     except KeyError:
82
#         raise ValueError('Coast regions not defined for {0} model.'.format(
83
#             cfg.get('init', 'map')))
84
#
85
#     region_column='{0}_region'.format(cfg.get('init', 'map'))
86
#
87
#     for ttype in ['Solar', 'Bioenergy', 'Wind']:
88
#         for region in offshore_regions:
89
#             logging.debug("Clean {1} from {0}.".format(region, ttype))
90
#
91
#             c1=df['energy_source_level_2'] == ttype
92
#             c2=df[region_column] == region
93
#
94
#             condition=c1 & c2
95
#
96
#             if ttype == 'Wind':
97
#                 condition=c1 & c2 & (df['technology'] == 'Onshore')
98
#
99
#             for i, v in df.loc[condition].iterrows():
100
#                 df.loc[i, region_column]=(
101
#                     dc[df.loc[i, 'federal_states']])
102
#     return df
103
104
105
def process_pp_table(pp):
106
    # # Remove powerplants outside Germany
107
    # for state in cfg.get_list('powerplants', 'remove_states'):
108
    #     pp=pp.loc[pp.state != state]
109
    #
110
    # if clean_offshore:
111
    #     pp=remove_onshore_technology_from_offshore_regions(pp)
112
    # Remove PHES (storages)
113
    if cfg.get("powerplants", "remove_phes"):
114
        pp = pp.loc[pp.technology != "Pumped storage"]
115
    return pp
116
117
118
def get_deflex_pp_by_year(
119
    regions, year, name, overwrite_capacity=False, filename=None
120
):
121
    """
122
123
    Parameters
124
    ----------
125
    regions : GeoDataFrame
126
    year : int
127
    name : str
128
    filename : str
129
    overwrite_capacity : bool
130
        By default (False) a new column "capacity_<year>" is created. If set to
131
        True the old capacity column will be overwritten.
132
133
    Returns
134
    -------
135
136
    """
137
    if filename is None:
138
        filename = os.path.join(
139
            cfg.get("paths", "powerplants"),
140
            cfg.get("powerplants", "deflex_pp"),
141
        ).format(map=name)
142
    logging.info("Get deflex power plants for {0}.".format(year))
143
    if not os.path.isfile(filename):
144
        msg = "File '{0}' does not exist. Will create it from reegis file."
145
        logging.debug(msg.format(filename))
146
        filename = pp_reegis2deflex(regions, name, filename_out=filename)
147
    pp = pd.DataFrame(pd.read_hdf(filename, "pp"))
148
149
    # Remove unwanted data sets
150
    pp = process_pp_table(pp)
151
152
    filter_columns = ["capacity_{0}", "capacity_in_{0}"]
153
154
    # Get all powerplants for the given year.
155
    # If com_month exist the power plants will be considered month-wise.
156
    # Otherwise the commission/decommission within the given year is not
157
    # considered.
158
159
    for fcol in filter_columns:
160
        filter_column = fcol.format(year)
161
        orig_column = fcol[:-4]
162
        c1 = (pp["com_year"] < year) & (pp["decom_year"] > year)
163
        pp.loc[c1, filter_column] = pp.loc[c1, orig_column]
164
165
        c2 = pp["com_year"] == year
166
        pp.loc[c2, filter_column] = (
167
            pp.loc[c2, orig_column] * (12 - pp.loc[c2, "com_month"]) / 12
168
        )
169
        c3 = pp["decom_year"] == year
170
        pp.loc[c3, filter_column] = (
171
            pp.loc[c3, orig_column] * pp.loc[c3, "com_month"] / 12
172
        )
173
174
        if overwrite_capacity:
175
            pp[orig_column] = 0
176
            pp[orig_column] = pp[filter_column]
177
            del pp[filter_column]
178
179
    return pp
180
181
182
def scenario_powerplants(table_collection, regions, year, name):
183
    """Get power plants for the scenario year
184
185
    Examples
186
    --------
187
    >>> from reegis import geometries
188
    >>> fs=geometries.get_federal_states_polygon()
189
    >>> my_pp=scenario_powerplants(
190
    ...     dict(), fs, 2014, "federal_states")  # doctest: +SKIP
191
    >>> my_pp["volatile plants"].loc[("DE03", "wind"), "capacity"
192
    ...     ] # doctest: +SKIP
193
    3052.8
194
    >>> my_pp["power plants"].loc[("DE03", "lignite"), "capacity"
195
    ...     ] # doctest: +SKIP
196
    1135.6
197
    """
198
    pp = get_deflex_pp_by_year(regions, year, name, overwrite_capacity=True)
199
    tables = create_powerplants(pp, table_collection, year, name)
200
    tables["power plants"]["source region"] = "DE"
201
    return tables
202
203
204
def create_powerplants(
205
    pp, table_collection, year, region_column="deflex_region"
206
):
207
    """This function works for all power plant tables with an equivalent
208
    structure e.g. power plants by state or other regions."""
209
    logging.info("Adding power plants to your scenario.")
210
211
    replace_names = cfg.get_dict("source_names")
212
213
    # TODO Waste is not "other"
214
    replace_names.update(cfg.get_dict("source_groups"))
215
    pp["count"] = 1
216
    pp["energy_source_level_2"].replace(replace_names, inplace=True)
217
218
    pp["model_classes"] = pp["energy_source_level_2"].replace(
219
        cfg.get_dict("model_classes")
220
    )
221
222
    power_plants = {
223
        "volatile plants": pp.groupby(
224
            ["model_classes", region_column, "energy_source_level_2"]
225
        )
226
        .sum()[["capacity", "count"]]
227
        .loc["volatile plants"]
228
    }
229
230
    if cfg.get("creator", "group_transformer"):
231
        power_plants["power plants"] = (
232
            pp.groupby(
233
                ["model_classes", region_column, "energy_source_level_2"]
234
            )
235
            .sum()[["capacity", "capacity_in", "count"]]
236
            .loc["power plants"]
237
        )
238
        power_plants["power plants"]["fuel"] = power_plants[
239
            "power plants"
240
        ].index.get_level_values(1)
241
    else:
242
        pp["efficiency"] = pp["efficiency"].round(2)
243
        power_plants["power plants"] = (
244
            pp.groupby(
245
                [
246
                    "model_classes",
247
                    region_column,
248
                    "energy_source_level_2",
249
                    "efficiency",
250
                ]
251
            )
252
            .sum()[["capacity", "capacity_in", "count"]]
253
            .loc["power plants"]
254
        )
255
        power_plants["power plants"]["fuel"] = power_plants[
256
            "power plants"
257
        ].index.get_level_values(1)
258
        power_plants["power plants"].index = [
259
            power_plants["power plants"].index.get_level_values(0),
260
            power_plants["power plants"].index.map("{0[1]} - {0[2]}".format),
261
        ]
262
263
    for class_name, pp_class in power_plants.items():
264
        if "capacity_in" in pp_class:
265
            pp_class["efficiency"] = (
266
                pp_class["capacity"] / pp_class["capacity_in"] * 100
267
            )
268
            del pp_class["capacity_in"]
269
        if cfg.get("creator", "round") is not None:
270
            pp_class = pp_class.round(cfg.get("creator", "round"))
271
        if "efficiency" in pp_class:
272
            pp_class["efficiency"] = pp_class["efficiency"].div(100)
273
        pp_class = pp_class.transpose()
274
        pp_class.index.name = "parameter"
275
        table_collection[class_name] = pp_class.transpose()
276
277
    table_collection = add_pp_limit(table_collection, year)
278
    table_collection = add_additional_values(table_collection)
279
    return table_collection
280
281
282
def add_additional_values(table_collection):
283
    """
284
285
    Parameters
286
    ----------
287
    table_collection
288
289
    Returns
290
    -------
291
292
    """
293
    transf = table_collection["power plants"]
294
    for values in ["variable_costs", "downtime_factor"]:
295
        if cfg.get("creator", "use_{0}".format(values)) is True:
296
            add_values = getattr(data.get_ewi_data(), values)
297
            if cfg.has_option("creator", "downtime_bioenergy"):
298
                add_values.loc["bioenergy", "value"] = cfg.get(
299
                    "creator", "downtime_bioenergy"
300
                )
301
            transf = transf.merge(
302
                add_values,
303
                right_index=True,
304
                how="left",
305
                left_on="fuel",
306
            )
307
            transf.drop(["unit", "source"], axis=1, inplace=True)
308
            transf.rename({"value": values}, axis=1, inplace=True)
309
        else:
310
            transf[values] = 0
311
    table_collection["power plants"] = transf
312
    return table_collection
313
314
315
def add_pp_limit(table_collection, year):
316
    """
317
318
    Parameters
319
    ----------
320
    table_collection
321
    year
322
323
    Returns
324
    -------
325
326
    """
327
    if len(cfg.get_list("creator", "limited_transformer")) > 0:
328
        # Multiply with 1000 to get MWh (bmwi: GWh)
329
        repp = bmwi.bmwi_re_energy_capacity() * 1000
330
        trsf = table_collection["power plants"]
331
        for limit_trsf in cfg.get_list("creator", "limited_transformer"):
332
            trsf = table_collection["power plants"]
333
            try:
334
                limit = repp.loc[year, (limit_trsf, "energy")]
335
            except KeyError:
336
                msg = "Cannot calculate limit for {0} in {1}."
337
                raise ValueError(msg.format(limit_trsf, year))
338
            cond = trsf["fuel"] == limit_trsf
339
            cap_sum = trsf.loc[pd.Series(cond)[cond].index, "capacity"].sum()
340
            trsf.loc[pd.Series(cond)[cond].index, "limit_elec_pp"] = (
341
                trsf.loc[pd.Series(cond)[cond].index, "capacity"]
342
                .div(cap_sum)
343
                .multiply(limit)
344
                + 0.5
345
            )
346
        trsf["limit_elec_pp"] = trsf["limit_elec_pp"].fillna(float("inf"))
347
348
        table_collection["power plants"] = trsf
349
    return table_collection
350
351
352
def scenario_chp(table_collection, regions, year, name, weather_year=None):
353
    """
354
355
    Parameters
356
    ----------
357
    table_collection
358
    regions
359
    year
360
    name
361
    weather_year
362
363
    Returns
364
    -------
365
366
    Examples
367
    --------
368
    >>> from reegis import geometries
369
    >>> fs=geometries.get_federal_states_polygon()
370
    >>> pp=scenario_powerplants(dict(), fs, 2014, "federal_states"
371
    ...     )  # doctest: +SKIP
372
    >>> int(pp["power plants"].loc[("NW", "hard coal"), "capacity"]
373
    ...     )  # doctest: +SKIP
374
    1291
375
    >>> table=scenario_chp(pp, fs, 2014, "federal_states")  # doctest: +SKIP
376
    >>> transf=table["power plants"]  # doctest: +SKIP
377
    >>> chp_hp=table["heat-chp plants"]  # doctest: +SKIP
378
    >>> int(transf.loc[("MV", "hard coal"), "capacity"])  # doctest: +SKIP
379
    623
380
    >>> int(chp_hp.loc[("HH", "hard coal"), "capacity_elec_chp"]
381
    ...     )  # doctest: +SKIP
382
    667
383
    """
384
    # values from heat balance
385
386
    cb = energy_balance.get_transformation_balance_by_region(
387
        regions, year, name
388
    )
389
    cb.rename(columns={"re": "bioenergy"}, inplace=True)
390
    heat_b = powerplants.calculate_chp_share_and_efficiency(cb)
391
392
    heat_demand = demand.get_heat_profiles_deflex(
393
        regions, year, weather_year=weather_year
394
    )
395
    tables = chp_table(heat_b, heat_demand, table_collection)
396
    tables["heat-chp plants"]["source region"] = "DE"
397
    return tables
398
399
400
def chp_table(heat_b, heat_demand, table_collection, regions=None):
401
    """
402
403
    Parameters
404
    ----------
405
    heat_b
406
    heat_demand
407
    table_collection
408
    regions
409
410
    Returns
411
    -------
412
413
    """
414
415
    chp_hp = pd.DataFrame(
416
        columns=pd.MultiIndex(levels=[[], []], codes=[[], []])
417
    )
418
419
    rows = ["Heizkraftwerke der allgemeinen Versorgung (nur KWK)", "Heizwerke"]
420
    if regions is None:
421
        regions = sorted(heat_b.keys())
422
423
    eta_heat_chp = None
424
    eta_elec_chp = None
425
426
    for region in regions:
427
        eta_hp = round(heat_b[region]["sys_heat"] * heat_b[region]["hp"], 2)
428
        eta_heat_chp = round(
429
            heat_b[region]["sys_heat"] * heat_b[region]["heat_chp"], 2
430
        )
431
        eta_elec_chp = round(heat_b[region]["elec_chp"], 2)
432
433
        # Due to the different efficiency between heat from chp-plants and
434
        # heat from heat-plants the share of the output is different to the
435
        # share of the input. As heat-plants will produce more heat per fuel
436
        # factor will be greater than 1 and for chp-plants smaller than 1.
437
        out_share_factor_chp = heat_b[region]["out_share_factor_chp"]
438
        out_share_factor_hp = heat_b[region]["out_share_factor_hp"]
439
440
        # Remove "district heating" and "electricity" and spread the share
441
        # to the remaining columns.
442
        share = pd.DataFrame(columns=heat_b[region]["fuel_share"].columns)
443
        for row in rows:
444
            tmp = heat_b[region]["fuel_share"].loc[region, :, row]
445
            tot = float(tmp["total"])
446
447
            d = float(tmp["district heating"] + tmp["electricity"])
448
            tmp = tmp + tmp / (tot - d) * d
449
            tmp = tmp.reset_index(drop=True)
450
            share.loc[row] = tmp.loc[0]
451
        del share["district heating"]
452
        del share["electricity"]
453
454
        # Remove the total share
455
        del share["total"]
456
457
        max_val = float(heat_demand[region]["district heating"].max())
458
        sum_val = float(heat_demand[region]["district heating"].sum())
459
460
        share = share.rename({"gas": "natural gas"}, axis=1)
461
462
        for fuel in share.columns:
463
            # CHP
464
            chp_hp.loc["limit_heat_chp", (region, fuel)] = round(
465
                sum_val * share.loc[rows[0], fuel] * out_share_factor_chp + 0.5
466
            )
467
            cap_heat_chp = round(
468
                max_val * share.loc[rows[0], fuel] * out_share_factor_chp
469
                + 0.005,
470
                2,
471
            )
472
            chp_hp.loc["capacity_heat_chp", (region, fuel)] = cap_heat_chp
473
            cap_elec = cap_heat_chp / eta_heat_chp * eta_elec_chp
474
            chp_hp.loc["capacity_elec_chp", (region, fuel)] = round(
475
                cap_elec, 2
476
            )
477
            chp_hp[region] = chp_hp[region].fillna(0)
478
479
            # HP
480
            chp_hp.loc["limit_hp", (region, fuel)] = round(
481
                sum_val * share.loc[rows[1], fuel] * out_share_factor_hp + 0.5
482
            )
483
            chp_hp.loc["capacity_hp", (region, fuel)] = round(
484
                max_val * share.loc[rows[1], fuel] * out_share_factor_hp
485
                + 0.005,
486
                2,
487
            )
488
            if chp_hp.loc["capacity_hp", (region, fuel)] > 0:
489
                chp_hp.loc["efficiency_hp", (region, fuel)] = eta_hp
490
            if cap_heat_chp * cap_elec > 0:
491
                chp_hp.loc[
492
                    "efficiency_heat_chp", (region, fuel)
493
                ] = eta_heat_chp
494
                chp_hp.loc[
495
                    "efficiency_elec_chp", (region, fuel)
496
                ] = eta_elec_chp
497
            chp_hp.loc["fuel", (region, fuel)] = fuel
498
499
    logging.info("Done")
500
501
    chp_hp.sort_index(axis=1, inplace=True)
502
503
    # for col in trsf.sum().loc[trsf.sum() == 0].index:
504
    #     del trsf[col]
505
    # trsf[trsf < 0] = 0
506
507
    table_collection["heat-chp plants"] = chp_hp.transpose()
508
509
    table_collection = substract_chp_capacity_and_limit_from_pp(
510
        table_collection, eta_heat_chp, eta_elec_chp
511
    )
512
513
    return {
514
        "heat-chp plants": table_collection["heat-chp plants"],
515
        "power plants": table_collection["power plants"],
516
    }
517
518
519
def substract_chp_capacity_and_limit_from_pp(tc, eta_heat_chp, eta_elec_chp):
520
    """
521
522
    Parameters
523
    ----------
524
    tc
525
    eta_heat_chp
526
    eta_elec_chp
527
528
    Returns
529
    -------
530
531
    """
532
    chp_hp = tc["heat-chp plants"]
533
    pp = tc["power plants"]
534
    diff = 0
535
    for region in chp_hp.index.get_level_values(0).unique():
536
        for fuel in chp_hp.loc[region].index:
537
            # If the power plant limit is not "inf" the limited electricity
538
            # output of the chp plant has to be subtracted from the power plant
539
            # limit because this is related to the overall electricity output.
540
            limit_elec_pp = pp.loc[
541
                (pp.index.get_level_values(0) == region) & (pp.fuel == fuel),
542
                "limit_elec_pp",
543
            ].sum()
544
            if not limit_elec_pp == float("inf"):
545
                limit_elec_chp = (
546
                    chp_hp.loc[(region, fuel), "limit_heat_chp"]
547
                    / eta_heat_chp
548
                    * eta_elec_chp
549
                )
550
                factor = 1 - limit_elec_chp / limit_elec_pp
551
                pp.loc[
552
                    (pp.index.get_level_values(0) == region)
553
                    & (pp.fuel == fuel),
554
                    "limit_elec_pp",
555
                ] *= factor
556
557
            # Substract the electric capacity of the chp from the capacity
558
            # of the power plant.
559
            capacity_elec_pp = pp.loc[
560
                (pp.index.get_level_values(0) == region) & (pp.fuel == fuel),
561
                "capacity",
562
            ].sum()
563
            capacity_elec_chp = chp_hp.loc[(region, fuel), "capacity_elec_chp"]
564
            if capacity_elec_chp < capacity_elec_pp:
565
                factor = 1 - capacity_elec_chp / capacity_elec_pp
566
            elif capacity_elec_chp == capacity_elec_pp:
567
                factor = 0
568
            else:
569
                factor = 0
570
                diff += capacity_elec_chp - capacity_elec_pp
571
                msg = (
572
                    "Electricity capacity of chp plant it greater than "
573
                    "existing electricity capacity in one region.\n"
574
                    "Region: {0}, capacity_elec: {1}, capacity_elec_chp: "
575
                    "{2}, fuel: {3}"
576
                )
577
                warn(
578
                    msg.format(
579
                        region, capacity_elec_pp, capacity_elec_chp, fuel
580
                    ),
581
                    UserWarning,
582
                )
583
            pp.loc[
584
                (pp.index.get_level_values(0) == region) & (pp.fuel == fuel),
585
                "capacity",
586
            ] *= factor
587
    if diff > 0:
588
        msg = (
589
            "Electricity capacity of some chp plants it greater than "
590
            "existing electricity capacity.\n"
591
            "Overall difference: {0}"
592
        )
593
        warn(msg.format(diff), UserWarning)
594
    return tc
595
596
597
if __name__ == "__main__":
598
    pass
599