sanitycheck_emobility_mit()   F
last analyzed

Complexity

Conditions 26

Size

Total Lines 556
Code Lines 358

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 358
dl 0
loc 556
rs 0
c 0
b 0
f 0
cc 26
nop 0

How to fix   Long Method    Complexity   

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:

Complexity

Complex classes like data.datasets.sanity_checks.sanitycheck_emobility_mit() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
"""
2
This module does sanity checks for both the eGon2035 and the eGon100RE scenario
3
separately where a percentage error is given to showcase difference in output
4
and input values. Please note that there are missing input technologies in the
5
supply tables.
6
Authors: @ALonso, @dana, @nailend, @nesnoj, @khelfen
7
"""
8
9
from math import isclose
10
from pathlib import Path
11
import ast
12
13
from sqlalchemy import Numeric
14
from sqlalchemy.sql import and_, cast, func, or_
15
import matplotlib.pyplot as plt
16
import numpy as np
17
import pandas as pd
18
import seaborn as sns
19
20
from egon.data import config, db, logger
21
from egon.data.datasets import Dataset
22
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
23
    EgonCtsElectricityDemandBuildingShare,
24
    EgonCtsHeatDemandBuildingShare,
25
)
26
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (  # noqa: E501
27
    EgonEvCountMunicipality,
28
    EgonEvCountMvGridDistrict,
29
    EgonEvCountRegistrationDistrict,
30
    EgonEvMvGridDistrict,
31
    EgonEvPool,
32
    EgonEvTrip,
33
)
34
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
35
    DATASET_CFG,
36
    read_simbev_metadata_file,
37
)
38
from egon.data.datasets.etrago_setup import (
39
    EgonPfHvLink,
40
    EgonPfHvLinkTimeseries,
41
    EgonPfHvLoad,
42
    EgonPfHvLoadTimeseries,
43
    EgonPfHvStore,
44
    EgonPfHvStoreTimeseries,
45
)
46
from egon.data.datasets.gas_grid import (
47
    define_gas_buses_abroad,
48
    define_gas_nodes_list,
49
    define_gas_pipeline_list,
50
)
51
from egon.data.datasets.gas_neighbours.eGon2035 import (
52
    calc_capacities,
53
    calc_ch4_storage_capacities,
54
    calc_global_ch4_demand,
55
    calc_global_power_to_h2_demand,
56
    calculate_ch4_grid_capacities,
57
    import_ch4_demandTS,
58
)
59
from egon.data.datasets.hydrogen_etrago.storage import (
60
    calculate_and_map_saltcavern_storage_potential,
61
)
62
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
63
    PV_CAP_PER_SQ_M,
64
    ROOF_FACTOR,
65
    SCENARIOS,
66
    load_building_data,
67
    scenario_data,
68
)
69
from egon.data.datasets.pypsaeur import read_network
70
from egon.data.datasets.scenario_parameters import get_sector_parameters
71
from egon.data.datasets.storages.home_batteries import get_cbat_pbat_ratio
72
import egon.data
73
74
TESTMODE_OFF = (
75
    config.settings()["egon-data"]["--dataset-boundary"] == "Everything"
76
)
77
78
79
def etrago_eGon2035_electricity():
80
    """Execute basic sanity checks.
81
82
    Returns print statements as sanity checks for the electricity sector in
83
    the eGon2035 scenario.
84
85
    Parameters
86
    ----------
87
    None
88
89
    Returns
90
    -------
91
    None
92
    """
93
94
    scn = "eGon2035"
95
96
    # Section to check generator capacities
97
    logger.info(f"Sanity checks for scenario {scn}")
98
    logger.info(
99
        "For German electricity generators the following deviations between "
100
        "the inputs and outputs can be observed:"
101
    )
102
103
    carriers_electricity = [
104
        "others",
105
        "reservoir",
106
        "run_of_river",
107
        "oil",
108
        "wind_onshore",
109
        "wind_offshore",
110
        "solar",
111
        "solar_rooftop",
112
        "biomass",
113
    ]
114
115
    for carrier in carriers_electricity:
116
117
        if carrier == "biomass":
118
            sum_output = db.select_dataframe(
119
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
120
                    FROM grid.egon_etrago_generator
121
                    WHERE bus IN (
122
                        SELECT bus_id FROM grid.egon_etrago_bus
123
                        WHERE scn_name = 'eGon2035'
124
                        AND country = 'DE')
125
                    AND carrier IN ('biomass', 'industrial_biomass_CHP',
126
                    'central_biomass_CHP')
127
                    GROUP BY (scn_name);
128
                """,
129
                warning=False,
130
            )
131
132
        else:
133
            sum_output = db.select_dataframe(
134
                f"""SELECT scn_name,
135
                 SUM(p_nom::numeric) as output_capacity_mw
136
                         FROM grid.egon_etrago_generator
137
                         WHERE scn_name = '{scn}'
138
                         AND carrier IN ('{carrier}')
139
                         AND bus IN
140
                             (SELECT bus_id
141
                               FROM grid.egon_etrago_bus
142
                               WHERE scn_name = 'eGon2035'
143
                               AND country = 'DE')
144
                         GROUP BY (scn_name);
145
                    """,
146
                warning=False,
147
            )
148
149
        sum_input = db.select_dataframe(
150
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
151
                     FROM supply.egon_scenario_capacities
152
                     WHERE carrier= '{carrier}'
153
                     AND scenario_name ='{scn}'
154
                     GROUP BY (carrier);
155
                """,
156
            warning=False,
157
        )
158
159 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
160
            sum_output.output_capacity_mw.sum() == 0
161
            and sum_input.input_capacity_mw.sum() == 0
162
        ):
163
            logger.info(
164
                f"No capacity for carrier '{carrier}' needed to be"
165
                f" distributed. Everything is fine"
166
            )
167
168
        elif (
169
            sum_input.input_capacity_mw.sum() > 0
170
            and sum_output.output_capacity_mw.sum() == 0
171
        ):
172
            logger.info(
173
                f"Error: Capacity for carrier '{carrier}' was not distributed "
174
                f"at all!"
175
            )
176
177
        elif (
178
            sum_output.output_capacity_mw.sum() > 0
179
            and sum_input.input_capacity_mw.sum() == 0
180
        ):
181
            logger.info(
182
                f"Error: Eventhough no input capacity was provided for carrier"
183
                f"'{carrier}' a capacity got distributed!"
184
            )
185
186
        else:
187
            sum_input["error"] = (
188
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
189
                / sum_input.input_capacity_mw
190
            ) * 100
191
            g = sum_input["error"].values[0]
192
193
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
194
195
    # Section to check storage units
196
197
    logger.info(f"Sanity checks for scenario {scn}")
198
    logger.info(
199
        "For German electrical storage units the following deviations between"
200
        "the inputs and outputs can be observed:"
201
    )
202
203
    carriers_electricity = ["pumped_hydro"]
204
205
    for carrier in carriers_electricity:
206
207
        sum_output = db.select_dataframe(
208
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
209
                         FROM grid.egon_etrago_storage
210
                         WHERE scn_name = '{scn}'
211
                         AND carrier IN ('{carrier}')
212
                         AND bus IN
213
                             (SELECT bus_id
214
                               FROM grid.egon_etrago_bus
215
                               WHERE scn_name = 'eGon2035'
216
                               AND country = 'DE')
217
                         GROUP BY (scn_name);
218
                    """,
219
            warning=False,
220
        )
221
222
        sum_input = db.select_dataframe(
223
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
224
                     FROM supply.egon_scenario_capacities
225
                     WHERE carrier= '{carrier}'
226
                     AND scenario_name ='{scn}'
227
                     GROUP BY (carrier);
228
                """,
229
            warning=False,
230
        )
231
232 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
233
            sum_output.output_capacity_mw.sum() == 0
234
            and sum_input.input_capacity_mw.sum() == 0
235
        ):
236
            print(
237
                f"No capacity for carrier '{carrier}' needed to be "
238
                f"distributed. Everything is fine"
239
            )
240
241
        elif (
242
            sum_input.input_capacity_mw.sum() > 0
243
            and sum_output.output_capacity_mw.sum() == 0
244
        ):
245
            print(
246
                f"Error: Capacity for carrier '{carrier}' was not distributed"
247
                f" at all!"
248
            )
249
250
        elif (
251
            sum_output.output_capacity_mw.sum() > 0
252
            and sum_input.input_capacity_mw.sum() == 0
253
        ):
254
            print(
255
                f"Error: Eventhough no input capacity was provided for carrier"
256
                f" '{carrier}' a capacity got distributed!"
257
            )
258
259
        else:
260
            sum_input["error"] = (
261
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
262
                / sum_input.input_capacity_mw
263
            ) * 100
264
            g = sum_input["error"].values[0]
265
266
            print(f"{carrier}: " + str(round(g, 2)) + " %")
267
268
    # Section to check loads
269
270
    print(
271
        "For German electricity loads the following deviations between the"
272
        " input and output can be observed:"
273
    )
