sanity_check_CH4_stores()   A
last analyzed

Complexity

Conditions 3

Size

Total Lines 59
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 20
dl 0
loc 59
rs 9.4
c 0
b 0
f 0
cc 3
nop 1

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, @nailend, @nesnoj, @khelfen
7
"""
8
9
from math import isclose
10
from pathlib import Path
11
import ast
12
13
from sqlalchemy import Numeric
14
from sqlalchemy.sql import and_, cast, func, or_
15
import matplotlib.pyplot as plt
16
import numpy as np
17
import pandas as pd
18
import seaborn as sns
19
20
from egon.data import config, db, logger
21
from egon.data.datasets import Dataset
22
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
23
    EgonCtsElectricityDemandBuildingShare,
24
    EgonCtsHeatDemandBuildingShare,
25
)
26
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (  # noqa: E501
27
    EgonEvCountMunicipality,
28
    EgonEvCountMvGridDistrict,
29
    EgonEvCountRegistrationDistrict,
30
    EgonEvMvGridDistrict,
31
    EgonEvPool,
32
    EgonEvTrip,
33
)
34
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
35
    DATASET_CFG,
36
    read_simbev_metadata_file,
37
)
38
from egon.data.datasets.etrago_setup import (
39
    EgonPfHvLink,
40
    EgonPfHvLinkTimeseries,
41
    EgonPfHvLoad,
42
    EgonPfHvLoadTimeseries,
43
    EgonPfHvStore,
44
    EgonPfHvStoreTimeseries,
45
)
46
from egon.data.datasets.gas_grid import (
47
    define_gas_buses_abroad,
48
    define_gas_nodes_list,
49
    define_gas_pipeline_list,
50
)
51
from egon.data.datasets.gas_neighbours.eGon2035 import (
52
    calc_capacities,
53
    calc_ch4_storage_capacities,
54
    calc_global_ch4_demand,
55
    calc_global_power_to_h2_demand,
56
    calculate_ch4_grid_capacities,
57
    import_ch4_demandTS,
58
)
59
from egon.data.datasets.hydrogen_etrago.storage import (
60
    calculate_and_map_saltcavern_storage_potential,
61
)
62
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
63
    PV_CAP_PER_SQ_M,
64
    ROOF_FACTOR,
65
    SCENARIOS,
66
    load_building_data,
67
    scenario_data,
68
)
69
from egon.data.datasets.pypsaeur import read_network
70
from egon.data.datasets.scenario_parameters import get_sector_parameters
71
from egon.data.datasets.storages.home_batteries import get_cbat_pbat_ratio
72
import egon.data
73
74
TESTMODE_OFF = (
75
    config.settings()["egon-data"]["--dataset-boundary"] == "Everything"
76
)
77
78
79
def etrago_eGon2035_electricity():
80
    """Execute basic sanity checks.
81
82
    Returns print statements as sanity checks for the electricity sector in
83
    the eGon2035 scenario.
84
85
    Parameters
86
    ----------
87
    None
88
89
    Returns
90
    -------
91
    None
92
    """
93
94
    scn = "eGon2035"
95
96
    # Section to check generator capacities
97
    logger.info(f"Sanity checks for scenario {scn}")
98
    logger.info(
99
        "For German electricity generators the following deviations between "
100
        "the inputs and outputs can be observed:"
101
    )
102
103
    carriers_electricity = [
104
        "others",
105
        "reservoir",
106
        "run_of_river",
107
        "oil",
108
        "wind_onshore",
109
        "wind_offshore",
110
        "solar",
111
        "solar_rooftop",
112
        "biomass",
113
    ]
114
115
    for carrier in carriers_electricity:
116
117
        if carrier == "biomass":
118
            sum_output = db.select_dataframe(
119
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
120
                    FROM grid.egon_etrago_generator
121
                    WHERE bus IN (
122
                        SELECT bus_id FROM grid.egon_etrago_bus
123
                        WHERE scn_name = 'eGon2035'
124
                        AND country = 'DE')
125
                    AND carrier IN ('biomass', 'industrial_biomass_CHP',
126
                    'central_biomass_CHP')
127
                    GROUP BY (scn_name);
128
                """,
129
                warning=False,
130
            )
131
132
        else:
133
            sum_output = db.select_dataframe(
134
                f"""SELECT scn_name,
135
                 SUM(p_nom::numeric) as output_capacity_mw
136
                         FROM grid.egon_etrago_generator
137
                         WHERE scn_name = '{scn}'
138
                         AND carrier IN ('{carrier}')
139
                         AND bus IN
140
                             (SELECT bus_id
141
                               FROM grid.egon_etrago_bus
142
                               WHERE scn_name = 'eGon2035'
143
                               AND country = 'DE')
144
                         GROUP BY (scn_name);
145
                    """,
146
                warning=False,
147
            )
148
149
        sum_input = db.select_dataframe(
150
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
151
                     FROM supply.egon_scenario_capacities
152
                     WHERE carrier= '{carrier}'
153
                     AND scenario_name ='{scn}'
154
                     GROUP BY (carrier);
155
                """,
156
            warning=False,
157
        )
158
159 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
160
            sum_output.output_capacity_mw.sum() == 0
161
            and sum_input.input_capacity_mw.sum() == 0
162
        ):
163
            logger.info(
164
                f"No capacity for carrier '{carrier}' needed to be"
165
                f" distributed. Everything is fine"
166
            )
167
168
        elif (
169
            sum_input.input_capacity_mw.sum() > 0
170
            and sum_output.output_capacity_mw.sum() == 0
171
        ):
172
            logger.info(
173
                f"Error: Capacity for carrier '{carrier}' was not distributed "
174
                f"at all!"
175
            )
176
177
        elif (
178
            sum_output.output_capacity_mw.sum() > 0
179
            and sum_input.input_capacity_mw.sum() == 0
180
        ):
181
            logger.info(
182
                f"Error: Eventhough no input capacity was provided for carrier"
183
                f"'{carrier}' a capacity got distributed!"
184
            )
185
186
        else:
187
            sum_input["error"] = (
188
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
189
                / sum_input.input_capacity_mw
190
            ) * 100
191
            g = sum_input["error"].values[0]
192
193
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
194
195
    # Section to check storage units
196
197
    logger.info(f"Sanity checks for scenario {scn}")
198
    logger.info(
199
        "For German electrical storage units the following deviations between"
200
        "the inputs and outputs can be observed:"
201
    )
202
203
    carriers_electricity = ["pumped_hydro"]
204
205
    for carrier in carriers_electricity:
206
207
        sum_output = db.select_dataframe(
208
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
209
                         FROM grid.egon_etrago_storage
210
                         WHERE scn_name = '{scn}'
211
                         AND carrier IN ('{carrier}')
212
                         AND bus IN
213
                             (SELECT bus_id
214
                               FROM grid.egon_etrago_bus
215
                               WHERE scn_name = 'eGon2035'
216
                               AND country = 'DE')
217
                         GROUP BY (scn_name);
218
                    """,
219
            warning=False,
220
        )
221
222
        sum_input = db.select_dataframe(
223
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
224
                     FROM supply.egon_scenario_capacities
225
                     WHERE carrier= '{carrier}'
226
                     AND scenario_name ='{scn}'
227
                     GROUP BY (carrier);
228
                """,
229
            warning=False,
230
        )
231
232 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
233
            sum_output.output_capacity_mw.sum() == 0
234
            and sum_input.input_capacity_mw.sum() == 0
235
        ):
236
            print(
237
                f"No capacity for carrier '{carrier}' needed to be "
238
                f"distributed. Everything is fine"
239
            )
240
241
        elif (
242
            sum_input.input_capacity_mw.sum() > 0
243
            and sum_output.output_capacity_mw.sum() == 0
244
        ):
245
            print(
246
                f"Error: Capacity for carrier '{carrier}' was not distributed"
247
                f" at all!"
248
            )
249
250
        elif (
251
            sum_output.output_capacity_mw.sum() > 0
252
            and sum_input.input_capacity_mw.sum() == 0
253
        ):
254
            print(
255
                f"Error: Eventhough no input capacity was provided for carrier"
256
                f" '{carrier}' a capacity got distributed!"
257
            )
258
259
        else:
260
            sum_input["error"] = (
261
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
262
                / sum_input.input_capacity_mw
263
            ) * 100
264
            g = sum_input["error"].values[0]
265
266
            print(f"{carrier}: " + str(round(g, 2)) + " %")
267
268
    # Section to check loads
269
270
    print(
271
        "For German electricity loads the following deviations between the"
272
        " input and output can be observed:"
273
    )
274
275
    output_demand = db.select_dataframe(
276
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
277
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
278
            FROM grid.egon_etrago_load a
279
            JOIN grid.egon_etrago_load_timeseries b
280
            ON (a.load_id = b.load_id)
281
            JOIN grid.egon_etrago_bus c
282
            ON (a.bus=c.bus_id)
283
            AND b.scn_name = 'eGon2035'
284
            AND a.scn_name = 'eGon2035'
285
            AND a.carrier = 'AC'
286
            AND c.scn_name= 'eGon2035'
287
            AND c.country='DE'
288
            GROUP BY (a.scn_name, a.carrier);
289
290
    """,
291
        warning=False,
292
    )["load_twh"].values[0]
293
294
    input_cts_ind = db.select_dataframe(
295
        """SELECT scenario,
296
         SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
297
            FROM demand.egon_demandregio_cts_ind
298
            WHERE scenario= 'eGon2035'
299
            AND year IN ('2035')
300
            GROUP BY (scenario);
301
302
        """,
303
        warning=False,
304
    )["demand_mw_regio_cts_ind"].values[0]
