sanity_check_H2_saltcavern_stores()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 51
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 15
dl 0
loc 51
rs 9.65
c 0
b 0
f 0
cc 1
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
from math import isclose
9
from pathlib import Path
10
import ast
11
12
from sqlalchemy import Numeric
13
from sqlalchemy.sql import and_, cast, func, or_
14
import matplotlib.pyplot as plt
15
import numpy as np
16
import pandas as pd
17
import seaborn as sns
18
19
from egon.data import config, db, logger
20
from egon.data.datasets import Dataset
21
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
22
    EgonCtsElectricityDemandBuildingShare,
23
    EgonCtsHeatDemandBuildingShare,
24
)
25
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (  # noqa: E501
26
    EgonEvCountMunicipality,
27
    EgonEvCountMvGridDistrict,
28
    EgonEvCountRegistrationDistrict,
29
    EgonEvMvGridDistrict,
30
    EgonEvPool,
31
    EgonEvTrip,
32
)
33
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
34
    DATASET_CFG,
35
    read_simbev_metadata_file,
36
)
37
from egon.data.datasets.etrago_setup import (
38
    EgonPfHvLink,
39
    EgonPfHvLinkTimeseries,
40
    EgonPfHvLoad,
41
    EgonPfHvLoadTimeseries,
42
    EgonPfHvStore,
43
    EgonPfHvStoreTimeseries,
44
)
45
from egon.data.datasets.gas_grid import (
46
    define_gas_buses_abroad,
47
    define_gas_nodes_list,
48
    define_gas_pipeline_list,
49
)
50
from egon.data.datasets.gas_neighbours.eGon2035 import (
51
    calc_capacities,
52
    calc_ch4_storage_capacities,
53
    calc_global_ch4_demand,
54
    calc_global_power_to_h2_demand,
55
    calculate_ch4_grid_capacities,
56
    import_ch4_demandTS,
57
)
58
from egon.data.datasets.hydrogen_etrago.storage import (
59
    calculate_and_map_saltcavern_storage_potential,
60
)
61
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
62
    PV_CAP_PER_SQ_M,
63
    ROOF_FACTOR,
64
    SCENARIOS,
65
    load_building_data,
66
    scenario_data,
67
)
68
from egon.data.datasets.pypsaeur import read_network
69
from egon.data.datasets.scenario_parameters import get_sector_parameters
70
from egon.data.datasets.storages.home_batteries import get_cbat_pbat_ratio
71
from egon.data.datasets.pypsaeur import read_network
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=1e-5):
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=1e-5):
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=1e-5):
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 eGon2035, eGon2035_lowflex and eGon100RE scenario
810
    using assertions:
811
812
    1. Allocated EV numbers and EVs allocated to grid districts
813
    2. Trip data (original inout data from simBEV)
814
    3. Model data in eTraGo PF tables (grid.egon_etrago_*)
815
816
    Parameters
817
    ----------
818
    None
819
820
    Returns
821
    -------
822
    None