274
275
    output_demand = db.select_dataframe(
276
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
277
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
278
            FROM grid.egon_etrago_load a
279
            JOIN grid.egon_etrago_load_timeseries b
280
            ON (a.load_id = b.load_id)
281
            JOIN grid.egon_etrago_bus c
282
            ON (a.bus=c.bus_id)
283
            AND b.scn_name = 'eGon2035'
284
            AND a.scn_name = 'eGon2035'
285
            AND a.carrier = 'AC'
286
            AND c.scn_name= 'eGon2035'
287
            AND c.country='DE'
288
            GROUP BY (a.scn_name, a.carrier);
289
290
    """,
291
        warning=False,
292
    )["load_twh"].values[0]
293
294
    input_cts_ind = db.select_dataframe(
295
        """SELECT scenario,
296
         SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
297
            FROM demand.egon_demandregio_cts_ind
298
            WHERE scenario= 'eGon2035'
299
            AND year IN ('2035')
300
            GROUP BY (scenario);
301
302
        """,
303
        warning=False,
304
    )["demand_mw_regio_cts_ind"].values[0]
305
306
    input_hh = db.select_dataframe(
307
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
308
            FROM demand.egon_demandregio_hh
309
            WHERE scenario= 'eGon2035'
310
            AND year IN ('2035')
311
            GROUP BY (scenario);
312
        """,
313
        warning=False,
314
    )["demand_mw_regio_hh"].values[0]
315
316
    input_demand = input_hh + input_cts_ind
317
318
    e = round((output_demand - input_demand) / input_demand, 2) * 100
319
320
    print(f"electricity demand: {e} %")
321
322
323
def etrago_eGon2035_heat():
324
    """Execute basic sanity checks.
325
326
    Returns print statements as sanity checks for the heat sector in
327
    the eGon2035 scenario.
328
329
    Parameters
330
    ----------
331
    None
332
333
    Returns
334
    -------
335
    None
336
    """
337
338
    # Check input and output values for the carriers "others",
339
    # "reservoir", "run_of_river" and "oil"
340
341
    scn = "eGon2035"
342
343
    # Section to check generator capacities
344
    print(f"Sanity checks for scenario {scn}")
345
    print(
346
        "For German heat demands the following deviations between the inputs"
347
        " and outputs can be observed:"
348
    )
349
350
    # Sanity checks for heat demand
351
352
    output_heat_demand = db.select_dataframe(
353
        """SELECT a.scn_name,
354
          (SUM(
355
          (SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
356
            FROM grid.egon_etrago_load a
357
            JOIN grid.egon_etrago_load_timeseries b
358
            ON (a.load_id = b.load_id)
359
            JOIN grid.egon_etrago_bus c
360
            ON (a.bus=c.bus_id)
361
            AND b.scn_name = 'eGon2035'
362
            AND a.scn_name = 'eGon2035'
363
            AND c.scn_name= 'eGon2035'
364
            AND c.country='DE'
365
            AND a.carrier IN ('rural_heat', 'central_heat')
366
            GROUP BY (a.scn_name);
367
        """,
368
        warning=False,
369
    )["load_twh"].values[0]
370
371
    input_heat_demand = db.select_dataframe(
372
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
373
            FROM demand.egon_peta_heat
374
            WHERE scenario= 'eGon2035'
375
            GROUP BY (scenario);
376
        """,
377
        warning=False,
378
    )["demand_mw_peta_heat"].values[0]
379
380
    e_demand = (
381
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
382
        * 100
383
    )
384
385
    logger.info(f"heat demand: {e_demand} %")
386
387
    # Sanity checks for heat supply
388
389
    logger.info(
390
        "For German heat supplies the following deviations between the inputs "
391
        "and outputs can be observed:"
392
    )
393
394
    # Comparison for central heat pumps
395
    heat_pump_input = db.select_dataframe(
396
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
397
            FROM supply.egon_scenario_capacities
398
            WHERE carrier= 'urban_central_heat_pump'
399
            AND scenario_name IN ('eGon2035')
400
            GROUP BY (carrier);
401
        """,
402
        warning=False,
403
    )["urban_central_heat_pump_mw"].values[0]
404
405
    heat_pump_output = db.select_dataframe(
406
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
407
            FROM grid.egon_etrago_link
408
            WHERE carrier= 'central_heat_pump'
409
            AND scn_name IN ('eGon2035')
410
            GROUP BY (carrier);
411
    """,
412
        warning=False,
413
    )["central_heat_pump_mw"].values[0]
414
415
    e_heat_pump = (
416
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
417
    )
418
419
    logger.info(f"'central_heat_pump': {e_heat_pump} % ")
420
421
    # Comparison for residential heat pumps
422
423
    input_residential_heat_pump = db.select_dataframe(
424
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
425
            FROM supply.egon_scenario_capacities
426
            WHERE carrier= 'residential_rural_heat_pump'
427
            AND scenario_name IN ('eGon2035')
428
            GROUP BY (carrier);
429
        """,
430
        warning=False,
431
    )["residential_heat_pump_mw"].values[0]
432
433
    output_residential_heat_pump = db.select_dataframe(
434
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
435
            FROM grid.egon_etrago_link
436
            WHERE carrier= 'rural_heat_pump'
437
            AND scn_name IN ('eGon2035')
438
            GROUP BY (carrier);
439
    """,
440
        warning=False,
441
    )["rural_heat_pump_mw"].values[0]
442
443
    e_residential_heat_pump = (
444
        round(
445
            (output_residential_heat_pump - input_residential_heat_pump)
446
            / input_residential_heat_pump,
447
            2,
448
        )
449
        * 100
450
    )
451
    logger.info(f"'residential heat pumps': {e_residential_heat_pump} %")
452
453
    # Comparison for resistive heater
454
    resistive_heater_input = db.select_dataframe(
455
        """SELECT carrier,
456
         SUM(capacity::numeric) as Urban_central_resistive_heater_MW
457
            FROM supply.egon_scenario_capacities
458
            WHERE carrier= 'urban_central_resistive_heater'
459
            AND scenario_name IN ('eGon2035')
460
            GROUP BY (carrier);
461
        """,
462
        warning=False,
463
    )["urban_central_resistive_heater_mw"].values[0]
464
465
    resistive_heater_output = db.select_dataframe(
466
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
467
            FROM grid.egon_etrago_link
468
            WHERE carrier= 'central_resistive_heater'
469
            AND scn_name IN ('eGon2035')
470
            GROUP BY (carrier);
471
        """,
472
        warning=False,
473
    )["central_resistive_heater_mw"].values[0]
474
475
    e_resistive_heater = (
476
        round(
477
            (resistive_heater_output - resistive_heater_input)
478
            / resistive_heater_input,
479
            2,
480
        )
481
        * 100
482
    )
483
484
    logger.info(f"'resistive heater': {e_resistive_heater} %")
485
486
    # Comparison for solar thermal collectors
487
488
    input_solar_thermal = db.select_dataframe(
489
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
490
            FROM supply.egon_scenario_capacities
491
            WHERE carrier= 'urban_central_solar_thermal_collector'
492
            AND scenario_name IN ('eGon2035')
493
            GROUP BY (carrier);
494
        """,
495
        warning=False,
496
    )["solar_thermal_collector_mw"].values[0]
497
498
    output_solar_thermal = db.select_dataframe(
499
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
500
            FROM grid.egon_etrago_generator
501
            WHERE carrier= 'solar_thermal_collector'
502
            AND scn_name IN ('eGon2035')
503
            GROUP BY (carrier);
504
        """,
505
        warning=False,
506
    )["solar_thermal_collector_mw"].values[0]
507
508
    e_solar_thermal = (
509
        round(
510
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
511
            2,
512
        )
513
        * 100
514
    )
515
    logger.info(f"'solar thermal collector': {e_solar_thermal} %")
516
517
    # Comparison for geothermal
518
519
    input_geo_thermal = db.select_dataframe(
520
        """SELECT carrier,
521
         SUM(capacity::numeric) as Urban_central_geo_thermal_MW
522
            FROM supply.egon_scenario_capacities
523
            WHERE carrier= 'urban_central_geo_thermal'
524
            AND scenario_name IN ('eGon2035')
525
            GROUP BY (carrier);
526
        """,
527
        warning=False,
528
    )["urban_central_geo_thermal_mw"].values[0]