305
306
    input_hh = db.select_dataframe(
307
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
308
            FROM demand.egon_demandregio_hh
309
            WHERE scenario= 'eGon2035'
310
            AND year IN ('2035')
311
            GROUP BY (scenario);
312
        """,
313
        warning=False,
314
    )["demand_mw_regio_hh"].values[0]
315
316
    input_demand = input_hh + input_cts_ind
317
318
    e = round((output_demand - input_demand) / input_demand, 2) * 100
319
320
    print(f"electricity demand: {e} %")
321
322
323
def etrago_eGon2035_heat():
324
    """Execute basic sanity checks.
325
326
    Returns print statements as sanity checks for the heat sector in
327
    the eGon2035 scenario.
328
329
    Parameters
330
    ----------
331
    None
332
333
    Returns
334
    -------
335
    None
336
    """
337
338
    # Check input and output values for the carriers "others",
339
    # "reservoir", "run_of_river" and "oil"
340
341
    scn = "eGon2035"
342
343
    # Section to check generator capacities
344
    print(f"Sanity checks for scenario {scn}")
345
    print(
346
        "For German heat demands the following deviations between the inputs"
347
        " and outputs can be observed:"
348
    )
349
350
    # Sanity checks for heat demand
351
352
    output_heat_demand = db.select_dataframe(
353
        """SELECT a.scn_name,
354
          (SUM(
355
          (SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
356
            FROM grid.egon_etrago_load a
357
            JOIN grid.egon_etrago_load_timeseries b
358
            ON (a.load_id = b.load_id)
359
            JOIN grid.egon_etrago_bus c
360
            ON (a.bus=c.bus_id)
361
            AND b.scn_name = 'eGon2035'
362
            AND a.scn_name = 'eGon2035'
363
            AND c.scn_name= 'eGon2035'
364
            AND c.country='DE'
365
            AND a.carrier IN ('rural_heat', 'central_heat')
366
            GROUP BY (a.scn_name);
367
        """,
368
        warning=False,
369
    )["load_twh"].values[0]
370
371
    input_heat_demand = db.select_dataframe(
372
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
373
            FROM demand.egon_peta_heat
374
            WHERE scenario= 'eGon2035'
375
            GROUP BY (scenario);
376
        """,
377
        warning=False,
378
    )["demand_mw_peta_heat"].values[0]
379
380
    e_demand = (
381
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
382
        * 100
383
    )
384
385
    logger.info(f"heat demand: {e_demand} %")
386
387
    # Sanity checks for heat supply
388
389
    logger.info(
390
        "For German heat supplies the following deviations between the inputs "
391
        "and outputs can be observed:"
392
    )
393
394
    # Comparison for central heat pumps
395
    heat_pump_input = db.select_dataframe(
396
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
397
            FROM supply.egon_scenario_capacities
398
            WHERE carrier= 'urban_central_heat_pump'
399
            AND scenario_name IN ('eGon2035')
400
            GROUP BY (carrier);
401
        """,
402
        warning=False,
403
    )["urban_central_heat_pump_mw"].values[0]
404
405
    heat_pump_output = db.select_dataframe(
406
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
407
            FROM grid.egon_etrago_link
408
            WHERE carrier= 'central_heat_pump'
409
            AND scn_name IN ('eGon2035')
410
            GROUP BY (carrier);
411
    """,
412
        warning=False,
413
    )["central_heat_pump_mw"].values[0]
414
415
    e_heat_pump = (
416
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
417
    )
418
419
    logger.info(f"'central_heat_pump': {e_heat_pump} % ")
420
421
    # Comparison for residential heat pumps
422
423
    input_residential_heat_pump = db.select_dataframe(
424
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
425
            FROM supply.egon_scenario_capacities
426
            WHERE carrier= 'residential_rural_heat_pump'
427
            AND scenario_name IN ('eGon2035')
428
            GROUP BY (carrier);
429
        """,
430
        warning=False,
431
    )["residential_heat_pump_mw"].values[0]
432
433
    output_residential_heat_pump = db.select_dataframe(
434
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
435
            FROM grid.egon_etrago_link
436
            WHERE carrier= 'rural_heat_pump'
437
            AND scn_name IN ('eGon2035')
438
            GROUP BY (carrier);
439
    """,
440
        warning=False,
441
    )["rural_heat_pump_mw"].values[0]
442
443
    e_residential_heat_pump = (
444
        round(
445
            (output_residential_heat_pump - input_residential_heat_pump)
446
            / input_residential_heat_pump,
447
            2,
448
        )
449
        * 100
450
    )
451
    logger.info(f"'residential heat pumps': {e_residential_heat_pump} %")
452
453
    # Comparison for resistive heater
454
    resistive_heater_input = db.select_dataframe(
455
        """SELECT carrier,
456
         SUM(capacity::numeric) as Urban_central_resistive_heater_MW
457
            FROM supply.egon_scenario_capacities
458
            WHERE carrier= 'urban_central_resistive_heater'
459
            AND scenario_name IN ('eGon2035')
460
            GROUP BY (carrier);
461
        """,
462
        warning=False,
463
    )["urban_central_resistive_heater_mw"].values[0]
464
465
    resistive_heater_output = db.select_dataframe(
466
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
467
            FROM grid.egon_etrago_link
468
            WHERE carrier= 'central_resistive_heater'
469
            AND scn_name IN ('eGon2035')
470
            GROUP BY (carrier);
471
        """,
472
        warning=False,
473
    )["central_resistive_heater_mw"].values[0]
474
475
    e_resistive_heater = (
476
        round(
477
            (resistive_heater_output - resistive_heater_input)
478
            / resistive_heater_input,
479
            2,
480
        )
481
        * 100
482
    )
483
484
    logger.info(f"'resistive heater': {e_resistive_heater} %")
485
486
    # Comparison for solar thermal collectors
487
488
    input_solar_thermal = db.select_dataframe(
489
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
490
            FROM supply.egon_scenario_capacities
491
            WHERE carrier= 'urban_central_solar_thermal_collector'
492
            AND scenario_name IN ('eGon2035')
493
            GROUP BY (carrier);
494
        """,
495
        warning=False,
496
    )["solar_thermal_collector_mw"].values[0]
497
498
    output_solar_thermal = db.select_dataframe(
499
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
500
            FROM grid.egon_etrago_generator
501
            WHERE carrier= 'solar_thermal_collector'
502
            AND scn_name IN ('eGon2035')
503
            GROUP BY (carrier);
504
        """,
505
        warning=False,
506
    )["solar_thermal_collector_mw"].values[0]
507
508
    e_solar_thermal = (
509
        round(
510
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
511
            2,
512
        )
513
        * 100
514
    )
515
    logger.info(f"'solar thermal collector': {e_solar_thermal} %")
516
517
    # Comparison for geothermal
518
519
    input_geo_thermal = db.select_dataframe(
520
        """SELECT carrier,
521
         SUM(capacity::numeric) as Urban_central_geo_thermal_MW
522
            FROM supply.egon_scenario_capacities
523
            WHERE carrier= 'urban_central_geo_thermal'
524
            AND scenario_name IN ('eGon2035')
525
            GROUP BY (carrier);
526
        """,
527
        warning=False,
528
    )["urban_central_geo_thermal_mw"].values[0]
529
530
    output_geo_thermal = db.select_dataframe(
531
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
532
            FROM grid.egon_etrago_generator
533
            WHERE carrier= 'geo_thermal'
534
            AND scn_name IN ('eGon2035')
535
            GROUP BY (carrier);
536
    """,
537
        warning=False,
538
    )["geo_thermal_mw"].values[0]
539
540
    e_geo_thermal = (
541
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
542
        * 100
543
    )
544
    logger.info(f"'geothermal': {e_geo_thermal} %")
545
546
547
def residential_electricity_annual_sum(rtol=0.005):
548
    """Sanity check for dataset electricity_demand_timeseries :
549
    Demand_Building_Assignment
550
551
    Aggregate the annual demand of all census cells at NUTS3 to compare
552
    with initial scaling parameters from DemandRegio.
553
    """
554
555
    df_nuts3_annual_sum = db.select_dataframe(
556
        sql="""
557
        SELECT dr.nuts3, dr.scenario, dr.demand_regio_sum, profiles.profile_sum
558
        FROM (
559
            SELECT scenario, SUM(demand) AS profile_sum, vg250_nuts3
560
            FROM demand.egon_demandregio_zensus_electricity AS egon,
561
             boundaries.egon_map_zensus_vg250 AS boundaries
562
            Where egon.zensus_population_id = boundaries.zensus_population_id
563
            AND sector = 'residential'
564
            GROUP BY vg250_nuts3, scenario
565
            ) AS profiles
566
        JOIN (
567
            SELECT nuts3, scenario, sum(demand) AS demand_regio_sum
568
            FROM demand.egon_demandregio_hh
569
            GROUP BY year, scenario, nuts3
570
              ) AS dr
571
        ON profiles.vg250_nuts3 = dr.nuts3 and profiles.scenario  = dr.scenario
572
        """
573
    )
574
575
    np.testing.assert_allclose(
576
        actual=df_nuts3_annual_sum["profile_sum"],
577
        desired=df_nuts3_annual_sum["demand_regio_sum"],
578
        rtol=rtol,
579
        verbose=False,
580
    )
581
582
    logger.info(
583
        "Aggregated annual residential electricity demand"
584
        " matches with DemandRegio at NUTS-3."
585
    )
586
587
588
def residential_electricity_hh_refinement(rtol=1e-5):
589
    """Sanity check for dataset electricity_demand_timeseries :
590
    Household Demands
591
592
    Check sum of aggregated household types after refinement method
593
    was applied and compare it to the original census values."""
594
595
    df_refinement = db.select_dataframe(
596
        sql="""
597
        SELECT refined.nuts3, refined.characteristics_code,
598
                refined.sum_refined::int, census.sum_census::int
599
        FROM(
600
            SELECT nuts3, characteristics_code, SUM(hh_10types) as sum_refined
601
            FROM society.egon_destatis_zensus_household_per_ha_refined
602
            GROUP BY nuts3, characteristics_code)
603
            AS refined
604
        JOIN(
605
            SELECT t.nuts3, t.characteristics_code, sum(orig) as sum_census
606
            FROM(
607
                SELECT nuts3, cell_id, characteristics_code,
608
                        sum(DISTINCT(hh_5types))as orig
609
                FROM society.egon_destatis_zensus_household_per_ha_refined
610
                GROUP BY cell_id, characteristics_code, nuts3) AS t
611
            GROUP BY t.nuts3, t.characteristics_code    ) AS census
612
        ON refined.nuts3 = census.nuts3
613
        AND refined.characteristics_code = census.characteristics_code
614
    """
615
    )
616
617
    np.testing.assert_allclose(
618
        actual=df_refinement["sum_refined"],
619
        desired=df_refinement["sum_census"],
620
        rtol=rtol,
621
        verbose=False,
622
    )
623
624
    logger.info("All Aggregated household types match at NUTS-3.")
625
626
627
def cts_electricity_demand_share(rtol=0.005):
628
    """Sanity check for dataset electricity_demand_timeseries :
629
    CtsBuildings
630
631
    Check sum of aggregated cts electricity demand share which equals to one
632
    for every substation as the substation profile is linearly disaggregated
633
    to all buildings."""
634
635
    with db.session_scope() as session:
636
        cells_query = session.query(EgonCtsElectricityDemandBuildingShare)
637
638
    df_demand_share = pd.read_sql(
639
        cells_query.statement, cells_query.session.bind, index_col=None
640
    )
641
642
    np.testing.assert_allclose(
643
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
644
            "profile_share"
645
        ].sum(),
646
        desired=1,
647
        rtol=rtol,
648
        verbose=False,
649
    )
650
651
    logger.info("The aggregated demand shares equal to one!.")
652
653
654
def cts_heat_demand_share(rtol=0.005):
655
    """Sanity check for dataset electricity_demand_timeseries
656
    : CtsBuildings
657
658
    Check sum of aggregated cts heat demand share which equals to one
659
    for every substation as the substation profile is linearly disaggregated
660
    to all buildings."""
661
662
    with db.session_scope() as session:
663
        cells_query = session.query(EgonCtsHeatDemandBuildingShare)
664
665
    df_demand_share = pd.read_sql(
666
        cells_query.statement, cells_query.session.bind, index_col=None
667
    )
668
669
    np.testing.assert_allclose(
670
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
671
            "profile_share"
672
        ].sum(),
673
        desired=1,
674
        rtol=rtol,
675
        verbose=False,
676
    )
677
678
    logger.info("The aggregated demand shares equal to one!.")
679
680
681
def sanitycheck_pv_rooftop_buildings():
682
    def egon_power_plants_pv_roof_building():
683
        sql = """
684
        SELECT *
685
        FROM supply.egon_power_plants_pv_roof_building
686
        """
687
688
        return db.select_dataframe(sql, index_col="index")
689
690
    pv_roof_df = egon_power_plants_pv_roof_building()
691
692
    valid_buildings_gdf = load_building_data()
693
694
    valid_buildings_gdf = valid_buildings_gdf.assign(
695
        bus_id=valid_buildings_gdf.bus_id.astype(int),
696
        overlay_id=valid_buildings_gdf.overlay_id.astype(int),
697
        max_cap=valid_buildings_gdf.building_area.multiply(
698
            ROOF_FACTOR * PV_CAP_PER_SQ_M
699
        ),
700
    )
701
702
    merge_df = pv_roof_df.merge(
703
        valid_buildings_gdf[["building_area"]],
704
        how="left",
705
        left_on="building_id",
706
        right_index=True,
707
    )
708
709
    assert (
710
        len(merge_df.loc[merge_df.building_area.isna()]) == 0
711
    ), f"{len(merge_df.loc[merge_df.building_area.isna()])} != 0"
712
713
    scenarios = ["status_quo", "eGon2035"]
714
715
    base_path = Path(egon.data.__path__[0]).resolve()
716
717
    res_dir = base_path / "sanity_checks"
718
719
    res_dir.mkdir(parents=True, exist_ok=True)
720
721
    for scenario in scenarios:
722
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 8))
723
724
        scenario_df = merge_df.loc[merge_df.scenario == scenario]
725
726
        logger.info(
727
            scenario + " Capacity:\n" + str(scenario_df.capacity.describe())
728
        )
729
730
        small_gens_df = scenario_df.loc[scenario_df.capacity < 100]
731
732
        sns.histplot(data=small_gens_df, x="capacity", ax=ax1).set_title(
733
            scenario
734
        )
735
736
        sns.scatterplot(
737
            data=small_gens_df, x="capacity", y="building_area", ax=ax2
738
        ).set_title(scenario)
739
740
        plt.tight_layout()
741
742
        plt.savefig(
743
            res_dir / f"{scenario}_pv_rooftop_distribution.png",
744
            bbox_inches="tight",
745
        )
746
747
    for scenario in SCENARIOS:
748
        if scenario == "eGon2035":
749
            assert isclose(
750
                scenario_data(scenario=scenario).capacity.sum(),
751
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
752
                rel_tol=1e-02,
753
            ), (
754
                f"{scenario_data(scenario=scenario).capacity.sum()} != "
755
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
756
            )
757
        elif scenario == "eGon100RE":
758
            sources = config.datasets()["solar_rooftop"]["sources"]
759
760
            target = db.select_dataframe(
761
                f"""
762
                SELECT capacity
763
                FROM {sources['scenario_capacities']['schema']}.
764
                {sources['scenario_capacities']['table']} a
765
                WHERE carrier = 'solar_rooftop'
766
                AND scenario_name = '{scenario}'
767
                """
768
            ).capacity[0]
769
770
            dataset = config.settings()["egon-data"]["--dataset-boundary"]
771
772 View Code Duplication
            if dataset == "Schleswig-Holstein":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
773
                sources = config.datasets()["scenario_input"]["sources"]
774
775
                path = Path(
776
                    f"./data_bundle_egon_data/nep2035_version2021/"
777
                    f"{sources['eGon2035']['capacities']}"
778
                ).resolve()
779
780
                total_2035 = (
781
                    pd.read_excel(
782
                        path,
783
                        sheet_name="1.Entwurf_NEP2035_V2021",
784
                        index_col="Unnamed: 0",
785
                    ).at["PV (Aufdach)", "Summe"]
786
                    * 1000
787
                )
788
                sh_2035 = scenario_data(scenario="eGon2035").capacity.sum()
789
790
                share = sh_2035 / total_2035
791
792
                target *= share
793
794
            assert isclose(
795
                target,
796
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
797
                rel_tol=1e-02,
798
            ), (
799
                f"{target} != "
800
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
801
            )
802
        else:
803
            raise ValueError(f"Scenario {scenario} is not valid.")
804
805
806
def sanitycheck_emobility_mit():
807
    """Execute sanity checks for eMobility: motorized individual travel
808
809
    Checks data integrity for scenarios using assertions:
810
811
    1. Allocated EV numbers and EVs allocated to grid districts
812
    2. Trip data (original inout data from simBEV)
813
    3. Model data in eTraGo PF tables (grid.egon_etrago_*)
814
815
    Parameters
816
    ----------
817
    None
818
819
    Returns
820
    -------
821
    None
822
    """
823
824
    def check_ev_allocation():
825
        # Get target number for scenario
