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

scenario_builder.powerplants.pp_reegis2deflex()   A

Complexity

Conditions 2

Size

Total Lines 30
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

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