823
    """
824
825
    def check_ev_allocation():
826
        # Get target number for scenario
827
        ev_count_target = scenario_variation_parameters["ev_count"]
828
        print(f"  Target count: {str(ev_count_target)}")
829
830
        # Get allocated numbers
831
        ev_counts_dict = {}
832
        with db.session_scope() as session:
833
            for table, level in zip(
834
                [
835
                    EgonEvCountMvGridDistrict,
836
                    EgonEvCountMunicipality,
837
                    EgonEvCountRegistrationDistrict,
838
                ],
839
                ["Grid District", "Municipality", "Registration District"],
840
            ):
841
                query = session.query(
842
                    func.sum(
843
                        table.bev_mini
844
                        + table.bev_medium
845
                        + table.bev_luxury
846
                        + table.phev_mini
847
                        + table.phev_medium
848
                        + table.phev_luxury
849
                    ).label("ev_count")
850
                ).filter(
851
                    table.scenario == scenario_name,
852
                    table.scenario_variation == scenario_var_name,
853
                )
854
855
                ev_counts = pd.read_sql(
856
                    query.statement, query.session.bind, index_col=None
857
                )
858
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
859
                print(
860
                    f"    Count table: Total count for level {level} "
861
                    f"(table: {table.__table__}): "
862
                    f"{str(ev_counts_dict[level])}"
863
                )
864
865
        # Compare with scenario target (only if not in testmode)
866
        if TESTMODE_OFF:
867
            for level, count in ev_counts_dict.items():
868
                np.testing.assert_allclose(
869
                    count,
870
                    ev_count_target,
871
                    rtol=0.0001,
872
                    err_msg=f"EV numbers in {level} seems to be flawed.",
873
                )
874
        else:
875
            print("    Testmode is on, skipping sanity check...")
876
877
        # Get allocated EVs in grid districts
878
        with db.session_scope() as session:
879
            query = session.query(
880
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
881
                    "ev_count"
882
                ),
883
            ).filter(
884
                EgonEvMvGridDistrict.scenario == scenario_name,
885
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
886
            )
887
        ev_count_alloc = (
888
            pd.read_sql(query.statement, query.session.bind, index_col=None)
889
            .iloc[0]
890
            .ev_count
891
        )
892
        print(
893
            f"    EVs allocated to Grid Districts "
894
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
895
            f"{str(ev_count_alloc)}"
896
        )
897
898
        # Compare with scenario target (only if not in testmode)
899
        if TESTMODE_OFF:
900
            np.testing.assert_allclose(
901
                ev_count_alloc,
902
                ev_count_target,
903
                rtol=0.0001,
904
                err_msg=(
905
                    "EV numbers allocated to Grid Districts seems to be "
906
                    "flawed."
907
                ),
908
            )
909
        else:
910
            print("    Testmode is on, skipping sanity check...")
911
912
        return ev_count_alloc
913
914
    def check_trip_data():
915
        # Check if trips start at timestep 0 and have a max. of 35040 steps
916
        # (8760h in 15min steps)
917
        print("  Checking timeranges...")
918
        with db.session_scope() as session:
919
            query = session.query(
920
                func.count(EgonEvTrip.event_id).label("cnt")
921
            ).filter(
922
                or_(
923
                    and_(
924
                        EgonEvTrip.park_start > 0,
925
                        EgonEvTrip.simbev_event_id == 0,
926
                    ),
927
                    EgonEvTrip.park_end
928
                    > (60 / int(meta_run_config.stepsize)) * 8760,
929
                ),
930
                EgonEvTrip.scenario == scenario_name,
931
            )
932
        invalid_trips = pd.read_sql(
933
            query.statement, query.session.bind, index_col=None
934
        )
935
        np.testing.assert_equal(
936
            invalid_trips.iloc[0].cnt,
937
            0,
938
            err_msg=(
939
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
940
                f"{EgonEvTrip.__table__} have invalid timesteps."
941
            ),
942
        )
943
944
        # Check if charging demand can be covered by available charging energy
945
        # while parking
946
        print("  Compare charging demand with available power...")
947
        with db.session_scope() as session:
948
            query = session.query(
949
                func.count(EgonEvTrip.event_id).label("cnt")
950
            ).filter(
951
                func.round(
952
                    cast(
953
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
954
                        * EgonEvTrip.charging_capacity_nominal
955
                        * (int(meta_run_config.stepsize) / 60),
956
                        Numeric,
957
                    ),
958
                    3,
959
                )
960
                < cast(EgonEvTrip.charging_demand, Numeric),
961
                EgonEvTrip.scenario == scenario_name,
962
            )
963
        invalid_trips = pd.read_sql(
964
            query.statement, query.session.bind, index_col=None
965
        )
966
        np.testing.assert_equal(
967
            invalid_trips.iloc[0].cnt,
968
            0,
969
            err_msg=(
970
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
971
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
972
                f"covered by available charging power."
973
            ),
974
        )
975
976
    def check_model_data():
977
        # Check if model components were fully created
978
        print("  Check if all model components were created...")
979
        # Get MVGDs which got EV allocated
980
        with db.session_scope() as session:
981
            query = (
982
                session.query(
983
                    EgonEvMvGridDistrict.bus_id,
984
                )
985
                .filter(
986
                    EgonEvMvGridDistrict.scenario == scenario_name,
987
                    EgonEvMvGridDistrict.scenario_variation
988
                    == scenario_var_name,
989
                )
990
                .group_by(EgonEvMvGridDistrict.bus_id)
991
            )
992
        mvgds_with_ev = (
993
            pd.read_sql(query.statement, query.session.bind, index_col=None)
994
            .bus_id.sort_values()
995
            .to_list()
996
        )
997
998
        # Load model components
999
        with db.session_scope() as session:
1000
            query = (
1001
                session.query(
1002
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
1003
                    EgonPfHvLoad.bus.label("emob_bus_id"),
1004
                    EgonPfHvLoad.load_id.label("load_id"),
1005
                    EgonPfHvStore.store_id.label("store_id"),
1006
                )
1007
                .select_from(EgonPfHvLoad, EgonPfHvStore)
1008
                .join(
1009
                    EgonPfHvLoadTimeseries,
1010
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1011
                )
1012
                .join(
1013
                    EgonPfHvStoreTimeseries,
1014
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
1015
                )
1016
                .filter(
1017
                    EgonPfHvLoad.carrier == "land_transport_EV",
1018
                    EgonPfHvLoad.scn_name == scenario_name,
1019
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
1020
                    EgonPfHvStore.carrier == "battery_storage",
1021
                    EgonPfHvStore.scn_name == scenario_name,
1022
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
1023
                    EgonPfHvLink.scn_name == scenario_name,
1024
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
1025
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
1026
                )
1027
            )
1028
        model_components = pd.read_sql(
1029
            query.statement, query.session.bind, index_col=None
1030
        )
1031
1032
        # Check number of buses with model components connected
1033
        mvgd_buses_with_ev = model_components.loc[
1034
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
1035
        ]
1036
        np.testing.assert_equal(
1037
            len(mvgds_with_ev),
1038
            len(mvgd_buses_with_ev),
1039
            err_msg=(
1040
                f"Number of Grid Districts with connected model components "
1041
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
1042
                f"differ from number of Grid Districts that got EVs "
1043
                f"allocated ({len(mvgds_with_ev)} in table "
1044
                f"{EgonEvMvGridDistrict.__table__})."
1045
            ),
1046
        )
1047
1048
        # Check if all required components exist (if no id is NaN)
1049
        np.testing.assert_equal(
1050
            model_components.drop_duplicates().isna().any().any(),
1051
            False,
1052
            err_msg=(
1053
                f"Some components are missing (see True values): "
1054
                f"{model_components.drop_duplicates().isna().any()}"
1055
            ),
1056
        )
1057
1058
        # Get all model timeseries
1059
        print("  Loading model timeseries...")
1060
        # Get all model timeseries
1061
        model_ts_dict = {
1062
            "Load": {
1063
                "carrier": "land_transport_EV",
1064
                "table": EgonPfHvLoad,
1065
                "table_ts": EgonPfHvLoadTimeseries,
1066
                "column_id": "load_id",
1067
                "columns_ts": ["p_set"],
1068
                "ts": None,
1069
            },
1070
            "Link": {
1071
                "carrier": "BEV_charger",
1072
                "table": EgonPfHvLink,
1073
                "table_ts": EgonPfHvLinkTimeseries,
1074
                "column_id": "link_id",
1075
                "columns_ts": ["p_max_pu"],
1076
                "ts": None,
1077
            },
1078
            "Store": {
1079
                "carrier": "battery_storage",
1080
                "table": EgonPfHvStore,
1081
                "table_ts": EgonPfHvStoreTimeseries,
1082
                "column_id": "store_id",
1083
                "columns_ts": ["e_min_pu", "e_max_pu"],
1084
                "ts": None,
1085
            },
1086
        }
1087
1088
        with db.session_scope() as session:
1089
            for node, attrs in model_ts_dict.items():
1090
                print(f"    Loading {node} timeseries...")
1091
                subquery = (
1092
                    session.query(getattr(attrs["table"], attrs["column_id"]))
1093
                    .filter(attrs["table"].carrier == attrs["carrier"])
1094
                    .filter(attrs["table"].scn_name == scenario_name)
1095
                    .subquery()
1096
                )
1097
1098
                cols = [
1099
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
1100
                ]
1101
                query = session.query(
1102
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
1103
                ).filter(
1104
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
1105
                        subquery
1106
                    ),
1107
                    attrs["table_ts"].scn_name == scenario_name,
1108
                )
1109
                attrs["ts"] = pd.read_sql(
1110
                    query.statement,
1111
                    query.session.bind,
1112
                    index_col=attrs["column_id"],
1113
                )
1114
1115
        # Check if all timeseries have 8760 steps
1116
        print("    Checking timeranges...")
1117
        for node, attrs in model_ts_dict.items():
1118
            for col in attrs["columns_ts"]:
1119
                ts = attrs["ts"]
1120
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
1121
                    col
1122
                ].apply(len)
1123
                np.testing.assert_equal(
1124
                    len(invalid_ts),
1125
                    0,
1126
                    err_msg=(
1127
                        f"{str(len(invalid_ts))} rows in timeseries do not "
1128
                        f"have 8760 timesteps. Table: "
1129
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
1130
                        f"{str(list(invalid_ts.index))}"
1131
                    ),
1132
                )
1133
1134
        # Compare total energy demand in model with some approximate values
1135
        # (per EV: 14,000 km/a, 0.17 kWh/km)
1136
        print("  Checking energy demand in model...")
1137
        total_energy_model = (
1138
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
1139
            / 1e6
1140
        )
1141
        print(f"    Total energy amount in model: {total_energy_model} TWh")
1142
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
1143
        print(
1144
            f"    Total approximated energy amount in scenario: "
1145
            f"{total_energy_scenario_approx} TWh"
1146
        )
1147
        np.testing.assert_allclose(
1148
            total_energy_model,
1149
            total_energy_scenario_approx,
1150
            rtol=0.1,
1151
            err_msg=(
1152
                "The total energy amount in the model deviates heavily "
1153
                "from the approximated value for current scenario."
1154
            ),
1155
        )
1156
1157
        # Compare total storage capacity
1158
        print("  Checking storage capacity...")
1159
        # Load storage capacities from model
1160
        with db.session_scope() as session:
1161
            query = session.query(
1162
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
1163
            ).filter(
1164
                EgonPfHvStore.scn_name == scenario_name,
1165
                EgonPfHvStore.carrier == "battery_storage",
1166
            )
1167
        storage_capacity_model = (
1168
            pd.read_sql(
1169
                query.statement, query.session.bind, index_col=None
1170
            ).e_nom.sum()
1171
            / 1e3
1172
        )
1173
        print(
1174
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
1175
            f"{round(storage_capacity_model, 1)} GWh"
1176
        )
1177
1178
        # Load occurences of each EV
1179
        with db.session_scope() as session:
1180
            query = (
1181
                session.query(
1182
                    EgonEvMvGridDistrict.bus_id,
1183
                    EgonEvPool.type,
1184
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
1185
                        "count"
1186
                    ),
1187
                )
1188
                .join(
1189
                    EgonEvPool,
1190
                    EgonEvPool.ev_id
1191
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
1192
                )
1193
                .filter(
1194
                    EgonEvMvGridDistrict.scenario == scenario_name,
1195
                    EgonEvMvGridDistrict.scenario_variation
1196
                    == scenario_var_name,
1197
                    EgonEvPool.scenario == scenario_name,
1198
                )
1199
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
1200
            )
1201
        count_per_ev_all = pd.read_sql(
1202
            query.statement, query.session.bind, index_col="bus_id"
1203
        )
1204
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
1205
            meta_tech_data.battery_capacity
1206
        )
1207
        count_per_ev_all["bat_cap_total_MWh"] = (
1208
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
1209
        )
1210
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
1211
            1e3
1212
        ).sum()
1213
        print(
1214
            f"    Total storage capacity (simBEV): "
1215
            f"{round(storage_capacity_simbev, 1)} GWh"
1216
        )
1217
1218
        np.testing.assert_allclose(
1219
            storage_capacity_model,
1220
            storage_capacity_simbev,
1221
            rtol=0.01,
1222
            err_msg=(
1223
                "The total storage capacity in the model deviates heavily "
1224
                "from the input data provided by simBEV for current 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 ["eGon2035", "eGon100RE"]:
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
    stores_cap_D = 266424202 # MWh GIE https://www.gie.eu/transparency/databases/storage-database/
1554
1555
    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...
1556
1557
    e_CH4_stores = (
1558
        round(
1559
            (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
1560
            2,
1561
        )
1562
        * 100
1563
    )
1564
    logger.info(f"Deviation CH4 stores: {e_CH4_stores} %")
1565
1566
1567
def sanity_check_H2_saltcavern_stores(scn):
1568
    """Execute sanity checks for the H2 saltcavern stores in Germany
1569
1570
    Returns print as sanity checks for the H2 saltcavern potential
1571
    storage capacity in Germany. The deviation is calculated between:
1572
1573
    * the sum of the of the H2 saltcavern potential storage capacity
1574
      (e_nom_max) in the database and
1575
    * the sum of the H2 saltcavern potential storage capacity
1576
      assumed to be the ratio of the areas of 500 m radius around
1577
      substations in each german federal state and the estimated
1578
      total hydrogen storage potential of the corresponding federal
1579
      state (data from InSpEE-DS report).
1580
1581
    This test works also in test mode.
1582
1583
    Parameters
1584
    ----------
1585
    scn_name : str
1586
        Name of the scenario
