Passed
Pull Request — dev (#1006)
by
unknown
02:01
created

sanitycheck_emobility_mit()   F

Complexity

Conditions 24

Size

Total Lines 555
Code Lines 358

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 358
dl 0
loc 555
rs 0
c 0
b 0
f 0
cc 24
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
import ast
9
from math import isclose
10
from pathlib import Path
11
12
from sqlalchemy import Numeric
13
from sqlalchemy.sql import and_, cast, func, or_
14
import matplotlib.pyplot as plt
15
import numpy as np
16
import pandas as pd
17
import seaborn as sns
18
19
from egon.data import config, db, logger
20
from egon.data.datasets import Dataset
21
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
22
    EgonCtsElectricityDemandBuildingShare,
23
    EgonCtsHeatDemandBuildingShare,
24
)
25
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (  # noqa: E501
26
    EgonEvCountMunicipality,
27
    EgonEvCountMvGridDistrict,
28
    EgonEvCountRegistrationDistrict,
29
    EgonEvMvGridDistrict,
30
    EgonEvPool,
31
    EgonEvTrip,
32
)
33
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
34
    DATASET_CFG,
35
    read_simbev_metadata_file,
36
)
37
from egon.data.datasets.etrago_setup import (
38
    EgonPfHvLink,
39
    EgonPfHvLinkTimeseries,
40
    EgonPfHvLoad,
41
    EgonPfHvLoadTimeseries,
42
    EgonPfHvStore,
43
    EgonPfHvStoreTimeseries,
44
)
45
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
46
    PV_CAP_PER_SQ_M,
47
    ROOF_FACTOR,
48
    SCENARIOS,
49
    load_building_data,
50
    scenario_data,
51
)
52
from egon.data.datasets.scenario_parameters import get_sector_parameters
53
from egon.data.datasets.storages.home_batteries import get_cbat_pbat_ratio
54
import egon.data
55
56
TESTMODE_OFF = (
57
    config.settings()["egon-data"]["--dataset-boundary"] == "Everything"
58
)
59
60
61
class SanityChecks(Dataset):
62
    #:
63
    name: str = "SanityChecks"
64
    #:
65
    version: str = "0.0.6"
66
67
    def __init__(self, dependencies):
68
        super().__init__(
69
            name=self.name,
70
            version=self.version,
71
            dependencies=dependencies,
72
            tasks={
73
                etrago_eGon2035_electricity,
74
                etrago_eGon2035_heat,
75
                residential_electricity_annual_sum,
76
                residential_electricity_hh_refinement,
77
                cts_electricity_demand_share,
78
                cts_heat_demand_share,
79
                sanitycheck_emobility_mit,
80
                etrago_eGon100RE_gas,
81
                etrago_eGon2035_gas,
82
                sanitycheck_pv_rooftop_buildings,
83
                sanitycheck_home_batteries,
84
            },
85
        )
86
87
88
def etrago_eGon2035_electricity():
89
    """Execute basic sanity checks.
90
91
    Returns print statements as sanity checks for the electricity sector in
92
    the eGon2035 scenario.
93
94
    Parameters
95
    ----------
96
    None
97
98
    Returns
99
    -------
100
    None
101
    """
102
103
    scn = "eGon2035"
104
105
    # Section to check generator capacities
106
    logger.info(f"Sanity checks for scenario {scn}")
107
    logger.info(
108
        "For German electricity generators the following deviations between "
109
        "the inputs and outputs can be observed:"
110
    )
111
112
    carriers_electricity = [
113
        "others",
114
        "reservoir",
115
        "run_of_river",
116
        "oil",
117
        "wind_onshore",
118
        "wind_offshore",
119
        "solar",
120
        "solar_rooftop",
121
        "biomass",
122
    ]
123
124
    for carrier in carriers_electricity:
125
126
        if carrier == "biomass":
127
            sum_output = db.select_dataframe(
128
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
129
                    FROM grid.egon_etrago_generator
130
                    WHERE bus IN (
131
                        SELECT bus_id FROM grid.egon_etrago_bus
132
                        WHERE scn_name = 'eGon2035'
133
                        AND country = 'DE')
134
                    AND carrier IN ('biomass', 'industrial_biomass_CHP',
135
                    'central_biomass_CHP')
136
                    GROUP BY (scn_name);
137
                """,
138
                warning=False,
139
            )
140
141
        else:
142
            sum_output = db.select_dataframe(
143
                f"""SELECT scn_name,
144
                 SUM(p_nom::numeric) as output_capacity_mw
145
                         FROM grid.egon_etrago_generator
146
                         WHERE scn_name = '{scn}'
147
                         AND carrier IN ('{carrier}')
148
                         AND bus IN
149
                             (SELECT bus_id
150
                               FROM grid.egon_etrago_bus
151
                               WHERE scn_name = 'eGon2035'
152
                               AND country = 'DE')
153
                         GROUP BY (scn_name);
154
                    """,
155
                warning=False,
156
            )
157
158
        sum_input = db.select_dataframe(
159
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
160
                     FROM supply.egon_scenario_capacities
161
                     WHERE carrier= '{carrier}'
162
                     AND scenario_name ='{scn}'
163
                     GROUP BY (carrier);
164
                """,
165
            warning=False,
166
        )
167
168 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
169
            sum_output.output_capacity_mw.sum() == 0
170
            and sum_input.input_capacity_mw.sum() == 0
171
        ):
172
            logger.info(
173
                f"No capacity for carrier '{carrier}' needed to be"
174
                f" distributed. Everything is fine"
175
            )
176
177
        elif (
178
            sum_input.input_capacity_mw.sum() > 0
179
            and sum_output.output_capacity_mw.sum() == 0
180
        ):
181
            logger.info(
182
                f"Error: Capacity for carrier '{carrier}' was not distributed "
183
                f"at all!"
184
            )
185
186
        elif (
187
            sum_output.output_capacity_mw.sum() > 0
188
            and sum_input.input_capacity_mw.sum() == 0
189
        ):
190
            logger.info(
191
                f"Error: Eventhough no input capacity was provided for carrier"
192
                f"'{carrier}' a capacity got distributed!"
193
            )
194
195
        else:
196
            sum_input["error"] = (
197
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
198
                / sum_input.input_capacity_mw
199
            ) * 100
200
            g = sum_input["error"].values[0]
201
202
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
203
204
    # Section to check storage units
205
206
    logger.info(f"Sanity checks for scenario {scn}")
207
    logger.info(
208
        "For German electrical storage units the following deviations between"
209
        "the inputs and outputs can be observed:"
210
    )
211
212
    carriers_electricity = ["pumped_hydro"]
213
214
    for carrier in carriers_electricity:
215
216
        sum_output = db.select_dataframe(
217
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
218
                         FROM grid.egon_etrago_storage
219
                         WHERE scn_name = '{scn}'
220
                         AND carrier IN ('{carrier}')
221
                         AND bus IN
222
                             (SELECT bus_id
223
                               FROM grid.egon_etrago_bus
224
                               WHERE scn_name = 'eGon2035'
225
                               AND country = 'DE')
226
                         GROUP BY (scn_name);
227
                    """,
228
            warning=False,
229
        )
230
231
        sum_input = db.select_dataframe(
232
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
233
                     FROM supply.egon_scenario_capacities
234
                     WHERE carrier= '{carrier}'
235
                     AND scenario_name ='{scn}'
236
                     GROUP BY (carrier);
237
                """,
238
            warning=False,
239
        )
240
241 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
242
            sum_output.output_capacity_mw.sum() == 0
243
            and sum_input.input_capacity_mw.sum() == 0
244
        ):
245
            print(
246
                f"No capacity for carrier '{carrier}' needed to be "
247
                f"distributed. Everything is fine"
248
            )
249
250
        elif (
251
            sum_input.input_capacity_mw.sum() > 0
252
            and sum_output.output_capacity_mw.sum() == 0
253
        ):
254
            print(
255
                f"Error: Capacity for carrier '{carrier}' was not distributed"
256
                f" at all!"
257
            )
258
259
        elif (
260
            sum_output.output_capacity_mw.sum() > 0
261
            and sum_input.input_capacity_mw.sum() == 0
262
        ):
263
            print(
264
                f"Error: Eventhough no input capacity was provided for carrier"
265
                f" '{carrier}' a capacity got distributed!"
266
            )
267
268
        else:
269
            sum_input["error"] = (
270
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
271
                / sum_input.input_capacity_mw
272
            ) * 100
273
            g = sum_input["error"].values[0]
274
275
            print(f"{carrier}: " + str(round(g, 2)) + " %")
276
277
    # Section to check loads
278
279
    print(
280
        "For German electricity loads the following deviations between the"
281
        " input and output can be observed:"
282
    )
