Passed
Pull Request — master (#2)
by Uwe
01:06
created

scenario_builder.powerplants.create_powerplants()   B

Complexity

Conditions 6

Size

Total Lines 75
Code Lines 54

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 54
nop 4
dl 0
loc 75
rs 7.5721
c 0
b 0
f 0

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
"""Create a basic scenario from the internal data structure.
2
3
SPDX-FileCopyrightText: 2016-2019 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 energy_balance
15
from reegis import geometries as reegis_geometries
16
from reegis import powerplants
17
18
from scenario_builder import config as cfg
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=cfg.get("init", "map"))
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
    >>> regions=geometries.deflex_regions(rmap="de21")
188
    >>> pp=scenario_powerplants(
189
    ...     dict(), regions, 2014, "de21")  # doctest: +SKIP
190
    >>> pp["volatile_source"].loc[("DE03", "wind"), "capacity"
191
    ...     ] # doctest: +SKIP
192
    3052.8
193
    >>> pp["transformer"].loc[("DE03", "lignite"), "capacity"
194
    ...     ] # doctest: +SKIP
195
    1135.6
196
    """
197
    pp = powerplants.get_deflex_pp_by_year(
198
        regions, year, name, overwrite_capacity=True
199
    )
200
    return create_powerplants(pp, table_collection, year, name)
201
202
203
def create_powerplants(
204
    pp, table_collection, year, region_column="deflex_region"
205
):
206
    """This function works for all power plant tables with an equivalent
207
    structure e.g. power plants by state or other regions."""
208
    logging.info("Adding power plants to your scenario.")
209
210
    replace_names = cfg.get_dict("source_names")
211
212
    # TODO Waste is not "other"
213
    replace_names.update(cfg.get_dict("source_groups"))
214
    pp["count"] = 1
215
    pp["energy_source_level_2"].replace(replace_names, inplace=True)
216
217
    pp["model_classes"] = pp["energy_source_level_2"].replace(
218
        cfg.get_dict("model_classes")
219
    )
220
221
    power_plants = {
222
        "volatile_source": pp.groupby(
223
            ["model_classes", region_column, "energy_source_level_2"]
224
        )
225
        .sum()[["capacity", "count"]]
226
        .loc["volatile_source"]
227
    }
228
229
    if cfg.get("basic", "group_transformer"):
230
        power_plants["transformer"] = (
231
            pp.groupby(
232
                ["model_classes", region_column, "energy_source_level_2"]
233
            )
234
            .sum()[["capacity", "capacity_in", "count"]]
235
            .loc["transformer"]
236
        )
237
        power_plants["transformer"]["fuel"] = power_plants[
238
            "transformer"
239
        ].index.get_level_values(1)
240
    else:
241
        pp["efficiency"] = pp["efficiency"].round(2)
242
        power_plants["transformer"] = (
243
            pp.groupby(
244
                [
245
                    "model_classes",
246
                    region_column,
247
                    "energy_source_level_2",
248
                    "efficiency",
249
                ]
250
            )
251
            .sum()[["capacity", "capacity_in", "count"]]
252
            .loc["transformer"]
253
        )
254
        power_plants["transformer"]["fuel"] = power_plants[
255
            "transformer"
256
        ].index.get_level_values(1)
257
        power_plants["transformer"].index = [
258
            power_plants["transformer"].index.get_level_values(0),
259
            power_plants["transformer"].index.map("{0[1]} - {0[2]}".format),
260
        ]
261
262
    for class_name, pp_class in power_plants.items():
263
        if "capacity_in" in pp_class:
264
            pp_class["efficiency"] = (
265
                pp_class["capacity"] / pp_class["capacity_in"] * 100
266
            )
267
            del pp_class["capacity_in"]
268
        if cfg.get("basic", "round") is not None:
269
            pp_class = pp_class.round(cfg.get("basic", "round"))
270
        if "efficiency" in pp_class:
271
            pp_class["efficiency"] = pp_class["efficiency"].div(100)
272
        pp_class = pp_class.transpose()
273
        pp_class.index.name = "parameter"
274
        table_collection[class_name] = pp_class.transpose()
275
    table_collection = add_pp_limit(table_collection, year)
276
    table_collection = add_additional_values(table_collection)
277
    return table_collection
278
279
280
def add_additional_values(table_collection):
281
    """
282
283
    Parameters
284
    ----------
285
    table_collection
286
287
    Returns
288
    -------
289
290
    """
291
    transf = table_collection["transformer"]
292
    for values in ["variable_costs", "downtime_factor"]:
293
        if cfg.get("basic", "use_{0}".format(values)) is True:
294
            add_values = getattr(data.get_ewi_data(), values)
295
            transf = transf.merge(
296
                add_values, right_index=True, how="left", left_on="fuel",
297
            )
298
            transf.drop(["unit", "source"], axis=1, inplace=True)
299
            transf.rename({"value": values}, axis=1, inplace=True)
300
        else:
301
            transf[values] = 0
302
    table_collection["transformer"] = transf
303
    return table_collection
304
305
306
def add_pp_limit(table_collection, year):
307
    """
308
309
    Parameters
310
    ----------
311
    table_collection
312
    year
313
314
    Returns
315
    -------
316
317
    """
318
    if len(cfg.get_dict("limited_transformer").keys()) > 0:
319
        # Multiply with 1000 to get MWh (bmwi: GWh)
320
        repp = bmwi.bmwi_re_energy_capacity() * 1000
321
        trsf = table_collection["transformer"]
322
        for limit_trsf in cfg.get_dict("limited_transformer").keys():
323
            trsf = table_collection["transformer"]
324
            try:
325
                limit = repp.loc[year, (limit_trsf, "energy")]
326
            except KeyError:
327
                msg = "Cannot calculate limit for {0} in {1}."
328
                raise ValueError(msg.format(limit_trsf, year))
329
            cond = trsf["fuel"] == limit_trsf
330
            cap_sum = trsf.loc[pd.Series(cond)[cond].index, "capacity"].sum()
331
            trsf.loc[pd.Series(cond)[cond].index, "limit_elec_pp"] = (
332
                trsf.loc[pd.Series(cond)[cond].index, "capacity"]
333
                .div(cap_sum)
334
                .multiply(limit)
335
                + 0.5
336
            )
337
        trsf["limit_elec_pp"] = trsf["limit_elec_pp"].fillna(float("inf"))
338
339
        table_collection["transformer"] = trsf
340
    return table_collection
341
342
343
def scenario_chp(table_collection, regions, year, name, weather_year=None):
344
    """
345
346
    Parameters
347
    ----------
348
    table_collection
349
    regions
350
    year
351
    name
352
    weather_year
353
354
    Returns
355
    -------
356
357
    Examples
358
    --------
359
    >>> regions=geometries.deflex_regions(rmap="de21")  # doctest: +SKIP
360
    >>> pp=scenario_powerplants(dict(), regions, 2014, "de21"
361
    ...     )  # doctest: +SKIP
362
    >>> int(pp["transformer"].loc[("DE01", "hard coal"), "capacity"]
363
    ...     )  # doctest: +SKIP
364
    1291
365
    >>> table=scenario_chp(pp, regions, 2014, "de21")  # doctest: +SKIP
366
    >>> transf=table["transformer"]  # doctest: +SKIP
367
    >>> chp_hp=table["chp_hp"]  # doctest: +SKIP
368
    >>> int(transf.loc[("DE01", "hard coal"), "capacity"])  # doctest: +SKIP
369
    623
370
    >>> int(chp_hp.loc[("DE01", "hard coal"), "capacity_elec_chp"]
371
    ...     )  # doctest: +SKIP
372
    667
373
    """
374
    # values from heat balance
375
376
    cb = energy_balance.get_transformation_balance_by_region(
377
        regions, year, name
378
    )
379
    cb.rename(columns={"re": cfg.get("chp", "renewable_source")}, inplace=True)
380
    heat_b = powerplants.calculate_chp_share_and_efficiency(cb)
381
382
    heat_demand = demand.get_heat_profiles_deflex(
383
        regions, year, weather_year=weather_year
384
    )
385
    return chp_table(heat_b, heat_demand, table_collection)
386
387
388
def chp_table(heat_b, heat_demand, table_collection, regions=None):
389
    """
390
391
    Parameters
392
    ----------
393
    heat_b
394
    heat_demand
395
    table_collection
396
    regions
397
398
    Returns
399
    -------
400
401
    """
402
403
    chp_hp = pd.DataFrame(
404
        columns=pd.MultiIndex(levels=[[], []], codes=[[], []])
405
    )
406
407
    rows = ["Heizkraftwerke der allgemeinen Versorgung (nur KWK)", "Heizwerke"]
408
    if regions is None:
409
        regions = sorted(heat_b.keys())
410
411
    eta_heat_chp = None
412
    eta_elec_chp = None
413
414
    for region in regions:
415
        eta_hp = round(heat_b[region]["sys_heat"] * heat_b[region]["hp"], 2)
416
        eta_heat_chp = round(
417
            heat_b[region]["sys_heat"] * heat_b[region]["heat_chp"], 2
418
        )
419
        eta_elec_chp = round(heat_b[region]["elec_chp"], 2)
420
421
        # Due to the different efficiency between heat from chp-plants and
422
        # heat from heat-plants the share of the output is different to the
423
        # share of the input. As heat-plants will produce more heat per fuel
424
        # factor will be greater than 1 and for chp-plants smaller than 1.
425
        out_share_factor_chp = heat_b[region]["out_share_factor_chp"]
426
        out_share_factor_hp = heat_b[region]["out_share_factor_hp"]
427
428
        # Remove "district heating" and "electricity" and spread the share
429
        # to the remaining columns.
430
        share = pd.DataFrame(columns=heat_b[region]["fuel_share"].columns)
431
        for row in rows:
432
            tmp = heat_b[region]["fuel_share"].loc[region, :, row]
433
            tot = float(tmp["total"])
434
435
            d = float(tmp["district heating"] + tmp["electricity"])
436
            tmp = tmp + tmp / (tot - d) * d
437
            tmp = tmp.reset_index(drop=True)
438
            share.loc[row] = tmp.loc[0]
439
        del share["district heating"]
440
        del share["electricity"]
441
442
        # Remove the total share
443
        del share["total"]
444
445
        max_val = float(heat_demand[region]["district heating"].max())
446
        sum_val = float(heat_demand[region]["district heating"].sum())
447
448
        share = share.rename({"gas": "natural gas"}, axis=1)
449
450
        for fuel in share.columns:
451
            # CHP
452
            chp_hp.loc["limit_heat_chp", (region, fuel)] = round(
453
                sum_val * share.loc[rows[0], fuel] * out_share_factor_chp + 0.5
454
            )
455
            cap_heat_chp = round(
456
                max_val * share.loc[rows[0], fuel] * out_share_factor_chp
457
                + 0.005,
458
                2,
459
            )
460
            chp_hp.loc["capacity_heat_chp", (region, fuel)] = cap_heat_chp
461
            cap_elec = cap_heat_chp / eta_heat_chp * eta_elec_chp
462
            chp_hp.loc["capacity_elec_chp", (region, fuel)] = round(
463
                cap_elec, 2
464
            )
465
            chp_hp[region] = chp_hp[region].fillna(0)
466
467
            # HP
468
            chp_hp.loc["limit_hp", (region, fuel)] = round(
469
                sum_val * share.loc[rows[1], fuel] * out_share_factor_hp + 0.5
470
            )
471
            chp_hp.loc["capacity_hp", (region, fuel)] = round(
472
                max_val * share.loc[rows[1], fuel] * out_share_factor_hp
473
                + 0.005,
474
                2,
475
            )
476
            if chp_hp.loc["capacity_hp", (region, fuel)] > 0:
477
                chp_hp.loc["efficiency_hp", (region, fuel)] = eta_hp
478
            if cap_heat_chp * cap_elec > 0:
479
                chp_hp.loc[
480
                    "efficiency_heat_chp", (region, fuel)
481
                ] = eta_heat_chp
482
                chp_hp.loc[
483
                    "efficiency_elec_chp", (region, fuel)
484
                ] = eta_elec_chp
485
            chp_hp.loc["fuel", (region, fuel)] = fuel
486
487
    logging.info("Done")
488
489
    chp_hp.sort_index(axis=1, inplace=True)
490
491
    # for col in trsf.sum().loc[trsf.sum() == 0].index:
492
    #     del trsf[col]
493
    # trsf[trsf < 0] = 0
494
495
    table_collection["chp_hp"] = chp_hp.transpose()
496
497
    table_collection = substract_chp_capacity_and_limit_from_pp(
498
        table_collection, eta_heat_chp, eta_elec_chp
499
    )
500
501
    return table_collection
502
503
504
def substract_chp_capacity_and_limit_from_pp(tc, eta_heat_chp, eta_elec_chp):
505
    """
506
507
    Parameters
508
    ----------
509
    tc
510
    eta_heat_chp
511
    eta_elec_chp
512
513
    Returns
514
    -------
515
516
    """
517
    chp_hp = tc["chp_hp"]
518
    pp = tc["transformer"]
519
    diff = 0
520
    for region in chp_hp.index.get_level_values(0).unique():
521
        for fuel in chp_hp.loc[region].index:
522
            # If the power plant limit is not "inf" the limited electricity
523
            # output of the chp plant has to be subtracted from the power plant
524
            # limit because this is related to the overall electricity output.
525
            limit_elec_pp = pp.loc[
526
                (pp.index.get_level_values(0) == region) & (pp.fuel == fuel),
527
                "limit_elec_pp",
528
            ].sum()
529
            if not limit_elec_pp == float("inf"):
530
                limit_elec_chp = (
531
                    chp_hp.loc[(region, fuel), "limit_heat_chp"]
532
                    / eta_heat_chp
533
                    * eta_elec_chp
534
                )
535
                factor = 1 - limit_elec_chp / limit_elec_pp
536
                pp.loc[
537
                    (pp.index.get_level_values(0) == region)
538
                    & (pp.fuel == fuel),
539
                    "limit_elec_pp",
540
                ] *= factor
541
542
            # Substract the electric capacity of the chp from the capacity
543
            # of the power plant.
544
            capacity_elec_pp = pp.loc[
545
                (pp.index.get_level_values(0) == region) & (pp.fuel == fuel),
546
                "capacity",
547
            ].sum()
548
            capacity_elec_chp = chp_hp.loc[(region, fuel), "capacity_elec_chp"]
549
            if capacity_elec_chp < capacity_elec_pp:
550
                factor = 1 - capacity_elec_chp / capacity_elec_pp
551
            elif capacity_elec_chp == capacity_elec_pp:
552
                factor = 0
553
            else:
554
                factor = 0
555
                diff += capacity_elec_chp - capacity_elec_pp
556
                msg = (
557
                    "Electricity capacity of chp plant it greater than "
558
                    "existing electricity capacity in one region.\n"
559
                    "Region: {0}, capacity_elec: {1}, capacity_elec_chp: "
560
                    "{2}, fuel: {3}"
561
                )
562
                warn(
563
                    msg.format(
564
                        region, capacity_elec_pp, capacity_elec_chp, fuel
565
                    ),
566
                    UserWarning,
567
                )
568
            pp.loc[
569
                (pp.index.get_level_values(0) == region) & (pp.fuel == fuel),
570
                "capacity",
571
            ] *= factor
572
    if diff > 0:
573
        msg = (
574
            "Electricity capacity of some chp plants it greater than "
575
            "existing electricity capacity.\n"
576
            "Overall difference: {0}"
577
        )
578
        warn(msg.format(diff), UserWarning)
579
    return tc
580
581
582
if __name__ == "__main__":
583
    pass
584