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