283
284
    output_demand = db.select_dataframe(
285
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
286
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
287
            FROM grid.egon_etrago_load a
288
            JOIN grid.egon_etrago_load_timeseries b
289
            ON (a.load_id = b.load_id)
290
            JOIN grid.egon_etrago_bus c
291
            ON (a.bus=c.bus_id)
292
            AND b.scn_name = 'eGon2035'
293
            AND a.scn_name = 'eGon2035'
294
            AND a.carrier = 'AC'
295
            AND c.scn_name= 'eGon2035'
296
            AND c.country='DE'
297
            GROUP BY (a.scn_name, a.carrier);
298
299
    """,
300
        warning=False,
301
    )["load_twh"].values[0]
302
303
    input_cts_ind = db.select_dataframe(
304
        """SELECT scenario,
305
         SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
306
            FROM demand.egon_demandregio_cts_ind
307
            WHERE scenario= 'eGon2035'
308
            AND year IN ('2035')
309
            GROUP BY (scenario);
310
311
        """,
312
        warning=False,
313
    )["demand_mw_regio_cts_ind"].values[0]
314
315
    input_hh = db.select_dataframe(
316
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
317
            FROM demand.egon_demandregio_hh
318
            WHERE scenario= 'eGon2035'
319
            AND year IN ('2035')
320
            GROUP BY (scenario);
321
        """,
322
        warning=False,
323
    )["demand_mw_regio_hh"].values[0]
324
325
    input_demand = input_hh + input_cts_ind
326
327
    e = round((output_demand - input_demand) / input_demand, 2) * 100
328
329
    print(f"electricity demand: {e} %")
330
331
332
def etrago_eGon2035_heat():
333
    """Execute basic sanity checks.
334
335
    Returns print statements as sanity checks for the heat sector in
336
    the eGon2035 scenario.
337
338
    Parameters
339
    ----------
340
    None
341
342
    Returns
343
    -------
344
    None
345
    """
346
347
    # Check input and output values for the carriers "others",
348
    # "reservoir", "run_of_river" and "oil"
349
350
    scn = "eGon2035"
351
352
    # Section to check generator capacities
353
    print(f"Sanity checks for scenario {scn}")
354
    print(
355
        "For German heat demands the following deviations between the inputs"
356
        " and outputs can be observed:"
357
    )
358
359
    # Sanity checks for heat demand
360
361
    output_heat_demand = db.select_dataframe(
362
        """SELECT a.scn_name,
363
          (SUM(
364
          (SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
365
            FROM grid.egon_etrago_load a
366
            JOIN grid.egon_etrago_load_timeseries b
367
            ON (a.load_id = b.load_id)
368
            JOIN grid.egon_etrago_bus c
369
            ON (a.bus=c.bus_id)
370
            AND b.scn_name = 'eGon2035'
371
            AND a.scn_name = 'eGon2035'
372
            AND c.scn_name= 'eGon2035'
373
            AND c.country='DE'
374
            AND a.carrier IN ('rural_heat', 'central_heat')
375
            GROUP BY (a.scn_name);
376
        """,
377
        warning=False,
378
    )["load_twh"].values[0]
379
380
    input_heat_demand = db.select_dataframe(
381
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
382
            FROM demand.egon_peta_heat
383
            WHERE scenario= 'eGon2035'
384
            GROUP BY (scenario);
385
        """,
386
        warning=False,
387
    )["demand_mw_peta_heat"].values[0]
388
389
    e_demand = (
390
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
391
        * 100
392
    )
393
394
    logger.info(f"heat demand: {e_demand} %")
395
396
    # Sanity checks for heat supply
397
398
    logger.info(
399
        "For German heat supplies the following deviations between the inputs "
400
        "and outputs can be observed:"
401
    )
402
403
    # Comparison for central heat pumps
404
    heat_pump_input = db.select_dataframe(
405
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
406
            FROM supply.egon_scenario_capacities
407
            WHERE carrier= 'urban_central_heat_pump'
408
            AND scenario_name IN ('eGon2035')
409
            GROUP BY (carrier);
410
        """,
411
        warning=False,
412
    )["urban_central_heat_pump_mw"].values[0]
413
414
    heat_pump_output = db.select_dataframe(
415
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
416
            FROM grid.egon_etrago_link
417
            WHERE carrier= 'central_heat_pump'
418
            AND scn_name IN ('eGon2035')
419
            GROUP BY (carrier);
420
    """,
421
        warning=False,
422
    )["central_heat_pump_mw"].values[0]
423
424
    e_heat_pump = (
425
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
426
    )
427
428
    logger.info(f"'central_heat_pump': {e_heat_pump} % ")
429
430
    # Comparison for residential heat pumps
431
432
    input_residential_heat_pump = db.select_dataframe(
433
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
434
            FROM supply.egon_scenario_capacities
435
            WHERE carrier= 'residential_rural_heat_pump'
436
            AND scenario_name IN ('eGon2035')
437
            GROUP BY (carrier);
438
        """,
439
        warning=False,
440
    )["residential_heat_pump_mw"].values[0]
441
442
    output_residential_heat_pump = db.select_dataframe(
443
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
444
            FROM grid.egon_etrago_link
445
            WHERE carrier= 'rural_heat_pump'
446
            AND scn_name IN ('eGon2035')
447
            GROUP BY (carrier);
448
    """,
449
        warning=False,
450
    )["rural_heat_pump_mw"].values[0]
451
452
    e_residential_heat_pump = (
453
        round(
454
            (output_residential_heat_pump - input_residential_heat_pump)
455
            / input_residential_heat_pump,
456
            2,
457
        )
458
        * 100
459
    )
460
    logger.info(f"'residential heat pumps': {e_residential_heat_pump} %")
461
462
    # Comparison for resistive heater
463
    resistive_heater_input = db.select_dataframe(
464
        """SELECT carrier,
465
         SUM(capacity::numeric) as Urban_central_resistive_heater_MW
466
            FROM supply.egon_scenario_capacities
467
            WHERE carrier= 'urban_central_resistive_heater'
468
            AND scenario_name IN ('eGon2035')
469
            GROUP BY (carrier);
470
        """,
471
        warning=False,
472
    )["urban_central_resistive_heater_mw"].values[0]
473
474
    resistive_heater_output = db.select_dataframe(
475
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
476
            FROM grid.egon_etrago_link
477
            WHERE carrier= 'central_resistive_heater'
478
            AND scn_name IN ('eGon2035')
479
            GROUP BY (carrier);
480
        """,
481
        warning=False,
482
    )["central_resistive_heater_mw"].values[0]
483
484
    e_resistive_heater = (
485
        round(
486
            (resistive_heater_output - resistive_heater_input)
487
            / resistive_heater_input,
488
            2,
489
        )
490
        * 100
491
    )
492
493
    logger.info(f"'resistive heater': {e_resistive_heater} %")
494
495
    # Comparison for solar thermal collectors
496
497
    input_solar_thermal = db.select_dataframe(
498
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
499
            FROM supply.egon_scenario_capacities
500
            WHERE carrier= 'urban_central_solar_thermal_collector'
501
            AND scenario_name IN ('eGon2035')
502
            GROUP BY (carrier);
503
        """,
504
        warning=False,
505
    )["solar_thermal_collector_mw"].values[0]
506
507
    output_solar_thermal = db.select_dataframe(
508
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
509
            FROM grid.egon_etrago_generator
510
            WHERE carrier= 'solar_thermal_collector'
511
            AND scn_name IN ('eGon2035')
512
            GROUP BY (carrier);
513
        """,
514
        warning=False,
515
    )["solar_thermal_collector_mw"].values[0]
516
517
    e_solar_thermal = (
518
        round(
519
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
520
            2,
521
        )
522
        * 100
523
    )
524
    logger.info(f"'solar thermal collector': {e_solar_thermal} %")
525
526
    # Comparison for geothermal
527
528
    input_geo_thermal = db.select_dataframe(
529
        """SELECT carrier,
530
         SUM(capacity::numeric) as Urban_central_geo_thermal_MW
531
            FROM supply.egon_scenario_capacities
532
            WHERE carrier= 'urban_central_geo_thermal'
533
            AND scenario_name IN ('eGon2035')
534
            GROUP BY (carrier);
535
        """,
536
        warning=False,
537
    )["urban_central_geo_thermal_mw"].values[0]
