Passed
Pull Request — dev (#924)
by
unknown
01:34
created

sanitycheck_eGon2035_heat()   B

Complexity

Conditions 1

Size

Total Lines 216
Code Lines 86

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 86
dl 0
loc 216
rs 7.4581
c 0
b 0
f 0
cc 1
nop 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
"""
2
This module does sanity checks for both the eGon2035 and the eGon100RE scenario
3
separately where a percentage error is given to showcase difference in output
4
and input values. Please note that there are missing input technologies in the
5
supply tables.
6
Authors: @ALonso, @dana
7
"""
8
9
import numpy as np
10
import pandas as pd
11
12
from egon.data import db, logger
13
from egon.data.datasets import Dataset
14
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
15
    EgonCtsElectricityDemandBuildingShare,
16
    EgonCtsHeatDemandBuildingShare,
17
)
18
19
20
class SanityChecks(Dataset):
21
    def __init__(self, dependencies):
22
        super().__init__(
23
            name="SanityChecks",
24
            version="0.0.4",
25
            dependencies=dependencies,
26
            tasks={
27
                etrago_eGon2035_electricity,
28
                etrago_eGon2035_heat,
29
                residential_electricity_annual_sum,
30
                residential_electricity_hh_refinement,
31
            },
32
        )
33
34
35
def etrago_eGon2035_electricity():
36
    """Execute basic sanity checks.
37
38
    Returns print statements as sanity checks for the electricity sector in
39
    the eGon2035 scenario.
40
41
    Parameters
42
    ----------
43
    None
44
45
    Returns
46
    -------
47
    None
48
    """
49
50
    scn = "eGon2035"
51
52
    # Section to check generator capacities
53
    logger.info(f"Sanity checks for scenario {scn}")
54
    logger.info(
55
        "For German electricity generators the following deviations between "
56
        "the inputs and outputs can be observed:"
57
    )
58
59
    carriers_electricity = [
60
        "other_non_renewable",
61
        "other_renewable",
62
        "reservoir",
63
        "run_of_river",
64
        "oil",
65
        "wind_onshore",
66
        "wind_offshore",
67
        "solar",
68
        "solar_rooftop",
69
        "biomass",
70
    ]
71
72
    for carrier in carriers_electricity:
73
74
        if carrier == "biomass":
75
            sum_output = db.select_dataframe(
76
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
77
                    FROM grid.egon_etrago_generator
78
                    WHERE bus IN (
79
                        SELECT bus_id FROM grid.egon_etrago_bus
80
                        WHERE scn_name = 'eGon2035'
81
                        AND country = 'DE')
82
                    AND carrier IN ('biomass', 'industrial_biomass_CHP',
83
                    'central_biomass_CHP')
84
                    GROUP BY (scn_name);
85
                """,
86
                warning=False,
87
            )
88
89
        else:
90
            sum_output = db.select_dataframe(
91
                f"""SELECT scn_name,
92
                 SUM(p_nom::numeric) as output_capacity_mw
93
                         FROM grid.egon_etrago_generator
94
                         WHERE scn_name = '{scn}'
95
                         AND carrier IN ('{carrier}')
96
                         AND bus IN
97
                             (SELECT bus_id
98
                               FROM grid.egon_etrago_bus
99
                               WHERE scn_name = 'eGon2035'
100
                               AND country = 'DE')
101
                         GROUP BY (scn_name);
102
                    """,
103
                warning=False,
104
            )
105
106
        sum_input = db.select_dataframe(
107
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
108
                     FROM supply.egon_scenario_capacities
109
                     WHERE carrier= '{carrier}'
110
                     AND scenario_name ='{scn}'
111
                     GROUP BY (carrier);
112
                """,
113
            warning=False,
114
        )
115
116 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
117
            sum_output.output_capacity_mw.sum() == 0
118
            and sum_input.input_capacity_mw.sum() == 0
119
        ):
120
            logger.info(
121
                f"No capacity for carrier '{carrier}' needed to be"
122
                f" distributed. Everything is fine"
123
            )
124
125
        elif (
126
            sum_input.input_capacity_mw.sum() > 0
127
            and sum_output.output_capacity_mw.sum() == 0
128
        ):
129
            logger.info(
130
                f"Error: Capacity for carrier '{carrier}' was not distributed "
131
                f"at all!"
132
            )