529
530
    output_geo_thermal = db.select_dataframe(
531
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
532
            FROM grid.egon_etrago_generator
533
            WHERE carrier= 'geo_thermal'
534
            AND scn_name IN ('eGon2035')
535
            GROUP BY (carrier);
536
    """,
537
        warning=False,
538
    )["geo_thermal_mw"].values[0]
539
540
    e_geo_thermal = (
541
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
542
        * 100
543
    )
544
    logger.info(f"'geothermal': {e_geo_thermal} %")
545
546
547
def residential_electricity_annual_sum(rtol=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 scenarios using assertions:
810
811
    1. Allocated EV numbers and EVs allocated to grid districts
812
    2. Trip data (original inout data from simBEV)
813
    3. Model data in eTraGo PF tables (grid.egon_etrago_*)
814
815
    Parameters
816
    ----------
817
    None
818
819
    Returns
820
    -------
821
    None
822
    """
823
824
    def check_ev_allocation():
825
        # Get target number for scenario
826
        ev_count_target = scenario_variation_parameters["ev_count"]
827
        print(f"  Target count: {str(ev_count_target)}")
828
829
        # Get allocated numbers
830
        ev_counts_dict = {}
831
        with db.session_scope() as session:
832
            for table, level in zip(
833
                [
834
                    EgonEvCountMvGridDistrict,
835
                    EgonEvCountMunicipality,
836
                    EgonEvCountRegistrationDistrict,
837
                ],
838
                ["Grid District", "Municipality", "Registration District"],
839
            ):
840
                query = session.query(
841
                    func.sum(
842
                        table.bev_mini
843
                        + table.bev_medium
844
                        + table.bev_luxury
845
                        + table.phev_mini
846
                        + table.phev_medium
847
                        + table.phev_luxury
848
                    ).label("ev_count")
849
                ).filter(
850
                    table.scenario == scenario_name,
851
                    table.scenario_variation == scenario_var_name,
852
                )
853
854
                ev_counts = pd.read_sql(
855
                    query.statement, query.session.bind, index_col=None
856
                )
857
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
858
                print(
859
                    f"    Count table: Total count for level {level} "
860
                    f"(table: {table.__table__}): "
861
                    f"{str(ev_counts_dict[level])}"
862
                )
863
864
        # Compare with scenario target (only if not in testmode)
865
        if TESTMODE_OFF:
866
            for level, count in ev_counts_dict.items():
867
                np.testing.assert_allclose(
868
                    count,
869
                    ev_count_target,
870
                    rtol=0.0001 if ev_count_target > 1e6 else 0.02,
871
                    err_msg=f"EV numbers in {level} seems to be flawed.",
872
                )
873
        else:
874
            print("    Testmode is on, skipping sanity check...")
875
876
        # Get allocated EVs in grid districts
877
        with db.session_scope() as session:
878
            query = session.query(
879
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
880
                    "ev_count"
881
                ),
882
            ).filter(
883
                EgonEvMvGridDistrict.scenario == scenario_name,
884
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
885
            )
886
        ev_count_alloc = (
887
            pd.read_sql(query.statement, query.session.bind, index_col=None)
888
            .iloc[0]
889
            .ev_count
890
        )
891
        print(
892
            f"    EVs allocated to Grid Districts "
893
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
894
            f"{str(ev_count_alloc)}"
895
        )
896
897
        # Compare with scenario target (only if not in testmode)
898
        if TESTMODE_OFF:
899
            np.testing.assert_allclose(
900
                ev_count_alloc,
901
                ev_count_target,
902
                rtol=0.0001 if ev_count_target > 1e6 else 0.02,
903
                err_msg=(
904
                    "EV numbers allocated to Grid Districts seems to be "
905
                    "flawed."
906
                ),
907
            )
908
        else:
909
            print("    Testmode is on, skipping sanity check...")
910
911
        return ev_count_alloc
912
913
    def check_trip_data():
914
        # Check if trips start at timestep 0 and have a max. of 35040 steps
915
        # (8760h in 15min steps)
916
        print("  Checking timeranges...")
917
        with db.session_scope() as session:
918
            query = session.query(
919
                func.count(EgonEvTrip.event_id).label("cnt")
920
            ).filter(
921
                or_(
922
                    and_(
923
                        EgonEvTrip.park_start > 0,
924
                        EgonEvTrip.simbev_event_id == 0,
925
                    ),
926
                    EgonEvTrip.park_end
927
                    > (60 / int(meta_run_config.stepsize)) * 8760,
928
                ),
929
                EgonEvTrip.scenario == scenario_name,
930
            )
931
        invalid_trips = pd.read_sql(
932
            query.statement, query.session.bind, index_col=None
933
        )
934
        np.testing.assert_equal(
935
            invalid_trips.iloc[0].cnt,
936
            0,
937
            err_msg=(
938
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
939
                f"{EgonEvTrip.__table__} have invalid timesteps."
940
            ),
941
        )
942
943
        # Check if charging demand can be covered by available charging energy
944
        # while parking
945
        print("  Compare charging demand with available power...")
946
        with db.session_scope() as session:
947
            query = session.query(
948
                func.count(EgonEvTrip.event_id).label("cnt")
949
            ).filter(
950
                func.round(
951
                    cast(
952
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
953
                        * EgonEvTrip.charging_capacity_nominal
954
                        * (int(meta_run_config.stepsize) / 60),
955
                        Numeric,
956
                    ),
957
                    3,
958
                )
959
                < cast(EgonEvTrip.charging_demand, Numeric),
960
                EgonEvTrip.scenario == scenario_name,
961
            )
962
        invalid_trips = pd.read_sql(
963
            query.statement, query.session.bind, index_col=None
964
        )
965
        np.testing.assert_equal(
966
            invalid_trips.iloc[0].cnt,
967
            0,
968
            err_msg=(
969
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
970
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
971
                f"covered by available charging power."
972
            ),
973
        )
974
975
    def check_model_data():
976
        # Check if model components were fully created
977
        print("  Check if all model components were created...")
978
        # Get MVGDs which got EV allocated
979
        with db.session_scope() as session:
980
            query = (
981
                session.query(
982
                    EgonEvMvGridDistrict.bus_id,
983
                )
984
                .filter(
985
                    EgonEvMvGridDistrict.scenario == scenario_name,
986
                    EgonEvMvGridDistrict.scenario_variation
987
                    == scenario_var_name,
988
                )
989
                .group_by(EgonEvMvGridDistrict.bus_id)
990
            )
991
        mvgds_with_ev = (
992
            pd.read_sql(query.statement, query.session.bind, index_col=None)
993
            .bus_id.sort_values()
994
            .to_list()
995
        )
996
997
        # Load model components
998
        with db.session_scope() as session:
999
            query = (
1000
                session.query(
1001
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
1002
                    EgonPfHvLoad.bus.label("emob_bus_id"),
1003
                    EgonPfHvLoad.load_id.label("load_id"),
1004
                    EgonPfHvStore.store_id.label("store_id"),
1005
                )
1006
                .select_from(EgonPfHvLoad, EgonPfHvStore)
1007
                .join(
1008
                    EgonPfHvLoadTimeseries,
1009
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1010
                )
1011
                .join(
1012
                    EgonPfHvStoreTimeseries,
1013
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
1014
                )
1015
                .filter(
1016
                    EgonPfHvLoad.carrier == "land_transport_EV",
1017
                    EgonPfHvLoad.scn_name == scenario_name,
1018
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
1019
                    EgonPfHvStore.carrier == "battery_storage",
1020
                    EgonPfHvStore.scn_name == scenario_name,
1021
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
1022
                    EgonPfHvLink.scn_name == scenario_name,
1023
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
1024
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
1025
                )
1026
            )
1027
        model_components = pd.read_sql(
1028
            query.statement, query.session.bind, index_col=None
1029
        )
1030
1031
        # Check number of buses with model components connected
1032
        mvgd_buses_with_ev = model_components.loc[
1033
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
1034
        ]
1035
        np.testing.assert_equal(
1036
            len(mvgds_with_ev),
1037
            len(mvgd_buses_with_ev),
1038
            err_msg=(
1039
                f"Number of Grid Districts with connected model components "
1040
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
1041
                f"differ from number of Grid Districts that got EVs "
1042
                f"allocated ({len(mvgds_with_ev)} in table "
1043
                f"{EgonEvMvGridDistrict.__table__})."
1044
            ),
1045
        )
1046
1047
        # Check if all required components exist (if no id is NaN)
1048
        np.testing.assert_equal(
1049
            model_components.drop_duplicates().isna().any().any(),
1050
            False,
1051
            err_msg=(
1052
                f"Some components are missing (see True values): "
1053
                f"{model_components.drop_duplicates().isna().any()}"
1054
            ),
1055
        )
1056
1057
        # Get all model timeseries
1058
        print("  Loading model timeseries...")
1059
        # Get all model timeseries
1060
        model_ts_dict = {
1061
            "Load": {
1062
                "carrier": "land_transport_EV",
1063
                "table": EgonPfHvLoad,
1064
                "table_ts": EgonPfHvLoadTimeseries,
1065
                "column_id": "load_id",
1066
                "columns_ts": ["p_set"],
1067
                "ts": None,
1068
            },
1069
            "Link": {
1070
                "carrier": "BEV_charger",
1071
                "table": EgonPfHvLink,
1072
                "table_ts": EgonPfHvLinkTimeseries,
1073
                "column_id": "link_id",
1074
                "columns_ts": ["p_max_pu"],
1075
                "ts": None,
1076
            },
1077
            "Store": {
1078
                "carrier": "battery_storage",
1079
                "table": EgonPfHvStore,
1080
                "table_ts": EgonPfHvStoreTimeseries,
1081
                "column_id": "store_id",
1082
                "columns_ts": ["e_min_pu", "e_max_pu"],
1083
                "ts": None,
1084
            },
1085
        }