1587
1588
    """
1589
    output_H2_stores = db.select_dataframe(
1590
        f"""SELECT SUM(e_nom_max::numeric) as e_nom_max_germany
1591
                FROM grid.egon_etrago_store
1592
                WHERE scn_name = '{scn}'
1593
                AND carrier = 'H2_underground'
1594
                AND bus IN
1595
                    (SELECT bus_id
1596
                    FROM grid.egon_etrago_bus
1597
                    WHERE scn_name = '{scn}'
1598
                    AND country = 'DE'
1599
                    AND carrier = 'H2_saltcavern');
1600
                """,
1601
        warning=False,
1602
    )["e_nom_max_germany"].values[0]
1603
1604
    storage_potentials = calculate_and_map_saltcavern_storage_potential()
1605
    storage_potentials["storage_potential"] = (
1606
        storage_potentials["area_fraction"] * storage_potentials["potential"]
1607
    )
1608
    input_H2_stores = sum(storage_potentials["storage_potential"].to_list())
1609
1610
    e_H2_stores = (
1611
        round(
1612
            (output_H2_stores - input_H2_stores) / input_H2_stores,
1613
            2,
1614
        )
1615
        * 100
1616
    )
1617
    logger.info(f"Deviation H2 saltcavern stores: {e_H2_stores} %")
1618
1619
1620
def sanity_check_gas_one_port(scn):
1621
    """Check connections of gas one-port components
1622
1623
    Verify that gas one-port component (loads, generators, stores) are
1624
    all connected to a bus (of the right carrier) present in the data
1625
    base. Return print statements if this is not the case.
1626
    These sanity checks are not specific to Germany, they also include
1627
    the neighbouring countries.
1628
1629
    Parameters
1630
    ----------
1631
    scn_name : str
1632
        Name of the scenario
1633
1634
    """
1635
    if scn == "eGon2035":
1636
        # Loads
1637
        ## CH4_for_industry Germany
1638
        isolated_one_port_c = db.select_dataframe(
1639
            f"""
1640
            SELECT load_id, bus, carrier, scn_name
1641
                FROM grid.egon_etrago_load
1642
                WHERE scn_name = '{scn}'
1643
                AND carrier = 'CH4_for_industry'
1644
                AND bus NOT IN
1645
                    (SELECT bus_id
1646
                    FROM grid.egon_etrago_bus
1647
                    WHERE scn_name = '{scn}'
1648
                    AND country = 'DE'
1649
                    AND carrier = 'CH4')
1650
            ;
1651
            """,
1652
            warning=False,
1653
        )
1654
        if not isolated_one_port_c.empty:
1655
            logger.info("Isolated loads:")
1656
            logger.info(isolated_one_port_c)
1657
1658
        ## CH4_for_industry abroad
1659
        isolated_one_port_c = db.select_dataframe(
1660
            f"""
1661
            SELECT load_id, bus, carrier, scn_name
1662
                FROM grid.egon_etrago_load
1663
                WHERE scn_name = '{scn}'
1664
                AND carrier = 'CH4'
1665
                AND bus NOT IN
1666
                    (SELECT bus_id
1667
                    FROM grid.egon_etrago_bus
1668
                    WHERE scn_name = '{scn}'
1669
                    AND country != 'DE'
1670
                    AND carrier = 'CH4')
1671
            ;
1672
            """,
1673
            warning=False,
1674
        )
1675
        if not isolated_one_port_c.empty:
1676
            logger.info("Isolated loads:")
1677
            logger.info(isolated_one_port_c)
1678
1679
        ## H2_for_industry
1680
        isolated_one_port_c = db.select_dataframe(
1681
            f"""
1682
            SELECT load_id, bus, carrier, scn_name
1683
                FROM grid.egon_etrago_load
1684
                WHERE scn_name = '{scn}'
1685
                AND carrier = 'H2_for_industry'
1686
                AND (bus NOT IN
1687
                    (SELECT bus_id
1688
                    FROM grid.egon_etrago_bus
1689
                    WHERE scn_name = '{scn}'
1690
                    AND country = 'DE'
1691
                    AND carrier = 'H2_grid')
1692
                AND bus NOT IN
1693
                    (SELECT bus_id
1694
                    FROM grid.egon_etrago_bus
1695
                    WHERE scn_name = '{scn}'
1696
                    AND country != 'DE'
1697
                    AND carrier = 'AC'))
1698
            ;
1699
            """,
1700
            warning=False,
1701
        )
1702
        if not isolated_one_port_c.empty:
1703
            logger.info("Isolated loads:")
1704
            logger.info(isolated_one_port_c)
1705
1706
        # Genrators
1707
        isolated_one_port_c = db.select_dataframe(
1708
            f"""
1709
            SELECT generator_id, bus, carrier, scn_name
1710
                FROM grid.egon_etrago_generator
1711
                WHERE scn_name = '{scn}'
1712
                AND carrier = 'CH4'
1713
                AND bus NOT IN
1714
                    (SELECT bus_id
1715
                    FROM grid.egon_etrago_bus
1716
                    WHERE scn_name = '{scn}'
1717
                    AND carrier = 'CH4');
1718
            ;
1719
            """,
1720
            warning=False,
1721
        )
1722
        if not isolated_one_port_c.empty:
1723
            logger.info("Isolated generators:")
1724
            logger.info(isolated_one_port_c)
1725
1726
        # Stores
1727
        ## CH4 and H2_underground
1728
        corresponding_carriers = {
1729
            "CH4": "CH4",
1730
            "H2_saltcavern": "H2_underground",
1731
        }
1732
        for key in corresponding_carriers:
1733
            isolated_one_port_c = db.select_dataframe(
1734
                f"""
1735
                SELECT store_id, bus, carrier, scn_name
1736
                    FROM grid.egon_etrago_store
1737
                    WHERE scn_name = '{scn}'
1738
                    AND carrier = '{corresponding_carriers[key]}'
1739
                    AND bus NOT IN
1740
                        (SELECT bus_id
1741
                        FROM grid.egon_etrago_bus
1742
                        WHERE scn_name = '{scn}'
1743
                        AND carrier = '{key}')
1744
                ;
1745
                """,
1746
                warning=False,
1747
            )
1748
            if not isolated_one_port_c.empty:
1749
                logger.info("Isolated stores:")
1750
                logger.info(isolated_one_port_c)
1751
1752
        ## H2_overground
1753
        isolated_one_port_c = db.select_dataframe(
1754
            f"""
1755
            SELECT store_id, bus, carrier, scn_name
1756
                FROM grid.egon_etrago_store
1757
                WHERE scn_name = '{scn}'
1758
                AND carrier = 'H2_overground'
1759
                AND bus NOT IN
1760
                    (SELECT bus_id
1761
                    FROM grid.egon_etrago_bus
1762
                    WHERE scn_name = '{scn}'
1763
                    AND country = 'DE'
1764
                    AND carrier = 'H2_saltcavern')
1765
                AND bus NOT IN
1766
                    (SELECT bus_id
1767
                    FROM grid.egon_etrago_bus
1768
                    WHERE scn_name = '{scn}'
1769
                    AND country = 'DE'
1770
                    AND carrier = 'H2_grid')
1771
            ;
1772
            """,
1773
            warning=False,
1774
        )
1775
        if not isolated_one_port_c.empty:
1776
            logger.info("Isolated stores:")
1777
            logger.info(isolated_one_port_c)
1778
1779
    # elif scn == "eGon2035":
1780
1781
1782
def sanity_check_CH4_grid(scn):
1783
    """Execute sanity checks for the gas grid capacity in Germany
1784
1785
    Returns print statements as sanity checks for the CH4 links
1786
    (pipelines) in Germany. The deviation is calculated between
1787
    the sum of the power (p_nom) of all the CH4 pipelines in Germany
1788
    for one scenario in the database and the sum of the powers of the
1789
    imported pipelines.
1790
    In eGon100RE, the sum is reduced by the share of the grid that is
1791
    allocated to hydrogen (share calculated by PyPSA-eur-sec).
1792
    This test works also in test mode.
1793
1794
    Parameters
1795
    ----------
1796
    scn_name : str
1797
        Name of the scenario
1798
1799
    Returns
1800
    -------
1801
    scn_name : float
1802
        Sum of the power (p_nom) of all the pipelines in Germany
1803
1804
    """
1805
    grid_carrier = "CH4"
1806
    output_gas_grid = db.select_dataframe(
1807
        f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1808
            FROM grid.egon_etrago_link
1809
            WHERE scn_name = '{scn}'
1810
            AND carrier = '{grid_carrier}'
1811
            AND bus0 IN
1812
                (SELECT bus_id
1813
                FROM grid.egon_etrago_bus
1814
                WHERE scn_name = '{scn}'
1815
                AND country = 'DE'
1816
                AND carrier = '{grid_carrier}')
1817
            AND bus1 IN
1818
                (SELECT bus_id
1819
                FROM grid.egon_etrago_bus
1820
                WHERE scn_name = '{scn}'
1821
                AND country = 'DE'
1822
                AND carrier = '{grid_carrier}')
1823
                ;
1824
            """,