133
134
        elif (
135
            sum_output.output_capacity_mw.sum() > 0
136
            and sum_input.input_capacity_mw.sum() == 0
137
        ):
138
            logger.info(
139
                f"Error: Eventhough no input capacity was provided for carrier"
140
                f"'{carrier}' a capacity got distributed!"
141
            )
142
143
        else:
144
            sum_input["error"] = (
145
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
146
                / sum_input.input_capacity_mw
147
            ) * 100
148
            g = sum_input["error"].values[0]
149
150
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
151
152
    # Section to check storage units
153
154
    logger.info(f"Sanity checks for scenario {scn}")
155
    logger.info(
156
        "For German electrical storage units the following deviations between"
157
        "the inputs and outputs can be observed:"
158
    )
159
160
    carriers_electricity = ["pumped_hydro"]
161
162
    for carrier in carriers_electricity:
163
164
        sum_output = db.select_dataframe(
165
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
166
                         FROM grid.egon_etrago_storage
167
                         WHERE scn_name = '{scn}'
168
                         AND carrier IN ('{carrier}')
169
                         AND bus IN
170
                             (SELECT bus_id
171
                               FROM grid.egon_etrago_bus
172
                               WHERE scn_name = 'eGon2035'
173
                               AND country = 'DE')
174
                         GROUP BY (scn_name);
175
                    """,
176
            warning=False,
177
        )
178
179
        sum_input = db.select_dataframe(
180
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
181
                     FROM supply.egon_scenario_capacities
182
                     WHERE carrier= '{carrier}'
183
                     AND scenario_name ='{scn}'
184
                     GROUP BY (carrier);
185
                """,
186
            warning=False,
187
        )
188
189 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
190
            sum_output.output_capacity_mw.sum() == 0
191
            and sum_input.input_capacity_mw.sum() == 0
192
        ):
193
            logger.info(
194
                f"No capacity for carrier '{carrier}' needed to be "
195
                f"distributed. Everything is fine"
196
            )
197
198
        elif (
199
            sum_input.input_capacity_mw.sum() > 0
200
            and sum_output.output_capacity_mw.sum() == 0
201
        ):
202
            logger.info(
203
                f"Error: Capacity for carrier '{carrier}' was not distributed"
204
                f" at all!"
205
            )
206
207
        elif (
208
            sum_output.output_capacity_mw.sum() > 0
209
            and sum_input.input_capacity_mw.sum() == 0
210
        ):
211
            logger.info(
212
                f"Error: Eventhough no input capacity was provided for carrier"
213
                f" '{carrier}' a capacity got distributed!"
214
            )
215
216
        else:
217
            sum_input["error"] = (
218
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
219
                / sum_input.input_capacity_mw
220
            ) * 100
221
            g = sum_input["error"].values[0]
222
223
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
224
225
    # Section to check loads
226
227
    logger.info(
228
        "For German electricity loads the following deviations between the"
229
        " input and output can be observed:"
230
    )
231
232
    output_demand = db.select_dataframe(
233
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
234
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
235
            FROM grid.egon_etrago_load a
236
            JOIN grid.egon_etrago_load_timeseries b
237
            ON (a.load_id = b.load_id)
238
            JOIN grid.egon_etrago_bus c
239
            ON (a.bus=c.bus_id)
240
            AND b.scn_name = 'eGon2035'
241
            AND a.scn_name = 'eGon2035'
242
            AND a.carrier = 'AC'
243
            AND c.scn_name= 'eGon2035'
244
            AND c.country='DE'
245
            GROUP BY (a.scn_name, a.carrier);
246
247
    """,
248
        warning=False,
249
    )["load_twh"].values[0]
250
251
    input_cts_ind = db.select_dataframe(
252
        """SELECT scenario,
253
         SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
254
            FROM demand.egon_demandregio_cts_ind
255
            WHERE scenario= 'eGon2035'
256
            AND year IN ('2035')
257
            GROUP BY (scenario);
258
259
        """,
260
        warning=False,
261
    )["demand_mw_regio_cts_ind"].values[0]
262
263
    input_hh = db.select_dataframe(
264
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
265
            FROM demand.egon_demandregio_hh
266
            WHERE scenario= 'eGon2035'
267
            AND year IN ('2035')
268
            GROUP BY (scenario);
269
        """,
270
        warning=False,
271
    )["demand_mw_regio_hh"].values[0]