1086
1087
        with db.session_scope() as session:
1088
            for node, attrs in model_ts_dict.items():
1089
                print(f"    Loading {node} timeseries...")
1090
                subquery = (
1091
                    session.query(getattr(attrs["table"], attrs["column_id"]))
1092
                    .filter(attrs["table"].carrier == attrs["carrier"])
1093
                    .filter(attrs["table"].scn_name == scenario_name)
1094
                    .subquery()
1095
                )
1096
1097
                cols = [
1098
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
1099
                ]
1100
                query = session.query(
1101
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
1102
                ).filter(
1103
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
1104
                        subquery
1105
                    ),
1106
                    attrs["table_ts"].scn_name == scenario_name,
1107
                )
1108
                attrs["ts"] = pd.read_sql(
1109
                    query.statement,
1110
                    query.session.bind,
1111
                    index_col=attrs["column_id"],
1112
                )
1113
1114
        # Check if all timeseries have 8760 steps
1115
        print("    Checking timeranges...")
1116
        for node, attrs in model_ts_dict.items():
1117
            for col in attrs["columns_ts"]:
1118
                ts = attrs["ts"]
1119
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
1120
                    col
1121
                ].apply(len)
1122
                np.testing.assert_equal(
1123
                    len(invalid_ts),
1124
                    0,
1125
                    err_msg=(
1126
                        f"{str(len(invalid_ts))} rows in timeseries do not "
1127
                        f"have 8760 timesteps. Table: "
1128
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
1129
                        f"{str(list(invalid_ts.index))}"
1130
                    ),
1131
                )
1132
1133
        # Compare total energy demand in model with some approximate values
1134
        # (per EV: 14,000 km/a, 0.17 kWh/km)
1135
        print("  Checking energy demand in model...")
1136
        total_energy_model = (
1137
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
1138
            / 1e6
1139
        )
1140
        print(f"    Total energy amount in model: {total_energy_model} TWh")
1141
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
1142
        print(
1143
            f"    Total approximated energy amount in scenario: "
1144
            f"{total_energy_scenario_approx} TWh"
1145
        )
1146
        np.testing.assert_allclose(
1147
            total_energy_model,
1148
            total_energy_scenario_approx,
1149
            rtol=0.1,
1150
            err_msg=(
1151
                "The total energy amount in the model deviates more than 10% "
1152
                "from the approximated value for current scenario."
1153
            ),
1154
        )
1155
1156
        # Compare total storage capacity
1157
        print("  Checking storage capacity...")
1158
        # Load storage capacities from model
1159
        with db.session_scope() as session:
1160
            query = session.query(
1161
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
1162
            ).filter(
1163
                EgonPfHvStore.scn_name == scenario_name,
1164
                EgonPfHvStore.carrier == "battery_storage",
1165
            )
1166
        storage_capacity_model = (
1167
            pd.read_sql(
1168
                query.statement, query.session.bind, index_col=None
1169
            ).e_nom.sum()
1170
            / 1e3
1171
        )
1172
        print(
1173
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
1174
            f"{round(storage_capacity_model, 1)} GWh"
1175
        )
1176
1177
        # Load occurences of each EV
1178
        with db.session_scope() as session:
1179
            query = (
1180
                session.query(
1181
                    EgonEvMvGridDistrict.bus_id,
1182
                    EgonEvPool.type,
1183
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
1184
                        "count"
1185
                    ),
1186
                )
1187
                .join(
1188
                    EgonEvPool,
1189
                    EgonEvPool.ev_id
1190
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
1191
                )
1192
                .filter(
1193
                    EgonEvMvGridDistrict.scenario == scenario_name,
1194
                    EgonEvMvGridDistrict.scenario_variation
1195
                    == scenario_var_name,
1196
                    EgonEvPool.scenario == scenario_name,
1197
                )
1198
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
1199
            )
1200
        count_per_ev_all = pd.read_sql(
1201
            query.statement, query.session.bind, index_col="bus_id"
1202
        )
1203
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
1204
            meta_tech_data.battery_capacity
1205
        )
1206
        count_per_ev_all["bat_cap_total_MWh"] = (
1207
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
1208
        )
1209
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
1210
            1e3
1211
        ).sum()
1212
        print(
1213
            f"    Total storage capacity (simBEV): "
1214
            f"{round(storage_capacity_simbev, 1)} GWh"
1215
        )
1216
1217
        np.testing.assert_allclose(
1218
            storage_capacity_model,
1219
            storage_capacity_simbev,
1220
            rtol=0.01,
1221
            err_msg=(
1222
                "The total storage capacity in the model deviates more than "
1223
                "1% from the input data provided by simBEV for current "
1224
                "scenario."
1225
            ),
1226
        )
1227
1228
        # Check SoC storage constraint: e_min_pu < e_max_pu for all timesteps
1229
        print("  Validating SoC constraints...")
1230
        stores_with_invalid_soc = []
1231
        for idx, row in model_ts_dict["Store"]["ts"].iterrows():
1232
            ts = row[["e_min_pu", "e_max_pu"]]
1233
            x = np.array(ts.e_min_pu) > np.array(ts.e_max_pu)
1234
            if x.any():
1235
                stores_with_invalid_soc.append(idx)
1236
1237
        np.testing.assert_equal(
1238
            len(stores_with_invalid_soc),
1239
            0,
1240
            err_msg=(
1241
                f"The store constraint e_min_pu < e_max_pu does not apply "
1242
                f"for some storages in {EgonPfHvStoreTimeseries.__table__}. "
1243
                f"Invalid store_ids: {stores_with_invalid_soc}"
1244
            ),
1245
        )
1246
1247
    def check_model_data_lowflex_eGon2035():
1248
        # TODO: Add eGon100RE_lowflex
1249
        print("")
1250
        print("SCENARIO: eGon2035_lowflex")
1251
1252
        # Compare driving load and charging load
1253
        print("  Loading eGon2035 model timeseries: driving load...")
1254
        with db.session_scope() as session:
1255
            query = (
1256
                session.query(
1257
                    EgonPfHvLoad.load_id,
1258
                    EgonPfHvLoadTimeseries.p_set,
1259
                )
1260
                .join(
1261
                    EgonPfHvLoadTimeseries,
1262
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1263
                )
1264
                .filter(
1265
                    EgonPfHvLoad.carrier == "land_transport_EV",
1266
                    EgonPfHvLoad.scn_name == "eGon2035",
1267
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035",
1268
                )
1269
            )
1270
        model_driving_load = pd.read_sql(
1271
            query.statement, query.session.bind, index_col=None
1272
        )
1273
        driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0)
1274
1275
        print(
1276
            "  Loading eGon2035_lowflex model timeseries: dumb charging "
1277
            "load..."
1278
        )
1279
        with db.session_scope() as session:
1280
            query = (
1281
                session.query(
1282
                    EgonPfHvLoad.load_id,
1283
                    EgonPfHvLoadTimeseries.p_set,
1284
                )
1285
                .join(
1286
                    EgonPfHvLoadTimeseries,
1287
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1288
                )
1289
                .filter(
1290
                    EgonPfHvLoad.carrier == "land_transport_EV",
1291
                    EgonPfHvLoad.scn_name == "eGon2035_lowflex",
1292
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex",
1293
                )
1294
            )
1295
        model_charging_load_lowflex = pd.read_sql(
1296
            query.statement, query.session.bind, index_col=None
1297
        )
1298
        charging_load = np.array(
1299
            model_charging_load_lowflex.p_set.to_list()
1300
        ).sum(axis=0)
1301
1302
        # Ratio of driving and charging load should be 0.9 due to charging
1303
        # efficiency
1304
        print("  Compare cumulative loads...")
1305
        print(f"    Driving load (eGon2035): {driving_load.sum() / 1e6} TWh")
1306
        print(
1307
            f"    Dumb charging load (eGon2035_lowflex): "
1308
            f"{charging_load.sum() / 1e6} TWh"
1309
        )
1310
        driving_load_theoretical = (
1311
            float(meta_run_config.eta_cp) * charging_load.sum()
0 ignored issues
show
introduced by
The variable meta_run_config does not seem to be defined in case the for loop on line 1329 is not entered. Are you sure this can never be the case?
Loading history...
1312
        )
1313
        np.testing.assert_allclose(
1314
            driving_load.sum(),
1315
            driving_load_theoretical,
1316
            rtol=0.01,
1317
            err_msg=(
1318
                f"The driving load (eGon2035) deviates by more than 1% "
1319
                f"from the theoretical driving load calculated from charging "
1320
                f"load (eGon2035_lowflex) with an efficiency of "
1321
                f"{float(meta_run_config.eta_cp)}."
1322
            ),
1323
        )
1324
1325
    print("=====================================================")
1326
    print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===")
1327
    print("=====================================================")