1825
        warning=False,
1826
    )["p_nom_germany"].values[0]
1827
1828
    gas_nodes_list = define_gas_nodes_list()
1829
    abroad_gas_nodes_list = define_gas_buses_abroad()
1830
    gas_grid = define_gas_pipeline_list(gas_nodes_list, abroad_gas_nodes_list)
1831
    gas_grid_germany = gas_grid[
1832
        (gas_grid["country_0"] == "DE") & (gas_grid["country_1"] == "DE")
1833
    ]
1834
    p_nom_total = sum(gas_grid_germany["p_nom"].to_list())
1835
1836
    if scn == "eGon2035":
1837
        input_gas_grid = p_nom_total
1838
    if scn == "eGon100RE":
1839
        input_gas_grid = p_nom_total * (
1840
            1
1841
            - get_sector_parameters("gas", "eGon100RE")[
1842
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1843
            ]
1844
        )
1845
1846
    e_gas_grid = (
1847
        round(
1848
            (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 1836 is False. Are you sure this can never be the case?
Loading history...
1849
            2,
1850
        )
1851
        * 100
1852
    )
1853
    logger.info(f"Deviation of the capacity of the CH4 grid: {e_gas_grid} %")
1854
1855
    return p_nom_total
1856
1857
1858
def sanity_check_gas_links(scn):
1859
    """Check connections of gas links
1860
1861
    Verify that gas links are all connected to buses present in the data
1862
    base. Return print statements if this is not the case.
1863
    This sanity check is not specific to Germany, it also includes
1864
    the neighbouring countries.
1865
1866
    Parameters
1867
    ----------
1868
    scn_name : str
1869
        Name of the scenario
1870
1871
    """
1872
    carriers = [
1873
        "CH4",
1874
        "H2_feedin",
1875
        "H2_to_CH4",
1876
        "CH4_to_H2",
1877
        "H2_to_power",
1878
        "power_to_H2",
1879
        "OCGT",
1880
        "central_gas_boiler",
1881
        "central_gas_CHP",
1882
        "central_gas_CHP_heat",
1883
        "industrial_gas_CHP",
1884
    ]
1885
    for c in carriers:
1886
        link_with_missing_bus = db.select_dataframe(
1887
            f"""
1888
            SELECT link_id, bus0, bus1, carrier, scn_name
1889
                FROM grid.egon_etrago_link
1890
                WHERE scn_name = '{scn}'
1891
                AND carrier = '{c}'
1892
                AND (bus0 NOT IN
1893
                    (SELECT bus_id
1894
                    FROM grid.egon_etrago_bus
1895
                    WHERE scn_name = '{scn}')
1896
                OR bus1 NOT IN
1897
                    (SELECT bus_id
1898
                    FROM grid.egon_etrago_bus
1899
                    WHERE scn_name = '{scn}'))
1900
            ;
1901
            """,
1902
            warning=False,
1903
        )
1904
        if not link_with_missing_bus.empty:
1905
            logger.info("Links with missing bus:")
1906
            logger.info(link_with_missing_bus)
1907
1908
1909
def etrago_eGon2035_gas_DE():
1910
    """Execute basic sanity checks for the gas sector in eGon2035
1911
1912
    Returns print statements as sanity checks for the gas sector in
1913
    the eGon2035 scenario for the following components in Germany:
1914
1915
      * Buses: with the function :py:func:`sanity_check_gas_buses`
1916
      * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry'
1917
        the deviation is calculated between the sum of the loads in the
1918
        database and the sum the loads in the sources document
1919
        (opendata.ffe database)
1920
      * Generators: the deviation is calculated between the sums of the
1921
        nominal powers of the gas generators in the database and of
1922
        the ones in the sources document (Biogaspartner Einspeiseatlas
1923
        Deutschland from the dena and Productions from the SciGRID_gas
1924
        data)
1925
      * Stores: deviations for stores with following carriers are
1926
        calculated:
1927
1928
          * 'CH4': with the function :py:func:`sanity_check_CH4_stores`
1929
          * 'H2_underground': with the function :py:func:`sanity_check_H2_saltcavern_stores`
1930
      * One-port components (loads, generators, stores): verification
1931
        that they are all connected to a bus present in the data base
1932
        with the function :py:func:`sanity_check_gas_one_port`
1933
      * Links: verification:
1934
1935
        * that the gas links are all connected to buses present in
1936
          the data base with the function :py:func:`sanity_check_gas_links`
1937
        * of the capacity of the gas grid with the function
1938
          :py:func:`sanity_check_CH4_grid`
1939
1940
    """
1941
    scn = "eGon2035"
1942
1943
    if TESTMODE_OFF:
1944
        logger.info(f"Gas sanity checks for scenario {scn}")
1945
1946
        # Buses
1947
        sanity_check_gas_buses(scn)
1948
1949
        # Loads
1950
        logger.info("LOADS")
1951
1952
        path = Path(".") / "datasets" / "gas_data" / "demand"
1953
        corr_file = path / "region_corr.json"
1954
        df_corr = pd.read_json(corr_file)
1955
        df_corr = df_corr.loc[:, ["id_region", "name_short"]]
1956
        df_corr.set_index("id_region", inplace=True)
1957
1958
        for carrier in ["CH4_for_industry", "H2_for_industry"]:
1959
1960
            output_gas_demand = db.select_dataframe(
1961
                f"""SELECT (SUM(
1962
                    (SELECT SUM(p)
1963
                    FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
1964
                    FROM grid.egon_etrago_load a
1965
                    JOIN grid.egon_etrago_load_timeseries b
1966
                    ON (a.load_id = b.load_id)
1967
                    JOIN grid.egon_etrago_bus c
1968
                    ON (a.bus=c.bus_id)
1969
                    AND b.scn_name = '{scn}'
1970
                    AND a.scn_name = '{scn}'
1971
                    AND c.scn_name = '{scn}'
1972
                    AND c.country = 'DE'
1973
                    AND a.carrier = '{carrier}';
1974
                """,
1975
                warning=False,
1976
            )["load_twh"].values[0]
1977
1978
            input_gas_demand = pd.read_json(
1979
                path / (carrier + "_eGon2035.json")
1980
            )
1981
            input_gas_demand = input_gas_demand.loc[:, ["id_region", "value"]]
1982
            input_gas_demand.set_index("id_region", inplace=True)
1983
            input_gas_demand = pd.concat(
1984
                [input_gas_demand, df_corr], axis=1, join="inner"
1985
            )
1986
            input_gas_demand["NUTS0"] = (input_gas_demand["name_short"].str)[
1987
                0:2
1988
            ]
1989
            input_gas_demand = input_gas_demand[
1990
                input_gas_demand["NUTS0"].str.match("DE")
1991
            ]
1992
            input_gas_demand = sum(input_gas_demand.value.to_list()) / 1000000
1993
1994
            e_demand = (
1995
                round(
1996
                    (output_gas_demand - input_gas_demand) / input_gas_demand,
1997
                    2,
1998
                )
1999
                * 100
2000
            )
2001
            logger.info(f"Deviation {carrier}: {e_demand} %")
2002
2003
        # Generators
2004
        logger.info("GENERATORS")
2005
        carrier_generator = "CH4"
2006
2007
        output_gas_generation = db.select_dataframe(
2008
            f"""SELECT SUM(p_nom::numeric) as p_nom_germany
2009
                    FROM grid.egon_etrago_generator
2010
                    WHERE scn_name = '{scn}'
2011
                    AND carrier = '{carrier_generator}'
2012
                    AND bus IN
2013
                        (SELECT bus_id
2014
                        FROM grid.egon_etrago_bus
2015
                        WHERE scn_name = '{scn}'
2016
                        AND country = 'DE'
2017
                        AND carrier = '{carrier_generator}');
2018
                    """,
2019
            warning=False,
2020
        )["p_nom_germany"].values[0]
2021
2022
        target_file = (
2023
            Path(".")
2024
            / "datasets"
2025
            / "gas_data"
2026
            / "data"
2027
            / "IGGIELGN_Productions.csv"
2028
        )
2029
2030
        NG_generators_list = pd.read_csv(
2031
            target_file,
2032
            delimiter=";",
2033
            decimal=".",
2034
            usecols=["country_code", "param"],
2035
        )
2036
2037
        NG_generators_list = NG_generators_list[
2038
            NG_generators_list["country_code"].str.match("DE")
2039
        ]
2040
2041
        p_NG = 0
2042
        for index, row in NG_generators_list.iterrows():
2043
            param = ast.literal_eval(row["param"])
2044
            p_NG = p_NG + param["max_supply_M_m3_per_d"]
2045
        conversion_factor = 437.5  # MCM/day to MWh/h
2046
        p_NG = p_NG * conversion_factor
2047
2048
        basename = "Biogaspartner_Einspeiseatlas_Deutschland_2021.xlsx"
2049
        target_file = Path(".") / "data_bundle_egon_data" / "gas_data" / basename
2050
2051
        conversion_factor_b = 0.01083  # m^3/h to MWh/h
2052
        p_biogas = (
2053
            pd.read_excel(
2054
                target_file,
2055
                usecols=["Einspeisung Biomethan [(N*m^3)/h)]"],
2056
            )["Einspeisung Biomethan [(N*m^3)/h)]"].sum()
2057
            * conversion_factor_b
2058
        )
2059
2060
        input_gas_generation = p_NG + p_biogas
2061
        e_generation = (
2062
            round(
2063
                (output_gas_generation - input_gas_generation)
2064
                / input_gas_generation,
2065
                2,
2066
            )
2067
            * 100
2068
        )
2069
        logger.info(
2070
            f"Deviation {carrier_generator} generation: {e_generation} %"
2071
        )
2072
2073
        # Stores
2074
        logger.info("STORES")
2075
        sanity_check_CH4_stores(scn)
2076
        sanity_check_H2_saltcavern_stores(scn)
2077
2078
        # One-port components
2079
        sanity_check_gas_one_port(scn)
2080
2081
        # Links
2082
        logger.info("LINKS")
2083
        sanity_check_CH4_grid(scn)
2084
        sanity_check_gas_links(scn)
2085
2086
    else:
2087
        print("Testmode is on, skipping sanity check.")
2088
2089
2090
def etrago_eGon2035_gas_abroad():
2091
    """Execute basic sanity checks for the gas sector in eGon2035 abroad
2092
2093
    Returns print statements as sanity checks for the gas sector in
2094
    the eGon2035 scenario for the following components in Germany:
2095
2096
      * Buses
2097
      * Loads: for the carriers 'CH4' and 'H2_for_industry'
2098
        the deviation is calculated between the sum of the loads in the
2099
        database and the sum in the sources document (TYNDP)
2100
      * Generators: the deviation is calculated between the sums of the
2101
        nominal powers of the methane generators abroad in the database
2102
        and of the ones in the sources document (TYNDP)
2103
      * Stores: the deviation for methane stores abroad is calculated
2104
        between the sum of the capacities in the data base and the one
2105
        of the source document (SciGRID_gas data)
2106
      * Links: verification of the capacity of the crossbordering gas
2107
        grid pipelines.
2108
2109
    """
2110
    scn = "eGon2035"
2111
2112
    if TESTMODE_OFF:
2113
        logger.info(f"Gas sanity checks abroad for scenario {scn}")
2114
2115
        # Buses
2116
        logger.info("BUSES")
2117
2118
        # Are gas buses isolated?
2119
        corresponding_carriers = {
2120
            "eGon2035": {
2121
                "CH4": "CH4",
2122
            },
2123
            # "eGon100RE": {
2124
            #     "CH4": "CH4",
2125
            #     "H2_grid": "H2_retrofit",
2126
            # }
2127
        }
2128
        for key in corresponding_carriers[scn]:
2129
            isolated_gas_buses_abroad = db.select_dataframe(
2130
                f"""
2131
                SELECT bus_id, carrier, country
2132
                FROM grid.egon_etrago_bus
2133
                WHERE scn_name = '{scn}'
2134
                AND carrier = '{key}'
2135
                AND country != 'DE'
2136
                AND bus_id NOT IN
2137
                    (SELECT bus0
2138
                    FROM grid.egon_etrago_link
2139
                    WHERE scn_name = '{scn}'
2140
                    AND carrier = '{corresponding_carriers[scn][key]}')
2141
                AND bus_id NOT IN
2142
                    (SELECT bus1
2143
                    FROM grid.egon_etrago_link
2144
                    WHERE scn_name = '{scn}'
2145
                    AND carrier = '{corresponding_carriers[scn][key]}')
2146
                ;
2147
                """,
2148
                warning=False,
2149
            )
2150
            if not isolated_gas_buses_abroad.empty:
2151
                logger.info(f"Isolated {key} buses abroad:")
2152
                logger.info(isolated_gas_buses_abroad)
2153
2154
        # Loads
2155
        logger.info("LOADS")
2156
2157
        (
2158
            Norway_global_demand_1y,
2159
            normalized_ch4_demandTS,
2160
        ) = import_ch4_demandTS()
2161
        input_CH4_demand_abroad = calc_global_ch4_demand(
2162
            Norway_global_demand_1y
2163
        )
2164
        input_CH4_demand = input_CH4_demand_abroad["GlobD_2035"].sum()
2165
2166
        ## CH4
2167
        output_CH4_demand = db.select_dataframe(
2168
            f"""SELECT (SUM(
2169
                (SELECT SUM(p)
2170
                FROM UNNEST(b.p_set) p)))::numeric as load_mwh
2171
                FROM grid.egon_etrago_load a
2172
                JOIN grid.egon_etrago_load_timeseries b
2173
                ON (a.load_id = b.load_id)
2174
                JOIN grid.egon_etrago_bus c
2175
                ON (a.bus=c.bus_id)
2176
                AND b.scn_name = '{scn}'
2177
                AND a.scn_name = '{scn}'
2178
                AND c.scn_name = '{scn}'
2179
                AND c.country != 'DE'
2180
                AND a.carrier = 'CH4';
2181
            """,
2182
            warning=False,
2183
        )["load_mwh"].values[0]
2184
2185
        e_demand_CH4 = (
2186
            round(
2187
                (output_CH4_demand - input_CH4_demand) / input_CH4_demand,
2188
                2,
2189
            )
2190
            * 100
2191
        )
2192
        logger.info(f"Deviation CH4 load: {e_demand_CH4} %")
2193
2194
        ## H2_for_industry
2195
        input_power_to_h2_demand_abroad = calc_global_power_to_h2_demand()
2196
        input_H2_demand = input_power_to_h2_demand_abroad["GlobD_2035"].sum()
2197
2198
        output_H2_demand = db.select_dataframe(
2199
            f"""SELECT SUM(p_set::numeric) as p_set_abroad
2200
                    FROM grid.egon_etrago_load
2201
                    WHERE scn_name = '{scn}'
2202
                    AND carrier = 'H2_for_industry'
2203
                    AND bus IN
2204
                        (SELECT bus_id
2205
                        FROM grid.egon_etrago_bus
2206
                        WHERE scn_name = '{scn}'
2207
                        AND country != 'DE'
2208
                        AND carrier = 'AC');
2209
                    """,
2210
            warning=False,
2211
        )["p_set_abroad"].values[0]
2212
2213
        e_demand_H2 = (
2214
            round(
2215
                (output_H2_demand - input_H2_demand) / input_H2_demand,
2216
                2,
2217
            )
2218
            * 100
2219
        )
2220
        logger.info(f"Deviation H2_for_industry load: {e_demand_H2} %")
2221
2222
        # Generators
2223
        logger.info("GENERATORS ")
2224
        CH4_gen = calc_capacities()
2225
        input_CH4_gen = CH4_gen["cap_2035"].sum()
2226
2227
        output_CH4_gen = db.select_dataframe(
2228
            f"""SELECT SUM(p_nom::numeric) as p_nom_abroad
2229
                    FROM grid.egon_etrago_generator
2230
                    WHERE scn_name = '{scn}'
2231
                    AND carrier = 'CH4'
2232
                    AND bus IN
2233
                        (SELECT bus_id
2234
                        FROM grid.egon_etrago_bus
2235
                        WHERE scn_name = '{scn}'
2236
                        AND country != 'DE'
2237
                        AND carrier = 'CH4');
2238
                    """,
2239
            warning=False,
2240
        )["p_nom_abroad"].values[0]
2241
2242
        e_gen = (
2243
            round(
2244
                (output_CH4_gen - input_CH4_gen) / input_CH4_gen,
2245
                2,
2246
            )
2247
            * 100
2248
        )
2249
        logger.info(f"Deviation CH4 generators: {e_gen} %")
2250
2251
        # Stores
2252
        logger.info("STORES")
2253
        ch4_input_capacities = calc_ch4_storage_capacities()
2254
        input_CH4_stores = ch4_input_capacities["e_nom"].sum()
2255
2256
        output_CH4_stores = db.select_dataframe(
2257
            f"""SELECT SUM(e_nom::numeric) as e_nom_abroad
2258
                    FROM grid.egon_etrago_store
2259
                    WHERE scn_name = '{scn}'
2260
                    AND carrier = 'CH4'
2261
                    AND bus IN
2262
                        (SELECT bus_id
2263
                        FROM grid.egon_etrago_bus
2264
                        WHERE scn_name = '{scn}'
2265
                        AND country != 'DE'
2266
                        AND carrier = 'CH4');
2267
                    """,
2268
            warning=False,
2269
        )["e_nom_abroad"].values[0]
2270
2271
        e_stores = (
2272
            round(
2273
                (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
2274
                2,
2275
            )
2276
            * 100
2277
        )
2278
        logger.info(f"Deviation CH4 stores: {e_stores} %")
2279
2280
        # Links
2281
        logger.info("LINKS")
2282
        ch4_grid_input_capacities = calculate_ch4_grid_capacities()
2283
        input_CH4_grid = ch4_grid_input_capacities["p_nom"].sum()
2284
2285
        grid_carrier = "CH4"
2286
        output_gas_grid = db.select_dataframe(
2287
            f"""SELECT SUM(p_nom::numeric) as p_nom
2288
            FROM grid.egon_etrago_link
2289
            WHERE scn_name = '{scn}'
2290
            AND carrier = '{grid_carrier}'
2291
            AND (bus0 IN
2292
                (SELECT bus_id
2293
                FROM grid.egon_etrago_bus
2294
                WHERE scn_name = '{scn}'
2295
                AND country != 'DE'
2296
                AND carrier = '{grid_carrier}')
2297
            OR bus1 IN
2298
                (SELECT bus_id
2299
                FROM grid.egon_etrago_bus
2300
                WHERE scn_name = '{scn}'
2301
                AND country != 'DE'
2302
                AND carrier = '{grid_carrier}'))
2303
                ;
2304
            """,
2305
            warning=False,
2306
        )["p_nom"].values[0]
2307
2308
        e_gas_grid = (
2309
            round(
2310
                (output_gas_grid - input_CH4_grid) / input_CH4_grid,
2311
                2,
2312
            )
2313
            * 100
2314
        )
2315
        logger.info(
2316
            f"Deviation of the capacity of the crossbordering CH4 grid: {e_gas_grid} %"
2317
        )
2318
2319
    else:
2320
        print("Testmode is on, skipping sanity check.")
2321
2322
2323
def sanitycheck_dsm():
2324
    def df_from_series(s: pd.Series):
2325
        return pd.DataFrame.from_dict(dict(zip(s.index, s.values)))
2326
2327
    for scenario in ["eGon2035", "eGon100RE"]:
2328
        # p_min and p_max
2329
        sql = f"""
2330
        SELECT link_id, bus0 as bus, p_nom FROM grid.egon_etrago_link
2331
        WHERE carrier = 'dsm'
2332
        AND scn_name = '{scenario}'
2333
        ORDER BY link_id
2334
        """
2335
2336
        meta_df = db.select_dataframe(sql, index_col="link_id")
2337
        link_ids = str(meta_df.index.tolist())[1:-1]
2338
2339
        sql = f"""
2340
        SELECT link_id, p_min_pu, p_max_pu
2341
        FROM grid.egon_etrago_link_timeseries
2342
        WHERE scn_name = '{scenario}'
2343
        AND link_id IN ({link_ids})
2344
        ORDER BY link_id
2345
        """
2346
2347
        ts_df = db.select_dataframe(sql, index_col="link_id")
2348
2349
        p_max_df = df_from_series(ts_df.p_max_pu).mul(meta_df.p_nom)
2350
        p_min_df = df_from_series(ts_df.p_min_pu).mul(meta_df.p_nom)
2351
2352
        p_max_df.columns = meta_df.bus.tolist()
2353
        p_min_df.columns = meta_df.bus.tolist()
2354
2355
        targets = config.datasets()["DSM_CTS_industry"]["targets"]
2356
2357
        tables = [
2358
            "cts_loadcurves_dsm",
2359
            "ind_osm_loadcurves_individual_dsm",
2360
            "demandregio_ind_sites_dsm",
2361
            "ind_sites_loadcurves_individual",
2362
        ]
2363
2364
        df_list = []
2365
2366
        for table in tables:
2367
            target = targets[table]
2368
            sql = f"""
2369
            SELECT bus, p_min, p_max, e_max, e_min
2370
            FROM {target["schema"]}.{target["table"]}
2371
            WHERE scn_name = '{scenario}'
2372
            ORDER BY bus
2373
            """
2374
2375
            df_list.append(db.select_dataframe(sql))
2376
2377
        individual_ts_df = pd.concat(df_list, ignore_index=True)
2378
2379
        groups = individual_ts_df[["bus"]].reset_index().groupby("bus").groups
2380
2381
        individual_p_max_df = df_from_series(individual_ts_df.p_max)
2382
2383
        individual_p_max_df = pd.DataFrame(
2384
            [
2385
                individual_p_max_df[idxs].sum(axis=1)
2386
                for idxs in groups.values()
2387
            ],
2388
            index=groups.keys(),
2389
        ).T
2390
2391
        individual_p_min_df = df_from_series(individual_ts_df.p_min)
2392
2393
        individual_p_min_df = pd.DataFrame(
2394
            [
2395
                individual_p_min_df[idxs].sum(axis=1)
2396
                for idxs in groups.values()
2397
            ],
2398
            index=groups.keys(),
2399
        ).T
2400
2401
        # due to the fact that time series are clipped at zero (either
2402
        # direction) there is a little difference between the sum of the
2403
        # individual time series and the aggregated time series as the second
2404
        # is generated independent of the others. This makes atol=1e-01
2405
        # necessary.
2406
        atol = 1e-01
2407
        assert np.allclose(p_max_df, individual_p_max_df, atol=atol)
2408
        assert np.allclose(p_min_df, individual_p_min_df, atol=atol)
2409
2410
        # e_min and e_max
2411
        sql = f"""
2412
        SELECT store_id, bus, e_nom FROM grid.egon_etrago_store
2413
        WHERE carrier = 'dsm'
2414
        AND scn_name = '{scenario}'
2415
        ORDER BY store_id
2416
        """
2417
2418
        meta_df = db.select_dataframe(sql, index_col="store_id")
2419
        store_ids = str(meta_df.index.tolist())[1:-1]
2420
2421
        sql = f"""
2422
        SELECT store_id, e_min_pu, e_max_pu
2423
        FROM grid.egon_etrago_store_timeseries
2424
        WHERE scn_name = '{scenario}'
2425
        AND store_id IN ({store_ids})
2426
        ORDER BY store_id
2427
        """
2428
2429
        ts_df = db.select_dataframe(sql, index_col="store_id")
2430
2431
        e_max_df = df_from_series(ts_df.e_max_pu).mul(meta_df.e_nom)
2432
        e_min_df = df_from_series(ts_df.e_min_pu).mul(meta_df.e_nom)
2433
2434
        e_max_df.columns = meta_df.bus.tolist()
2435
        e_min_df.columns = meta_df.bus.tolist()
2436
2437
        individual_e_max_df = df_from_series(individual_ts_df.e_max)
2438
2439
        individual_e_max_df = pd.DataFrame(
2440
            [
2441
                individual_e_max_df[idxs].sum(axis=1)
2442
                for idxs in groups.values()
2443
            ],
2444
            index=groups.keys(),
2445
        ).T
2446
        individual_e_min_df = df_from_series(individual_ts_df.e_min)
2447
2448
        individual_e_min_df = pd.DataFrame(
2449
            [
2450
                individual_e_min_df[idxs].sum(axis=1)
2451
                for idxs in groups.values()
2452
            ],
2453
            index=groups.keys(),
2454
        ).T
2455
2456
        assert np.allclose(e_max_df, individual_e_max_df)
2457
        assert np.allclose(e_min_df, individual_e_min_df)
2458
2459
2460
def etrago_timeseries_length():
2461
2462
    for component in ["generator", "load", "link", "store", "storage"]:
2463
2464
        columns = db.select_dataframe(
2465
            f"""
2466
            SELECT *
2467
            FROM information_schema.columns
2468
            WHERE table_schema = 'grid'
2469
            AND table_name = 'egon_etrago_{component}_timeseries'
2470
            """
2471
        )
2472
        columns = columns[columns.data_type == "ARRAY"].column_name.values
2473
2474
        for col in columns:
2475
            lengths = db.select_dataframe(
2476
                f"""
2477
                SELECT array_length({col}, 1)
2478
                FROM grid.egon_etrago_{component}_timeseries;
2479
                """
2480
            )["array_length"]
2481
2482
            if not lengths.dropna().empty:
2483
                assert (lengths.dropna() == 8760).all(), (
2484
                    f"Timeseries with a length != 8760 for {component} {col}")
2485
            else:
2486
                print(f"Empty timeseries for {component} {col}")
2487
2488
2489
def generators_links_storages_stores_100RE(scn="eGon100RE"):
2490
    # Generators
2491
    scn_capacities = db.select_dataframe(
2492
        f"""
2493
        SELECT * FROM supply.egon_scenario_capacities
2494
        WHERE scenario_name = '{scn}'
2495
        """,
2496
        index_col="index",
2497
    )
2498
2499
    map_carrier = {
2500
        "urban_central_solar_thermal_collector": "solar_thermal_collector",
2501
        "urban_central_geo_thermal": "geo_thermal",
2502
        "urban_central_gas_boiler": "central_gas_boiler",
2503
        "urban_central_heat_pump": "central_heat_pump",
2504
        "urban_central_resistive_heater": "central_resistive_heater",
2505
        "gas": "OCGT"
2506
    }
2507
2508
    scn_capacities["carrier"] = scn_capacities["carrier"].apply(
2509
        lambda x: map_carrier[x] if x in map_carrier.keys() else x
2510
    )
2511
2512
    carriers_gen_from_supply = [
2513
        "oil",
2514
        "solar",
2515
        "solar_rooftop",
2516
        "wind_onshore",
2517
        "lignite",
2518
        "coal",
2519
        "wind_offshore",
2520
        "solar_thermal_collector",
2521
        "geo_thermal",
2522
        "run_of_river",
2523
        "rural_solar_thermal",
2524
        "urban_central_gas_CHP",
2525
        "urban_central_solid_biomass_CHP",
2526
    ]
2527
2528
    gen_etrago = db.select_dataframe(
2529
        f"""
2530
        SELECT * FROM grid.egon_etrago_generator
2531
        WHERE scn_name = '{scn}'
2532
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2533
                    WHERE scn_name = '{scn}'
2534
                    AND country = 'DE')
2535
        """,
2536
        warning=False,
2537
    )
2538
2539
    carriers_gen = set(
2540
        carriers_gen_from_supply
2541
        + list(gen_etrago["carrier"])
2542
    )
2543
2544
    gen_capacities = pd.DataFrame(index=list(
2545
        carriers_gen), columns=["supply_table", scn])
2546
    gen_capacities[scn] = (
2547
        gen_etrago.groupby("carrier").p_nom.sum()
2548
    )
2549
2550
    gen_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2551
        "capacity"]
2552
2553
    gen_capacities.dropna(how="all", inplace=True)
2554
2555
    print(f"\nMain results regarding generators for {scn}\n")
2556
    print(gen_capacities)
2557
2558
    ###########################################################################
2559
    # Links
2560
2561
    carriers_links_from_supply = [
2562
        "central_gas_boiler",
2563
        "central_heat_pump",
2564
        "central_resistive_heater",
2565
        "gas",
2566
        "rural_biomass_boiler",
2567
        "rural_gas_boiler",
2568
        "rural_heat_pump",
2569
        "rural_oil_boiler",
2570
        "rural_resistive_heater",
2571
    ]
2572
2573
    link_etrago = db.select_dataframe(
2574
        f"""
2575
        SELECT * FROM grid.egon_etrago_link
2576
        WHERE scn_name = '{scn}'
2577
        AND (bus0 IN (SELECT bus_id from grid.egon_etrago_bus
2578
                    WHERE scn_name = '{scn}'
2579
                    AND country = 'DE')
2580
             OR
2581
             bus1 IN (SELECT bus_id from grid.egon_etrago_bus
2582
                    WHERE scn_name = '{scn}'
2583
                    AND country = 'DE')
2584
             )
2585
        """,
2586
        warning=False,
2587
    )
2588
2589
    carriers_link = set(
2590
        carriers_links_from_supply
2591
        + list(link_etrago["carrier"])
2592
    )
2593
2594
    link_capacities = pd.DataFrame(index=list(
2595
        carriers_link), columns=["supply_table", scn])
2596
2597
    link_capacities["eGon100RE"] = (
2598
        link_etrago.groupby("carrier").p_nom.sum()
2599
    )
2600
2601
    link_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2602
        "capacity"]