272
273
    input_demand = input_hh + input_cts_ind
274
275
    e = round((output_demand - input_demand) / input_demand, 2) * 100
276
277
    logger.info(f"electricity demand: {e} %")
278
279
280
def etrago_eGon2035_heat():
281
    """Execute basic sanity checks.
282
283
    Returns print statements as sanity checks for the heat sector in
284
    the eGon2035 scenario.
285
286
    Parameters
287
    ----------
288
    None
289
290
    Returns
291
    -------
292
    None
293
    """
294
295
    # Check input and output values for the carriers "other_non_renewable",
296
    # "other_renewable", "reservoir", "run_of_river" and "oil"
297
298
    scn = "eGon2035"
299
300
    # Section to check generator capacities
301
    logger.info(f"Sanity checks for scenario {scn}")
302
    logger.info(
303
        "For German heat demands the following deviations between the inputs"
304
        " and outputs can be observed:"
305
    )
306
307
    # Sanity checks for heat demand
308
309
    output_heat_demand = db.select_dataframe(
310
        """SELECT a.scn_name,
311
          (SUM(
312
          (SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
313
            FROM grid.egon_etrago_load a
314
            JOIN grid.egon_etrago_load_timeseries b
315
            ON (a.load_id = b.load_id)
316
            JOIN grid.egon_etrago_bus c
317
            ON (a.bus=c.bus_id)
318
            AND b.scn_name = 'eGon2035'
319
            AND a.scn_name = 'eGon2035'
320
            AND c.scn_name= 'eGon2035'
321
            AND c.country='DE'
322
            AND a.carrier IN ('rural_heat', 'central_heat')
323
            GROUP BY (a.scn_name);
324
        """,
325
        warning=False,
326
    )["load_twh"].values[0]
327
328
    input_heat_demand = db.select_dataframe(
329
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
330
            FROM demand.egon_peta_heat
331
            WHERE scenario= 'eGon2035'
332
            GROUP BY (scenario);
333
        """,
334
        warning=False,
335
    )["demand_mw_peta_heat"].values[0]
336
337
    e_demand = (
338
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
339
        * 100
340
    )
341
342
    logger.info(f"heat demand: {e_demand} %")
343
344
    # Sanity checks for heat supply
345
346
    logger.info(
347
        "For German heat supplies the following deviations between the inputs "
348
        "and outputs can be observed:"
349
    )
350
351
    # Comparison for central heat pumps
352
    heat_pump_input = db.select_dataframe(
353
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
354
            FROM supply.egon_scenario_capacities
355
            WHERE carrier= 'urban_central_heat_pump'
356
            AND scenario_name IN ('eGon2035')
357
            GROUP BY (carrier);
358
        """,
359
        warning=False,
360
    )["urban_central_heat_pump_mw"].values[0]
361
362
    heat_pump_output = db.select_dataframe(
363
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
364
            FROM grid.egon_etrago_link
365
            WHERE carrier= 'central_heat_pump'
366
            AND scn_name IN ('eGon2035')
367
            GROUP BY (carrier);
368
    """,
369
        warning=False,
370
    )["central_heat_pump_mw"].values[0]
371
372
    e_heat_pump = (
373
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
374
    )
375
376
    logger.info(f"'central_heat_pump': {e_heat_pump} % ")
377
378
    # Comparison for residential heat pumps
379
380
    input_residential_heat_pump = db.select_dataframe(
381
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
382
            FROM supply.egon_scenario_capacities
383
            WHERE carrier= 'residential_rural_heat_pump'
384
            AND scenario_name IN ('eGon2035')
385
            GROUP BY (carrier);
386
        """,
387
        warning=False,
388
    )["residential_heat_pump_mw"].values[0]