1328
1329
    for scenario_name in ["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 = (
2050
            Path(".") / "data_bundle_egon_data" / "gas_data" / basename
2051
        )
2052
2053
        conversion_factor_b = 0.01083  # m^3/h to MWh/h
2054
        p_biogas = (
2055
            pd.read_excel(
2056
                target_file,
2057
                usecols=["Einspeisung Biomethan [(N*m^3)/h)]"],
2058
            )["Einspeisung Biomethan [(N*m^3)/h)]"].sum()
2059
            * conversion_factor_b
2060
        )
2061
2062
        input_gas_generation = p_NG + p_biogas
2063
        e_generation = (
2064
            round(
2065
                (output_gas_generation - input_gas_generation)
2066
                / input_gas_generation,
2067
                2,
2068
            )
2069
            * 100
2070
        )
2071
        logger.info(
2072
            f"Deviation {carrier_generator} generation: {e_generation} %"
2073
        )
2074
2075
        # Stores
2076
        logger.info("STORES")
2077
        sanity_check_CH4_stores(scn)
2078
        sanity_check_H2_saltcavern_stores(scn)
2079
2080
        # One-port components
2081
        sanity_check_gas_one_port(scn)
2082
2083
        # Links
2084
        logger.info("LINKS")
2085
        sanity_check_CH4_grid(scn)
2086
        sanity_check_gas_links(scn)
2087
2088
    else:
2089
        print("Testmode is on, skipping sanity check.")
2090
2091
2092
def etrago_eGon2035_gas_abroad():
2093
    """Execute basic sanity checks for the gas sector in eGon2035 abroad
2094
2095
    Returns print statements as sanity checks for the gas sector in
2096
    the eGon2035 scenario for the following components in Germany:
2097
2098
      * Buses
2099
      * Loads: for the carriers 'CH4' and 'H2_for_industry'
2100
        the deviation is calculated between the sum of the loads in the
2101
        database and the sum in the sources document (TYNDP)
2102
      * Generators: the deviation is calculated between the sums of the
2103
        nominal powers of the methane generators abroad in the database
2104
        and of the ones in the sources document (TYNDP)
2105
      * Stores: the deviation for methane stores abroad is calculated
2106
        between the sum of the capacities in the data base and the one
2107
        of the source document (SciGRID_gas data)
2108
      * Links: verification of the capacity of the crossbordering gas
2109
        grid pipelines.
2110
2111
    """
2112
    scn = "eGon2035"
2113
2114
    if TESTMODE_OFF:
2115
        logger.info(f"Gas sanity checks abroad for scenario {scn}")
2116
2117
        # Buses
2118
        logger.info("BUSES")
2119
2120
        # Are gas buses isolated?
2121
        corresponding_carriers = {
2122
            "eGon2035": {
2123
                "CH4": "CH4",
2124
            },
2125
            # "eGon100RE": {
2126
            #     "CH4": "CH4",
2127
            #     "H2_grid": "H2_retrofit",
2128
            # }
2129
        }
2130
        for key in corresponding_carriers[scn]:
2131
            isolated_gas_buses_abroad = db.select_dataframe(
2132
                f"""
2133
                SELECT bus_id, carrier, country
2134
                FROM grid.egon_etrago_bus
2135
                WHERE scn_name = '{scn}'
2136
                AND carrier = '{key}'
2137
                AND country != 'DE'
2138
                AND bus_id NOT IN
2139
                    (SELECT bus0
2140
                    FROM grid.egon_etrago_link
2141
                    WHERE scn_name = '{scn}'
2142
                    AND carrier = '{corresponding_carriers[scn][key]}')
2143
                AND bus_id NOT IN
2144
                    (SELECT bus1
2145
                    FROM grid.egon_etrago_link
2146
                    WHERE scn_name = '{scn}'
2147
                    AND carrier = '{corresponding_carriers[scn][key]}')
2148
                ;
2149
                """,
2150
                warning=False,
2151
            )
2152
            if not isolated_gas_buses_abroad.empty:
2153
                logger.info(f"Isolated {key} buses abroad:")
2154
                logger.info(isolated_gas_buses_abroad)
2155
2156
        # Loads
2157
        logger.info("LOADS")
2158
2159
        (
2160
            Norway_global_demand_1y,
2161
            normalized_ch4_demandTS,
2162
        ) = import_ch4_demandTS()
2163
        input_CH4_demand_abroad = calc_global_ch4_demand(
2164
            Norway_global_demand_1y
2165
        )
2166
        input_CH4_demand = input_CH4_demand_abroad["GlobD_2035"].sum()
2167
2168
        ## CH4
2169
        output_CH4_demand = db.select_dataframe(
2170
            f"""SELECT (SUM(
2171
                (SELECT SUM(p)
2172
                FROM UNNEST(b.p_set) p)))::numeric as load_mwh
2173
                FROM grid.egon_etrago_load a
2174
                JOIN grid.egon_etrago_load_timeseries b
2175
                ON (a.load_id = b.load_id)
2176
                JOIN grid.egon_etrago_bus c
2177
                ON (a.bus=c.bus_id)
2178
                AND b.scn_name = '{scn}'
2179
                AND a.scn_name = '{scn}'
2180
                AND c.scn_name = '{scn}'
2181
                AND c.country != 'DE'
2182
                AND a.carrier = 'CH4';
2183
            """,
2184
            warning=False,
2185
        )["load_mwh"].values[0]
2186
2187
        e_demand_CH4 = (
2188
            round(
2189
                (output_CH4_demand - input_CH4_demand) / input_CH4_demand,
2190
                2,
2191
            )
2192
            * 100
2193
        )
2194
        logger.info(f"Deviation CH4 load: {e_demand_CH4} %")
2195
2196
        ## H2_for_industry
2197
        input_power_to_h2_demand_abroad = calc_global_power_to_h2_demand()
2198
        input_H2_demand = input_power_to_h2_demand_abroad["GlobD_2035"].sum()
2199
2200
        output_H2_demand = db.select_dataframe(
2201
            f"""SELECT SUM(p_set::numeric) as p_set_abroad
2202
                    FROM grid.egon_etrago_load
2203
                    WHERE scn_name = '{scn}'
2204
                    AND carrier = 'H2_for_industry'
2205
                    AND bus IN
2206
                        (SELECT bus_id
2207
                        FROM grid.egon_etrago_bus
2208
                        WHERE scn_name = '{scn}'
2209
                        AND country != 'DE'
2210
                        AND carrier = 'AC');
2211
                    """,
2212
            warning=False,
2213
        )["p_set_abroad"].values[0]
2214
2215
        e_demand_H2 = (
2216
            round(
2217
                (output_H2_demand - input_H2_demand) / input_H2_demand,
2218
                2,
2219
            )
2220
            * 100
2221
        )
2222
        logger.info(f"Deviation H2_for_industry load: {e_demand_H2} %")
2223
2224
        # Generators
2225
        logger.info("GENERATORS ")
2226
        CH4_gen = calc_capacities()
2227
        input_CH4_gen = CH4_gen["cap_2035"].sum()
2228
2229
        output_CH4_gen = db.select_dataframe(
2230
            f"""SELECT SUM(p_nom::numeric) as p_nom_abroad
2231
                    FROM grid.egon_etrago_generator
2232
                    WHERE scn_name = '{scn}'
2233
                    AND carrier = 'CH4'
2234
                    AND bus IN
2235
                        (SELECT bus_id
2236
                        FROM grid.egon_etrago_bus
2237
                        WHERE scn_name = '{scn}'
2238
                        AND country != 'DE'
2239
                        AND carrier = 'CH4');
2240
                    """,
2241
            warning=False,
2242
        )["p_nom_abroad"].values[0]
2243
2244
        e_gen = (
2245
            round(
2246
                (output_CH4_gen - input_CH4_gen) / input_CH4_gen,
2247
                2,
2248
            )
2249
            * 100
2250
        )
2251
        logger.info(f"Deviation CH4 generators: {e_gen} %")
2252
2253
        # Stores
2254
        logger.info("STORES")
2255
        ch4_input_capacities = calc_ch4_storage_capacities()
2256
        input_CH4_stores = ch4_input_capacities["e_nom"].sum()
2257
2258
        output_CH4_stores = db.select_dataframe(
2259
            f"""SELECT SUM(e_nom::numeric) as e_nom_abroad
2260
                    FROM grid.egon_etrago_store
2261
                    WHERE scn_name = '{scn}'
2262
                    AND carrier = 'CH4'
2263
                    AND bus IN
2264
                        (SELECT bus_id
2265
                        FROM grid.egon_etrago_bus
2266
                        WHERE scn_name = '{scn}'
2267
                        AND country != 'DE'
2268
                        AND carrier = 'CH4');
2269
                    """,
2270
            warning=False,
2271
        )["e_nom_abroad"].values[0]