538
539
    output_geo_thermal = db.select_dataframe(
540
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
541
            FROM grid.egon_etrago_generator
542
            WHERE carrier= 'geo_thermal'
543
            AND scn_name IN ('eGon2035')
544
            GROUP BY (carrier);
545
    """,
546
        warning=False,
547
    )["geo_thermal_mw"].values[0]
548
549
    e_geo_thermal = (
550
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
551
        * 100
552
    )
553
    logger.info(f"'geothermal': {e_geo_thermal} %")
554
555
556
def residential_electricity_annual_sum(rtol=1e-5):
557
    """Sanity check for dataset electricity_demand_timeseries :
558
    Demand_Building_Assignment
559
560
    Aggregate the annual demand of all census cells at NUTS3 to compare
561
    with initial scaling parameters from DemandRegio.
562
    """
563
564
    df_nuts3_annual_sum = db.select_dataframe(
565
        sql="""
566
        SELECT dr.nuts3, dr.scenario, dr.demand_regio_sum, profiles.profile_sum
567
        FROM (
568
            SELECT scenario, SUM(demand) AS profile_sum, vg250_nuts3
569
            FROM demand.egon_demandregio_zensus_electricity AS egon,
570
             boundaries.egon_map_zensus_vg250 AS boundaries
571
            Where egon.zensus_population_id = boundaries.zensus_population_id
572
            AND sector = 'residential'
573
            GROUP BY vg250_nuts3, scenario
574
            ) AS profiles
575
        JOIN (
576
            SELECT nuts3, scenario, sum(demand) AS demand_regio_sum
577
            FROM demand.egon_demandregio_hh
578
            GROUP BY year, scenario, nuts3
579
              ) AS dr
580
        ON profiles.vg250_nuts3 = dr.nuts3 and profiles.scenario  = dr.scenario
581
        """
582
    )
583
584
    np.testing.assert_allclose(
585
        actual=df_nuts3_annual_sum["profile_sum"],
586
        desired=df_nuts3_annual_sum["demand_regio_sum"],
587
        rtol=rtol,
588
        verbose=False,
589
    )
590
591
    logger.info(
592
        "Aggregated annual residential electricity demand"
593
        " matches with DemandRegio at NUTS-3."
594
    )
595
596
597
def residential_electricity_hh_refinement(rtol=1e-5):
598
    """Sanity check for dataset electricity_demand_timeseries :
599
    Household Demands
600
601
    Check sum of aggregated household types after refinement method
602
    was applied and compare it to the original census values."""
603
604
    df_refinement = db.select_dataframe(
605
        sql="""
606
        SELECT refined.nuts3, refined.characteristics_code,
607
                refined.sum_refined::int, census.sum_census::int
608
        FROM(
609
            SELECT nuts3, characteristics_code, SUM(hh_10types) as sum_refined
610
            FROM society.egon_destatis_zensus_household_per_ha_refined
611
            GROUP BY nuts3, characteristics_code)
612
            AS refined
613
        JOIN(
614
            SELECT t.nuts3, t.characteristics_code, sum(orig) as sum_census
615
            FROM(
616
                SELECT nuts3, cell_id, characteristics_code,
617
                        sum(DISTINCT(hh_5types))as orig
618
                FROM society.egon_destatis_zensus_household_per_ha_refined
619
                GROUP BY cell_id, characteristics_code, nuts3) AS t
620
            GROUP BY t.nuts3, t.characteristics_code    ) AS census
621
        ON refined.nuts3 = census.nuts3
622
        AND refined.characteristics_code = census.characteristics_code
623
    """
624
    )
625
626
    np.testing.assert_allclose(
627
        actual=df_refinement["sum_refined"],
628
        desired=df_refinement["sum_census"],
629
        rtol=rtol,
630
        verbose=False,
631
    )
632
633
    logger.info("All Aggregated household types match at NUTS-3.")
634
635
636
def cts_electricity_demand_share(rtol=1e-5):
637
    """Sanity check for dataset electricity_demand_timeseries :
638
    CtsBuildings
639
640
    Check sum of aggregated cts electricity demand share which equals to one
641
    for every substation as the substation profile is linearly disaggregated
642
    to all buildings."""
643
644
    with db.session_scope() as session:
645
        cells_query = session.query(EgonCtsElectricityDemandBuildingShare)
646
647
    df_demand_share = pd.read_sql(
648
        cells_query.statement, cells_query.session.bind, index_col=None
649
    )
650
651
    np.testing.assert_allclose(
652
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
653
            "profile_share"
654
        ].sum(),
655
        desired=1,
656
        rtol=rtol,
657
        verbose=False,
658
    )
659
660
    logger.info("The aggregated demand shares equal to one!.")
661
662
663
def cts_heat_demand_share(rtol=1e-5):
664
    """Sanity check for dataset electricity_demand_timeseries
665
    : CtsBuildings
666
667
    Check sum of aggregated cts heat demand share which equals to one
668
    for every substation as the substation profile is linearly disaggregated
669
    to all buildings."""
670
671
    with db.session_scope() as session:
672
        cells_query = session.query(EgonCtsHeatDemandBuildingShare)
673
674
    df_demand_share = pd.read_sql(
675
        cells_query.statement, cells_query.session.bind, index_col=None
676
    )
677
678
    np.testing.assert_allclose(
679
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
680
            "profile_share"
681
        ].sum(),
682
        desired=1,
683
        rtol=rtol,
684
        verbose=False,
685
    )
686
687
    logger.info("The aggregated demand shares equal to one!.")
688
689
690
def sanitycheck_pv_rooftop_buildings():
691
    def egon_power_plants_pv_roof_building():
692
        sql = """
693
        SELECT *
694
        FROM supply.egon_power_plants_pv_roof_building
695
        """
696
697
        return db.select_dataframe(sql, index_col="index")
698
699
    pv_roof_df = egon_power_plants_pv_roof_building()
700
701
    valid_buildings_gdf = load_building_data()
702
703
    valid_buildings_gdf = valid_buildings_gdf.assign(
704
        bus_id=valid_buildings_gdf.bus_id.astype(int),
705
        overlay_id=valid_buildings_gdf.overlay_id.astype(int),
706
        max_cap=valid_buildings_gdf.building_area.multiply(
707
            ROOF_FACTOR * PV_CAP_PER_SQ_M
708
        ),
709
    )
710
711
    merge_df = pv_roof_df.merge(
712
        valid_buildings_gdf[["building_area"]],
713
        how="left",
714
        left_on="building_id",
715
        right_index=True,
716
    )
717
718
    assert (
719
        len(merge_df.loc[merge_df.building_area.isna()]) == 0
720
    ), f"{len(merge_df.loc[merge_df.building_area.isna()])} != 0"
721
722
    scenarios = ["status_quo", "eGon2035"]
723
724
    base_path = Path(egon.data.__path__[0]).resolve()
725
726
    res_dir = base_path / "sanity_checks"
727
728
    res_dir.mkdir(parents=True, exist_ok=True)
729
730
    for scenario in scenarios:
731
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 8))
732
733
        scenario_df = merge_df.loc[merge_df.scenario == scenario]
734
735
        logger.info(
736
            scenario + " Capacity:\n" + str(scenario_df.capacity.describe())
737
        )
738
739
        small_gens_df = scenario_df.loc[scenario_df.capacity < 100]
740
741
        sns.histplot(data=small_gens_df, x="capacity", ax=ax1).set_title(
742
            scenario
743
        )
744
745
        sns.scatterplot(
746
            data=small_gens_df, x="capacity", y="building_area", ax=ax2
747
        ).set_title(scenario)
748
749
        plt.tight_layout()
750
751
        plt.savefig(
752
            res_dir / f"{scenario}_pv_rooftop_distribution.png",
753
            bbox_inches="tight",
754
        )
755
756
    for scenario in SCENARIOS:
757
        if scenario == "eGon2035":
758
            assert isclose(
759
                scenario_data(scenario=scenario).capacity.sum(),
760
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
761
                rel_tol=1e-02,
762
            ), (
763
                f"{scenario_data(scenario=scenario).capacity.sum()} != "
764
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
765
            )
766
        elif scenario == "eGon100RE":
767
            sources = config.datasets()["solar_rooftop"]["sources"]
768
769
            target = db.select_dataframe(
770
                f"""
771
                SELECT capacity
772
                FROM {sources['scenario_capacities']['schema']}.
773
                {sources['scenario_capacities']['table']} a
774
                WHERE carrier = 'solar_rooftop'
775
                AND scenario_name = '{scenario}'
776
                """
777
            ).capacity[0]
778
779
            dataset = config.settings()["egon-data"]["--dataset-boundary"]
780
781 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...
782
                sources = config.datasets()["scenario_input"]["sources"]
783
784
                path = Path(
785
                    f"./data_bundle_egon_data/nep2035_version2021/"
786
                    f"{sources['eGon2035']['capacities']}"
787
                ).resolve()
788
789
                total_2035 = (
790
                    pd.read_excel(
791
                        path,
792
                        sheet_name="1.Entwurf_NEP2035_V2021",
793
                        index_col="Unnamed: 0",
794
                    ).at["PV (Aufdach)", "Summe"]
795
                    * 1000
796
                )
797
                sh_2035 = scenario_data(scenario="eGon2035").capacity.sum()
798
799
                share = sh_2035 / total_2035
800
801
                target *= share
802
803
            assert isclose(
804
                target,
805
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
806
                rel_tol=1e-02,
807
            ), (
808
                f"{target} != "
809
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
810
            )
811
        else:
812
            raise ValueError(f"Scenario {scenario} is not valid.")
813
814
815
def sanitycheck_emobility_mit():
816
    """Execute sanity checks for eMobility: motorized individual travel
817
818
    Checks data integrity for eGon2035, eGon2035_lowflex and eGon100RE scenario
819
    using assertions:
820
      1. Allocated EV numbers and EVs allocated to grid districts
821
      2. Trip data (original inout data from simBEV)
822
      3. Model data in eTraGo PF tables (grid.egon_etrago_*)
823
824
    Parameters
825
    ----------
826
    None
827
828
    Returns
829
    -------
830
    None
831
    """
832
833
    def check_ev_allocation():
834
        # Get target number for scenario
835
        ev_count_target = scenario_variation_parameters["ev_count"]
836
        print(f"  Target count: {str(ev_count_target)}")
837
838
        # Get allocated numbers
839
        ev_counts_dict = {}
840
        with db.session_scope() as session:
841
            for table, level in zip(
842
                [
843
                    EgonEvCountMvGridDistrict,
844
                    EgonEvCountMunicipality,
845
                    EgonEvCountRegistrationDistrict,
846
                ],
847
                ["Grid District", "Municipality", "Registration District"],
848
            ):
849
                query = session.query(
850
                    func.sum(
851
                        table.bev_mini
852
                        + table.bev_medium
853
                        + table.bev_luxury
854
                        + table.phev_mini
855
                        + table.phev_medium
856
                        + table.phev_luxury
857
                    ).label("ev_count")
858
                ).filter(
859
                    table.scenario == scenario_name,
860
                    table.scenario_variation == scenario_var_name,
861
                )
862
863
                ev_counts = pd.read_sql(
864
                    query.statement, query.session.bind, index_col=None
865
                )
866
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
867
                print(
868
                    f"    Count table: Total count for level {level} "
869
                    f"(table: {table.__table__}): "
870
                    f"{str(ev_counts_dict[level])}"
871
                )
872
873
        # Compare with scenario target (only if not in testmode)
874
        if TESTMODE_OFF:
875
            for level, count in ev_counts_dict.items():
876
                np.testing.assert_allclose(
877
                    count,
878
                    ev_count_target,
879
                    rtol=0.0001,
880
                    err_msg=f"EV numbers in {level} seems to be flawed.",
881
                )
882
        else:
883
            print("    Testmode is on, skipping sanity check...")
884
885
        # Get allocated EVs in grid districts
886
        with db.session_scope() as session:
887
            query = session.query(
888
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
889
                    "ev_count"
890
                ),
891
            ).filter(
892
                EgonEvMvGridDistrict.scenario == scenario_name,
893
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
894
            )
895
        ev_count_alloc = (
896
            pd.read_sql(query.statement, query.session.bind, index_col=None)
897
            .iloc[0]
898
            .ev_count
899
        )
900
        print(
901
            f"    EVs allocated to Grid Districts "
902
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
903
            f"{str(ev_count_alloc)}"
904
        )
905
906
        # Compare with scenario target (only if not in testmode)
907
        if TESTMODE_OFF:
908
            np.testing.assert_allclose(
909
                ev_count_alloc,
910
                ev_count_target,
911
                rtol=0.0001,
912
                err_msg=(
913
                    "EV numbers allocated to Grid Districts seems to be "
914
                    "flawed."
915
                ),
916
            )
917
        else:
918
            print("    Testmode is on, skipping sanity check...")
919
920
        return ev_count_alloc
921
922
    def check_trip_data():
923
        # Check if trips start at timestep 0 and have a max. of 35040 steps
924
        # (8760h in 15min steps)
925
        print("  Checking timeranges...")
926
        with db.session_scope() as session:
927
            query = session.query(
928
                func.count(EgonEvTrip.event_id).label("cnt")
929
            ).filter(
930
                or_(
931
                    and_(
932
                        EgonEvTrip.park_start > 0,
933
                        EgonEvTrip.simbev_event_id == 0,
934
                    ),
935
                    EgonEvTrip.park_end
936
                    > (60 / int(meta_run_config.stepsize)) * 8760,
937
                ),
938
                EgonEvTrip.scenario == scenario_name,
939
            )
940
        invalid_trips = pd.read_sql(
941
            query.statement, query.session.bind, index_col=None
942
        )
943
        np.testing.assert_equal(
944
            invalid_trips.iloc[0].cnt,
945
            0,
946
            err_msg=(
947
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
948
                f"{EgonEvTrip.__table__} have invalid timesteps."
949
            ),
950
        )
951
952
        # Check if charging demand can be covered by available charging energy
953
        # while parking
954
        print("  Compare charging demand with available power...")
955
        with db.session_scope() as session:
956
            query = session.query(
957
                func.count(EgonEvTrip.event_id).label("cnt")
958
            ).filter(
959
                func.round(
960
                    cast(
961
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
962
                        * EgonEvTrip.charging_capacity_nominal
963
                        * (int(meta_run_config.stepsize) / 60),
964
                        Numeric,
965
                    ),
966
                    3,
967
                )
968
                < cast(EgonEvTrip.charging_demand, Numeric),
969
                EgonEvTrip.scenario == scenario_name,
970
            )
971
        invalid_trips = pd.read_sql(
972
            query.statement, query.session.bind, index_col=None
973
        )
974
        np.testing.assert_equal(
975
            invalid_trips.iloc[0].cnt,
976
            0,
977
            err_msg=(
978
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
979
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
980
                f"covered by available charging power."
981
            ),
982
        )
983
984
    def check_model_data():
985
        # Check if model components were fully created
986
        print("  Check if all model components were created...")
987
        # Get MVGDs which got EV allocated
988
        with db.session_scope() as session:
989
            query = (
990
                session.query(
991
                    EgonEvMvGridDistrict.bus_id,
992
                )
993
                .filter(
994
                    EgonEvMvGridDistrict.scenario == scenario_name,
995
                    EgonEvMvGridDistrict.scenario_variation
996
                    == scenario_var_name,
997
                )
998
                .group_by(EgonEvMvGridDistrict.bus_id)
999
            )
1000
        mvgds_with_ev = (
1001
            pd.read_sql(query.statement, query.session.bind, index_col=None)
1002
            .bus_id.sort_values()
1003
            .to_list()
1004
        )
1005
1006
        # Load model components
1007
        with db.session_scope() as session:
1008
            query = (
1009
                session.query(
1010
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
1011
                    EgonPfHvLoad.bus.label("emob_bus_id"),
1012
                    EgonPfHvLoad.load_id.label("load_id"),
1013
                    EgonPfHvStore.store_id.label("store_id"),
1014
                )
1015
                .select_from(EgonPfHvLoad, EgonPfHvStore)
1016
                .join(
1017
                    EgonPfHvLoadTimeseries,
1018
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1019
                )
1020
                .join(
1021
                    EgonPfHvStoreTimeseries,
1022
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
1023
                )
1024
                .filter(
1025
                    EgonPfHvLoad.carrier == "land transport EV",
1026
                    EgonPfHvLoad.scn_name == scenario_name,
1027
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
1028
                    EgonPfHvStore.carrier == "battery storage",
1029
                    EgonPfHvStore.scn_name == scenario_name,
1030
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
1031
                    EgonPfHvLink.scn_name == scenario_name,
1032
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
1033
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
1034
                )
1035
            )
1036
        model_components = pd.read_sql(
1037
            query.statement, query.session.bind, index_col=None
1038
        )
1039
1040
        # Check number of buses with model components connected
1041
        mvgd_buses_with_ev = model_components.loc[
1042
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
1043
        ]
1044
        np.testing.assert_equal(
1045
            len(mvgds_with_ev),
1046
            len(mvgd_buses_with_ev),
1047
            err_msg=(
1048
                f"Number of Grid Districts with connected model components "
1049
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
1050
                f"differ from number of Grid Districts that got EVs "
1051
                f"allocated ({len(mvgds_with_ev)} in table "
1052
                f"{EgonEvMvGridDistrict.__table__})."
1053
            ),
1054
        )
1055
1056
        # Check if all required components exist (if no id is NaN)
1057
        np.testing.assert_equal(
1058
            model_components.drop_duplicates().isna().any().any(),
1059
            False,
1060
            err_msg=(
1061
                f"Some components are missing (see True values): "
1062
                f"{model_components.drop_duplicates().isna().any()}"
1063
            ),
1064
        )
1065
1066
        # Get all model timeseries
1067
        print("  Loading model timeseries...")
1068
        # Get all model timeseries
1069
        model_ts_dict = {
1070
            "Load": {
1071
                "carrier": "land transport EV",
1072
                "table": EgonPfHvLoad,
1073
                "table_ts": EgonPfHvLoadTimeseries,
1074
                "column_id": "load_id",
1075
                "columns_ts": ["p_set"],
1076
                "ts": None,
1077
            },
1078
            "Link": {
1079
                "carrier": "BEV charger",
1080
                "table": EgonPfHvLink,
1081
                "table_ts": EgonPfHvLinkTimeseries,
1082
                "column_id": "link_id",
1083
                "columns_ts": ["p_max_pu"],
1084
                "ts": None,
1085
            },
1086
            "Store": {
1087
                "carrier": "battery storage",
1088
                "table": EgonPfHvStore,
1089
                "table_ts": EgonPfHvStoreTimeseries,
1090
                "column_id": "store_id",
1091
                "columns_ts": ["e_min_pu", "e_max_pu"],
1092
                "ts": None,
1093
            },
1094
        }
1095
1096
        with db.session_scope() as session:
1097
            for node, attrs in model_ts_dict.items():
1098
                print(f"    Loading {node} timeseries...")
1099
                subquery = (
1100
                    session.query(getattr(attrs["table"], attrs["column_id"]))
1101
                    .filter(attrs["table"].carrier == attrs["carrier"])
1102
                    .filter(attrs["table"].scn_name == scenario_name)
1103
                    .subquery()
1104
                )
1105
1106
                cols = [
1107
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
1108
                ]
1109
                query = session.query(
1110
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
1111
                ).filter(
1112
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
1113
                        subquery
1114
                    ),
1115
                    attrs["table_ts"].scn_name == scenario_name,
1116
                )
1117
                attrs["ts"] = pd.read_sql(
1118
                    query.statement,
1119
                    query.session.bind,
1120
                    index_col=attrs["column_id"],
1121
                )
1122
1123
        # Check if all timeseries have 8760 steps
1124
        print("    Checking timeranges...")
1125
        for node, attrs in model_ts_dict.items():
1126
            for col in attrs["columns_ts"]:
1127
                ts = attrs["ts"]
1128
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
1129
                    col
1130
                ].apply(len)
1131
                np.testing.assert_equal(
1132
                    len(invalid_ts),
1133
                    0,
1134
                    err_msg=(
1135
                        f"{str(len(invalid_ts))} rows in timeseries do not "
1136
                        f"have 8760 timesteps. Table: "
1137
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
1138
                        f"{str(list(invalid_ts.index))}"
1139
                    ),
1140
                )
1141
1142
        # Compare total energy demand in model with some approximate values
1143
        # (per EV: 14,000 km/a, 0.17 kWh/km)
1144
        print("  Checking energy demand in model...")
1145
        total_energy_model = (
1146
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
1147
            / 1e6
1148
        )
1149
        print(f"    Total energy amount in model: {total_energy_model} TWh")
1150
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
1151
        print(
1152
            f"    Total approximated energy amount in scenario: "
1153
            f"{total_energy_scenario_approx} TWh"
1154
        )
1155
        np.testing.assert_allclose(
1156
            total_energy_model,
1157
            total_energy_scenario_approx,
1158
            rtol=0.1,
1159
            err_msg=(
1160
                "The total energy amount in the model deviates heavily "
1161
                "from the approximated value for current scenario."
1162
            ),
1163
        )
1164
1165
        # Compare total storage capacity
1166
        print("  Checking storage capacity...")
1167
        # Load storage capacities from model
1168
        with db.session_scope() as session:
1169
            query = session.query(
1170
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
1171
            ).filter(
1172
                EgonPfHvStore.scn_name == scenario_name,
1173
                EgonPfHvStore.carrier == "battery storage",
1174
            )
1175
        storage_capacity_model = (
1176
            pd.read_sql(
1177
                query.statement, query.session.bind, index_col=None
1178
            ).e_nom.sum()
1179
            / 1e3
1180
        )
1181
        print(
1182
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
1183
            f"{round(storage_capacity_model, 1)} GWh"
1184
        )
1185
1186
        # Load occurences of each EV
1187
        with db.session_scope() as session:
1188
            query = (
1189
                session.query(
1190
                    EgonEvMvGridDistrict.bus_id,
1191
                    EgonEvPool.type,
1192
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
1193
                        "count"
1194
                    ),
1195
                )
1196
                .join(
1197
                    EgonEvPool,
1198
                    EgonEvPool.ev_id
1199
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
1200
                )
1201
                .filter(
1202
                    EgonEvMvGridDistrict.scenario == scenario_name,
1203
                    EgonEvMvGridDistrict.scenario_variation
1204
                    == scenario_var_name,
1205
                    EgonEvPool.scenario == scenario_name,
1206
                )
1207
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
1208
            )
1209
        count_per_ev_all = pd.read_sql(
1210
            query.statement, query.session.bind, index_col="bus_id"
1211
        )
1212
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
1213
            meta_tech_data.battery_capacity
1214
        )
1215
        count_per_ev_all["bat_cap_total_MWh"] = (
1216
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
1217
        )
1218
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
1219
            1e3
1220
        ).sum()
1221
        print(
1222
            f"    Total storage capacity (simBEV): "
1223
            f"{round(storage_capacity_simbev, 1)} GWh"
1224
        )
1225
1226
        np.testing.assert_allclose(
1227
            storage_capacity_model,
1228
            storage_capacity_simbev,
1229
            rtol=0.01,
1230
            err_msg=(
1231
                "The total storage capacity in the model deviates heavily "
1232
                "from the input data provided by simBEV for current scenario."
1233
            ),
1234
        )
1235
1236
        # Check SoC storage constraint: e_min_pu < e_max_pu for all timesteps
1237
        print("  Validating SoC constraints...")
1238
        stores_with_invalid_soc = []
1239
        for idx, row in model_ts_dict["Store"]["ts"].iterrows():
1240
            ts = row[["e_min_pu", "e_max_pu"]]
1241
            x = np.array(ts.e_min_pu) > np.array(ts.e_max_pu)
1242
            if x.any():
1243
                stores_with_invalid_soc.append(idx)
1244
1245
        np.testing.assert_equal(
1246
            len(stores_with_invalid_soc),
1247
            0,
1248
            err_msg=(
1249
                f"The store constraint e_min_pu < e_max_pu does not apply "
1250
                f"for some storages in {EgonPfHvStoreTimeseries.__table__}. "
1251
                f"Invalid store_ids: {stores_with_invalid_soc}"
1252
            ),
1253
        )
1254
1255
    def check_model_data_lowflex_eGon2035():
1256
        # TODO: Add eGon100RE_lowflex
1257
        print("")
1258
        print("SCENARIO: eGon2035_lowflex")
1259
1260
        # Compare driving load and charging load
1261
        print("  Loading eGon2035 model timeseries: driving load...")
1262
        with db.session_scope() as session:
1263
            query = (
1264
                session.query(
1265
                    EgonPfHvLoad.load_id,
1266
                    EgonPfHvLoadTimeseries.p_set,
1267
                )
1268
                .join(
1269
                    EgonPfHvLoadTimeseries,
1270
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1271
                )
1272
                .filter(
1273
                    EgonPfHvLoad.carrier == "land transport EV",
1274
                    EgonPfHvLoad.scn_name == "eGon2035",
1275
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035",
1276
                )
1277
            )
1278
        model_driving_load = pd.read_sql(
1279
            query.statement, query.session.bind, index_col=None
1280
        )
1281
        driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0)
1282
1283
        print(
1284
            "  Loading eGon2035_lowflex model timeseries: dumb charging "
1285
            "load..."
1286
        )
1287
        with db.session_scope() as session:
1288
            query = (
1289
                session.query(
1290
                    EgonPfHvLoad.load_id,
1291
                    EgonPfHvLoadTimeseries.p_set,
1292
                )
1293
                .join(
1294
                    EgonPfHvLoadTimeseries,
1295
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1296
                )
1297
                .filter(
1298
                    EgonPfHvLoad.carrier == "land transport EV",
1299
                    EgonPfHvLoad.scn_name == "eGon2035_lowflex",
1300
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex",
1301
                )
1302
            )
1303
        model_charging_load_lowflex = pd.read_sql(
1304
            query.statement, query.session.bind, index_col=None
1305
        )
1306
        charging_load = np.array(
1307
            model_charging_load_lowflex.p_set.to_list()
1308
        ).sum(axis=0)
1309
1310
        # Ratio of driving and charging load should be 0.9 due to charging
1311
        # efficiency
1312
        print("  Compare cumulative loads...")
1313
        print(f"    Driving load (eGon2035): {driving_load.sum() / 1e6} TWh")
1314
        print(
1315
            f"    Dumb charging load (eGon2035_lowflex): "
1316
            f"{charging_load.sum() / 1e6} TWh"
1317
        )
1318
        driving_load_theoretical = (
1319
            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 1337 is not entered. Are you sure this can never be the case?
Loading history...
1320
        )
1321
        np.testing.assert_allclose(
1322
            driving_load.sum(),
1323
            driving_load_theoretical,
1324
            rtol=0.01,
1325
            err_msg=(
1326
                f"The driving load (eGon2035) deviates by more than 1% "
1327
                f"from the theoretical driving load calculated from charging "
1328
                f"load (eGon2035_lowflex) with an efficiency of "
1329
                f"{float(meta_run_config.eta_cp)}."
1330
            ),
1331
        )
1332
1333
    print("=====================================================")
1334
    print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===")
1335
    print("=====================================================")
1336
1337
    for scenario_name in ["eGon2035", "eGon100RE"]:
1338
        scenario_var_name = DATASET_CFG["scenario"]["variation"][scenario_name]
1339
1340
        print("")
1341
        print(f"SCENARIO: {scenario_name}, VARIATION: {scenario_var_name}")
1342
1343
        # Load scenario params for scenario and scenario variation
1344
        scenario_variation_parameters = get_sector_parameters(
1345
            "mobility", scenario=scenario_name
1346
        )["motorized_individual_travel"][scenario_var_name]
1347
1348
        # Load simBEV run config and tech data
1349
        meta_run_config = read_simbev_metadata_file(
1350
            scenario_name, "config"
1351
        ).loc["basic"]
1352
        meta_tech_data = read_simbev_metadata_file(scenario_name, "tech_data")
1353
1354
        print("")
1355
        print("Checking EV counts...")
1356
        ev_count_alloc = check_ev_allocation()
1357
1358
        print("")
1359
        print("Checking trip data...")
1360
        check_trip_data()
1361
1362
        print("")
1363
        print("Checking model data...")
1364
        check_model_data()
1365
1366
    print("")
1367
    check_model_data_lowflex_eGon2035()
1368
1369
    print("=====================================================")
1370
1371
1372
def sanity_check_gas_buses(scn):
1373
    """Execute sanity checks for the gas buses in Germany
1374
1375
    Returns print statements as sanity checks for the CH4 and
1376
    H2_grid grid buses in Germany. The deviation is calculated between
1377
    the number gas grid buses in the database and the original
1378
    Scigrid_gas number of gas buses.
1379
1380
    Parameters
1381
    ----------
1382
    scn_name : str
1383
        Name of the scenario
1384
1385
    """
1386
    logger.info(f"BUSES")
1387
1388
    target_file = (
1389
        Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Nodes.csv"
1390
    )
1391
1392
    Grid_buses_list = pd.read_csv(
1393
        target_file,
1394
        delimiter=";",
1395
        decimal=".",
1396
        usecols=["country_code"],
1397
    )
1398
1399
    Grid_buses_list = Grid_buses_list[
1400
        Grid_buses_list["country_code"].str.match("DE")
1401
    ]
1402
    input_grid_buses = len(Grid_buses_list.index)
1403
1404
    for carrier in ["CH4", "H2_grid"]:
1405
1406
        output_grid_buses_df = db.select_dataframe(
1407
            f"""
1408
            SELECT bus_id
1409
            FROM grid.egon_etrago_bus
1410
            WHERE scn_name = '{scn}'
1411
            AND country = 'DE'
1412
            AND carrier = '{carrier}';
1413
            """,
1414
            warning=False,
1415
        )
1416
        output_grid_buses = len(output_grid_buses_df.index)
1417
1418
        e_grid_buses = (
1419
            round(
1420
                (output_grid_buses - input_grid_buses) / input_grid_buses,
1421
                2,
1422
            )
1423
            * 100
1424
        )
1425
        logger.info(f"Deviation {carrier} buses: {e_grid_buses} %")
1426
1427
1428
def sanity_check_CH4_stores(scn):
1429
    """Execute sanity checks for the CH4 stores in Germany
1430
1431
    Returns print statements as sanity checks for the CH4 stores
1432
    capacity in Germany. The deviation is calculated between:
1433
      * the sum of the capacities of the stores with carrier 'CH4'
1434
        in the database (for one scenario) and
1435
      * the sum of:
1436
          * the capacity the gas grid allocated to CH4 (total capacity
1437
            in eGon2035 and capacity reduced the share of the grid
1438
            allocated to H2 in eGon100RE) and
1439
          * the sum of the capacities of the stores in the source
1440
            document (Storages from the SciGRID_gas data)
1441
1442
    Parameters
1443
    ----------
1444
    scn_name : str
1445
        Name of the scenario
1446
1447
    """
1448
    output_CH4_stores = db.select_dataframe(
1449
        f"""SELECT SUM(e_nom::numeric) as e_nom_germany
1450
                FROM grid.egon_etrago_store
1451
                WHERE scn_name = '{scn}'
1452
                AND carrier = 'CH4'
1453
                AND bus IN
1454
                    (SELECT bus_id
1455
                    FROM grid.egon_etrago_bus
1456
                    WHERE scn_name = '{scn}'
1457
                    AND country = 'DE'
1458
                    AND carrier = 'CH4');
1459
                """,
1460
        warning=False,
1461
    )["e_nom_germany"].values[0]
1462
1463
    target_file = (
1464
        Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Storages.csv"
1465
    )
1466
1467
    CH4_storages_list = pd.read_csv(
1468
        target_file,
1469
        delimiter=";",
1470
        decimal=".",
1471
        usecols=["country_code", "param"],
1472
    )
1473
1474
    CH4_storages_list = CH4_storages_list[
1475
        CH4_storages_list["country_code"].str.match("DE")
1476
    ]
1477
1478
    max_workingGas_M_m3 = []
1479
    end_year = []
1480
    for index, row in CH4_storages_list.iterrows():
1481
        param = ast.literal_eval(row["param"])
1482
        end_year.append(param["end_year"])
1483
        max_workingGas_M_m3.append(param["max_workingGas_M_m3"])
1484
    CH4_storages_list["max_workingGas_M_m3"] = max_workingGas_M_m3
1485
    CH4_storages_list["end_year"] = [
1486
        float("inf") if x == None else x for x in end_year
1487
    ]
1488
1489
    # Remove unused storage units
1490
    CH4_storages_list = CH4_storages_list[
1491
        CH4_storages_list["end_year"]
1492
        >= get_sector_parameters("global", scn)["population_year"]
1493
    ]
1494
1495
    if scn == "eGon2035":
1496
        grid_cap = 130000
1497
    elif scn == "eGon100RE":
1498
        grid_cap = 13000 * (
1499
            1
1500
            - get_sector_parameters("gas", "eGon100RE")[
1501
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1502
            ]
1503
        )
1504
    conv_factor = 10830  # gross calorific value = 39 MJ/m3 (eurogas.org)
1505
    input_CH4_stores = (
1506
        conv_factor * sum(CH4_storages_list["max_workingGas_M_m3"].to_list())
1507
        + grid_cap
0 ignored issues
show
introduced by
The variable grid_cap does not seem to be defined for all execution paths.
Loading history...
1508
    )
1509
1510
    e_CH4_stores = (
1511
        round(
1512
            (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
1513
            2,
1514
        )
1515
        * 100
1516
    )
1517
    logger.info(f"Deviation CH4 stores: {e_CH4_stores} %")
1518
1519
1520
def sanity_check_H2_saltcavern_stores(scn):
1521
    """Execute sanity checks for the H2 saltcavern stores in Germany
1522
1523
    Returns print as sanity checks for the H2 saltcavern potential
1524
    storage capacity in Germany. The deviation is calculated between:
1525
      * the sum of the of the H2 saltcavern potential storage capacity
1526
        (e_nom_max) in the database and
1527
      * the sum of the H2 saltcavern potential storage capacity
1528
        assumed to be the ratio of the areas of 500 m radius around
1529
        substations in each german federal state and the estimated
1530
        total hydrogen storage potential of the corresponding federal
1531
        state (data from InSpEE-DS report).
1532
1533
    This test works also in test mode.
1534
1535
    Parameters
1536
    ----------
1537
    scn_name : str
1538
        Name of the scenario
1539
1540
    """
1541
    output_H2_stores = db.select_dataframe(
1542
        f"""SELECT SUM(e_nom_max::numeric) as e_nom_max_germany
1543
                FROM grid.egon_etrago_store
1544
                WHERE scn_name = '{scn}'
1545
                AND carrier = 'H2_underground'
1546
                AND bus IN
1547
                    (SELECT bus_id
1548
                    FROM grid.egon_etrago_bus
1549
                    WHERE scn_name = '{scn}'
1550
                    AND country = 'DE'
1551
                    AND carrier = 'H2_saltcavern');
1552
                """,
1553
        warning=False,
1554
    )["e_nom_max_germany"].values[0]
1555
1556
    storage_potentials = calculate_and_map_saltcavern_storage_potential()
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable calculate_and_map_saltcavern_storage_potential does not seem to be defined.
Loading history...
1557
    storage_potentials["storage_potential"] = (
1558
        storage_potentials["area_fraction"] * storage_potentials["potential"]
1559
    )
1560
    input_H2_stores = sum(storage_potentials["storage_potential"].to_list())
1561
1562
    e_H2_stores = (
1563
        round(
1564
            (output_H2_stores - input_H2_stores) / input_H2_stores,
1565
            2,
1566
        )
1567
        * 100
1568
    )
1569
    logger.info(f"Deviation H2 saltcavern stores: {e_H2_stores} %")
1570
1571
1572
def sanity_check_CH4_grid(scn):
1573
    """Execute sanity checks for the gas grid capacity in Germany
1574
1575
    Returns print statements as sanity checks for the CH4 links
1576
    (pipelines) in Germany. The deviation is calculated between
1577
    the sum of the power (p_nom) of all the CH4 pipelines in Germany
1578
    for one scenario in the database and the sum of the powers of the
1579
    imported pipelines.
1580
    In eGon100RE, the sum is reduced by the share of the grid that is
1581
    allocated to hydrogen (share calculated by PyPSA-eur-sec).
1582
1583
    This test works also in test mode.
1584
1585
    Parameters
1586
    ----------
1587
    scn_name : str
1588
        Name of the scenario
1589
1590
    Returns
1591
    -------
1592
    scn_name : float
1593
        Sum of the power (p_nom) of all the pipelines in Germany
1594
1595
    """
1596
    grid_carrier = "CH4"
1597
    output_gas_grid = db.select_dataframe(
1598
        f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1599
            FROM grid.egon_etrago_link
1600
            WHERE scn_name = '{scn}'
1601
            AND carrier = '{grid_carrier}'
1602
            AND bus0 IN
1603
                (SELECT bus_id
1604
                FROM grid.egon_etrago_bus
1605
                WHERE scn_name = '{scn}'
1606
                AND country = 'DE'
1607
                AND carrier = '{grid_carrier}')
1608
            AND bus1 IN
1609
                (SELECT bus_id
1610
                FROM grid.egon_etrago_bus
1611
                WHERE scn_name = '{scn}'
1612
                AND country = 'DE'
1613
                AND carrier = '{grid_carrier}')
1614
                ;
1615
            """,
1616
        warning=False,
1617
    )["p_nom_germany"].values[0]