389
390
    output_residential_heat_pump = db.select_dataframe(
391
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
392
            FROM grid.egon_etrago_link
393
            WHERE carrier= 'rural_heat_pump'
394
            AND scn_name IN ('eGon2035')
395
            GROUP BY (carrier);
396
    """,
397
        warning=False,
398
    )["rural_heat_pump_mw"].values[0]
399
400
    e_residential_heat_pump = (
401
        round(
402
            (output_residential_heat_pump - input_residential_heat_pump)
403
            / input_residential_heat_pump,
404
            2,
405
        )
406
        * 100
407
    )
408
    logger.info(f"'residential heat pumps': {e_residential_heat_pump} %")
409
410
    # Comparison for resistive heater
411
    resistive_heater_input = db.select_dataframe(
412
        """SELECT carrier,
413
         SUM(capacity::numeric) as Urban_central_resistive_heater_MW
414
            FROM supply.egon_scenario_capacities
415
            WHERE carrier= 'urban_central_resistive_heater'
416
            AND scenario_name IN ('eGon2035')
417
            GROUP BY (carrier);
418
        """,
419
        warning=False,
420
    )["urban_central_resistive_heater_mw"].values[0]
421
422
    resistive_heater_output = db.select_dataframe(
423
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
424
            FROM grid.egon_etrago_link
425
            WHERE carrier= 'central_resistive_heater'
426
            AND scn_name IN ('eGon2035')
427
            GROUP BY (carrier);
428
        """,
429
        warning=False,
430
    )["central_resistive_heater_mw"].values[0]
431
432
    e_resistive_heater = (
433
        round(
434
            (resistive_heater_output - resistive_heater_input)
435
            / resistive_heater_input,
436
            2,
437
        )
438
        * 100
439
    )
440
441
    logger.info(f"'resistive heater': {e_resistive_heater} %")
442
443
    # Comparison for solar thermal collectors
444
445
    input_solar_thermal = db.select_dataframe(
446
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
447
            FROM supply.egon_scenario_capacities
448
            WHERE carrier= 'urban_central_solar_thermal_collector'
449
            AND scenario_name IN ('eGon2035')
450
            GROUP BY (carrier);
451
        """,
452
        warning=False,
453
    )["solar_thermal_collector_mw"].values[0]
454
455
    output_solar_thermal = db.select_dataframe(
456
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
457
            FROM grid.egon_etrago_generator
458
            WHERE carrier= 'solar_thermal_collector'
459
            AND scn_name IN ('eGon2035')
460
            GROUP BY (carrier);
461
        """,
462
        warning=False,
463
    )["solar_thermal_collector_mw"].values[0]
464
465
    e_solar_thermal = (
466
        round(
467
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
468
            2,
469
        )
470
        * 100
471
    )
472
    logger.info(f"'solar thermal collector': {e_solar_thermal} %")
473
474
    # Comparison for geothermal
475
476
    input_geo_thermal = db.select_dataframe(
477
        """SELECT carrier,
478
         SUM(capacity::numeric) as Urban_central_geo_thermal_MW
479
            FROM supply.egon_scenario_capacities
480
            WHERE carrier= 'urban_central_geo_thermal'
481
            AND scenario_name IN ('eGon2035')
482
            GROUP BY (carrier);
483
        """,
484
        warning=False,
485
    )["urban_central_geo_thermal_mw"].values[0]