2603
2604
    link_capacities.dropna(how="all", inplace=True)
2605
2606
    print(f"\nMain results regarding links for {scn}\n")
2607
    print(link_capacities)
2608
    ###########################################################################
2609
    # storage
2610
    storage_etrago = db.select_dataframe(
2611
        f"""
2612
        SELECT * FROM grid.egon_etrago_storage
2613
        WHERE scn_name = '{scn}'
2614
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2615
                    WHERE scn_name = '{scn}'
2616
                    AND country = 'DE')
2617
        """,
2618
    )
2619
2620
    carriers_storage_from_supply = ["pumped_hydro"]
2621
2622
    carriers_storage = set(
2623
        carriers_storage_from_supply
2624
        + list(storage_etrago["carrier"])
2625
    )
2626
2627
    storage_capacities = pd.DataFrame(
2628
        index=list(carriers_storage), columns=["supply_table", scn]
2629
    )
2630
2631
    storage_capacities[scn] = (
2632
        storage_etrago.groupby("carrier").p_nom.sum()
2633
    )
2634
2635
    storage_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2636
        "capacity"]
2637
2638
    print(f"\nMain results regarding storage units for {scn}\n")
2639
    print(storage_capacities)
2640
    ###########################################################################
2641
    # stores
2642
    stores_etrago = db.select_dataframe(
2643
        f"""
2644
        SELECT * FROM grid.egon_etrago_store
2645
        WHERE scn_name = '{scn}'
2646
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2647
                    WHERE scn_name = '{scn}'
2648
                    AND country = 'DE')
2649
        """,
2650
    )