826
        ev_count_target = scenario_variation_parameters["ev_count"]
827
        print(f"  Target count: {str(ev_count_target)}")
828
829
        # Get allocated numbers
830
        ev_counts_dict = {}
831
        with db.session_scope() as session:
832
            for table, level in zip(
833
                [
834
                    EgonEvCountMvGridDistrict,
835
                    EgonEvCountMunicipality,
836
                    EgonEvCountRegistrationDistrict,
837
                ],
838
                ["Grid District", "Municipality", "Registration District"],
839
            ):
840
                query = session.query(
841
                    func.sum(
842
                        table.bev_mini
843
                        + table.bev_medium
844
                        + table.bev_luxury
845
                        + table.phev_mini
846
                        + table.phev_medium
847
                        + table.phev_luxury
848
                    ).label("ev_count")
849
                ).filter(
850
                    table.scenario == scenario_name,
851
                    table.scenario_variation == scenario_var_name,
852
                )
853
854
                ev_counts = pd.read_sql(
855
                    query.statement, query.session.bind, index_col=None
856
                )
857
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
858
                print(
859
                    f"    Count table: Total count for level {level} "
860
                    f"(table: {table.__table__}): "
861
                    f"{str(ev_counts_dict[level])}"
862
                )
863
864
        # Compare with scenario target (only if not in testmode)
865
        if TESTMODE_OFF:
866
            for level, count in ev_counts_dict.items():
867
                np.testing.assert_allclose(
868
                    count,
869
                    ev_count_target,
870
                    rtol=0.0001 if ev_count_target > 1e6 else 0.02,
871
                    err_msg=f"EV numbers in {level} seems to be flawed.",
872
                )
873
        else:
874
            print("    Testmode is on, skipping sanity check...")
875
876
        # Get allocated EVs in grid districts
877
        with db.session_scope() as session:
878
            query = session.query(
879
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
880
                    "ev_count"
881
                ),
882
            ).filter(
883
                EgonEvMvGridDistrict.scenario == scenario_name,
884
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
885
            )
886
        ev_count_alloc = (
887
            pd.read_sql(query.statement, query.session.bind, index_col=None)
888
            .iloc[0]
889
            .ev_count
890
        )
891
        print(
892
            f"    EVs allocated to Grid Districts "
893
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
894
            f"{str(ev_count_alloc)}"
895
        )
896
897
        # Compare with scenario target (only if not in testmode)
898
        if TESTMODE_OFF:
899
            np.testing.assert_allclose(
900
                ev_count_alloc,
901
                ev_count_target,
902
                rtol=0.0001 if ev_count_target > 1e6 else 0.02,
903
                err_msg=(
904
                    "EV numbers allocated to Grid Districts seems to be "
905
                    "flawed."
906
                ),
907
            )
908
        else:
909
            print("    Testmode is on, skipping sanity check...")
910
911
        return ev_count_alloc
912
913
    def check_trip_data():
914
        # Check if trips start at timestep 0 and have a max. of 35040 steps
915
        # (8760h in 15min steps)
916
        print("  Checking timeranges...")
917
        with db.session_scope() as session:
918
            query = session.query(
919
                func.count(EgonEvTrip.event_id).label("cnt")
920
            ).filter(
921
                or_(
922
                    and_(
923
                        EgonEvTrip.park_start > 0,
924
                        EgonEvTrip.simbev_event_id == 0,
925
                    ),
926
                    EgonEvTrip.park_end
927
                    > (60 / int(meta_run_config.stepsize)) * 8760,
928
                ),
929
                EgonEvTrip.scenario == scenario_name,
930
            )
931
        invalid_trips = pd.read_sql(
932
            query.statement, query.session.bind, index_col=None
933
        )
934
        np.testing.assert_equal(
935
            invalid_trips.iloc[0].cnt,
936
            0,
937
            err_msg=(
938
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
939
                f"{EgonEvTrip.__table__} have invalid timesteps."
940
            ),
941
        )
942
943
        # Check if charging demand can be covered by available charging energy
944
        # while parking
945
        print("  Compare charging demand with available power...")
946
        with db.session_scope() as session:
947
            query = session.query(
948
                func.count(EgonEvTrip.event_id).label("cnt")
949
            ).filter(
950
                func.round(
951
                    cast(
952
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
953
                        * EgonEvTrip.charging_capacity_nominal
954
                        * (int(meta_run_config.stepsize) / 60),
955
                        Numeric,
956
                    ),
957
                    3,
958
                )
959
                < cast(EgonEvTrip.charging_demand, Numeric),
960
                EgonEvTrip.scenario == scenario_name,
961
            )
962
        invalid_trips = pd.read_sql(
963
            query.statement, query.session.bind, index_col=None
964
        )
965
        np.testing.assert_equal(
966
            invalid_trips.iloc[0].cnt,
967
            0,
968
            err_msg=(
969
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
970
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
971
                f"covered by available charging power."
972
            ),
973
        )
974
975
    def check_model_data():
976
        # Check if model components were fully created
977
        print("  Check if all model components were created...")
978
        # Get MVGDs which got EV allocated
979
        with db.session_scope() as session:
980
            query = (
981
                session.query(
982
                    EgonEvMvGridDistrict.bus_id,
983
                )
984
                .filter(
985
                    EgonEvMvGridDistrict.scenario == scenario_name,
986
                    EgonEvMvGridDistrict.scenario_variation
987
                    == scenario_var_name,
988
                )
989
                .group_by(EgonEvMvGridDistrict.bus_id)
990
            )
991
        mvgds_with_ev = (
992
            pd.read_sql(query.statement, query.session.bind, index_col=None)
993
            .bus_id.sort_values()
994
            .to_list()
995
        )
996
997
        # Load model components
998
        with db.session_scope() as session:
999
            query = (
1000
                session.query(
1001
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
1002
                    EgonPfHvLoad.bus.label("emob_bus_id"),
1003
                    EgonPfHvLoad.load_id.label("load_id"),
1004
                    EgonPfHvStore.store_id.label("store_id"),
1005
                )
1006
                .select_from(EgonPfHvLoad, EgonPfHvStore)
1007
                .join(
1008
                    EgonPfHvLoadTimeseries,
1009
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1010
                )
1011
                .join(
1012
                    EgonPfHvStoreTimeseries,
1013
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
1014
                )
1015
                .filter(
1016
                    EgonPfHvLoad.carrier == "land_transport_EV",
1017
                    EgonPfHvLoad.scn_name == scenario_name,
1018
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
1019
                    EgonPfHvStore.carrier == "battery_storage",
1020
                    EgonPfHvStore.scn_name == scenario_name,
1021
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
1022
                    EgonPfHvLink.scn_name == scenario_name,
1023
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
1024
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
1025
                )
1026
            )
1027
        model_components = pd.read_sql(
1028
            query.statement, query.session.bind, index_col=None
1029
        )
1030
1031
        # Check number of buses with model components connected
1032
        mvgd_buses_with_ev = model_components.loc[
1033
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
1034
        ]
1035
        np.testing.assert_equal(
1036
            len(mvgds_with_ev),
1037
            len(mvgd_buses_with_ev),
1038
            err_msg=(
1039
                f"Number of Grid Districts with connected model components "
1040
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
1041
                f"differ from number of Grid Districts that got EVs "
1042
                f"allocated ({len(mvgds_with_ev)} in table "
1043
                f"{EgonEvMvGridDistrict.__table__})."
1044
            ),
1045
        )
1046
1047
        # Check if all required components exist (if no id is NaN)
1048
        np.testing.assert_equal(
1049
            model_components.drop_duplicates().isna().any().any(),
1050
            False,
1051
            err_msg=(
1052
                f"Some components are missing (see True values): "
1053
                f"{model_components.drop_duplicates().isna().any()}"
1054
            ),
1055
        )
1056
1057
        # Get all model timeseries
1058
        print("  Loading model timeseries...")
1059
        # Get all model timeseries
1060
        model_ts_dict = {
1061
            "Load": {
1062
                "carrier": "land_transport_EV",
1063
                "table": EgonPfHvLoad,
1064
                "table_ts": EgonPfHvLoadTimeseries,
1065
                "column_id": "load_id",
1066
                "columns_ts": ["p_set"],
1067
                "ts": None,
1068
            },
1069
            "Link": {
1070
                "carrier": "BEV_charger",
1071
                "table": EgonPfHvLink,
1072
                "table_ts": EgonPfHvLinkTimeseries,
1073
                "column_id": "link_id",
1074
                "columns_ts": ["p_max_pu"],
1075
                "ts": None,
1076
            },
1077
            "Store": {
1078
                "carrier": "battery_storage",
1079
                "table": EgonPfHvStore,
1080
                "table_ts": EgonPfHvStoreTimeseries,
1081
                "column_id": "store_id",
1082
                "columns_ts": ["e_min_pu", "e_max_pu"],
1083
                "ts": None,
1084
            },
1085
        }
1086
1087
        with db.session_scope() as session:
1088
            for node, attrs in model_ts_dict.items():
1089
                print(f"    Loading {node} timeseries...")
1090
                subquery = (
1091
                    session.query(getattr(attrs["table"], attrs["column_id"]))
1092
                    .filter(attrs["table"].carrier == attrs["carrier"])
1093
                    .filter(attrs["table"].scn_name == scenario_name)
1094
                    .subquery()
1095
                )
1096
1097
                cols = [
1098
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
1099
                ]
1100
                query = session.query(
1101
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
1102
                ).filter(
1103
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
1104
                        subquery
1105
                    ),
1106
                    attrs["table_ts"].scn_name == scenario_name,
1107
                )
1108
                attrs["ts"] = pd.read_sql(
1109
                    query.statement,
1110
                    query.session.bind,
1111
                    index_col=attrs["column_id"],
1112
                )
1113
1114
        # Check if all timeseries have 8760 steps
1115
        print("    Checking timeranges...")
1116
        for node, attrs in model_ts_dict.items():
1117
            for col in attrs["columns_ts"]:
1118
                ts = attrs["ts"]
1119
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
1120
                    col
1121
                ].apply(len)
1122
                np.testing.assert_equal(
1123
                    len(invalid_ts),
1124
                    0,
1125
                    err_msg=(
1126
                        f"{str(len(invalid_ts))} rows in timeseries do not "
1127
                        f"have 8760 timesteps. Table: "
1128
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
1129
                        f"{str(list(invalid_ts.index))}"
1130
                    ),
1131
                )
1132
1133
        # Compare total energy demand in model with some approximate values
1134
        # (per EV: 14,000 km/a, 0.17 kWh/km)
1135
        print("  Checking energy demand in model...")
1136
        total_energy_model = (
1137
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
1138
            / 1e6
1139
        )
1140
        print(f"    Total energy amount in model: {total_energy_model} TWh")
1141
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
1142
        print(
1143
            f"    Total approximated energy amount in scenario: "
1144
            f"{total_energy_scenario_approx} TWh"
1145
        )
1146
        np.testing.assert_allclose(
1147
            total_energy_model,
1148
            total_energy_scenario_approx,
1149
            rtol=0.1,
1150
            err_msg=(
1151
                "The total energy amount in the model deviates more than 10% "
1152
                "from the approximated value for current scenario."
1153
            ),
1154
        )
1155
1156
        # Compare total storage capacity
1157
        print("  Checking storage capacity...")
1158
        # Load storage capacities from model
1159
        with db.session_scope() as session:
1160
            query = session.query(
1161
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
1162
            ).filter(
1163
                EgonPfHvStore.scn_name == scenario_name,
1164
                EgonPfHvStore.carrier == "battery_storage",
1165
            )
1166
        storage_capacity_model = (
1167
            pd.read_sql(
1168
                query.statement, query.session.bind, index_col=None
1169
            ).e_nom.sum()
1170
            / 1e3
1171
        )
1172
        print(
1173
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
1174
            f"{round(storage_capacity_model, 1)} GWh"
1175
        )
1176
1177
        # Load occurences of each EV
1178
        with db.session_scope() as session:
1179
            query = (
1180
                session.query(
1181
                    EgonEvMvGridDistrict.bus_id,
1182
                    EgonEvPool.type,
1183
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
1184
                        "count"
1185
                    ),
1186
                )
1187
                .join(
1188
                    EgonEvPool,
1189
                    EgonEvPool.ev_id
1190
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
1191
                )
1192
                .filter(
1193
                    EgonEvMvGridDistrict.scenario == scenario_name,
1194
                    EgonEvMvGridDistrict.scenario_variation
1195
                    == scenario_var_name,
1196
                    EgonEvPool.scenario == scenario_name,
1197
                )
1198
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
1199
            )
1200
        count_per_ev_all = pd.read_sql(
1201
            query.statement, query.session.bind, index_col="bus_id"
1202
        )
1203
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
1204
            meta_tech_data.battery_capacity
1205
        )
1206
        count_per_ev_all["bat_cap_total_MWh"] = (
1207
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
1208
        )
1209
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
1210
            1e3
1211
        ).sum()
1212
        print(
1213
            f"    Total storage capacity (simBEV): "
1214
            f"{round(storage_capacity_simbev, 1)} GWh"
1215
        )
1216
1217
        np.testing.assert_allclose(
1218
            storage_capacity_model,
1219
            storage_capacity_simbev,
1220
            rtol=0.01,
1221
            err_msg=(
1222
                "The total storage capacity in the model deviates more than "
1223
                "1% from the input data provided by simBEV for current "
1224
                "scenario."
1225
            ),
1226
        )