486
487
    output_geo_thermal = db.select_dataframe(
488
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
489
            FROM grid.egon_etrago_generator
490
            WHERE carrier= 'geo_thermal'
491
            AND scn_name IN ('eGon2035')
492
            GROUP BY (carrier);
493
    """,
494
        warning=False,
495
    )["geo_thermal_mw"].values[0]
496
497
    e_geo_thermal = (
498
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
499
        * 100
500
    )
501
    logger.info(f"'geothermal': {e_geo_thermal} %")
502
503
504
def residential_electricity_annual_sum(rtol=1e-5):
505
    """Sanity check for dataset electricity_demand_timeseries :
506
    Demand_Building_Assignment
507
508
    Aggregate the annual demand of all census cells at NUTS3 to compare
509
    with initial scaling parameters from DemandRegio.
510
    """
511
512
    df_nuts3_annual_sum = db.select_dataframe(
513
        sql="""
514
        SELECT dr.nuts3, dr.scenario, dr.demand_regio_sum, profiles.profile_sum
515
        FROM (
516
            SELECT scenario, SUM(demand) AS profile_sum, vg250_nuts3
517
            FROM demand.egon_demandregio_zensus_electricity AS egon,
518
             boundaries.egon_map_zensus_vg250 AS boundaries
519
            Where egon.zensus_population_id = boundaries.zensus_population_id
520
            AND sector = 'residential'
521
            GROUP BY vg250_nuts3, scenario
522
            ) AS profiles
523
        JOIN (
524
            SELECT nuts3, scenario, sum(demand) AS demand_regio_sum
525
            FROM demand.egon_demandregio_hh
526
            GROUP BY year, scenario, nuts3
527
              ) AS dr
528
        ON profiles.vg250_nuts3 = dr.nuts3 and profiles.scenario  = dr.scenario
529
        """
530
    )
531
532
    np.testing.assert_allclose(
533
        actual=df_nuts3_annual_sum["profile_sum"],
534
        desired=df_nuts3_annual_sum["demand_regio_sum"],
535
        rtol=rtol,
536
        verbose=False,
537
    )
538
539
    logger.info(
540
        "Aggregated annual residential electricity demand"
541
        " matches with DemandRegio at NUTS-3."
542
    )
543
544
545
def residential_electricity_hh_refinement(rtol=1e-5):
546
    """Sanity check for dataset electricity_demand_timeseries :
547
    Household Demands
548
549
    Check sum of aggregated household types after refinement method
550
    was applied and compare it to the original census values."""
551
552
    df_refinement = db.select_dataframe(
553
        sql="""
554
        SELECT refined.nuts3, refined.characteristics_code,
555
                refined.sum_refined::int, census.sum_census::int
556
        FROM(
557
            SELECT nuts3, characteristics_code, SUM(hh_10types) as sum_refined
558
            FROM society.egon_destatis_zensus_household_per_ha_refined
559
            GROUP BY nuts3, characteristics_code)
560
            AS refined
561
        JOIN(
562
            SELECT t.nuts3, t.characteristics_code, sum(orig) as sum_census
563
            FROM(
564
                SELECT nuts3, cell_id, characteristics_code,
565
                        sum(DISTINCT(hh_5types))as orig
566
                FROM society.egon_destatis_zensus_household_per_ha_refined
567
                GROUP BY cell_id, characteristics_code, nuts3) AS t
568
            GROUP BY t.nuts3, t.characteristics_code    ) AS census
569
        ON refined.nuts3 = census.nuts3
570
        AND refined.characteristics_code = census.characteristics_code
571
    """
572
    )
573
574
    np.testing.assert_allclose(
575
        actual=df_refinement["sum_refined"],
576
        desired=df_refinement["sum_census"],
577
        rtol=rtol,
578
        verbose=False,
579
    )
580
581
    logger.info("All Aggregated household types match at NUTS-3.")
582
583
584
def cts_electricity_demand_share(rtol=1e-5):
585
    """Sanity check for dataset electricity_demand_timeseries :
586
    CtsBuildings
587
588
    Check sum of aggregated cts electricity demand share which equals to one
589
    for every substation as the substation profile is linearly disaggregated
590
    to all buildings."""
591
592
    with db.session_scope() as session:
593
        cells_query = session.query(EgonCtsElectricityDemandBuildingShare)
594
595
    df_demand_share = pd.read_sql(
596
        cells_query.statement, cells_query.session.bind, index_col=None
597
    )
598
599
    np.testing.assert_allclose(
600
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
601
            "profile_share"
602
        ].sum(),
603
        desired=1,
604
        rtol=rtol,
605
        verbose=False,
606
    )
607
608
    logger.info("The aggregated demand shares equal to one!.")
609
610
611
def cts_heat_demand_share(rtol=1e-5):
612
    """Sanity check for dataset electricity_demand_timeseries
613
    : CtsBuildings
614
615
    Check sum of aggregated cts heat demand share which equals to one
616
    for every substation as the substation profile is linearly disaggregated
617
    to all buildings."""
618
619
    with db.session_scope() as session:
620
        cells_query = session.query(EgonCtsHeatDemandBuildingShare)
621
622
    df_demand_share = pd.read_sql(
623
        cells_query.statement, cells_query.session.bind, index_col=None
624
    )
625
626
    np.testing.assert_allclose(
627
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
628
            "profile_share"
629
        ].sum(),
630
        desired=1,
631
        rtol=rtol,
632
        verbose=False,
633
    )
634
635
    logger.info("The aggregated demand shares equal to one!.")
636
637