2272
2273
        e_stores = (
2274
            round(
2275
                (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
2276
                2,
2277
            )
2278
            * 100
2279
        )
2280
        logger.info(f"Deviation CH4 stores: {e_stores} %")
2281
2282
        # Links
2283
        logger.info("LINKS")
2284
        ch4_grid_input_capacities = calculate_ch4_grid_capacities()
2285
        input_CH4_grid = ch4_grid_input_capacities["p_nom"].sum()
2286
2287
        grid_carrier = "CH4"
2288
        output_gas_grid = db.select_dataframe(
2289
            f"""SELECT SUM(p_nom::numeric) as p_nom
2290
            FROM grid.egon_etrago_link
2291
            WHERE scn_name = '{scn}'
2292
            AND carrier = '{grid_carrier}'
2293
            AND (bus0 IN
2294
                (SELECT bus_id
2295
                FROM grid.egon_etrago_bus
2296
                WHERE scn_name = '{scn}'
2297
                AND country != 'DE'
2298
                AND carrier = '{grid_carrier}')
2299
            OR bus1 IN
2300
                (SELECT bus_id
2301
                FROM grid.egon_etrago_bus
2302
                WHERE scn_name = '{scn}'
2303
                AND country != 'DE'
2304
                AND carrier = '{grid_carrier}'))
2305
                ;
2306
            """,
2307
            warning=False,
2308
        )["p_nom"].values[0]
2309
2310
        e_gas_grid = (
2311
            round(
2312
                (output_gas_grid - input_CH4_grid) / input_CH4_grid,
2313
                2,
2314
            )
2315
            * 100
2316
        )
2317
        logger.info(
2318
            f"Deviation of the capacity of the crossbordering CH4 grid: {e_gas_grid} %"
2319
        )
2320
2321
    else:
2322
        print("Testmode is on, skipping sanity check.")
2323
2324
2325
def sanitycheck_dsm():
2326
    def df_from_series(s: pd.Series):
2327
        return pd.DataFrame.from_dict(dict(zip(s.index, s.values)))
2328
2329
    for scenario in ["eGon2035", "eGon100RE"]:
2330
        # p_min and p_max
2331
        sql = f"""
2332
        SELECT link_id, bus0 as bus, p_nom FROM grid.egon_etrago_link
2333
        WHERE carrier = 'dsm'
2334
        AND scn_name = '{scenario}'
2335
        ORDER BY link_id
2336
        """
2337
2338
        meta_df = db.select_dataframe(sql, index_col="link_id")
2339
        link_ids = str(meta_df.index.tolist())[1:-1]
2340
2341
        sql = f"""
2342
        SELECT link_id, p_min_pu, p_max_pu
2343
        FROM grid.egon_etrago_link_timeseries
2344
        WHERE scn_name = '{scenario}'
2345
        AND link_id IN ({link_ids})
2346
        ORDER BY link_id
2347
        """
2348
2349
        ts_df = db.select_dataframe(sql, index_col="link_id")
2350
2351
        p_max_df = df_from_series(ts_df.p_max_pu).mul(meta_df.p_nom)
2352
        p_min_df = df_from_series(ts_df.p_min_pu).mul(meta_df.p_nom)
2353
2354
        p_max_df.columns = meta_df.bus.tolist()
2355
        p_min_df.columns = meta_df.bus.tolist()
2356
2357
        targets = config.datasets()["DSM_CTS_industry"]["targets"]
2358
2359
        tables = [
2360
            "cts_loadcurves_dsm",
2361
            "ind_osm_loadcurves_individual_dsm",
2362
            "demandregio_ind_sites_dsm",
2363
            "ind_sites_loadcurves_individual",
2364
        ]
2365
2366
        df_list = []
2367
2368
        for table in tables:
2369
            target = targets[table]
2370
            sql = f"""
2371
            SELECT bus, p_min, p_max, e_max, e_min
2372
            FROM {target["schema"]}.{target["table"]}
2373
            WHERE scn_name = '{scenario}'
2374
            ORDER BY bus
2375
            """
2376
2377
            df_list.append(db.select_dataframe(sql))
2378
2379
        individual_ts_df = pd.concat(df_list, ignore_index=True)
2380
2381
        groups = individual_ts_df[["bus"]].reset_index().groupby("bus").groups
2382
2383
        individual_p_max_df = df_from_series(individual_ts_df.p_max)
2384
2385
        individual_p_max_df = pd.DataFrame(
2386
            [
2387
                individual_p_max_df[idxs].sum(axis=1)
2388
                for idxs in groups.values()
2389
            ],
2390
            index=groups.keys(),
2391
        ).T
2392
2393
        individual_p_min_df = df_from_series(individual_ts_df.p_min)
2394
2395
        individual_p_min_df = pd.DataFrame(
2396
            [
2397
                individual_p_min_df[idxs].sum(axis=1)
2398
                for idxs in groups.values()
2399
            ],
2400
            index=groups.keys(),
2401
        ).T
2402
2403
        # due to the fact that time series are clipped at zero (either
2404
        # direction) there is a little difference between the sum of the
2405
        # individual time series and the aggregated time series as the second
2406
        # is generated independent of the others. This makes atol=1e-01
2407
        # necessary.
2408
        atol = 1e-01
2409
        assert np.allclose(p_max_df, individual_p_max_df, atol=atol)
2410
        assert np.allclose(p_min_df, individual_p_min_df, atol=atol)
2411
2412
        # e_min and e_max
2413
        sql = f"""
2414
        SELECT store_id, bus, e_nom FROM grid.egon_etrago_store
2415
        WHERE carrier = 'dsm'
2416
        AND scn_name = '{scenario}'
2417
        ORDER BY store_id
2418
        """
2419
2420
        meta_df = db.select_dataframe(sql, index_col="store_id")
2421
        store_ids = str(meta_df.index.tolist())[1:-1]
2422
2423
        sql = f"""
2424
        SELECT store_id, e_min_pu, e_max_pu
2425
        FROM grid.egon_etrago_store_timeseries
2426
        WHERE scn_name = '{scenario}'
2427
        AND store_id IN ({store_ids})
2428
        ORDER BY store_id
2429
        """
2430
2431
        ts_df = db.select_dataframe(sql, index_col="store_id")
2432
2433
        e_max_df = df_from_series(ts_df.e_max_pu).mul(meta_df.e_nom)
2434
        e_min_df = df_from_series(ts_df.e_min_pu).mul(meta_df.e_nom)
2435
2436
        e_max_df.columns = meta_df.bus.tolist()
2437
        e_min_df.columns = meta_df.bus.tolist()
2438
2439
        individual_e_max_df = df_from_series(individual_ts_df.e_max)
2440
2441
        individual_e_max_df = pd.DataFrame(
2442
            [
2443
                individual_e_max_df[idxs].sum(axis=1)
2444
                for idxs in groups.values()
2445
            ],
2446
            index=groups.keys(),
2447
        ).T
2448
        individual_e_min_df = df_from_series(individual_ts_df.e_min)
2449
2450
        individual_e_min_df = pd.DataFrame(
2451
            [
2452
                individual_e_min_df[idxs].sum(axis=1)
2453
                for idxs in groups.values()
2454
            ],
2455
            index=groups.keys(),
2456
        ).T
2457
2458
        assert np.allclose(e_max_df, individual_e_max_df)
2459
        assert np.allclose(e_min_df, individual_e_min_df)
2460
2461
2462
def etrago_timeseries_length():
2463
2464
    for component in ["generator", "load", "link", "store", "storage"]:
2465
2466
        columns = db.select_dataframe(
2467
            f"""
2468
            SELECT *
2469
            FROM information_schema.columns
2470
            WHERE table_schema = 'grid'
2471
            AND table_name = 'egon_etrago_{component}_timeseries'
2472
            """
2473
        )
2474
        columns = columns[columns.data_type == "ARRAY"].column_name.values
2475
2476
        for col in columns:
2477
            lengths = db.select_dataframe(
2478
                f"""
2479
                SELECT array_length({col}, 1)
2480
                FROM grid.egon_etrago_{component}_timeseries;
2481
                """
2482
            )["array_length"]
2483
2484
            if not lengths.dropna().empty:
2485
                assert (
2486
                    lengths.dropna() == 8760
2487
                ).all(), (
2488
                    f"Timeseries with a length != 8760 for {component} {col}"
2489
                )
2490
            else:
2491
                print(f"Empty timeseries for {component} {col}")
2492
2493
2494
def generators_links_storages_stores_100RE(scn="eGon100RE"):
2495
    # Generators
2496
    scn_capacities = db.select_dataframe(
2497
        f"""
2498
        SELECT * FROM supply.egon_scenario_capacities
2499
        WHERE scenario_name = '{scn}'
2500
        """,
2501
        index_col="index",
2502
    )
2503
2504
    map_carrier = {
2505
        "urban_central_solar_thermal_collector": "solar_thermal_collector",
2506
        "urban_central_geo_thermal": "geo_thermal",
2507
        "urban_central_gas_boiler": "central_gas_boiler",
2508
        "urban_central_heat_pump": "central_heat_pump",
2509
        "urban_central_resistive_heater": "central_resistive_heater",
2510
        "gas": "OCGT",
2511
    }
2512
2513
    scn_capacities["carrier"] = scn_capacities["carrier"].apply(
2514
        lambda x: map_carrier[x] if x in map_carrier.keys() else x
2515
    )
2516
2517
    carriers_gen_from_supply = [
2518
        "oil",
2519
        "solar",
2520
        "solar_rooftop",
2521
        "wind_onshore",
2522
        "lignite",
2523
        "coal",
2524
        "wind_offshore",
2525
        "solar_thermal_collector",
2526
        "geo_thermal",
2527
        "run_of_river",
2528
        "rural_solar_thermal",
2529
        "urban_central_gas_CHP",
2530
        "urban_central_solid_biomass_CHP",
2531
    ]
2532
2533
    gen_etrago = db.select_dataframe(
2534
        f"""
2535
        SELECT * FROM grid.egon_etrago_generator
2536
        WHERE scn_name = '{scn}'
2537
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2538
                    WHERE scn_name = '{scn}'
2539
                    AND country = 'DE')
2540
        """,
2541
        warning=False,
2542
    )
2543
2544
    carriers_gen = set(carriers_gen_from_supply + list(gen_etrago["carrier"]))
2545
2546
    gen_capacities = pd.DataFrame(
2547
        index=list(carriers_gen), columns=["supply_table", scn]
2548
    )
2549
    gen_capacities[scn] = gen_etrago.groupby("carrier").p_nom.sum()
2550
2551
    gen_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2552
        "capacity"
2553
    ]