1227
1228
        # Check SoC storage constraint: e_min_pu < e_max_pu for all timesteps
1229
        print("  Validating SoC constraints...")
1230
        stores_with_invalid_soc = []
1231
        for idx, row in model_ts_dict["Store"]["ts"].iterrows():
1232
            ts = row[["e_min_pu", "e_max_pu"]]
1233
            x = np.array(ts.e_min_pu) > np.array(ts.e_max_pu)
1234
            if x.any():
1235
                stores_with_invalid_soc.append(idx)
1236
1237
        np.testing.assert_equal(
1238
            len(stores_with_invalid_soc),
1239
            0,
1240
            err_msg=(
1241
                f"The store constraint e_min_pu < e_max_pu does not apply "
1242
                f"for some storages in {EgonPfHvStoreTimeseries.__table__}. "
1243
                f"Invalid store_ids: {stores_with_invalid_soc}"
1244
            ),
1245
        )
1246
1247
    def check_model_data_lowflex_eGon2035():
1248
        # TODO: Add eGon100RE_lowflex
1249
        print("")
1250
        print("SCENARIO: eGon2035_lowflex")
1251
1252
        # Compare driving load and charging load
1253
        print("  Loading eGon2035 model timeseries: driving load...")
1254
        with db.session_scope() as session:
1255
            query = (
1256
                session.query(
1257
                    EgonPfHvLoad.load_id,
1258
                    EgonPfHvLoadTimeseries.p_set,
1259
                )
1260
                .join(
1261
                    EgonPfHvLoadTimeseries,
1262
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1263
                )
1264
                .filter(
1265
                    EgonPfHvLoad.carrier == "land_transport_EV",
1266
                    EgonPfHvLoad.scn_name == "eGon2035",
1267
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035",
1268
                )
1269
            )
1270
        model_driving_load = pd.read_sql(
1271
            query.statement, query.session.bind, index_col=None
1272
        )
1273
        driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0)
1274
1275
        print(
1276
            "  Loading eGon2035_lowflex model timeseries: dumb charging "
1277
            "load..."
1278
        )
1279
        with db.session_scope() as session:
1280
            query = (
1281
                session.query(
1282
                    EgonPfHvLoad.load_id,
1283
                    EgonPfHvLoadTimeseries.p_set,
1284
                )
1285
                .join(
1286
                    EgonPfHvLoadTimeseries,
1287
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1288
                )
1289
                .filter(
1290
                    EgonPfHvLoad.carrier == "land_transport_EV",
1291
                    EgonPfHvLoad.scn_name == "eGon2035_lowflex",
1292
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex",
1293
                )
1294
            )
1295
        model_charging_load_lowflex = pd.read_sql(
1296
            query.statement, query.session.bind, index_col=None
1297
        )
1298
        charging_load = np.array(
1299
            model_charging_load_lowflex.p_set.to_list()
1300
        ).sum(axis=0)
1301
1302
        # Ratio of driving and charging load should be 0.9 due to charging
1303
        # efficiency
1304
        print("  Compare cumulative loads...")
1305
        print(f"    Driving load (eGon2035): {driving_load.sum() / 1e6} TWh")
1306
        print(
1307
            f"    Dumb charging load (eGon2035_lowflex): "
1308
            f"{charging_load.sum() / 1e6} TWh"
1309
        )
1310
        driving_load_theoretical = (
1311
            float(meta_run_config.eta_cp) * charging_load.sum()
0 ignored issues
show
introduced by
The variable meta_run_config does not seem to be defined in case the for loop on line 1329 is not entered. Are you sure this can never be the case?
Loading history...
1312
        )
1313
        np.testing.assert_allclose(
1314
            driving_load.sum(),
1315
            driving_load_theoretical,
1316
            rtol=0.01,
1317
            err_msg=(
1318
                f"The driving load (eGon2035) deviates by more than 1% "
1319
                f"from the theoretical driving load calculated from charging "
1320
                f"load (eGon2035_lowflex) with an efficiency of "
1321
                f"{float(meta_run_config.eta_cp)}."
1322
            ),
1323
        )
1324
1325
    print("=====================================================")
1326
    print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===")
1327
    print("=====================================================")
1328
1329
    for scenario_name in config.settings()["egon-data"]["--scenarios"]:
1330
        scenario_var_name = DATASET_CFG["scenario"]["variation"][scenario_name]
1331
1332
        print("")
1333
        print(f"SCENARIO: {scenario_name}, VARIATION: {scenario_var_name}")
1334
1335
        # Load scenario params for scenario and scenario variation
1336
        scenario_variation_parameters = get_sector_parameters(
1337
            "mobility", scenario=scenario_name
1338
        )["motorized_individual_travel"][scenario_var_name]
1339
1340
        # Load simBEV run config and tech data
1341
        meta_run_config = read_simbev_metadata_file(
1342
            scenario_name, "config"
1343
        ).loc["basic"]
1344
        meta_tech_data = read_simbev_metadata_file(scenario_name, "tech_data")
1345
1346
        print("")
1347
        print("Checking EV counts...")
1348
        ev_count_alloc = check_ev_allocation()
1349
1350
        print("")
1351
        print("Checking trip data...")
1352
        check_trip_data()
1353
1354
        print("")
1355
        print("Checking model data...")
1356
        check_model_data()
1357
1358
    print("")
1359
    check_model_data_lowflex_eGon2035()
1360
1361
    print("=====================================================")
1362
1363
1364
def sanitycheck_home_batteries():
1365
    # get constants
1366
    constants = config.datasets()["home_batteries"]["constants"]
1367
    scenarios = constants["scenarios"]
1368
    cbat_pbat_ratio = get_cbat_pbat_ratio()
1369
1370
    sources = config.datasets()["home_batteries"]["sources"]
1371
    targets = config.datasets()["home_batteries"]["targets"]
1372
1373
    for scenario in scenarios:
1374
        # get home battery capacity per mv grid id
1375
        sql = f"""
1376
        SELECT el_capacity as p_nom, bus_id FROM
1377
        {sources["storage"]["schema"]}
1378
        .{sources["storage"]["table"]}
1379
        WHERE carrier = 'home_battery'
1380
        AND scenario = '{scenario}'
1381
        """
1382
1383
        home_batteries_df = db.select_dataframe(sql, index_col="bus_id")
1384
1385
        home_batteries_df = home_batteries_df.assign(
1386
            capacity=home_batteries_df.p_nom * cbat_pbat_ratio
1387
        )
1388
1389
        sql = f"""
1390
        SELECT * FROM
1391
        {targets["home_batteries"]["schema"]}
1392
        .{targets["home_batteries"]["table"]}
1393
        WHERE scenario = '{scenario}'
1394
        """
1395
1396
        home_batteries_buildings_df = db.select_dataframe(
1397
            sql, index_col="index"
1398
        )
1399
1400
        df = (
1401
            home_batteries_buildings_df[["bus_id", "p_nom", "capacity"]]
1402
            .groupby("bus_id")
1403
            .sum()
1404
        )
1405
1406
        assert (home_batteries_df.round(6) == df.round(6)).all().all()
1407
1408
1409
def sanity_check_gas_buses(scn):
1410
    """Execute sanity checks for the gas buses in Germany
1411
1412
    Returns print statements as sanity checks for the CH4, H2_grid and
1413
    H2_saltcavern buses.
1414
1415
    * For all of them, it is checked if they are not isolated.
1416
    * For the grid buses, the deviation is calculated between the
1417
      number of gas grid buses in the database and the original
1418
      Scigrid_gas number of gas buses in Germany.
1419
1420
    Parameters
1421
    ----------
1422
    scn_name : str
1423
        Name of the scenario
1424
1425
    """
1426
    logger.info("BUSES")
1427
1428
    # Are gas buses isolated?
1429
    corresponding_carriers = {
1430
        "eGon2035": {
1431
            "CH4": "CH4",
1432
            "H2_grid": "H2_feedin",
1433
            "H2_saltcavern": "power_to_H2",
1434
        },
1435
        # "eGon100RE": {
1436
        #     "CH4": "CH4",
1437
        #     "H2_grid": "H2_retrofit",
1438
        #     "H2_saltcavern": "H2_extension",
1439
        # }
1440
    }
1441
    for key in corresponding_carriers[scn]:
1442
        isolated_gas_buses = db.select_dataframe(
1443
            f"""
1444
            SELECT bus_id, carrier, country
1445
            FROM grid.egon_etrago_bus
1446
            WHERE scn_name = '{scn}'
1447
            AND carrier = '{key}'
1448
            AND country = 'DE'
1449
            AND bus_id NOT IN
1450
                (SELECT bus0
1451
                FROM grid.egon_etrago_link
1452
                WHERE scn_name = '{scn}'
1453
                AND carrier = '{corresponding_carriers[scn][key]}')
1454
            AND bus_id NOT IN
1455
                (SELECT bus1
1456
                FROM grid.egon_etrago_link
1457
                WHERE scn_name = '{scn}'
1458
                AND carrier = '{corresponding_carriers[scn][key]}')
1459
            ;
1460
            """,
1461
            warning=False,
1462
        )
1463
        if not isolated_gas_buses.empty:
1464
            logger.info(f"Isolated {key} buses:")
1465
            logger.info(isolated_gas_buses)
1466
1467
    # Deviation of the gas grid buses number
1468
    target_file = (
1469
        Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Nodes.csv"
1470
    )
1471
1472
    Grid_buses_list = pd.read_csv(
1473
        target_file,
1474
        delimiter=";",
1475
        decimal=".",
1476
        usecols=["country_code"],
1477
    )
1478
1479
    Grid_buses_list = Grid_buses_list[
1480
        Grid_buses_list["country_code"].str.match("DE")
1481
    ]
1482
    input_grid_buses = len(Grid_buses_list.index)
1483
1484
    for carrier in ["CH4", "H2_grid"]:
1485
        output_grid_buses_df = db.select_dataframe(
1486
            f"""
1487
            SELECT bus_id
1488
            FROM grid.egon_etrago_bus
1489
            WHERE scn_name = '{scn}'
1490
            AND country = 'DE'
1491
            AND carrier = '{carrier}';
1492
            """,
1493
            warning=False,
1494
        )
1495
        output_grid_buses = len(output_grid_buses_df.index)
1496
1497
        e_grid_buses = (
1498
            round(
1499
                (output_grid_buses - input_grid_buses) / input_grid_buses,
1500
                2,
1501
            )
1502
            * 100
1503
        )
1504
        logger.info(f"Deviation {carrier} buses: {e_grid_buses} %")
1505
1506
1507
def sanity_check_CH4_stores(scn):
1508
    """Execute sanity checks for the CH4 stores in Germany
1509
1510
    Returns print statements as sanity checks for the CH4 stores
1511
    capacity in Germany. The deviation is calculated between:
1512
1513
      * the sum of the capacities of the stores with carrier 'CH4'
1514
        in the database (for one scenario) and
1515
      * the sum of:
1516
1517
        * the capacity the gas grid allocated to CH4 (total capacity
1518
          in eGon2035 and capacity reduced the share of the grid
1519
          allocated to H2 in eGon100RE)
1520
        * the total capacity of the CH4 stores in Germany (source: GIE)
1521
1522
    Parameters
1523
    ----------
1524
    scn_name : str
1525
        Name of the scenario
1526
1527
    """
1528
    output_CH4_stores = db.select_dataframe(
1529
        f"""SELECT SUM(e_nom::numeric) as e_nom_germany
1530
                FROM grid.egon_etrago_store
1531
                WHERE scn_name = '{scn}'
1532
                AND carrier = 'CH4'
1533
                AND bus IN
1534
                    (SELECT bus_id
1535
                    FROM grid.egon_etrago_bus
1536
                    WHERE scn_name = '{scn}'
1537
                    AND country = 'DE'
1538
                    AND carrier = 'CH4');
1539
                """,
1540
        warning=False,
1541
    )["e_nom_germany"].values[0]
1542
1543
    if scn == "eGon2035":
1544
        grid_cap = 130000
1545
    elif scn == "eGon100RE":
1546
        grid_cap = 13000 * (
1547
            1
1548
            - get_sector_parameters("gas", "eGon100RE")[
1549
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1550
            ]
1551
        )
1552
1553
    # MWh GIE https://www.gie.eu/transparency/databases/storage-database/
1554
    stores_cap_D = 266424202
1555
1556
    input_CH4_stores = stores_cap_D + grid_cap