1618
1619
    gas_nodes_list = define_gas_nodes_list()
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable define_gas_nodes_list does not seem to be defined.
Loading history...
1620
    abroad_gas_nodes_list = insert_gas_buses_abroad()
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable insert_gas_buses_abroad does not seem to be defined.
Loading history...
1621
    gas_grid = define_gas_pipeline_list(gas_nodes_list, abroad_gas_nodes_list)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable define_gas_pipeline_list does not seem to be defined.
Loading history...
1622
    gas_grid_germany = gas_grid[
1623
        (gas_grid["country_0"] == "DE") & (gas_grid["country_1"] == "DE")
1624
    ]
1625
    p_nom_total = sum(gas_grid_germany["p_nom"].to_list())
1626
1627
    if scn == "eGon2035":
1628
        input_gas_grid = p_nom_total
1629
    if scn == "eGon100RE":
1630
        input_gas_grid = p_nom_total * (
1631
            1
1632
            - get_sector_parameters("gas", "eGon100RE")[
1633
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1634
            ]
1635
        )
1636
1637
    e_gas_grid = (
1638
        round(
1639
            (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 1627 is False. Are you sure this can never be the case?
Loading history...
1640
            2,
1641
        )
1642
        * 100
1643
    )
1644
    logger.info(f"Deviation of the capacity of the CH4 grid: {e_gas_grid} %")
1645
1646
    return p_nom_total
1647
1648
1649
def etrago_eGon2035_gas():
1650
    """Execute basic sanity checks for the gas sector in eGon2035
1651
1652
    Returns print statements as sanity checks for the gas sector in
1653
    the eGon2035 scenario for the following components in Germany:
1654
      * Buses: with the function :py:func:`sanity_check_gas_buses`
1655
      * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry'
1656
        the deviation is calculated between the sum of the loads in the
1657
        database and the sum the loads in the sources document
1658
        (opendata.ffe database)
1659
      * Generators: the deviation is calculated between the sums of the
1660
        nominal powers of the gas generators in the database and of
1661
        the ones in the sources document (Biogaspartner Einspeiseatlas
1662
        Deutschland from the dena and Productions from the SciGRID_gas
1663
        data)
1664
      * Stores: deviations for stores with following carriers are
1665
        calculated:
1666
          * 'CH4': with the function :py:func:`sanity_check_CH4_stores`
1667
          * 'H2_underground': with the function :py:func:`sanity_check_H2_saltcavern_stores`
1668
      * Links: with the function :py:func:`sanity_check_CH4_grid`
1669
1670
    """
1671
    scn = "eGon2035"
1672
1673
    if TESTMODE_OFF:
1674
        logger.info(f"Gas sanity checks for scenario {scn}")
1675
1676
        # Buses
1677
        sanity_check_gas_buses(scn)
1678
1679
        # Loads
1680
        logger.info(f"LOADS")
1681
1682
        path = Path(".") / "datasets" / "gas_data" / "demand"
1683
        corr_file = path / "region_corr.json"
1684
        df_corr = pd.read_json(corr_file)
1685
        df_corr = df_corr.loc[:, ["id_region", "name_short"]]
1686
        df_corr.set_index("id_region", inplace=True)
1687
1688
        for carrier in ["CH4_for_industry", "H2_for_industry"]:
1689
1690
            output_gas_demand = db.select_dataframe(
1691
                f"""SELECT (SUM(
1692
                    (SELECT SUM(p)
1693
                    FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
1694
                    FROM grid.egon_etrago_load a
1695
                    JOIN grid.egon_etrago_load_timeseries b
1696
                    ON (a.load_id = b.load_id)
1697
                    JOIN grid.egon_etrago_bus c
1698
                    ON (a.bus=c.bus_id)
1699
                    AND b.scn_name = '{scn}'
1700
                    AND a.scn_name = '{scn}'
1701
                    AND c.scn_name = '{scn}'
1702
                    AND c.country = 'DE'
1703
                    AND a.carrier = '{carrier}';
1704
                """,
1705
                warning=False,
1706
            )["load_twh"].values[0]
1707
1708
            input_gas_demand = pd.read_json(
1709
                path / (carrier + "_eGon2035.json")
1710
            )
1711
            input_gas_demand = input_gas_demand.loc[:, ["id_region", "value"]]
1712
            input_gas_demand.set_index("id_region", inplace=True)
1713
            input_gas_demand = pd.concat(
1714
                [input_gas_demand, df_corr], axis=1, join="inner"
1715
            )
1716
            input_gas_demand["NUTS0"] = (input_gas_demand["name_short"].str)[
1717
                0:2
1718
            ]
1719
            input_gas_demand = input_gas_demand[
1720
                input_gas_demand["NUTS0"].str.match("DE")
1721
            ]
1722
            input_gas_demand = sum(input_gas_demand.value.to_list()) / 1000000
1723
1724
            e_demand = (
1725
                round(
1726
                    (output_gas_demand - input_gas_demand) / input_gas_demand,
1727
                    2,
1728
                )
1729
                * 100
1730
            )
1731
            logger.info(f"Deviation {carrier}: {e_demand} %")
1732
1733
        # Generators
1734
        logger.info(f"GENERATORS")
1735
        carrier_generator = "CH4"
1736
1737
        output_gas_generation = db.select_dataframe(
1738
            f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1739
                    FROM grid.egon_etrago_generator
1740
                    WHERE scn_name = '{scn}'
1741
                    AND carrier = '{carrier_generator}'
1742
                    AND bus IN
1743
                        (SELECT bus_id
1744
                        FROM grid.egon_etrago_bus
1745
                        WHERE scn_name = '{scn}'
1746
                        AND country = 'DE'
1747
                        AND carrier = '{carrier_generator}');
1748
                    """,
1749
            warning=False,
1750
        )["p_nom_germany"].values[0]
1751
1752
        target_file = (
1753
            Path(".")
1754
            / "datasets"
1755
            / "gas_data"
1756
            / "data"
1757
            / "IGGIELGN_Productions.csv"
1758
        )
1759
1760
        NG_generators_list = pd.read_csv(
1761
            target_file,
1762
            delimiter=";",
1763
            decimal=".",
1764
            usecols=["country_code", "param"],
1765
        )
1766
1767
        NG_generators_list = NG_generators_list[
1768
            NG_generators_list["country_code"].str.match("DE")
1769
        ]
1770
1771
        p_NG = 0
1772
        for index, row in NG_generators_list.iterrows():
1773
            param = ast.literal_eval(row["param"])
1774
            p_NG = p_NG + param["max_supply_M_m3_per_d"]
1775
        conversion_factor = 437.5  # MCM/day to MWh/h
1776
        p_NG = p_NG * conversion_factor
1777
1778
        basename = "Biogaspartner_Einspeiseatlas_Deutschland_2021.xlsx"
1779
        target_file = Path(".") / "datasets" / "gas_data" / basename
1780
1781
        conversion_factor_b = 0.01083  # m^3/h to MWh/h
1782
        p_biogas = (
1783
            pd.read_excel(
1784
                target_file,
1785
                usecols=["Einspeisung Biomethan [(N*m^3)/h)]"],
1786
            )["Einspeisung Biomethan [(N*m^3)/h)]"].sum()
1787
            * conversion_factor_b
1788
        )
1789
1790
        input_gas_generation = p_NG + p_biogas
1791
        e_generation = (
1792
            round(
1793
                (output_gas_generation - input_gas_generation)
1794
                / input_gas_generation,
1795
                2,
1796
            )
1797
            * 100
1798
        )
1799
        logger.info(
1800
            f"Deviation {carrier_generator} generation: {e_generation} %"
1801
        )
1802
1803
        # Stores
1804
        logger.info(f"STORES")
1805
        sanity_check_CH4_stores(scn)
1806
        sanity_check_H2_saltcavern_stores(scn)
1807
1808
        # Links
1809
        logger.info(f"LINKS")
1810
        sanity_check_CH4_grid(scn)
1811
1812
    else:
1813
        print("Testmode is on, skipping sanity check.")
1814
1815
1816
def etrago_eGon100RE_gas():
1817
    """Execute basic sanity checks for the gas sector in eGon100RE
1818
1819
    Returns print statements as sanity checks for the gas sector in
1820
    the eGon100RE scenario for the following components in Germany:
1821
      * Buses: with the function :py:func:`sanity_check_gas_buses`
1822
      * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry'
1823
        the deviation is calculated between the sum of the loads in the
1824
        database and the value calculated by PyPSA-eur-sec for Germany
1825
        (that as been spatial distributed)
1826
      * Generators: the deviation is calculated between the sums of the
1827
        nominal powers of the biogas generators in the database and of
1828
        the ones in the source document (Biogaspartner Einspeiseatlas
1829
        Deutschland from the dena)
1830
      * Stores: deviations for stores with following carriers are
1831
        calculated:
1832
          * 'CH4': with the function :py:func:`sanity_check_CH4_stores`
1833
          * 'H2_underground': with the function :py:func:`sanity_check_H2_saltcavern_stores`
1834
          * 'H2': the deviation is calculated between the store
1835
            capacity the gas grid allocated to H2 (total capacity
1836
            multiplied by the share of the grid associated to H2) and
1837
            the sum of the capacities of the storages with carrier 'H2'
1838
            in the database.
1839
      * Links: only the gas transport links do have sanity checks. The
1840
        CH4 pipelines with the function :py:func:`sanity_check_CH4_grid`.
1841
        For the H2 pipelines, the deviation is calculated between the
1842
        sum of the power (p_nom) of all the H2 pipelines in Germany in
1843
        the database and the sum of the powers of the imported pipelines.
1844
        multiplied by the share of the grid allocated to hydrogen
1845
        (share calculated by PyPSA-eur-sec). (This test works also in
1846
        test mode.)
1847
1848
    """
1849
    scn = "eGon100RE"
1850
1851
    if TESTMODE_OFF:
1852
        logger.info(f"Gas sanity checks for scenario {scn}")
1853
1854
        # Buses
1855
        sanity_check_gas_buses(scn)
1856
1857
        # Loads
1858
        logger.info(f"LOADS")
1859
1860
        for carrier in ["CH4_for_industry", "H2_for_industry"]:
1861
1862
            output_gas_demand = db.select_dataframe(
1863
                f"""SELECT (SUM(
1864
                    (SELECT SUM(p) 
1865
                    FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
1866
                    FROM grid.egon_etrago_load a
1867
                    JOIN grid.egon_etrago_load_timeseries b
1868
                    ON (a.load_id = b.load_id)
1869
                    JOIN grid.egon_etrago_bus c
1870
                    ON (a.bus=c.bus_id)
1871
                    AND b.scn_name = '{scn}'
1872
                    AND a.scn_name = '{scn}'
1873
                    AND c.scn_name = '{scn}'
1874
                    AND c.country = 'DE'
1875
                    AND a.carrier = '{carrier}';
1876
                """,
1877
                warning=False,
1878
            )["load_twh"].values[0]
1879
1880
            n = read_network()
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable read_network does not seem to be defined.
Loading history...
1881
            node_pes = {
1882
                "CH4_for_industry": "DE0 0 gas for industry",
1883
                "H2_for_industry": "DE0 0 H2 for industry",
1884
            }
1885
            input_gas_demand = (
1886
                n.loads.loc[node_pes[carrier], "p_set"] * 8760 / 1000000
1887
            )
1888
1889
            e_demand = (
1890
                round(
1891
                    (output_gas_demand - input_gas_demand) / input_gas_demand,
1892
                    2,
1893
                )
1894
                * 100
1895
            )
1896
            logger.info(f"Deviation {carrier}: {e_demand} %")
1897
1898
        # Generators
1899
        logger.info(f"GENERATORS")
1900
        carrier_generator = "CH4"
1901
1902
        output_biogas_generation = db.select_dataframe(
1903
            f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1904
                    FROM grid.egon_etrago_generator
1905
                    WHERE scn_name = '{scn}'
1906
                    AND carrier = '{carrier_generator}'
1907
                    AND bus IN
1908
                        (SELECT bus_id
1909
                        FROM grid.egon_etrago_bus
1910
                        WHERE scn_name = '{scn}'
1911
                        AND country = 'DE'
1912
                        AND carrier = '{carrier_generator}');
1913
                    """,
1914
            warning=False,
1915
        )["p_nom_germany"].values[0]
1916
1917
        basename = "Biogaspartner_Einspeiseatlas_Deutschland_2021.xlsx"
1918
        target_file = Path(".") / "datasets" / "gas_data" / basename
1919
1920
        conversion_factor_b = 0.01083  # m^3/h to MWh/h
1921
        input_biogas_generation = (
1922
            pd.read_excel(
1923
                target_file,
1924
                usecols=["Einspeisung Biomethan [(N*m^3)/h)]"],
1925
            )["Einspeisung Biomethan [(N*m^3)/h)]"].sum()
1926
            * conversion_factor_b
1927
        )
1928
1929
        e_biogas_generation = (
1930
            round(
1931
                (output_biogas_generation - input_biogas_generation)
1932
                / input_biogas_generation,
1933
                2,
1934
            )
1935
            * 100
1936
        )
1937
        logger.info(f"Deviation biogas generation: {e_biogas_generation} %")
1938
1939
        # Stores
1940
        logger.info(f"STORES")
1941
        sanity_check_CH4_stores(scn)
1942
        sanity_check_H2_saltcavern_stores(scn)
1943
1944
        output_H2_grid_cap_store = db.select_dataframe(
1945
            f"""SELECT SUM(e_nom::numeric) as e_nom_germany
1946
                FROM grid.egon_etrago_store
1947
                WHERE scn_name = '{scn}'
1948
                AND carrier = 'H2'
1949
                AND bus IN
1950
                    (SELECT bus_id
1951
                    FROM grid.egon_etrago_bus
1952
                    WHERE scn_name = '{scn}'
1953
                    AND country = 'DE'
1954
                    AND carrier = 'H2_grid');
1955
                """,
1956
            warning=False,
1957
        )["e_nom_germany"].values[0]
1958
1959
        input_H2_grid_cap_store = 13000 * (
1960
            get_sector_parameters("gas", "eGon100RE")[
1961
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1962
            ]
1963
        )
1964
1965
        e_H2_grid_cap_store = (
1966
            round(
1967
                (output_H2_grid_cap_store - input_H2_grid_cap_store)
1968
                / input_H2_grid_cap_store,
1969
                2,
1970
            )
1971
            * 100
1972
        )
1973
        logger.info(
1974
            f"Deviation H2 grid capacity stores: {e_H2_grid_cap_store} %"
1975
        )
1976
1977
        # Links
1978
        logger.info(f"LINKS")
1979
        p_nom_total = sanity_check_CH4_grid(scn)
1980
1981
        output_H2_grid = db.select_dataframe(
1982
            f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1983
                FROM grid.egon_etrago_link
1984
                WHERE scn_name = '{scn}'
1985
                AND carrier = 'H2_retrofit'
1986
                AND bus0 IN
1987
                    (SELECT bus_id
1988
                    FROM grid.egon_etrago_bus
1989
                    WHERE scn_name = '{scn}'
1990
                    AND country = 'DE'
1991
                    AND carrier = 'H2_grid')
1992
                AND bus1 IN
1993
                    (SELECT bus_id
1994
                    FROM grid.egon_etrago_bus
1995
                    WHERE scn_name = '{scn}'
1996
                    AND country = 'DE'
1997
                    AND carrier = 'H2_grid')
1998
                    ;
1999
                """,
2000
            warning=False,
2001
        )["p_nom_germany"].values[0]
2002
2003
        input_H2_grid = p_nom_total * (
2004
            get_sector_parameters("gas", "eGon100RE")[
2005
                "retrofitted_CH4pipeline-to-H2pipeline_share"
2006
            ]
2007
        )
2008
2009
        e_H2_grid = (
2010
            round(
2011
                (output_H2_grid - input_H2_grid) / input_H2_grid,
2012
                2,
2013
            )
2014
            * 100
2015
        )
2016
        logger.info(f"Deviation of the capacity of the H2 grid: {e_H2_grid} %")
2017
2018
    else:
2019
        print("Testmode is on, skipping sanity check.")
2020
def sanitycheck_home_batteries():
2021
    # get constants
2022
    constants = config.datasets()["home_batteries"]["constants"]
2023
    scenarios = constants["scenarios"]
2024
    cbat_pbat_ratio = get_cbat_pbat_ratio()
2025
2026
    sources = config.datasets()["home_batteries"]["sources"]
2027
    targets = config.datasets()["home_batteries"]["targets"]
2028
2029
    for scenario in scenarios:
2030
        # get home battery capacity per mv grid id
2031
        sql = f"""
2032
        SELECT el_capacity as p_nom, bus_id FROM
2033
        {sources["storage"]["schema"]}
2034
        .{sources["storage"]["table"]}
2035
        WHERE carrier = 'home_battery'
2036
        AND scenario = '{scenario}'
2037
        """
2038
2039
        home_batteries_df = db.select_dataframe(sql, index_col="bus_id")
2040
2041
        home_batteries_df = home_batteries_df.assign(
2042
            capacity=home_batteries_df.p_nom * cbat_pbat_ratio
2043
        )
2044
2045
        sql = f"""
2046
        SELECT * FROM
2047
        {targets["home_batteries"]["schema"]}
2048
        .{targets["home_batteries"]["table"]}
2049
        WHERE scenario = '{scenario}'
2050
        """
2051
2052
        home_batteries_buildings_df = db.select_dataframe(
2053
            sql, index_col="index"
2054
        )
2055
2056
        df = (
2057
            home_batteries_buildings_df[["bus_id", "p_nom", "capacity"]]
2058
            .groupby("bus_id")
2059
            .sum()
2060
        )
2061
2062
        assert (home_batteries_df.round(6) == df.round(6)).all().all()
2063