2554
2555
    gen_capacities.dropna(how="all", inplace=True)
2556
2557
    print(f"\nMain results regarding generators for {scn}\n")
2558
    print(gen_capacities)
2559
2560
    ###########################################################################
2561
    # Links
2562
2563
    carriers_links_from_supply = [
2564
        "central_gas_boiler",
2565
        "central_heat_pump",
2566
        "central_resistive_heater",
2567
        "gas",
2568
        "rural_biomass_boiler",
2569
        "rural_gas_boiler",
2570
        "rural_heat_pump",
2571
        "rural_oil_boiler",
2572
        "rural_resistive_heater",
2573
    ]
2574
2575
    link_etrago = db.select_dataframe(
2576
        f"""
2577
        SELECT * FROM grid.egon_etrago_link
2578
        WHERE scn_name = '{scn}'
2579
        AND (bus0 IN (SELECT bus_id from grid.egon_etrago_bus
2580
                    WHERE scn_name = '{scn}'
2581
                    AND country = 'DE')
2582
             OR
2583
             bus1 IN (SELECT bus_id from grid.egon_etrago_bus
2584
                    WHERE scn_name = '{scn}'
2585
                    AND country = 'DE')
2586
             )
2587
        """,
2588
        warning=False,
2589
    )
2590
2591
    carriers_link = set(
2592
        carriers_links_from_supply + list(link_etrago["carrier"])
2593
    )
2594
2595
    link_capacities = pd.DataFrame(
2596
        index=list(carriers_link), columns=["supply_table", scn]
2597
    )
2598
2599
    link_capacities["eGon100RE"] = link_etrago.groupby("carrier").p_nom.sum()
2600
2601
    link_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2602
        "capacity"
2603
    ]
2604
2605
    link_capacities.dropna(how="all", inplace=True)
2606
2607
    print(f"\nMain results regarding links for {scn}\n")
2608
    print(link_capacities)
2609
    ###########################################################################
2610
    # storage
2611
    storage_etrago = db.select_dataframe(
2612
        f"""
2613
        SELECT * FROM grid.egon_etrago_storage
2614
        WHERE scn_name = '{scn}'
2615
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2616
                    WHERE scn_name = '{scn}'
2617
                    AND country = 'DE')
2618
        """,
2619
    )
2620
2621
    carriers_storage_from_supply = ["pumped_hydro"]
2622
2623
    carriers_storage = set(
2624
        carriers_storage_from_supply + 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] = storage_etrago.groupby("carrier").p_nom.sum()
2632
2633
    storage_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2634
        "capacity"
2635
    ]
2636
2637
    print(f"\nMain results regarding storage units for {scn}\n")
2638
    print(storage_capacities)
2639
    ###########################################################################
2640
    # stores
2641
    stores_etrago = db.select_dataframe(
2642
        f"""
2643
        SELECT * FROM grid.egon_etrago_store
2644
        WHERE scn_name = '{scn}'
2645
        AND bus IN (SELECT bus_id from grid.egon_etrago_bus
2646
                    WHERE scn_name = '{scn}'
2647
                    AND country = 'DE')
2648
        """,
2649
    )
2650
2651
    carriers_stores_from_supply = []
2652
2653
    carriers_stores = set(
2654
        carriers_stores_from_supply + list(stores_etrago["carrier"])
2655
    )
2656
2657
    stores_capacities = pd.DataFrame(
2658
        index=list(carriers_stores), columns=["supply_table", scn]
2659
    )
2660
2661
    stores_capacities[scn] = stores_etrago.groupby("carrier").e_nom.sum()
2662
2663
    stores_capacities["supply_table"] = scn_capacities.set_index("carrier")[
2664
        "capacity"
2665
    ]
2666
2667
    print(f"\nMain results regarding stores for {scn}\n")
2668
    print(stores_capacities)
2669
2670
    return
2671
2672
2673
def electrical_load_100RE(scn="eGon100RE"):
2674
    load_summary = pd.DataFrame(
2675
        index=[
2676
            "residential",
2677
            "commercial",
2678
            "industrial",
2679
            "total",
2680
        ],
2681
        columns=["objective", "eGon100RE"],
2682
    )
2683
2684
    # Sector	Annual electricity demand in TWh
2685
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L480
2686
    load_summary.loc["residential", "objective"] = 90.4
2687
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L775
2688
    load_summary.loc["commercial", "objective"] = 146.7
2689
    # https://github.com/openego/powerd-data/blob/56b8215928a8dc4fe953d266c563ce0ed98e93f9/src/egon/data/datasets/demandregio/__init__.py#L775
2690
    load_summary.loc["industrial", "objective"] = 382.9
2691
    load_summary.loc["total", "objective"] = 620.0
2692
2693
    print(
2694
        "For German electricity loads the following deviations between the"
2695
        " input and output can be observed:"
2696
    )
