Passed
Pull Request — master (#2)
by Uwe
03:35
created

scenario_builder.powerplants.chp_table()   C

Complexity

Conditions 7

Size

Total Lines 114
Code Lines 63

Duplication

Lines 0
Ratio 0 %

Importance

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