2651
2652
    carriers_stores_from_supply = []
2653
2654
    carriers_stores = set(
2655
        carriers_stores_from_supply
2656
        + list(stores_etrago["carrier"])
2657
    )
2658
2659
    stores_capacities = pd.DataFrame(
2660
        index=list(carriers_stores), columns=["supply_table", scn]
2661
    )
2662
2663
    stores_capacities[scn] = (
2664
        stores_etrago.groupby("carrier").e_nom.sum()
2665
    )
2666
2667
    stores_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2668
        "capacity"]
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=["residential", "commercial", "industrial", "total",],
2679
        columns=["objective", "eGon100RE"])
2680
2681
    # Sector	Annual electricity demand in TWh
2682
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L480
2683
    load_summary.loc["residential", "objective"] = 90.4
2684
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L775
2685
    load_summary.loc["commercial", "objective"] = 146.7
2686
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L775
2687
    load_summary.loc["industrial", "objective"] = 382.9
2688
    load_summary.loc["total", "objective"] = 620.0
2689
2690
    print(
2691
        "For German electricity loads the following deviations between the"
2692
        " input and output can be observed:"
2693
    )
2694
2695
    load_summary.loc["total", "eGon100RE"] = db.select_dataframe(
2696
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
2697
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
2698
            FROM grid.egon_etrago_load a
2699
            JOIN grid.egon_etrago_load_timeseries b
2700
            ON (a.load_id = b.load_id)
2701
            JOIN grid.egon_etrago_bus c
2702
            ON (a.bus=c.bus_id)
2703
            AND b.scn_name = 'eGon100RE'
2704
            AND a.scn_name = 'eGon100RE'
2705
            AND a.carrier = 'AC'
2706
            AND c.scn_name= 'eGon100RE'
2707
            AND c.country='DE'
2708
            GROUP BY (a.scn_name, a.carrier);
2709
    """,
2710
        warning=False,
2711
    )["load_twh"].values[0]
2712
2713
    sources = egon.data.config.datasets()["etrago_electricity"]["sources"]
2714
    cts_curves = db.select_dataframe(
2715
        f"""SELECT bus_id AS bus, p_set FROM
2716
                {sources['cts_curves']['schema']}.
2717
                {sources['cts_curves']['table']}
2718
                WHERE scn_name = '{scn}'""",
2719
    )
2720
    sum_cts_curves = cts_curves.apply(
2721
        lambda x: sum(x["p_set"]), axis=1).sum()/1000000
2722
    load_summary.loc["commercial", "eGon100RE"] = sum_cts_curves
2723
2724
    # Select data on industrial demands assigned to osm landuse areas
2725
    ind_curves_osm = db.select_dataframe(
2726
        f"""SELECT bus, p_set FROM
2727
                {sources['osm_curves']['schema']}.
2728
                {sources['osm_curves']['table']}
2729
                WHERE scn_name = '{scn}'""",
2730
    )
2731
    sum_ind_curves_osm = ind_curves_osm.apply(
2732
        lambda x: sum(x["p_set"]), axis=1).sum()/1000000
2733
2734
    # Select data on industrial demands assigned to industrial sites
2735
2736
    ind_curves_sites = db.select_dataframe(
2737
        f"""SELECT bus, p_set FROM
2738
                {sources['sites_curves']['schema']}.
2739
                {sources['sites_curves']['table']}
2740
                WHERE scn_name = '{scn}'""",
2741
    )
2742
    sum_ind_curves_sites = ind_curves_sites.apply(
2743
        lambda x: sum(x["p_set"]), axis=1).sum()/1000000
2744
2745
    load_summary.loc["industrial",
2746
                     "eGon100RE"] = sum_ind_curves_osm + sum_ind_curves_sites
2747
2748
    # Select data on household electricity demands per bus
2749
    hh_curves = db.select_dataframe(
2750
        f"""SELECT bus_id AS bus, p_set FROM
2751
                {sources['household_curves']['schema']}.
2752
                {sources['household_curves']['table']}
2753
                WHERE scn_name = '{scn}'""",
2754
    )
2755
    sum_hh_curves = hh_curves.apply(
2756
        lambda x: sum(x["p_set"]), axis=1).sum()/1000000
2757
    load_summary.loc["residential", "eGon100RE"] = sum_hh_curves
2758
2759
    load_summary["diff"] = load_summary["eGon100RE"] - \
2760
        load_summary["objective"]
2761
    load_summary["diff[%]"] = load_summary["diff"] / \
2762
        load_summary["eGon100RE"] * 100
2763
2764
    print(load_summary)
2765
2766
    assert (load_summary["diff[%]"] < 1).all(
2767
    ), "electrical loads differ from objective values"
2768
2769
    return ()
2770
2771
2772
2773
def heat_gas_load_egon100RE(scn='eGon100RE'):
2774
    
2775
    #dictionary for matching pypsa_eur carrier with egon-data carriers
2776
    load_carrier_dict={
2777
        'DE0 0 land transport EV': 'land transport EV',
2778
        'DE0 0 rural heat': 'rural_heat',
2779
        'DE0 0 urban central heat': 'central_heat',
2780
        'DE0 0 urban decentral heat': 'rural_heat',
2781
        'rural heat': 'rural_heat',
2782
        'H2 for industry': 'H2_for_industry',
2783
        'gas for industry': 'CH4_for_industry',
2784
        'urban central heat': 'central_heat',
2785
        'urban decentral heat': 'rural_heat',
2786
        'land transport EV': 'land transport EV',
2787
        }
2788
    
2789
    #filter out NaN values central_heat timeseries
2790
    NaN_load_ids=db.select_dataframe(
2791
        """
2792
        SELECT load_id from grid.egon_etrago_load_timeseries 
2793
        WHERE load_id IN (Select load_id 
2794
            FROM grid.egon_etrago_load
2795
            WHERE carrier = 'central_heat') AND (SELECT 
2796
            bool_or(value::double precision::text = 'NaN') 
2797
        FROM unnest(p_set) AS value
2798
        )
2799
       """   
2800
    )
2801
    nan_load_list = tuple(NaN_load_ids["load_id"].tolist())
2802
    nan_load_str = ",".join(map(str, nan_load_list))  
2803
                           
2804
    #####loads for eGon100RE
2805
    loads_etrago_timeseries = db.select_dataframe(
2806
        f"""
2807
            SELECT 
2808
                l.carrier,
2809
                SUM(
2810
                    (SELECT SUM(p)
2811
                    FROM UNNEST(t.p_set) p)  
2812
                )  AS total_p_set_timeseries  
2813
            FROM 
2814
                grid.egon_etrago_load l
2815
            LEFT JOIN 
2816
                grid.egon_etrago_load_timeseries t ON l.load_id = t.load_id 
2817
            WHERE 
2818
                l.scn_name = '{scn}'
2819
                AND l.carrier != 'AC'
2820
                AND l.bus IN (
2821
                    SELECT bus_id
2822
                    FROM grid.egon_etrago_bus
2823
                    WHERE scn_name = '{scn}' 
2824
                    AND country = 'DE'
2825
                )
2826
                AND l.load_id NOT IN ({nan_load_str})
2827
                
2828
            GROUP BY 
2829
                l.carrier
2830
        """
2831
    )
2832
    
2833
    #####loads for pypsa_eur_network
2834
    n = read_network()
2835
    
2836
    #aggregate loads with values in timeseries dataframe 
2837
    df_load_timeseries = n.loads_t.p_set
2838
    filtered_columns = [col for col in df_load_timeseries.columns if col.startswith("DE") and "electricity" not in col]
2839
    german_loads_timeseries = df_load_timeseries[filtered_columns]
2840
    german_loads_timeseries = german_loads_timeseries.drop(columns=["DE0 0"])
2841
    german_loads_timeseries = german_loads_timeseries.mul(
2842
        n.snapshot_weightings.generators,axis= 0).sum()
2843
    german_loads_timeseries = german_loads_timeseries.rename(index=load_carrier_dict)
2844
    
2845
    
2846
    #sum loads with fixed p_set in loads dataframe
2847
    german_load_static_p_set = n.loads[n.loads.index.str.startswith('DE') & 
2848
                                       ~n.loads.carrier.str.contains('electricity')]
2849
    german_load_static_p_set = german_load_static_p_set.groupby('carrier').p_set.sum()*8760
2850
    german_load_static_p_set = german_load_static_p_set.rename(index=load_carrier_dict)
2851
    german_load_static_p_set["H2_for_industry"] =( german_load_static_p_set["H2_for_industry"]+                   
2852
        +n.links_t.p0[n.links.loc[
2853
                            n.links.index.str.contains(
2854
                                "DE0 0 Fischer-Tropsch")].index].mul(
2855
                                    n.snapshot_weightings.generators,
2856
                                    axis= 0).sum().sum()
2857
        + n.links_t.p0[n.links.loc[
2858
                            n.links.index.str.contains(
2859
                                "DE0 0 methanolisation")].index].mul(
2860
                                    n.snapshot_weightings.generators,
2861
                                    axis= 0).sum().sum())
2862
    
2863
    
2864
    
2865
    #combine p_set and timeseries dataframes from pypsa eur
2866
    german_loads_timeseries_df= german_loads_timeseries.to_frame()
2867
    german_loads_timeseries_df['carrier'] = german_loads_timeseries_df.index
2868
    german_loads_timeseries_df.set_index('carrier', inplace=True)
2869
    
2870
    german_load_static_p_set_df= german_load_static_p_set.to_frame()
2871
    german_load_static_p_set_df = german_load_static_p_set_df.groupby("carrier", as_index=True).sum()
2872
    german_loads_timeseries_df = german_loads_timeseries_df.groupby("carrier", as_index=True).sum()
2873
    combined= pd.merge( german_load_static_p_set_df,german_loads_timeseries_df , on="carrier", how="left")
2874
    
2875
    combined['p_set'] = np.where(combined['p_set'] == 0, combined[0], combined['p_set'])
2876
    combined = combined.drop(columns=[0])
2877
    
2878
    #carriers_for_comparison
2879
    carriers_loads = set(
2880
        german_load_static_p_set.index 
2881
        .union(german_loads_timeseries.index)  
2882
        .union(loads_etrago_timeseries["carrier"])  
2883
    )
2884
    
2885
    #create dataframe for comparison
2886
    loads_capacities = pd.DataFrame(index=list(carriers_loads), columns=["pypsa_eur", scn])
2887
    loads_capacities[scn] = loads_etrago_timeseries.groupby("carrier").total_p_set_timeseries.sum()
2888
    loads_capacities["pypsa_eur"] = combined['p_set']
2889
    loads_capacities["diff [%]"] = ((loads_capacities[scn] - loads_capacities["pypsa_eur"]) / 
2890
                                    loads_capacities["pypsa_eur"].replace(0, np.nan))*100
2891
    
2892
    print("="*50)
2893
    print("Comparison of Gas and Heat Loads with PyPSA-Eur Data".center(50, "="))
2894
    print("="*50)
2895
    print(loads_capacities)
2896
2897
2898
2899
tasks = ()
2900
2901
if "eGon2035" in SCENARIOS:
2902
    tasks = tasks + (etrago_eGon2035_electricity,
2903
                     etrago_eGon2035_heat,
2904
                     residential_electricity_annual_sum,
2905
                     residential_electricity_hh_refinement,
2906
                     cts_electricity_demand_share,
2907
                     cts_heat_demand_share,
2908
                     sanitycheck_emobility_mit,
2909
                     sanitycheck_pv_rooftop_buildings,
2910
                     sanitycheck_home_batteries,
2911
                     etrago_eGon2035_gas_DE,
2912
                     etrago_eGon2035_gas_abroad,
2913
                     sanitycheck_dsm,
2914
                     etrago_timeseries_length,)
2915
2916
if "eGon100RE" in SCENARIOS:
2917
    tasks = tasks + (electrical_load_100RE,
2918
                     generators_links_storages_stores_100RE,
2919
                     etrago_timeseries_length,
2920
                     heat_gas_load_egon100RE,)
2921
2922
2923
class SanityChecks(Dataset):
2924
    #:
2925
    name: str = "SanityChecks"
2926
    #:
2927
    version: str = "0.0.8"
2928
2929
    def __init__(self, dependencies):
2930
        super().__init__(
2931
            name=self.name,
2932
            version=self.version,
2933
            dependencies=dependencies,
2934
            tasks=tasks,
2935
        )
2936