2697
2698
    load_summary.loc["total", "eGon100RE"] = db.select_dataframe(
2699
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
2700
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
2701
            FROM grid.egon_etrago_load a
2702
            JOIN grid.egon_etrago_load_timeseries b
2703
            ON (a.load_id = b.load_id)
2704
            JOIN grid.egon_etrago_bus c
2705
            ON (a.bus=c.bus_id)
2706
            AND b.scn_name = 'eGon100RE'
2707
            AND a.scn_name = 'eGon100RE'
2708
            AND a.carrier = 'AC'
2709
            AND c.scn_name= 'eGon100RE'
2710
            AND c.country='DE'
2711
            GROUP BY (a.scn_name, a.carrier);
2712
    """,
2713
        warning=False,
2714
    )["load_twh"].values[0]
2715
2716
    sources = egon.data.config.datasets()["etrago_electricity"]["sources"]
2717
    cts_curves = db.select_dataframe(
2718
        f"""SELECT bus_id AS bus, p_set FROM
2719
                {sources['cts_curves']['schema']}.
2720
                {sources['cts_curves']['table']}
2721
                WHERE scn_name = '{scn}'""",
2722
    )
2723
    sum_cts_curves = (
2724
        cts_curves.apply(lambda x: sum(x["p_set"]), axis=1).sum() / 1000000
2725
    )
2726
    load_summary.loc["commercial", "eGon100RE"] = sum_cts_curves
2727
2728
    # Select data on industrial demands assigned to osm landuse areas
2729
    ind_curves_osm = db.select_dataframe(
2730
        f"""SELECT bus, p_set FROM
2731
                {sources['osm_curves']['schema']}.
2732
                {sources['osm_curves']['table']}
2733
                WHERE scn_name = '{scn}'""",
2734
    )
2735
    sum_ind_curves_osm = (
2736
        ind_curves_osm.apply(lambda x: sum(x["p_set"]), axis=1).sum() / 1000000
2737
    )
2738
2739
    # Select data on industrial demands assigned to industrial sites
2740
2741
    ind_curves_sites = db.select_dataframe(
2742
        f"""SELECT bus, p_set FROM
2743
                {sources['sites_curves']['schema']}.
2744
                {sources['sites_curves']['table']}
2745
                WHERE scn_name = '{scn}'""",
2746
    )
2747
    sum_ind_curves_sites = (
2748
        ind_curves_sites.apply(lambda x: sum(x["p_set"]), axis=1).sum()
2749
        / 1000000
2750
    )
2751
2752
    load_summary.loc["industrial", "eGon100RE"] = (
2753
        sum_ind_curves_osm + sum_ind_curves_sites
2754
    )
2755
2756
    # Select data on household electricity demands per bus
2757
    hh_curves = db.select_dataframe(
2758
        f"""SELECT bus_id AS bus, p_set FROM
2759
                {sources['household_curves']['schema']}.
2760
                {sources['household_curves']['table']}
2761
                WHERE scn_name = '{scn}'""",
2762
    )
2763
    sum_hh_curves = (
2764
        hh_curves.apply(lambda x: sum(x["p_set"]), axis=1).sum() / 1000000
2765
    )
2766
    load_summary.loc["residential", "eGon100RE"] = sum_hh_curves
2767
2768
    load_summary["diff"] = (
2769
        load_summary["eGon100RE"] - load_summary["objective"]
2770
    )
2771
    load_summary["diff[%]"] = (
2772
        load_summary["diff"] / load_summary["eGon100RE"] * 100
2773
    )
2774
2775
    print(load_summary)
2776
2777
    assert (
2778
        load_summary["diff[%]"] < 1
2779
    ).all(), "electrical loads differ from objective values"
2780
2781
    return ()
2782
2783
2784
def heat_gas_load_egon100RE(scn="eGon100RE"):
2785
2786
    # dictionary for matching pypsa_eur carrier with egon-data carriers
2787
    load_carrier_dict = {
2788
        "DE0 0 land transport EV": "land transport EV",
2789
        "DE0 0 rural heat": "rural_heat",
2790
        "DE0 0 urban central heat": "central_heat",
2791
        "DE0 0 urban decentral heat": "rural_heat",
2792
        "rural heat": "rural_heat",
2793
        "H2 for industry": "H2_for_industry",
2794
        "gas for industry": "CH4_for_industry",
2795
        "urban central heat": "central_heat",
2796
        "urban decentral heat": "rural_heat",
2797
        "land transport EV": "land transport EV",
2798
    }
2799
2800
    # filter out NaN values central_heat timeseries
2801
    NaN_load_ids = db.select_dataframe(
2802
        """
2803
        SELECT load_id from grid.egon_etrago_load_timeseries 
2804
        WHERE load_id IN (Select load_id 
2805
            FROM grid.egon_etrago_load
2806
            WHERE carrier = 'central_heat') AND (SELECT 
2807
            bool_or(value::double precision::text = 'NaN') 
2808
        FROM unnest(p_set) AS value
2809
        )
2810
       """
2811
    )
2812
    nan_load_list = tuple(NaN_load_ids["load_id"].tolist())
2813
    nan_load_str = ",".join(map(str, nan_load_list))
2814
2815
    #####loads for eGon100RE
2816
    loads_etrago_timeseries = db.select_dataframe(
2817
        f"""
2818
            SELECT 
2819
                l.carrier,
2820
                SUM(
2821
                    (SELECT SUM(p)
2822
                    FROM UNNEST(t.p_set) p)  
2823
                )  AS total_p_set_timeseries  
2824
            FROM 
2825
                grid.egon_etrago_load l
2826
            LEFT JOIN 
2827
                grid.egon_etrago_load_timeseries t ON l.load_id = t.load_id 
2828
            WHERE 
2829
                l.scn_name = '{scn}'
2830
                AND l.carrier != 'AC'
2831
                AND l.bus IN (
2832
                    SELECT bus_id
2833
                    FROM grid.egon_etrago_bus
2834
                    WHERE scn_name = '{scn}' 
2835
                    AND country = 'DE'
2836
                )
2837
                AND l.load_id NOT IN ({nan_load_str})
2838
                
2839
            GROUP BY 
2840
                l.carrier
2841
        """
2842
    )
2843
2844
    #####loads for pypsa_eur_network
2845
    n = read_network()
2846
2847
    # aggregate loads with values in timeseries dataframe
2848
    df_load_timeseries = n.loads_t.p_set
2849
    filtered_columns = [
2850
        col
2851
        for col in df_load_timeseries.columns
2852
        if col.startswith("DE") and "electricity" not in col
2853
    ]
2854
    german_loads_timeseries = df_load_timeseries[filtered_columns]
2855
    german_loads_timeseries = german_loads_timeseries.drop(columns=["DE0 0"])
2856
    german_loads_timeseries = german_loads_timeseries.mul(
2857
        n.snapshot_weightings.generators, axis=0
2858
    ).sum()
2859
    german_loads_timeseries = german_loads_timeseries.rename(
2860
        index=load_carrier_dict
2861
    )
2862
2863
    # sum loads with fixed p_set in loads dataframe
2864
    german_load_static_p_set = n.loads[
2865
        n.loads.index.str.startswith("DE")
2866
        & ~n.loads.carrier.str.contains("electricity")
2867
    ]
2868
    german_load_static_p_set = (
2869
        german_load_static_p_set.groupby("carrier").p_set.sum() * 8760
2870
    )
2871
    german_load_static_p_set = german_load_static_p_set.rename(
2872
        index=load_carrier_dict
2873
    )
2874
    german_load_static_p_set["H2_for_industry"] = (
2875
        german_load_static_p_set["H2_for_industry"]
2876
        + +n.links_t.p0[
2877
            n.links.loc[
2878
                n.links.index.str.contains("DE0 0 Fischer-Tropsch")
2879
            ].index
2880
        ]
2881
        .mul(n.snapshot_weightings.generators, axis=0)
2882
        .sum()
2883
        .sum()
2884
        + n.links_t.p0[
2885
            n.links.loc[
2886
                n.links.index.str.contains("DE0 0 methanolisation")
2887
            ].index
2888
        ]
2889
        .mul(n.snapshot_weightings.generators, axis=0)
2890
        .sum()
2891
        .sum()
2892
    )
2893
2894
    # combine p_set and timeseries dataframes from pypsa eur
2895
    german_loads_timeseries_df = german_loads_timeseries.to_frame()
2896
    german_loads_timeseries_df["carrier"] = german_loads_timeseries_df.index
2897
    german_loads_timeseries_df.set_index("carrier", inplace=True)
2898
2899
    german_load_static_p_set_df = german_load_static_p_set.to_frame()
2900
    german_load_static_p_set_df = german_load_static_p_set_df.groupby(
2901
        "carrier", as_index=True
2902
    ).sum()
2903
    german_loads_timeseries_df = german_loads_timeseries_df.groupby(
2904
        "carrier", as_index=True
2905
    ).sum()
2906
    combined = pd.merge(
2907
        german_load_static_p_set_df,
2908
        german_loads_timeseries_df,
2909
        on="carrier",
2910
        how="left",
2911
    )
2912
2913
    combined["p_set"] = np.where(
2914
        combined["p_set"] == 0, combined[0], combined["p_set"]
2915
    )
2916
    combined = combined.drop(columns=[0])
2917
2918
    # carriers_for_comparison
2919
    carriers_loads = set(
2920
        german_load_static_p_set.index.union(
2921
            german_loads_timeseries.index
2922
        ).union(loads_etrago_timeseries["carrier"])
2923
    )
2924
2925
    # create dataframe for comparison
2926
    loads_capacities = pd.DataFrame(
2927
        index=list(carriers_loads), columns=["pypsa_eur", scn]
2928
    )
2929
    loads_capacities[scn] = loads_etrago_timeseries.groupby(
2930
        "carrier"
2931
    ).total_p_set_timeseries.sum()
2932
    loads_capacities["pypsa_eur"] = combined["p_set"]
2933
    loads_capacities["diff [%]"] = (
2934
        (loads_capacities[scn] - loads_capacities["pypsa_eur"])
2935
        / loads_capacities["pypsa_eur"].replace(0, np.nan)
2936
    ) * 100
2937
2938
    print("=" * 50)
2939
    print(
2940
        "Comparison of Gas and Heat Loads with PyPSA-Eur Data".center(50, "=")
2941
    )
2942
    print("=" * 50)
2943
    print(loads_capacities)
2944
2945
2946
tasks = ()
2947
2948
if "eGon2035" in SCENARIOS:
2949
    tasks = tasks + (
2950
        etrago_eGon2035_electricity,
2951
        etrago_eGon2035_heat,
2952
        residential_electricity_annual_sum,
2953
        residential_electricity_hh_refinement,
2954
        cts_electricity_demand_share,
2955
        cts_heat_demand_share,
2956
        sanitycheck_emobility_mit,
2957
        sanitycheck_pv_rooftop_buildings,
2958
        sanitycheck_home_batteries,
2959
        etrago_eGon2035_gas_DE,
2960
        etrago_eGon2035_gas_abroad,
2961
        sanitycheck_dsm,
2962
    )
2963
2964
if "eGon100RE" in SCENARIOS:
2965
    tasks = tasks + (
2966
        electrical_load_100RE,
2967
        generators_links_storages_stores_100RE,
2968
        etrago_timeseries_length,
2969
        heat_gas_load_egon100RE,
2970
    )
2971
2972
2973
class SanityChecks(Dataset):
2974
    #:
2975
    name: str = "SanityChecks"
2976
    #:
2977
    version: str = "0.0.8"
2978
2979
    def __init__(self, dependencies):
2980
        super().__init__(
2981
            name=self.name,
2982
            version=self.version,
2983
            dependencies=dependencies,
2984
            tasks=tasks,
2985
        )
2986