0 ignored issues
show
introduced by
The variable grid_cap does not seem to be defined for all execution paths.
Loading history...
1557
1558
    e_CH4_stores = (
1559
        round(
1560
            (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
1561
            2,
1562
        )
1563
        * 100
1564
    )
1565
    logger.info(f"Deviation CH4 stores: {e_CH4_stores} %")
1566
1567
1568
def sanity_check_H2_saltcavern_stores(scn):
1569
    """Execute sanity checks for the H2 saltcavern stores in Germany
1570
1571
    Returns print as sanity checks for the H2 saltcavern potential
1572
    storage capacity in Germany. The deviation is calculated between:
1573
1574
    * the sum of the of the H2 saltcavern potential storage capacity
1575
      (e_nom_max) in the database and
1576
    * the sum of the H2 saltcavern potential storage capacity
1577
      assumed to be the ratio of the areas of 500 m radius around
1578
      substations in each german federal state and the estimated
1579
      total hydrogen storage potential of the corresponding federal
1580
      state (data from InSpEE-DS report).
1581
1582
    This test works also in test mode.
1583
1584
    Parameters
1585
    ----------
1586
    scn_name : str
1587
        Name of the scenario
1588
1589
    """
1590
    output_H2_stores = db.select_dataframe(
1591
        f"""SELECT SUM(e_nom_max::numeric) as e_nom_max_germany
1592
                FROM grid.egon_etrago_store
1593
                WHERE scn_name = '{scn}'
1594
                AND carrier = 'H2_underground'
1595
                AND bus IN
1596
                    (SELECT bus_id
1597
                    FROM grid.egon_etrago_bus
1598
                    WHERE scn_name = '{scn}'
1599
                    AND country = 'DE'
1600
                    AND carrier = 'H2_saltcavern');
1601
                """,
1602
        warning=False,
1603
    )["e_nom_max_germany"].values[0]
1604
1605
    storage_potentials = calculate_and_map_saltcavern_storage_potential()
1606
    storage_potentials["storage_potential"] = (
1607
        storage_potentials["area_fraction"] * storage_potentials["potential"]
1608
    )
1609
    input_H2_stores = sum(storage_potentials["storage_potential"].to_list())
1610
1611
    e_H2_stores = (
1612
        round(
1613
            (output_H2_stores - input_H2_stores) / input_H2_stores,
1614
            2,
1615
        )
1616
        * 100
1617
    )
1618
    logger.info(f"Deviation H2 saltcavern stores: {e_H2_stores} %")
1619
1620
1621
def sanity_check_gas_one_port(scn):
1622
    """Check connections of gas one-port components
1623
1624
    Verify that gas one-port component (loads, generators, stores) are
1625
    all connected to a bus (of the right carrier) present in the data
1626
    base. Return print statements if this is not the case.
1627
    These sanity checks are not specific to Germany, they also include
1628
    the neighbouring countries.
1629
1630
    Parameters
1631
    ----------
1632
    scn_name : str
1633
        Name of the scenario
1634
1635
    """
1636
    if scn == "eGon2035":
1637
        # Loads
1638
        ## CH4_for_industry Germany
1639
        isolated_one_port_c = db.select_dataframe(
1640
            f"""
1641
            SELECT load_id, bus, carrier, scn_name
1642
                FROM grid.egon_etrago_load
1643
                WHERE scn_name = '{scn}'
1644
                AND carrier = 'CH4_for_industry'
1645
                AND bus NOT IN
1646
                    (SELECT bus_id
1647
                    FROM grid.egon_etrago_bus
1648
                    WHERE scn_name = '{scn}'
1649
                    AND country = 'DE'
1650
                    AND carrier = 'CH4')
1651
            ;
1652
            """,
1653
            warning=False,
1654
        )
1655
        if not isolated_one_port_c.empty:
1656
            logger.info("Isolated loads:")
1657
            logger.info(isolated_one_port_c)
1658
1659
        ## CH4_for_industry abroad
1660
        isolated_one_port_c = db.select_dataframe(
1661
            f"""
1662
            SELECT load_id, bus, carrier, scn_name
1663
                FROM grid.egon_etrago_load
1664
                WHERE scn_name = '{scn}'
1665
                AND carrier = 'CH4'
1666
                AND bus NOT IN
1667
                    (SELECT bus_id
1668
                    FROM grid.egon_etrago_bus
1669
                    WHERE scn_name = '{scn}'
1670
                    AND country != 'DE'
1671
                    AND carrier = 'CH4')
1672
            ;
1673
            """,
1674
            warning=False,
1675
        )
1676
        if not isolated_one_port_c.empty:
1677
            logger.info("Isolated loads:")
1678
            logger.info(isolated_one_port_c)
1679
1680
        ## H2_for_industry
1681
        isolated_one_port_c = db.select_dataframe(
1682
            f"""
1683
            SELECT load_id, bus, carrier, scn_name
1684
                FROM grid.egon_etrago_load
1685
                WHERE scn_name = '{scn}'
1686
                AND carrier = 'H2_for_industry'
1687
                AND (bus NOT IN
1688
                    (SELECT bus_id
1689
                    FROM grid.egon_etrago_bus
1690
                    WHERE scn_name = '{scn}'
1691
                    AND country = 'DE'
1692
                    AND carrier = 'H2_grid')
1693
                AND bus NOT IN
1694
                    (SELECT bus_id
1695
                    FROM grid.egon_etrago_bus
1696
                    WHERE scn_name = '{scn}'
1697
                    AND country != 'DE'
1698
                    AND carrier = 'AC'))
1699
            ;
1700
            """,
1701
            warning=False,
1702
        )
1703
        if not isolated_one_port_c.empty:
1704
            logger.info("Isolated loads:")
1705
            logger.info(isolated_one_port_c)
1706
1707
        # Genrators
1708
        isolated_one_port_c = db.select_dataframe(
1709
            f"""
1710
            SELECT generator_id, bus, carrier, scn_name
1711
                FROM grid.egon_etrago_generator
1712
                WHERE scn_name = '{scn}'
1713
                AND carrier = 'CH4'
1714
                AND bus NOT IN
1715
                    (SELECT bus_id
1716
                    FROM grid.egon_etrago_bus
1717
                    WHERE scn_name = '{scn}'
1718
                    AND carrier = 'CH4');
1719
            ;
1720
            """,
1721
            warning=False,
1722
        )
1723
        if not isolated_one_port_c.empty:
1724
            logger.info("Isolated generators:")
1725
            logger.info(isolated_one_port_c)
1726
1727
        # Stores
1728
        ## CH4 and H2_underground
1729
        corresponding_carriers = {
1730
            "CH4": "CH4",
1731
            "H2_saltcavern": "H2_underground",
1732
        }
1733
        for key in corresponding_carriers:
1734
            isolated_one_port_c = db.select_dataframe(
1735
                f"""
1736
                SELECT store_id, bus, carrier, scn_name
1737
                    FROM grid.egon_etrago_store
1738
                    WHERE scn_name = '{scn}'
1739
                    AND carrier = '{corresponding_carriers[key]}'
1740
                    AND bus NOT IN
1741
                        (SELECT bus_id
1742
                        FROM grid.egon_etrago_bus
1743
                        WHERE scn_name = '{scn}'
1744
                        AND carrier = '{key}')
1745
                ;
1746
                """,
1747
                warning=False,
1748
            )
1749
            if not isolated_one_port_c.empty:
1750
                logger.info("Isolated stores:")
1751
                logger.info(isolated_one_port_c)
1752
1753
        ## H2_overground
1754
        isolated_one_port_c = db.select_dataframe(
1755
            f"""
1756
            SELECT store_id, bus, carrier, scn_name
1757
                FROM grid.egon_etrago_store
1758
                WHERE scn_name = '{scn}'
1759
                AND carrier = 'H2_overground'
1760
                AND bus NOT IN
1761
                    (SELECT bus_id
1762
                    FROM grid.egon_etrago_bus
1763
                    WHERE scn_name = '{scn}'
1764
                    AND country = 'DE'
1765
                    AND carrier = 'H2_saltcavern')
1766
                AND bus NOT IN
1767
                    (SELECT bus_id
1768
                    FROM grid.egon_etrago_bus
1769
                    WHERE scn_name = '{scn}'
1770
                    AND country = 'DE'
1771
                    AND carrier = 'H2_grid')
1772
            ;
1773
            """,
1774
            warning=False,
1775
        )
1776
        if not isolated_one_port_c.empty:
1777
            logger.info("Isolated stores:")
1778
            logger.info(isolated_one_port_c)
1779
1780
    # elif scn == "eGon2035":
1781
1782
1783
def sanity_check_CH4_grid(scn):
1784
    """Execute sanity checks for the gas grid capacity in Germany
1785
1786
    Returns print statements as sanity checks for the CH4 links
1787
    (pipelines) in Germany. The deviation is calculated between
1788
    the sum of the power (p_nom) of all the CH4 pipelines in Germany
1789
    for one scenario in the database and the sum of the powers of the
1790
    imported pipelines.
1791
    In eGon100RE, the sum is reduced by the share of the grid that is
1792
    allocated to hydrogen (share calculated by PyPSA-eur-sec).
1793
    This test works also in test mode.
1794
1795
    Parameters
1796
    ----------
1797
    scn_name : str
1798
        Name of the scenario
1799
1800
    Returns
1801
    -------
1802
    scn_name : float
1803
        Sum of the power (p_nom) of all the pipelines in Germany
1804
1805
    """
1806
    grid_carrier = "CH4"
1807
    output_gas_grid = db.select_dataframe(
1808
        f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1809
            FROM grid.egon_etrago_link
1810
            WHERE scn_name = '{scn}'
1811
            AND carrier = '{grid_carrier}'
1812
            AND bus0 IN
1813
                (SELECT bus_id
1814
                FROM grid.egon_etrago_bus
1815
                WHERE scn_name = '{scn}'
1816
                AND country = 'DE'
1817
                AND carrier = '{grid_carrier}')
1818
            AND bus1 IN
1819
                (SELECT bus_id
1820
                FROM grid.egon_etrago_bus
1821
                WHERE scn_name = '{scn}'
1822
                AND country = 'DE'
1823
                AND carrier = '{grid_carrier}')
1824
                ;
1825
            """,
1826
        warning=False,
1827
    )["p_nom_germany"].values[0]
1828
1829
    gas_nodes_list = define_gas_nodes_list()
1830
    abroad_gas_nodes_list = define_gas_buses_abroad()
1831
    gas_grid = define_gas_pipeline_list(gas_nodes_list, abroad_gas_nodes_list)
1832
    gas_grid_germany = gas_grid[
1833
        (gas_grid["country_0"] == "DE") & (gas_grid["country_1"] == "DE")
1834
    ]
1835
    p_nom_total = sum(gas_grid_germany["p_nom"].to_list())
1836
1837
    if scn == "eGon2035":
1838
        input_gas_grid = p_nom_total
1839
    if scn == "eGon100RE":
1840
        input_gas_grid = p_nom_total * (
1841
            1
1842
            - get_sector_parameters("gas", "eGon100RE")[
1843
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1844
            ]
1845
        )
1846
1847
    e_gas_grid = (
1848
        round(
1849
            (output_gas_grid - input_gas_grid) / input_gas_grid,
0 ignored issues
show
introduced by
The variable input_gas_grid does not seem to be defined in case scn == "eGon2035" on line 1837 is False. Are you sure this can never be the case?
Loading history...
1850
            2,
1851
        )
1852
        * 100
1853
    )
1854
    logger.info(f"Deviation of the capacity of the CH4 grid: {e_gas_grid} %")
1855
1856
    return p_nom_total
1857
1858
1859
def sanity_check_gas_links(scn):
1860
    """Check connections of gas links
1861
1862
    Verify that gas links are all connected to buses present in the data
1863
    base. Return print statements if this is not the case.
1864
    This sanity check is not specific to Germany, it also includes
1865
    the neighbouring countries.
1866
1867
    Parameters
1868
    ----------
1869
    scn_name : str
1870
        Name of the scenario
1871
1872
    """
1873
    carriers = [
1874
        "CH4",
1875
        "H2_feedin",
1876
        "H2_to_CH4",
1877
        "CH4_to_H2",
1878
        "H2_to_power",
1879
        "power_to_H2",
1880
        "OCGT",
1881
        "central_gas_boiler",
1882
        "central_gas_CHP",
1883
        "central_gas_CHP_heat",
1884
        "industrial_gas_CHP",
1885
    ]
1886
    for c in carriers:
1887
        link_with_missing_bus = db.select_dataframe(
1888
            f"""
1889
            SELECT link_id, bus0, bus1, carrier, scn_name
1890
                FROM grid.egon_etrago_link
1891
                WHERE scn_name = '{scn}'
1892
                AND carrier = '{c}'
1893
                AND (bus0 NOT IN
1894
                    (SELECT bus_id
1895
                    FROM grid.egon_etrago_bus
1896
                    WHERE scn_name = '{scn}')
1897
                OR bus1 NOT IN
1898
                    (SELECT bus_id
1899
                    FROM grid.egon_etrago_bus
1900
                    WHERE scn_name = '{scn}'))
1901
            ;
1902
            """,
1903
            warning=False,
1904
        )
1905
        if not link_with_missing_bus.empty:
1906
            logger.info("Links with missing bus:")
1907
            logger.info(link_with_missing_bus)
1908
1909
1910
def etrago_eGon2035_gas_DE():
1911
    """Execute basic sanity checks for the gas sector in eGon2035
1912
1913
    Returns print statements as sanity checks for the gas sector in
1914
    the eGon2035 scenario for the following components in Germany:
1915
1916
      * Buses: with the function :py:func:`sanity_check_gas_buses`
1917
      * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry'
1918
        the deviation is calculated between the sum of the loads in the
1919
        database and the sum the loads in the sources document
1920
        (opendata.ffe database)
1921
      * Generators: the deviation is calculated between the sums of the
1922
        nominal powers of the gas generators in the database and of
1923
        the ones in the sources document (Biogaspartner Einspeiseatlas
1924
        Deutschland from the dena and Productions from the SciGRID_gas
1925
        data)
1926
      * Stores: deviations for stores with following carriers are
1927
        calculated:
1928
1929
          * 'CH4': with the function :py:func:`sanity_check_CH4_stores`
1930
          * 'H2_underground': with the function
1931
            :py:func:`sanity_check_H2_saltcavern_stores`
1932
      * One-port components (loads, generators, stores): verification
1933
        that they are all connected to a bus present in the data base
1934
        with the function :py:func:`sanity_check_gas_one_port`
1935
      * Links: verification:
1936
1937
        * that the gas links are all connected to buses present in
1938
          the data base with the function :py:func:`sanity_check_gas_links`
1939
        * of the capacity of the gas grid with the function
1940
          :py:func:`sanity_check_CH4_grid`
1941
1942
    """
1943
    scn = "eGon2035"
1944
1945
    if TESTMODE_OFF:
1946
        logger.info(f"Gas sanity checks for scenario {scn}")
1947
1948
        # Buses
1949
        sanity_check_gas_buses(scn)
1950
1951
        # Loads
1952
        logger.info("LOADS")
1953
1954
        path = Path(".") / "datasets" / "gas_data" / "demand"
1955
        corr_file = path / "region_corr.json"
1956
        df_corr = pd.read_json(corr_file)
1957
        df_corr = df_corr.loc[:, ["id_region", "name_short"]]
1958
        df_corr.set_index("id_region", inplace=True)
1959
1960
        for carrier in ["CH4_for_industry", "H2_for_industry"]:
1961
1962
            output_gas_demand = db.select_dataframe(
1963
                f"""SELECT (SUM(
1964
                    (SELECT SUM(p)
1965
                    FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
1966
                    FROM grid.egon_etrago_load a
1967
                    JOIN grid.egon_etrago_load_timeseries b
1968
                    ON (a.load_id = b.load_id)
1969
                    JOIN grid.egon_etrago_bus c
1970
                    ON (a.bus=c.bus_id)
1971
                    AND b.scn_name = '{scn}'
1972
                    AND a.scn_name = '{scn}'
1973
                    AND c.scn_name = '{scn}'
1974
                    AND c.country = 'DE'
1975
                    AND a.carrier = '{carrier}';
1976
                """,
1977
                warning=False,
1978
            )["load_twh"].values[0]
1979
1980
            input_gas_demand = pd.read_json(
1981
                path / (carrier + "_eGon2035.json")
1982
            )
1983
            input_gas_demand = input_gas_demand.loc[:, ["id_region", "value"]]
1984
            input_gas_demand.set_index("id_region", inplace=True)
1985
            input_gas_demand = pd.concat(
1986
                [input_gas_demand, df_corr], axis=1, join="inner"
1987
            )
1988
            input_gas_demand["NUTS0"] = (input_gas_demand["name_short"].str)[
1989
                0:2
1990
            ]
1991
            input_gas_demand = input_gas_demand[
1992
                input_gas_demand["NUTS0"].str.match("DE")
1993
            ]
1994
            input_gas_demand = sum(input_gas_demand.value.to_list()) / 1000000
1995
1996
            e_demand = (
1997
                round(
1998
                    (output_gas_demand - input_gas_demand) / input_gas_demand,
1999
                    2,
2000
                )
2001
                * 100
2002
            )
2003
            logger.info(f"Deviation {carrier}: {e_demand} %")
2004
2005
        # Generators
2006
        logger.info("GENERATORS")
2007
        carrier_generator = "CH4"
2008
2009
        output_gas_generation = db.select_dataframe(
2010
            f"""SELECT SUM(p_nom::numeric) as p_nom_germany
2011
                    FROM grid.egon_etrago_generator
2012
                    WHERE scn_name = '{scn}'
2013
                    AND carrier = '{carrier_generator}'
2014
                    AND bus IN
2015
                        (SELECT bus_id
2016
                        FROM grid.egon_etrago_bus
2017
                        WHERE scn_name = '{scn}'
2018
                        AND country = 'DE'
2019
                        AND carrier = '{carrier_generator}');
2020
                    """,
2021
            warning=False,
2022
        )["p_nom_germany"].values[0]
2023
2024
        target_file = (
2025
            Path(".")
2026
            / "datasets"
2027
            / "gas_data"
2028
            / "data"
2029
            / "IGGIELGN_Productions.csv"
2030
        )
2031
2032
        NG_generators_list = pd.read_csv(
2033
            target_file,
2034
            delimiter=";",
2035
            decimal=".",
2036
            usecols=["country_code", "param"],
2037
        )
2038
2039
        NG_generators_list = NG_generators_list[
2040
            NG_generators_list["country_code"].str.match("DE")
2041
        ]
2042
2043
        p_NG = 0
2044
        for index, row in NG_generators_list.iterrows():
2045
            param = ast.literal_eval(row["param"])
2046
            p_NG = p_NG + param["max_supply_M_m3_per_d"]
2047
        conversion_factor = 437.5  # MCM/day to MWh/h
2048
        p_NG = p_NG * conversion_factor
2049
2050
        basename = "Biogaspartner_Einspeiseatlas_Deutschland_2021.xlsx"
2051
        target_file = (
2052
            Path(".") / "data_bundle_egon_data" / "gas_data" / basename
2053
        )
2054
2055
        conversion_factor_b = 0.01083  # m^3/h to MWh/h
2056
        p_biogas = (
2057
            pd.read_excel(
2058
                target_file,
2059
                usecols=["Einspeisung Biomethan [(N*m^3)/h)]"],
2060
            )["Einspeisung Biomethan [(N*m^3)/h)]"].sum()
2061
            * conversion_factor_b
2062
        )
2063
2064
        input_gas_generation = p_NG + p_biogas
2065
        e_generation = (
2066
            round(
2067
                (output_gas_generation - input_gas_generation)
2068
                / input_gas_generation,
2069
                2,
2070
            )
2071
            * 100
2072
        )
2073
        logger.info(
2074
            f"Deviation {carrier_generator} generation: {e_generation} %"
2075
        )
2076
2077
        # Stores
2078
        logger.info("STORES")
2079
        sanity_check_CH4_stores(scn)
2080
        sanity_check_H2_saltcavern_stores(scn)
2081
2082
        # One-port components
2083
        sanity_check_gas_one_port(scn)
2084
2085
        # Links
2086
        logger.info("LINKS")
2087
        sanity_check_CH4_grid(scn)
2088
        sanity_check_gas_links(scn)
2089
2090
    else:
2091
        print("Testmode is on, skipping sanity check.")
2092
2093
2094
def etrago_eGon2035_gas_abroad():
2095
    """Execute basic sanity checks for the gas sector in eGon2035 abroad
2096
2097
    Returns print statements as sanity checks for the gas sector in
2098
    the eGon2035 scenario for the following components in Germany:
2099
2100
      * Buses
2101
      * Loads: for the carriers 'CH4' and 'H2_for_industry'
2102
        the deviation is calculated between the sum of the loads in the
2103
        database and the sum in the sources document (TYNDP)
2104
      * Generators: the deviation is calculated between the sums of the
2105
        nominal powers of the methane generators abroad in the database
2106
        and of the ones in the sources document (TYNDP)
2107
      * Stores: the deviation for methane stores abroad is calculated
2108
        between the sum of the capacities in the data base and the one
2109
        of the source document (SciGRID_gas data)
2110
      * Links: verification of the capacity of the crossbordering gas
2111
        grid pipelines.
2112
2113
    """
2114
    scn = "eGon2035"
2115
2116
    if TESTMODE_OFF:
2117
        logger.info(f"Gas sanity checks abroad for scenario {scn}")
2118
2119
        # Buses
2120
        logger.info("BUSES")
2121
2122
        # Are gas buses isolated?
2123
        corresponding_carriers = {
2124
            "eGon2035": {
2125
                "CH4": "CH4",
2126
            },
2127
            # "eGon100RE": {
2128
            #     "CH4": "CH4",
2129
            #     "H2_grid": "H2_retrofit",
2130
            # }
2131
        }
2132
        for key in corresponding_carriers[scn]:
2133
            isolated_gas_buses_abroad = db.select_dataframe(
2134
                f"""
2135
                SELECT bus_id, carrier, country
2136
                FROM grid.egon_etrago_bus
2137
                WHERE scn_name = '{scn}'
2138
                AND carrier = '{key}'
2139
                AND country != 'DE'
2140
                AND bus_id NOT IN
2141
                    (SELECT bus0
2142
                    FROM grid.egon_etrago_link
2143
                    WHERE scn_name = '{scn}'
2144
                    AND carrier = '{corresponding_carriers[scn][key]}')
2145
                AND bus_id NOT IN
2146
                    (SELECT bus1
2147
                    FROM grid.egon_etrago_link
2148
                    WHERE scn_name = '{scn}'
2149
                    AND carrier = '{corresponding_carriers[scn][key]}')
2150
                ;
2151
                """,
2152
                warning=False,
2153
            )
2154
            if not isolated_gas_buses_abroad.empty:
2155
                logger.info(f"Isolated {key} buses abroad:")
2156
                logger.info(isolated_gas_buses_abroad)
2157
2158
        # Loads
2159
        logger.info("LOADS")
2160
2161
        (
2162
            Norway_global_demand_1y,
2163
            normalized_ch4_demandTS,
2164
        ) = import_ch4_demandTS()
2165
        input_CH4_demand_abroad = calc_global_ch4_demand(
2166
            Norway_global_demand_1y
2167
        )
2168
        input_CH4_demand = input_CH4_demand_abroad["GlobD_2035"].sum()
2169
2170
        ## CH4
2171
        output_CH4_demand = db.select_dataframe(
2172
            f"""SELECT (SUM(
2173
                (SELECT SUM(p)
2174
                FROM UNNEST(b.p_set) p)))::numeric as load_mwh
2175
                FROM grid.egon_etrago_load a
2176
                JOIN grid.egon_etrago_load_timeseries b
2177
                ON (a.load_id = b.load_id)
2178
                JOIN grid.egon_etrago_bus c
2179
                ON (a.bus=c.bus_id)
2180
                AND b.scn_name = '{scn}'
2181
                AND a.scn_name = '{scn}'
2182
                AND c.scn_name = '{scn}'
2183
                AND c.country != 'DE'
2184
                AND a.carrier = 'CH4';
2185
            """,
2186
            warning=False,
2187
        )["load_mwh"].values[0]
2188
2189
        e_demand_CH4 = (
2190
            round(
2191
                (output_CH4_demand - input_CH4_demand) / input_CH4_demand,
2192
                2,
2193
            )
2194
            * 100
2195
        )
2196
        logger.info(f"Deviation CH4 load: {e_demand_CH4} %")
2197
2198
        ## H2_for_industry
2199
        input_power_to_h2_demand_abroad = calc_global_power_to_h2_demand()
2200
        input_H2_demand = input_power_to_h2_demand_abroad["GlobD_2035"].sum()
2201
2202
        output_H2_demand = db.select_dataframe(
2203
            f"""SELECT SUM(p_set::numeric) as p_set_abroad
2204
                    FROM grid.egon_etrago_load
2205
                    WHERE scn_name = '{scn}'
2206
                    AND carrier = 'H2_for_industry'
2207
                    AND bus IN
2208
                        (SELECT bus_id
2209
                        FROM grid.egon_etrago_bus
2210
                        WHERE scn_name = '{scn}'
2211
                        AND country != 'DE'
2212
                        AND carrier = 'AC');
2213
                    """,
2214
            warning=False,
2215
        )["p_set_abroad"].values[0]
2216
2217
        e_demand_H2 = (
2218
            round(
2219
                (output_H2_demand - input_H2_demand) / input_H2_demand,
2220
                2,
2221
            )
2222
            * 100
2223
        )
2224
        logger.info(f"Deviation H2_for_industry load: {e_demand_H2} %")
2225
2226
        # Generators
2227
        logger.info("GENERATORS ")
2228
        CH4_gen = calc_capacities()
2229
        input_CH4_gen = CH4_gen["cap_2035"].sum()
2230
2231
        output_CH4_gen = db.select_dataframe(
2232
            f"""SELECT SUM(p_nom::numeric) as p_nom_abroad
2233
                    FROM grid.egon_etrago_generator
2234
                    WHERE scn_name = '{scn}'
2235
                    AND carrier = 'CH4'
2236
                    AND bus IN
2237
                        (SELECT bus_id
2238
                        FROM grid.egon_etrago_bus
2239
                        WHERE scn_name = '{scn}'
2240
                        AND country != 'DE'
2241
                        AND carrier = 'CH4');
2242
                    """,
2243
            warning=False,
2244
        )["p_nom_abroad"].values[0]
2245
2246
        e_gen = (
2247
            round(
2248
                (output_CH4_gen - input_CH4_gen) / input_CH4_gen,
2249
                2,
2250
            )
2251
            * 100
2252
        )
2253
        logger.info(f"Deviation CH4 generators: {e_gen} %")
2254
2255
        # Stores
2256
        logger.info("STORES")
2257
        ch4_input_capacities = calc_ch4_storage_capacities()
2258
        input_CH4_stores = ch4_input_capacities["e_nom"].sum()
2259
2260
        output_CH4_stores = db.select_dataframe(
2261
            f"""SELECT SUM(e_nom::numeric) as e_nom_abroad
2262
                    FROM grid.egon_etrago_store
2263
                    WHERE scn_name = '{scn}'
2264
                    AND carrier = 'CH4'
2265
                    AND bus IN
2266
                        (SELECT bus_id
2267
                        FROM grid.egon_etrago_bus
2268
                        WHERE scn_name = '{scn}'
2269
                        AND country != 'DE'
2270
                        AND carrier = 'CH4');
2271
                    """,
2272
            warning=False,
2273
        )["e_nom_abroad"].values[0]
2274
2275
        e_stores = (
2276
            round(
2277
                (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
2278
                2,
2279
            )
2280
            * 100
2281
        )
2282
        logger.info(f"Deviation CH4 stores: {e_stores} %")
2283
2284
        # Links
2285
        logger.info("LINKS")
2286
        ch4_grid_input_capacities = calculate_ch4_grid_capacities()
2287
        input_CH4_grid = ch4_grid_input_capacities["p_nom"].sum()
2288
2289
        grid_carrier = "CH4"
2290
        output_gas_grid = db.select_dataframe(
2291
            f"""SELECT SUM(p_nom::numeric) as p_nom
2292
            FROM grid.egon_etrago_link
2293
            WHERE scn_name = '{scn}'
2294
            AND carrier = '{grid_carrier}'
2295
            AND (bus0 IN
2296
                (SELECT bus_id
2297
                FROM grid.egon_etrago_bus
2298
                WHERE scn_name = '{scn}'
2299
                AND country != 'DE'
2300
                AND carrier = '{grid_carrier}')
2301
            OR bus1 IN
2302
                (SELECT bus_id
2303
                FROM grid.egon_etrago_bus
2304
                WHERE scn_name = '{scn}'
2305
                AND country != 'DE'
2306
                AND carrier = '{grid_carrier}'))
2307
                ;
2308
            """,
2309
            warning=False,
2310
        )["p_nom"].values[0]
2311
2312
        e_gas_grid = (
2313
            round(
2314
                (output_gas_grid - input_CH4_grid) / input_CH4_grid,
2315
                2,
2316
            )
2317
            * 100
2318
        )
2319
        logger.info(
2320
            f"Deviation of the capacity of the crossbordering CH4 grid: "
2321
            f"{e_gas_grid} %"
2322
        )
2323
2324
    else:
2325
        print("Testmode is on, skipping sanity check.")
2326
2327
2328
def sanitycheck_dsm():
2329
    def df_from_series(s: pd.Series):
2330
        return pd.DataFrame.from_dict(dict(zip(s.index, s.values)))
2331
2332
    for scenario in ["eGon2035", "eGon100RE"]:
2333
        # p_min and p_max
2334
        sql = f"""
2335
        SELECT link_id, bus0 as bus, p_nom FROM grid.egon_etrago_link
2336
        WHERE carrier = 'dsm'
2337
        AND scn_name = '{scenario}'
2338
        ORDER BY link_id
2339
        """
2340
2341
        meta_df = db.select_dataframe(sql, index_col="link_id")
2342
        link_ids = str(meta_df.index.tolist())[1:-1]
2343
2344
        sql = f"""
2345
        SELECT link_id, p_min_pu, p_max_pu
2346
        FROM grid.egon_etrago_link_timeseries
2347
        WHERE scn_name = '{scenario}'
2348
        AND link_id IN ({link_ids})
2349
        ORDER BY link_id
2350
        """
2351
2352
        ts_df = db.select_dataframe(sql, index_col="link_id")
2353
2354
        p_max_df = df_from_series(ts_df.p_max_pu).mul(meta_df.p_nom)
2355
        p_min_df = df_from_series(ts_df.p_min_pu).mul(meta_df.p_nom)
2356
2357
        p_max_df.columns = meta_df.bus.tolist()
2358
        p_min_df.columns = meta_df.bus.tolist()
2359
2360
        targets = config.datasets()["DSM_CTS_industry"]["targets"]
2361
2362
        tables = [
2363
            "cts_loadcurves_dsm",
2364
            "ind_osm_loadcurves_individual_dsm",
2365
            "demandregio_ind_sites_dsm",
2366
            "ind_sites_loadcurves_individual",
2367
        ]
2368
2369
        df_list = []
2370
2371
        for table in tables:
2372
            target = targets[table]
2373
            sql = f"""
2374
            SELECT bus, p_min, p_max, e_max, e_min
2375
            FROM {target["schema"]}.{target["table"]}
2376
            WHERE scn_name = '{scenario}'
2377
            ORDER BY bus
2378
            """
2379
2380
            df_list.append(db.select_dataframe(sql))
2381
2382
        individual_ts_df = pd.concat(df_list, ignore_index=True)
2383
2384
        groups = individual_ts_df[["bus"]].reset_index().groupby("bus").groups
2385
2386
        individual_p_max_df = df_from_series(individual_ts_df.p_max)
2387
2388
        individual_p_max_df = pd.DataFrame(
2389
            [
2390
                individual_p_max_df[idxs].sum(axis=1)
2391
                for idxs in groups.values()
2392
            ],
2393
            index=groups.keys(),
2394
        ).T
2395
2396
        individual_p_min_df = df_from_series(individual_ts_df.p_min)
2397
2398
        individual_p_min_df = pd.DataFrame(
2399
            [
2400
                individual_p_min_df[idxs].sum(axis=1)
2401
                for idxs in groups.values()
2402
            ],
2403
            index=groups.keys(),
2404
        ).T
2405
2406
        # due to the fact that time series are clipped at zero (either
2407
        # direction) there is a little difference between the sum of the
2408
        # individual time series and the aggregated time series as the second
2409
        # is generated independent of the others. This makes atol=1e-01
2410
        # necessary.
2411
        atol = 1e-01
2412
        assert np.allclose(p_max_df, individual_p_max_df, atol=atol)
2413
        assert np.allclose(p_min_df, individual_p_min_df, atol=atol)
2414
2415
        # e_min and e_max
2416
        sql = f"""
2417
        SELECT store_id, bus, e_nom FROM grid.egon_etrago_store
2418
        WHERE carrier = 'dsm'
2419
        AND scn_name = '{scenario}'
2420
        ORDER BY store_id
2421
        """
2422
2423
        meta_df = db.select_dataframe(sql, index_col="store_id")
2424
        store_ids = str(meta_df.index.tolist())[1:-1]
2425
2426
        sql = f"""
2427
        SELECT store_id, e_min_pu, e_max_pu
2428
        FROM grid.egon_etrago_store_timeseries
2429
        WHERE scn_name = '{scenario}'
2430
        AND store_id IN ({store_ids})
2431
        ORDER BY store_id
2432
        """
2433
2434
        ts_df = db.select_dataframe(sql, index_col="store_id")
2435
2436
        e_max_df = df_from_series(ts_df.e_max_pu).mul(meta_df.e_nom)
2437
        e_min_df = df_from_series(ts_df.e_min_pu).mul(meta_df.e_nom)
2438
2439
        e_max_df.columns = meta_df.bus.tolist()
2440
        e_min_df.columns = meta_df.bus.tolist()
2441
2442
        individual_e_max_df = df_from_series(individual_ts_df.e_max)
2443
2444
        individual_e_max_df = pd.DataFrame(
2445
            [
2446
                individual_e_max_df[idxs].sum(axis=1)
2447
                for idxs in groups.values()
2448
            ],
2449
            index=groups.keys(),
2450
        ).T
2451
        individual_e_min_df = df_from_series(individual_ts_df.e_min)
2452
2453
        individual_e_min_df = pd.DataFrame(
2454
            [
2455
                individual_e_min_df[idxs].sum(axis=1)
2456
                for idxs in groups.values()
2457
            ],
2458
            index=groups.keys(),
2459
        ).T
2460
2461
        assert np.allclose(e_max_df, individual_e_max_df)
2462
        assert np.allclose(e_min_df, individual_e_min_df)
2463
2464
2465
def etrago_timeseries_length():
2466
2467
    for component in ["generator", "load", "link", "store", "storage"]:
2468
2469
        columns = db.select_dataframe(
2470
            f"""
2471
            SELECT *
2472
            FROM information_schema.columns
2473
            WHERE table_schema = 'grid'
2474
            AND table_name = 'egon_etrago_{component}_timeseries'
2475
            """
2476
        )
2477
        columns = columns[columns.data_type == "ARRAY"].column_name.values
2478
2479
        for col in columns:
2480
            lengths = db.select_dataframe(
2481
                f"""
2482
                SELECT array_length({col}, 1)
2483
                FROM grid.egon_etrago_{component}_timeseries;
2484
                """
2485
            )["array_length"]
2486
2487
            if not lengths.dropna().empty:
2488
                assert (
2489
                    lengths.dropna() == 8760
2490
                ).all(), (
2491
                    f"Timeseries with a length != 8760 for {component} {col}"
2492
                )
2493
            else:
2494
                print(f"Empty timeseries for {component} {col}")
2495
2496
2497
def generators_links_storages_stores_100RE(scn="eGon100RE"):
2498
    # Generators
2499
    scn_capacities = db.select_dataframe(
2500
        f"""
2501
        SELECT * FROM supply.egon_scenario_capacities
2502
        WHERE scenario_name = '{scn}'
2503
        """,
2504
        index_col="index",
2505
    )
2506
2507
    map_carrier = {
2508
        "urban_central_solar_thermal_collector": "solar_thermal_collector",
2509
        "urban_central_geo_thermal": "geo_thermal",
2510
        "urban_central_gas_boiler": "central_gas_boiler",
2511
        "urban_central_heat_pump": "central_heat_pump",
2512
        "urban_central_resistive_heater": "central_resistive_heater",
2513
        "gas": "OCGT",
2514
    }
2515
2516
    scn_capacities["carrier"] = scn_capacities["carrier"].apply(
2517
        lambda x: map_carrier[x] if x in map_carrier.keys() else x
2518
    )
2519
2520
    carriers_gen_from_supply = [
2521
        "oil",
2522
        "solar",
2523
        "solar_rooftop",
2524
        "wind_onshore",
2525
        "lignite",
2526
        "coal",
2527
        "wind_offshore",
2528
        "solar_thermal_collector",
2529
        "geo_thermal",
2530
        "run_of_river",
2531
        "rural_solar_thermal",
2532
        "urban_central_gas_CHP",
2533
        "urban_central_solid_biomass_CHP",
2534
    ]
2535
2536
    gen_etrago = db.select_dataframe(
2537
        f"""
2538
        SELECT * FROM grid.egon_etrago_generator
2539
        WHERE scn_name = '{scn}'
2540
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2541
                    WHERE scn_name = '{scn}'
2542
                    AND country = 'DE')
2543
        """,
2544
        warning=False,
2545
    )
2546
2547
    carriers_gen = set(carriers_gen_from_supply + list(gen_etrago["carrier"]))
2548
2549
    gen_capacities = pd.DataFrame(
2550
        index=list(carriers_gen), columns=["supply_table", scn]
2551
    )
2552
    gen_capacities[scn] = gen_etrago.groupby("carrier").p_nom.sum()
2553
2554
    gen_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2555
        "capacity"
2556
    ]
2557
2558
    gen_capacities.dropna(how="all", inplace=True)
2559
2560
    print(f"\nMain results regarding generators for {scn}\n")
2561
    print(gen_capacities)
2562
2563
    ###########################################################################
2564
    # Links
2565
2566
    carriers_links_from_supply = [
2567
        "central_gas_boiler",
2568
        "central_heat_pump",
2569
        "central_resistive_heater",
2570
        "gas",
2571
        "rural_biomass_boiler",
2572
        "rural_gas_boiler",
2573
        "rural_heat_pump",
2574
        "rural_oil_boiler",
2575
        "rural_resistive_heater",
2576
    ]
2577
2578
    link_etrago = db.select_dataframe(
2579
        f"""
2580
        SELECT * FROM grid.egon_etrago_link
2581
        WHERE scn_name = '{scn}'
2582
        AND (bus0 IN (SELECT bus_id from grid.egon_etrago_bus
2583
                    WHERE scn_name = '{scn}'
2584
                    AND country = 'DE')
2585
             OR
2586
             bus1 IN (SELECT bus_id from grid.egon_etrago_bus
2587
                    WHERE scn_name = '{scn}'
2588
                    AND country = 'DE')
2589
             )
2590
        """,
2591
        warning=False,
2592
    )
2593
2594
    carriers_link = set(
2595
        carriers_links_from_supply + list(link_etrago["carrier"])
2596
    )
2597
2598
    link_capacities = pd.DataFrame(
2599
        index=list(carriers_link), columns=["supply_table", scn]
2600
    )
2601
2602
    link_capacities["eGon100RE"] = link_etrago.groupby("carrier").p_nom.sum()
2603
2604
    link_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2605
        "capacity"
2606
    ]
2607
2608
    link_capacities.dropna(how="all", inplace=True)
2609
2610
    print(f"\nMain results regarding links for {scn}\n")
2611
    print(link_capacities)
2612
    ###########################################################################
2613
    # storage
2614
    storage_etrago = db.select_dataframe(
2615
        f"""
2616
        SELECT * FROM grid.egon_etrago_storage
2617
        WHERE scn_name = '{scn}'
2618
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2619
                    WHERE scn_name = '{scn}'
2620
                    AND country = 'DE')
2621
        """,
2622
    )
2623
2624
    carriers_storage_from_supply = ["pumped_hydro"]
2625
2626
    carriers_storage = set(
2627
        carriers_storage_from_supply + list(storage_etrago["carrier"])
2628
    )
2629
2630
    storage_capacities = pd.DataFrame(
2631
        index=list(carriers_storage), columns=["supply_table", scn]
2632
    )
2633
2634
    storage_capacities[scn] = storage_etrago.groupby("carrier").p_nom.sum()
2635
2636
    storage_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2637
        "capacity"
2638
    ]
2639
2640
    print(f"\nMain results regarding storage units for {scn}\n")
2641
    print(storage_capacities)
2642
    ###########################################################################
2643
    # stores
2644
    stores_etrago = db.select_dataframe(
2645
        f"""
2646
        SELECT * FROM grid.egon_etrago_store
2647
        WHERE scn_name = '{scn}'
2648
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2649
                    WHERE scn_name = '{scn}'
2650
                    AND country = 'DE')
2651
        """,
2652
    )
2653
2654
    carriers_stores_from_supply = []
2655
2656
    carriers_stores = set(
2657
        carriers_stores_from_supply + list(stores_etrago["carrier"])
2658
    )
2659
2660
    stores_capacities = pd.DataFrame(
2661
        index=list(carriers_stores), columns=["supply_table", scn]
2662
    )
2663
2664
    stores_capacities[scn] = stores_etrago.groupby("carrier").e_nom.sum()
2665
2666
    stores_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2667
        "capacity"
2668
    ]
2669
2670
    print(f"\nMain results regarding stores for {scn}\n")
2671
    print(stores_capacities)
2672
2673
    return
2674
2675
2676
def electrical_load_100RE(scn="eGon100RE"):
2677
    load_summary = pd.DataFrame(
2678
        index=[
2679
            "residential",
2680
            "commercial",
2681
            "industrial",
2682
            "total",
2683
        ],
2684
        columns=["objective", "eGon100RE"],
2685
    )
2686
2687
    # Sector	Annual electricity demand in TWh
2688
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L480
2689
    load_summary.loc["residential", "objective"] = 90.4
2690
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L775
2691
    load_summary.loc["commercial", "objective"] = 146.7
2692
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L775
2693
    load_summary.loc["industrial", "objective"] = 382.9
2694
    load_summary.loc["total", "objective"] = 620.0
2695
2696
    print(
2697
        "For German electricity loads the following deviations between the"
2698
        " input and output can be observed:"
2699
    )
2700
2701
    load_summary.loc["total", "eGon100RE"] = db.select_dataframe(
2702
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
2703
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
2704
            FROM grid.egon_etrago_load a
2705
            JOIN grid.egon_etrago_load_timeseries b
2706
            ON (a.load_id = b.load_id)
2707
            JOIN grid.egon_etrago_bus c
2708
            ON (a.bus=c.bus_id)
2709
            AND b.scn_name = 'eGon100RE'
2710
            AND a.scn_name = 'eGon100RE'
2711
            AND a.carrier = 'AC'
2712
            AND c.scn_name= 'eGon100RE'
2713
            AND c.country='DE'
2714
            GROUP BY (a.scn_name, a.carrier);
2715
    """,
2716
        warning=False,
2717
    )["load_twh"].values[0]
2718
2719
    sources = egon.data.config.datasets()["etrago_electricity"]["sources"]
2720
    cts_curves = db.select_dataframe(
2721
        f"""SELECT bus_id AS bus, p_set FROM
2722
                {sources['cts_curves']['schema']}.
2723
                {sources['cts_curves']['table']}
2724
                WHERE scn_name = '{scn}'""",
2725
    )
2726
    sum_cts_curves = (
2727
        cts_curves.apply(lambda x: sum(x["p_set"]), axis=1).sum() / 1000000
2728
    )
2729
    load_summary.loc["commercial", "eGon100RE"] = sum_cts_curves
2730
2731
    # Select data on industrial demands assigned to osm landuse areas
2732
    ind_curves_osm = db.select_dataframe(
2733
        f"""SELECT bus, p_set FROM
2734
                {sources['osm_curves']['schema']}.
2735
                {sources['osm_curves']['table']}
2736
                WHERE scn_name = '{scn}'""",
2737
    )
2738
    sum_ind_curves_osm = (
2739
        ind_curves_osm.apply(lambda x: sum(x["p_set"]), axis=1).sum() / 1000000
2740
    )
2741
2742
    # Select data on industrial demands assigned to industrial sites
2743
2744
    ind_curves_sites = db.select_dataframe(
2745
        f"""SELECT bus, p_set FROM
2746
                {sources['sites_curves']['schema']}.
2747
                {sources['sites_curves']['table']}
2748
                WHERE scn_name = '{scn}'""",
2749
    )
2750
    sum_ind_curves_sites = (
2751
        ind_curves_sites.apply(lambda x: sum(x["p_set"]), axis=1).sum()
2752
        / 1000000
2753
    )
2754
2755
    load_summary.loc["industrial", "eGon100RE"] = (
2756
        sum_ind_curves_osm + sum_ind_curves_sites
2757
    )
2758
2759
    # Select data on household electricity demands per bus
2760
    hh_curves = db.select_dataframe(
2761
        f"""SELECT bus_id AS bus, p_set FROM
2762
                {sources['household_curves']['schema']}.
2763
                {sources['household_curves']['table']}
2764
                WHERE scn_name = '{scn}'""",
2765
    )
2766
    sum_hh_curves = (
2767
        hh_curves.apply(lambda x: sum(x["p_set"]), axis=1).sum() / 1000000
2768
    )
2769
    load_summary.loc["residential", "eGon100RE"] = sum_hh_curves
2770
2771
    load_summary["diff"] = (
2772
        load_summary["eGon100RE"] - load_summary["objective"]
2773
    )
2774
    load_summary["diff[%]"] = (
2775
        load_summary["diff"] / load_summary["eGon100RE"] * 100
2776
    )
2777
2778
    print(load_summary)
2779
2780
    assert (
2781
        load_summary["diff[%]"] < 1
2782
    ).all(), "electrical loads differ from objective values"
2783
2784
    return ()
2785
2786
2787
def heat_gas_load_egon100RE(scn="eGon100RE"):
2788
2789
    # dictionary for matching pypsa_eur carrier with egon-data carriers
2790
    load_carrier_dict = {
2791
        "DE0 0 land transport EV": "land transport EV",
2792
        "DE0 0 rural heat": "rural_heat",
2793
        "DE0 0 urban central heat": "central_heat",
2794
        "DE0 0 urban decentral heat": "rural_heat",
2795
        "rural heat": "rural_heat",
2796
        "H2 for industry": "H2_for_industry",
2797
        "gas for industry": "CH4_for_industry",
2798
        "urban central heat": "central_heat",
2799
        "urban decentral heat": "rural_heat",
2800
        "land transport EV": "land transport EV",
2801
    }
2802
2803
    # filter out NaN values central_heat timeseries
2804
    NaN_load_ids = db.select_dataframe(
2805
        """
2806
        SELECT load_id from grid.egon_etrago_load_timeseries
2807
        WHERE load_id IN (Select load_id
2808
            FROM grid.egon_etrago_load
2809
            WHERE carrier = 'central_heat') AND (SELECT
2810
            bool_or(value::double precision::text = 'NaN')
2811
        FROM unnest(p_set) AS value
2812
        )
2813
       """
2814
    )
2815
    nan_load_list = tuple(NaN_load_ids["load_id"].tolist())
2816
    nan_load_str = ",".join(map(str, nan_load_list))
2817
2818
    #####loads for eGon100RE
2819
    loads_etrago_timeseries = db.select_dataframe(
2820
        f"""
2821
            SELECT
2822
                l.carrier,
2823
                SUM(
2824
                    (SELECT SUM(p)
2825
                    FROM UNNEST(t.p_set) p)
2826
                )  AS total_p_set_timeseries
2827
            FROM
2828
                grid.egon_etrago_load l
2829
            LEFT JOIN
2830
                grid.egon_etrago_load_timeseries t ON l.load_id = t.load_id
2831
            WHERE
2832
                l.scn_name = '{scn}'
2833
                AND l.carrier != 'AC'
2834
                AND l.bus IN (
2835
                    SELECT bus_id
2836
                    FROM grid.egon_etrago_bus
2837
                    WHERE scn_name = '{scn}'
2838
                    AND country = 'DE'
2839
                )
2840
                AND l.load_id NOT IN ({nan_load_str})
2841
2842
            GROUP BY
2843
                l.carrier
2844
        """
2845
    )
2846
2847
    #####loads for pypsa_eur_network
2848
    n = read_network()
2849
2850
    # aggregate loads with values in timeseries dataframe
2851
    df_load_timeseries = n.loads_t.p_set
2852
    filtered_columns = [
2853
        col
2854
        for col in df_load_timeseries.columns
2855
        if col.startswith("DE") and "electricity" not in col
2856
    ]
2857
    german_loads_timeseries = df_load_timeseries[filtered_columns]
2858
    german_loads_timeseries = german_loads_timeseries.drop(columns=["DE0 0"])
2859
    german_loads_timeseries = german_loads_timeseries.mul(
2860
        n.snapshot_weightings.generators, axis=0
2861
    ).sum()
2862
    german_loads_timeseries = german_loads_timeseries.rename(
2863
        index=load_carrier_dict
2864
    )
2865
2866
    # sum loads with fixed p_set in loads dataframe
2867
    german_load_static_p_set = n.loads[
2868
        n.loads.index.str.startswith("DE")
2869
        & ~n.loads.carrier.str.contains("electricity")
2870
    ]
2871
    german_load_static_p_set = (
2872
        german_load_static_p_set.groupby("carrier").p_set.sum() * 8760
2873
    )
2874
    german_load_static_p_set = german_load_static_p_set.rename(
2875
        index=load_carrier_dict
2876
    )
2877
    german_load_static_p_set["H2_for_industry"] = (
2878
        german_load_static_p_set["H2_for_industry"]
2879
        + +n.links_t.p0[
2880
            n.links.loc[
2881
                n.links.index.str.contains("DE0 0 Fischer-Tropsch")
2882
            ].index
2883
        ]
2884
        .mul(n.snapshot_weightings.generators, axis=0)
2885
        .sum()
2886
        .sum()
2887
        + n.links_t.p0[
2888
            n.links.loc[
2889
                n.links.index.str.contains("DE0 0 methanolisation")
2890
            ].index
2891
        ]
2892
        .mul(n.snapshot_weightings.generators, axis=0)
2893
        .sum()
2894
        .sum()
2895
    )
2896
2897
    # combine p_set and timeseries dataframes from pypsa eur
2898
    german_loads_timeseries_df = german_loads_timeseries.to_frame()
2899
    german_loads_timeseries_df["carrier"] = german_loads_timeseries_df.index
2900
    german_loads_timeseries_df.set_index("carrier", inplace=True)
2901
2902
    german_load_static_p_set_df = german_load_static_p_set.to_frame()
2903
    german_load_static_p_set_df = german_load_static_p_set_df.groupby(
2904
        "carrier", as_index=True
2905
    ).sum()
2906
    german_loads_timeseries_df = german_loads_timeseries_df.groupby(
2907
        "carrier", as_index=True
2908
    ).sum()
2909
    combined = pd.merge(
2910
        german_load_static_p_set_df,
2911
        german_loads_timeseries_df,
2912
        on="carrier",
2913
        how="left",
2914
    )
2915
2916
    combined["p_set"] = np.where(
2917
        combined["p_set"] == 0, combined[0], combined["p_set"]
2918
    )
2919
    combined = combined.drop(columns=[0])
2920
2921
    # carriers_for_comparison
2922
    carriers_loads = set(
2923
        german_load_static_p_set.index.union(
2924
            german_loads_timeseries.index
2925
        ).union(loads_etrago_timeseries["carrier"])
2926
    )
2927
2928
    # create dataframe for comparison
2929
    loads_capacities = pd.DataFrame(
2930
        index=list(carriers_loads), columns=["pypsa_eur", scn]
2931
    )
2932
    loads_capacities[scn] = loads_etrago_timeseries.groupby(
2933
        "carrier"
2934
    ).total_p_set_timeseries.sum()
2935
    loads_capacities["pypsa_eur"] = combined["p_set"]
2936
    loads_capacities["diff [%]"] = (
2937
        (loads_capacities[scn] - loads_capacities["pypsa_eur"])
2938
        / loads_capacities["pypsa_eur"].replace(0, np.nan)
2939
    ) * 100
2940
2941
    print("=" * 50)
2942
    print(
2943
        "Comparison of Gas and Heat Loads with PyPSA-Eur Data".center(50, "=")
2944
    )
2945
    print("=" * 50)
2946
    print(loads_capacities)
2947
2948
2949
tasks = ()
2950
2951
if "eGon2035" in SCENARIOS:
2952
    tasks = tasks + (
2953
        etrago_eGon2035_electricity,
2954
        etrago_eGon2035_heat,
2955
        residential_electricity_annual_sum,
2956
        residential_electricity_hh_refinement,
2957
        cts_electricity_demand_share,
2958
        cts_heat_demand_share,
2959
        sanitycheck_emobility_mit,
2960
        sanitycheck_pv_rooftop_buildings,
2961
        sanitycheck_home_batteries,
2962
        etrago_eGon2035_gas_DE,
2963
        etrago_eGon2035_gas_abroad,
2964
        sanitycheck_dsm,
2965
    )
2966
2967
if "eGon100RE" in SCENARIOS:
2968
    tasks = tasks + (
2969
        electrical_load_100RE,
2970
        generators_links_storages_stores_100RE,
2971
        etrago_timeseries_length,
2972
        heat_gas_load_egon100RE,
2973
    )
2974
2975
2976
class SanityChecks(Dataset):
2977
    #:
2978
    name: str = "SanityChecks"
2979
    #:
2980
    version: str = "0.0.9"
2981
2982
    def __init__(self, dependencies):
2983
        super().__init__(
2984
            name=self.name,
2985
            version=self.version,
2986
            dependencies=dependencies,
2987
            tasks=tasks,
2988
        )
2989