Passed
Pull Request — dev (#934)
by
unknown
01:31
created

data.datasets.sanity_checks   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 1365
Duplicated Lines 5.13 %

Importance

Changes 0
Metric Value
wmc 54
eloc 717
dl 70
loc 1365
rs 6.3629
c 0
b 0
f 0

8 Functions

Rating   Name   Duplication   Size   Complexity  
B sanitycheck_pv_rooftop_buildings() 0 121 6
F etrago_eGon2035_electricity() 70 243 16
F sanitycheck_emobility_mit() 0 555 24
A cts_electricity_demand_share() 0 25 2
A residential_electricity_annual_sum() 0 37 1
A cts_heat_demand_share() 0 25 2
B etrago_eGon2035_heat() 0 222 1
A residential_electricity_hh_refinement() 0 37 1

1 Method

Rating   Name   Duplication   Size   Complexity  
A SanityChecks.__init__() 0 14 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like data.datasets.sanity_checks 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
from math import isclose
9
from pathlib import Path
10
11
from sqlalchemy import Numeric
12
from sqlalchemy.sql import and_, cast, func, or_
13
import matplotlib.pyplot as plt
14
import numpy as np
15
import pandas as pd
16
import seaborn as sns
17
18
from egon.data import config, db, logger
19
from egon.data.datasets import Dataset
20
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
21
    EgonCtsElectricityDemandBuildingShare,
22
    EgonCtsHeatDemandBuildingShare,
23
)
24
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (
25
    EgonEvCountMunicipality,
26
    EgonEvCountMvGridDistrict,
27
    EgonEvCountRegistrationDistrict,
28
    EgonEvMvGridDistrict,
29
    EgonEvPool,
30
    EgonEvTrip,
31
)
32
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
33
    DATASET_CFG,
34
    read_simbev_metadata_file,
35
)
36
from egon.data.datasets.etrago_setup import (
37
    EgonPfHvLink,
38
    EgonPfHvLinkTimeseries,
39
    EgonPfHvLoad,
40
    EgonPfHvLoadTimeseries,
41
    EgonPfHvStore,
42
    EgonPfHvStoreTimeseries,
43
)
44
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
45
    EPSG,
46
    PV_CAP_PER_SQ_M,
47
    ROOF_FACTOR,
48
    SCENARIOS,
49
    add_overlay_id_to_buildings,
50
    drop_buildings_outside_grids,
51
    federal_state_data,
52
    grid_districts,
53
    load_building_data,
54
    overlay_grid_districts_with_counties,
55
    scenario_data,
56
)
57
from egon.data.datasets.scenario_parameters import get_sector_parameters
58
import egon.data
59
60
TESTMODE_OFF = (
61
    config.settings()["egon-data"]["--dataset-boundary"] == "Everything"
62
)
63
64
65
class SanityChecks(Dataset):
66
    def __init__(self, dependencies):
67
        super().__init__(
68
            name="SanityChecks",
69
            version="0.0.5",
70
            dependencies=dependencies,
71
            tasks={
72
                etrago_eGon2035_electricity,
73
                etrago_eGon2035_heat,
74
                residential_electricity_annual_sum,
75
                residential_electricity_hh_refinement,
76
                cts_electricity_demand_share,
77
                cts_heat_demand_share,
78
                sanitycheck_emobility_mit,
79
                sanitycheck_pv_rooftop_buildings,
80
            },
81
        )
82
83
84
def etrago_eGon2035_electricity():
85
    """Execute basic sanity checks.
86
87
    Returns print statements as sanity checks for the electricity sector in
88
    the eGon2035 scenario.
89
90
    Parameters
91
    ----------
92
    None
93
94
    Returns
95
    -------
96
    None
97
    """
98
99
    scn = "eGon2035"
100
101
    # Section to check generator capacities
102
    logger.info(f"Sanity checks for scenario {scn}")
103
    logger.info(
104
        "For German electricity generators the following deviations between "
105
        "the inputs and outputs can be observed:"
106
    )
107
108
    carriers_electricity = [
109
        "other_non_renewable",
110
        "other_renewable",
111
        "reservoir",
112
        "run_of_river",
113
        "oil",
114
        "wind_onshore",
115
        "wind_offshore",
116
        "solar",
117
        "solar_rooftop",
118
        "biomass",
119
    ]
120
121
    for carrier in carriers_electricity:
122
123
        if carrier == "biomass":
124
            sum_output = db.select_dataframe(
125
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
126
                    FROM grid.egon_etrago_generator
127
                    WHERE bus IN (
128
                        SELECT bus_id FROM grid.egon_etrago_bus
129
                        WHERE scn_name = 'eGon2035'
130
                        AND country = 'DE')
131
                    AND carrier IN ('biomass', 'industrial_biomass_CHP',
132
                    'central_biomass_CHP')
133
                    GROUP BY (scn_name);
134
                """,
135
                warning=False,
136
            )
137
138
        else:
139
            sum_output = db.select_dataframe(
140
                f"""SELECT scn_name,
141
                 SUM(p_nom::numeric) as output_capacity_mw
142
                         FROM grid.egon_etrago_generator
143
                         WHERE scn_name = '{scn}'
144
                         AND carrier IN ('{carrier}')
145
                         AND bus IN
146
                             (SELECT bus_id
147
                               FROM grid.egon_etrago_bus
148
                               WHERE scn_name = 'eGon2035'
149
                               AND country = 'DE')
150
                         GROUP BY (scn_name);
151
                    """,
152
                warning=False,
153
            )
154
155
        sum_input = db.select_dataframe(
156
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
157
                     FROM supply.egon_scenario_capacities
158
                     WHERE carrier= '{carrier}'
159
                     AND scenario_name ='{scn}'
160
                     GROUP BY (carrier);
161
                """,
162
            warning=False,
163
        )
164
165 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
166
            sum_output.output_capacity_mw.sum() == 0
167
            and sum_input.input_capacity_mw.sum() == 0
168
        ):
169
            logger.info(
170
                f"No capacity for carrier '{carrier}' needed to be"
171
                f" distributed. Everything is fine"
172
            )
173
174
        elif (
175
            sum_input.input_capacity_mw.sum() > 0
176
            and sum_output.output_capacity_mw.sum() == 0
177
        ):
178
            logger.info(
179
                f"Error: Capacity for carrier '{carrier}' was not distributed "
180
                f"at all!"
181
            )
182
183
        elif (
184
            sum_output.output_capacity_mw.sum() > 0
185
            and sum_input.input_capacity_mw.sum() == 0
186
        ):
187
            logger.info(
188
                f"Error: Eventhough no input capacity was provided for carrier"
189
                f"'{carrier}' a capacity got distributed!"
190
            )
191
192
        else:
193
            sum_input["error"] = (
194
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
195
                / sum_input.input_capacity_mw
196
            ) * 100
197
            g = sum_input["error"].values[0]
198
199
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
200
201
    # Section to check storage units
202
203
    logger.info(f"Sanity checks for scenario {scn}")
204
    logger.info(
205
        "For German electrical storage units the following deviations between"
206
        "the inputs and outputs can be observed:"
207
    )
208
209
    carriers_electricity = ["pumped_hydro"]
210
211
    for carrier in carriers_electricity:
212
213
        sum_output = db.select_dataframe(
214
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
215
                         FROM grid.egon_etrago_storage
216
                         WHERE scn_name = '{scn}'
217
                         AND carrier IN ('{carrier}')
218
                         AND bus IN
219
                             (SELECT bus_id
220
                               FROM grid.egon_etrago_bus
221
                               WHERE scn_name = 'eGon2035'
222
                               AND country = 'DE')
223
                         GROUP BY (scn_name);
224
                    """,
225
            warning=False,
226
        )
227
228
        sum_input = db.select_dataframe(
229
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
230
                     FROM supply.egon_scenario_capacities
231
                     WHERE carrier= '{carrier}'
232
                     AND scenario_name ='{scn}'
233
                     GROUP BY (carrier);
234
                """,
235
            warning=False,
236
        )
237
238 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
239
            sum_output.output_capacity_mw.sum() == 0
240
            and sum_input.input_capacity_mw.sum() == 0
241
        ):
242
            print(
243
                f"No capacity for carrier '{carrier}' needed to be "
244
                f"distributed. Everything is fine"
245
            )
246
247
        elif (
248
            sum_input.input_capacity_mw.sum() > 0
249
            and sum_output.output_capacity_mw.sum() == 0
250
        ):
251
            print(
252
                f"Error: Capacity for carrier '{carrier}' was not distributed"
253
                f" at all!"
254
            )
255
256
        elif (
257
            sum_output.output_capacity_mw.sum() > 0
258
            and sum_input.input_capacity_mw.sum() == 0
259
        ):
260
            print(
261
                f"Error: Eventhough no input capacity was provided for carrier"
262
                f" '{carrier}' a capacity got distributed!"
263
            )
264
265
        else:
266
            sum_input["error"] = (
267
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
268
                / sum_input.input_capacity_mw
269
            ) * 100
270
            g = sum_input["error"].values[0]
271
272
            print(f"{carrier}: " + str(round(g, 2)) + " %")
273
274
    # Section to check loads
275
276
    print(
277
        "For German electricity loads the following deviations between the"
278
        " input and output can be observed:"
279
    )
280
281
    output_demand = db.select_dataframe(
282
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
283
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
284
            FROM grid.egon_etrago_load a
285
            JOIN grid.egon_etrago_load_timeseries b
286
            ON (a.load_id = b.load_id)
287
            JOIN grid.egon_etrago_bus c
288
            ON (a.bus=c.bus_id)
289
            AND b.scn_name = 'eGon2035'
290
            AND a.scn_name = 'eGon2035'
291
            AND a.carrier = 'AC'
292
            AND c.scn_name= 'eGon2035'
293
            AND c.country='DE'
294
            GROUP BY (a.scn_name, a.carrier);
295
296
    """,
297
        warning=False,
298
    )["load_twh"].values[0]
299
300
    input_cts_ind = db.select_dataframe(
301
        """SELECT scenario,
302
         SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
303
            FROM demand.egon_demandregio_cts_ind
304
            WHERE scenario= 'eGon2035'
305
            AND year IN ('2035')
306
            GROUP BY (scenario);
307
308
        """,
309
        warning=False,
310
    )["demand_mw_regio_cts_ind"].values[0]
311
312
    input_hh = db.select_dataframe(
313
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
314
            FROM demand.egon_demandregio_hh
315
            WHERE scenario= 'eGon2035'
316
            AND year IN ('2035')
317
            GROUP BY (scenario);
318
        """,
319
        warning=False,
320
    )["demand_mw_regio_hh"].values[0]
321
322
    input_demand = input_hh + input_cts_ind
323
324
    e = round((output_demand - input_demand) / input_demand, 2) * 100
325
326
    print(f"electricity demand: {e} %")
327
328
329
def etrago_eGon2035_heat():
330
    """Execute basic sanity checks.
331
332
    Returns print statements as sanity checks for the heat sector in
333
    the eGon2035 scenario.
334
335
    Parameters
336
    ----------
337
    None
338
339
    Returns
340
    -------
341
    None
342
    """
343
344
    # Check input and output values for the carriers "other_non_renewable",
345
    # "other_renewable", "reservoir", "run_of_river" and "oil"
346
347
    scn = "eGon2035"
348
349
    # Section to check generator capacities
350
    print(f"Sanity checks for scenario {scn}")
351
    print(
352
        "For German heat demands the following deviations between the inputs"
353
        " and outputs can be observed:"
354
    )
355
356
    # Sanity checks for heat demand
357
358
    output_heat_demand = db.select_dataframe(
359
        """SELECT a.scn_name,
360
          (SUM(
361
          (SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
362
            FROM grid.egon_etrago_load a
363
            JOIN grid.egon_etrago_load_timeseries b
364
            ON (a.load_id = b.load_id)
365
            JOIN grid.egon_etrago_bus c
366
            ON (a.bus=c.bus_id)
367
            AND b.scn_name = 'eGon2035'
368
            AND a.scn_name = 'eGon2035'
369
            AND c.scn_name= 'eGon2035'
370
            AND c.country='DE'
371
            AND a.carrier IN ('rural_heat', 'central_heat')
372
            GROUP BY (a.scn_name);
373
        """,
374
        warning=False,
375
    )["load_twh"].values[0]
376
377
    input_heat_demand = db.select_dataframe(
378
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
379
            FROM demand.egon_peta_heat
380
            WHERE scenario= 'eGon2035'
381
            GROUP BY (scenario);
382
        """,
383
        warning=False,
384
    )["demand_mw_peta_heat"].values[0]
385
386
    e_demand = (
387
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
388
        * 100
389
    )
390
391
    logger.info(f"heat demand: {e_demand} %")
392
393
    # Sanity checks for heat supply
394
395
    logger.info(
396
        "For German heat supplies the following deviations between the inputs "
397
        "and outputs can be observed:"
398
    )
399
400
    # Comparison for central heat pumps
401
    heat_pump_input = db.select_dataframe(
402
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
403
            FROM supply.egon_scenario_capacities
404
            WHERE carrier= 'urban_central_heat_pump'
405
            AND scenario_name IN ('eGon2035')
406
            GROUP BY (carrier);
407
        """,
408
        warning=False,
409
    )["urban_central_heat_pump_mw"].values[0]
410
411
    heat_pump_output = db.select_dataframe(
412
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
413
            FROM grid.egon_etrago_link
414
            WHERE carrier= 'central_heat_pump'
415
            AND scn_name IN ('eGon2035')
416
            GROUP BY (carrier);
417
    """,
418
        warning=False,
419
    )["central_heat_pump_mw"].values[0]
420
421
    e_heat_pump = (
422
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
423
    )
424
425
    logger.info(f"'central_heat_pump': {e_heat_pump} % ")
426
427
    # Comparison for residential heat pumps
428
429
    input_residential_heat_pump = db.select_dataframe(
430
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
431
            FROM supply.egon_scenario_capacities
432
            WHERE carrier= 'residential_rural_heat_pump'
433
            AND scenario_name IN ('eGon2035')
434
            GROUP BY (carrier);
435
        """,
436
        warning=False,
437
    )["residential_heat_pump_mw"].values[0]
438
439
    output_residential_heat_pump = db.select_dataframe(
440
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
441
            FROM grid.egon_etrago_link
442
            WHERE carrier= 'rural_heat_pump'
443
            AND scn_name IN ('eGon2035')
444
            GROUP BY (carrier);
445
    """,
446
        warning=False,
447
    )["rural_heat_pump_mw"].values[0]
448
449
    e_residential_heat_pump = (
450
        round(
451
            (output_residential_heat_pump - input_residential_heat_pump)
452
            / input_residential_heat_pump,
453
            2,
454
        )
455
        * 100
456
    )
457
    logger.info(f"'residential heat pumps': {e_residential_heat_pump} %")
458
459
    # Comparison for resistive heater
460
    resistive_heater_input = db.select_dataframe(
461
        """SELECT carrier,
462
         SUM(capacity::numeric) as Urban_central_resistive_heater_MW
463
            FROM supply.egon_scenario_capacities
464
            WHERE carrier= 'urban_central_resistive_heater'
465
            AND scenario_name IN ('eGon2035')
466
            GROUP BY (carrier);
467
        """,
468
        warning=False,
469
    )["urban_central_resistive_heater_mw"].values[0]
470
471
    resistive_heater_output = db.select_dataframe(
472
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
473
            FROM grid.egon_etrago_link
474
            WHERE carrier= 'central_resistive_heater'
475
            AND scn_name IN ('eGon2035')
476
            GROUP BY (carrier);
477
        """,
478
        warning=False,
479
    )["central_resistive_heater_mw"].values[0]
480
481
    e_resistive_heater = (
482
        round(
483
            (resistive_heater_output - resistive_heater_input)
484
            / resistive_heater_input,
485
            2,
486
        )
487
        * 100
488
    )
489
490
    logger.info(f"'resistive heater': {e_resistive_heater} %")
491
492
    # Comparison for solar thermal collectors
493
494
    input_solar_thermal = db.select_dataframe(
495
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
496
            FROM supply.egon_scenario_capacities
497
            WHERE carrier= 'urban_central_solar_thermal_collector'
498
            AND scenario_name IN ('eGon2035')
499
            GROUP BY (carrier);
500
        """,
501
        warning=False,
502
    )["solar_thermal_collector_mw"].values[0]
503
504
    output_solar_thermal = db.select_dataframe(
505
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
506
            FROM grid.egon_etrago_generator
507
            WHERE carrier= 'solar_thermal_collector'
508
            AND scn_name IN ('eGon2035')
509
            GROUP BY (carrier);
510
        """,
511
        warning=False,
512
    )["solar_thermal_collector_mw"].values[0]
513
514
    e_solar_thermal = (
515
        round(
516
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
517
            2,
518
        )
519
        * 100
520
    )
521
    logger.info(f"'solar thermal collector': {e_solar_thermal} %")
522
523
    # Comparison for geothermal
524
525
    input_geo_thermal = db.select_dataframe(
526
        """SELECT carrier,
527
         SUM(capacity::numeric) as Urban_central_geo_thermal_MW
528
            FROM supply.egon_scenario_capacities
529
            WHERE carrier= 'urban_central_geo_thermal'
530
            AND scenario_name IN ('eGon2035')
531
            GROUP BY (carrier);
532
        """,
533
        warning=False,
534
    )["urban_central_geo_thermal_mw"].values[0]
535
536
    output_geo_thermal = db.select_dataframe(
537
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
538
            FROM grid.egon_etrago_generator
539
            WHERE carrier= 'geo_thermal'
540
            AND scn_name IN ('eGon2035')
541
            GROUP BY (carrier);
542
    """,
543
        warning=False,
544
    )["geo_thermal_mw"].values[0]
545
546
    e_geo_thermal = (
547
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
548
        * 100
549
    )
550
    logger.info(f"'geothermal': {e_geo_thermal} %")
551
552
553
def residential_electricity_annual_sum(rtol=1e-5):
554
    """Sanity check for dataset electricity_demand_timeseries :
555
    Demand_Building_Assignment
556
557
    Aggregate the annual demand of all census cells at NUTS3 to compare
558
    with initial scaling parameters from DemandRegio.
559
    """
560
561
    df_nuts3_annual_sum = db.select_dataframe(
562
        sql="""
563
        SELECT dr.nuts3, dr.scenario, dr.demand_regio_sum, profiles.profile_sum
564
        FROM (
565
            SELECT scenario, SUM(demand) AS profile_sum, vg250_nuts3
566
            FROM demand.egon_demandregio_zensus_electricity AS egon,
567
             boundaries.egon_map_zensus_vg250 AS boundaries
568
            Where egon.zensus_population_id = boundaries.zensus_population_id
569
            AND sector = 'residential'
570
            GROUP BY vg250_nuts3, scenario
571
            ) AS profiles
572
        JOIN (
573
            SELECT nuts3, scenario, sum(demand) AS demand_regio_sum
574
            FROM demand.egon_demandregio_hh
575
            GROUP BY year, scenario, nuts3
576
              ) AS dr
577
        ON profiles.vg250_nuts3 = dr.nuts3 and profiles.scenario  = dr.scenario
578
        """
579
    )
580
581
    np.testing.assert_allclose(
582
        actual=df_nuts3_annual_sum["profile_sum"],
583
        desired=df_nuts3_annual_sum["demand_regio_sum"],
584
        rtol=rtol,
585
        verbose=False,
586
    )
587
588
    logger.info(
589
        "Aggregated annual residential electricity demand"
590
        " matches with DemandRegio at NUTS-3."
591
    )
592
593
594
def residential_electricity_hh_refinement(rtol=1e-5):
595
    """Sanity check for dataset electricity_demand_timeseries :
596
    Household Demands
597
598
    Check sum of aggregated household types after refinement method
599
    was applied and compare it to the original census values."""
600
601
    df_refinement = db.select_dataframe(
602
        sql="""
603
        SELECT refined.nuts3, refined.characteristics_code,
604
                refined.sum_refined::int, census.sum_census::int
605
        FROM(
606
            SELECT nuts3, characteristics_code, SUM(hh_10types) as sum_refined
607
            FROM society.egon_destatis_zensus_household_per_ha_refined
608
            GROUP BY nuts3, characteristics_code)
609
            AS refined
610
        JOIN(
611
            SELECT t.nuts3, t.characteristics_code, sum(orig) as sum_census
612
            FROM(
613
                SELECT nuts3, cell_id, characteristics_code,
614
                        sum(DISTINCT(hh_5types))as orig
615
                FROM society.egon_destatis_zensus_household_per_ha_refined
616
                GROUP BY cell_id, characteristics_code, nuts3) AS t
617
            GROUP BY t.nuts3, t.characteristics_code    ) AS census
618
        ON refined.nuts3 = census.nuts3
619
        AND refined.characteristics_code = census.characteristics_code
620
    """
621
    )
622
623
    np.testing.assert_allclose(
624
        actual=df_refinement["sum_refined"],
625
        desired=df_refinement["sum_census"],
626
        rtol=rtol,
627
        verbose=False,
628
    )
629
630
    logger.info("All Aggregated household types match at NUTS-3.")
631
632
633
def cts_electricity_demand_share(rtol=1e-5):
634
    """Sanity check for dataset electricity_demand_timeseries :
635
    CtsBuildings
636
637
    Check sum of aggregated cts electricity demand share which equals to one
638
    for every substation as the substation profile is linearly disaggregated
639
    to all buildings."""
640
641
    with db.session_scope() as session:
642
        cells_query = session.query(EgonCtsElectricityDemandBuildingShare)
643
644
    df_demand_share = pd.read_sql(
645
        cells_query.statement, cells_query.session.bind, index_col=None
646
    )
647
648
    np.testing.assert_allclose(
649
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
650
            "profile_share"
651
        ].sum(),
652
        desired=1,
653
        rtol=rtol,
654
        verbose=False,
655
    )
656
657
    logger.info("The aggregated demand shares equal to one!.")
658
659
660
def cts_heat_demand_share(rtol=1e-5):
661
    """Sanity check for dataset electricity_demand_timeseries
662
    : CtsBuildings
663
664
    Check sum of aggregated cts heat demand share which equals to one
665
    for every substation as the substation profile is linearly disaggregated
666
    to all buildings."""
667
668
    with db.session_scope() as session:
669
        cells_query = session.query(EgonCtsHeatDemandBuildingShare)
670
671
    df_demand_share = pd.read_sql(
672
        cells_query.statement, cells_query.session.bind, index_col=None
673
    )
674
675
    np.testing.assert_allclose(
676
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
677
            "profile_share"
678
        ].sum(),
679
        desired=1,
680
        rtol=rtol,
681
        verbose=False,
682
    )
683
684
    logger.info("The aggregated demand shares equal to one!.")
685
686
687
def sanitycheck_pv_rooftop_buildings():
688
    def egon_power_plants_pv_roof_building():
689
        sql = """
690
        SELECT *
691
        FROM supply.egon_power_plants_pv_roof_building
692
        """
693
694
        return db.select_dataframe(sql, index_col="index")
695
696
    pv_roof_df = egon_power_plants_pv_roof_building()
697
698
    buildings_gdf = load_building_data()
699
    grid_districts_gdf = grid_districts(EPSG)
700
    federal_state_gdf = federal_state_data(grid_districts_gdf.crs)
701
702
    grid_federal_state_gdf = overlay_grid_districts_with_counties(
703
        grid_districts_gdf,
704
        federal_state_gdf,
705
    )
706
707
    buildings_overlay_gdf = add_overlay_id_to_buildings(
708
        buildings_gdf,
709
        grid_federal_state_gdf,
710
    )
711
712
    valid_buildings_gdf = drop_buildings_outside_grids(buildings_overlay_gdf)
713
    valid_buildings_gdf = valid_buildings_gdf.assign(
714
        bus_id=valid_buildings_gdf.bus_id.astype(int),
715
        overlay_id=valid_buildings_gdf.overlay_id.astype(int),
716
        max_cap=valid_buildings_gdf.building_area.multiply(
717
            ROOF_FACTOR * PV_CAP_PER_SQ_M
718
        ),
719
    )
720
721
    merge_df = pv_roof_df.merge(
722
        valid_buildings_gdf[["building_area"]],
723
        how="left",
724
        left_on="building_id",
725
        right_index=True,
726
    )
727
728
    assert len(merge_df.loc[merge_df.building_area.isna()]) == 0
729
730
    scenarios = ["status_quo", "eGon2035"]
731
732
    base_path = Path(egon.data.__path__[0]).resolve()
733
734
    res_dir = base_path / "sanity_checks"
735
736
    res_dir.mkdir(parents=True, exist_ok=True)
737
738
    for scenario in scenarios:
739
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 8))
740
741
        scenario_df = merge_df.loc[merge_df.scenario == scenario]
742
743
        logger.info(
744
            scenario + " Capacity:\n" + str(scenario_df.capacity.describe())
745
        )
746
747
        small_gens_df = scenario_df.loc[scenario_df.capacity < 100]
748
749
        sns.histplot(data=small_gens_df, x="capacity", ax=ax1).set_title(
750
            scenario
751
        )
752
753
        sns.scatterplot(
754
            data=small_gens_df, x="capacity", y="building_area", ax=ax2
755
        ).set_title(scenario)
756
757
        plt.tight_layout()
758
759
        plt.savefig(
760
            res_dir / f"{scenario}_pv_rooftop_distribution.png",
761
            bbox_inches="tight",
762
        )
763
764
    for scenario in SCENARIOS:
765
        if scenario == "eGon2035":
766
            assert isclose(
767
                scenario_data(scenario=scenario).capacity.sum(),
768
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
769
                rel_tol=1e-02,
770
            ), (
771
                f"{scenario_data(scenario=scenario).capacity.sum()} != "
772
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
773
            )
774
        elif scenario == "eGon100RE":
775
            sources = config.datasets()["solar_rooftop"]["sources"]
776
777
            target = db.select_dataframe(
778
                f"""
779
                SELECT capacity
780
                FROM {sources['scenario_capacities']['schema']}.
781
                {sources['scenario_capacities']['table']} a
782
                WHERE carrier = 'solar_rooftop'
783
                AND scenario_name = '{scenario}'
784
                """
785
            ).capacity[0]
786
787
            dataset = config.settings()["egon-data"]["--dataset-boundary"]
788
789
            if dataset == "Schleswig-Holstein":
790
                # since the required data is missing for a SH run, it is implemented
791
                # manually here
792
                total_2035 = 84070
793
                sh_2035 = 2700
794
795
                share = sh_2035 / total_2035
796
797
                target *= share
798
799
            assert isclose(
800
                target,
801
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
802
            ), (
803
                f"{target} != "
804
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
805
            )
806
        else:
807
            raise ValueError(f"Scenario {scenario} is not valid.")
808
809
810
def sanitycheck_emobility_mit():
811
    """Execute sanity checks for eMobility: motorized individual travel
812
813
    Checks data integrity for eGon2035, eGon2035_lowflex and eGon100RE scenario
814
    using assertions:
815
      1. Allocated EV numbers and EVs allocated to grid districts
816
      2. Trip data (original inout data from simBEV)
817
      3. Model data in eTraGo PF tables (grid.egon_etrago_*)
818
819
    Parameters
820
    ----------
821
    None
822
823
    Returns
824
    -------
825
    None
826
    """
827
828
    def check_ev_allocation():
829
        # Get target number for scenario
830
        ev_count_target = scenario_variation_parameters["ev_count"]
831
        print(f"  Target count: {str(ev_count_target)}")
832
833
        # Get allocated numbers
834
        ev_counts_dict = {}
835
        with db.session_scope() as session:
836
            for table, level in zip(
837
                [
838
                    EgonEvCountMvGridDistrict,
839
                    EgonEvCountMunicipality,
840
                    EgonEvCountRegistrationDistrict,
841
                ],
842
                ["Grid District", "Municipality", "Registration District"],
843
            ):
844
                query = session.query(
845
                    func.sum(
846
                        table.bev_mini
847
                        + table.bev_medium
848
                        + table.bev_luxury
849
                        + table.phev_mini
850
                        + table.phev_medium
851
                        + table.phev_luxury
852
                    ).label("ev_count")
853
                ).filter(
854
                    table.scenario == scenario_name,
855
                    table.scenario_variation == scenario_var_name,
856
                )
857
858
                ev_counts = pd.read_sql(
859
                    query.statement, query.session.bind, index_col=None
860
                )
861
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
862
                print(
863
                    f"    Count table: Total count for level {level} "
864
                    f"(table: {table.__table__}): "
865
                    f"{str(ev_counts_dict[level])}"
866
                )
867
868
        # Compare with scenario target (only if not in testmode)
869
        if TESTMODE_OFF:
870
            for level, count in ev_counts_dict.items():
871
                np.testing.assert_allclose(
872
                    count,
873
                    ev_count_target,
874
                    rtol=0.0001,
875
                    err_msg=f"EV numbers in {level} seems to be flawed.",
876
                )
877
        else:
878
            print("    Testmode is on, skipping sanity check...")
879
880
        # Get allocated EVs in grid districts
881
        with db.session_scope() as session:
882
            query = session.query(
883
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
884
                    "ev_count"
885
                ),
886
            ).filter(
887
                EgonEvMvGridDistrict.scenario == scenario_name,
888
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
889
            )
890
        ev_count_alloc = (
891
            pd.read_sql(query.statement, query.session.bind, index_col=None)
892
            .iloc[0]
893
            .ev_count
894
        )
895
        print(
896
            f"    EVs allocated to Grid Districts "
897
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
898
            f"{str(ev_count_alloc)}"
899
        )
900
901
        # Compare with scenario target (only if not in testmode)
902
        if TESTMODE_OFF:
903
            np.testing.assert_allclose(
904
                ev_count_alloc,
905
                ev_count_target,
906
                rtol=0.0001,
907
                err_msg=(
908
                    "EV numbers allocated to Grid Districts seems to be "
909
                    "flawed."
910
                ),
911
            )
912
        else:
913
            print("    Testmode is on, skipping sanity check...")
914
915
        return ev_count_alloc
916
917
    def check_trip_data():
918
        # Check if trips start at timestep 0 and have a max. of 35040 steps
919
        # (8760h in 15min steps)
920
        print("  Checking timeranges...")
921
        with db.session_scope() as session:
922
            query = session.query(
923
                func.count(EgonEvTrip.event_id).label("cnt")
924
            ).filter(
925
                or_(
926
                    and_(
927
                        EgonEvTrip.park_start > 0,
928
                        EgonEvTrip.simbev_event_id == 0,
929
                    ),
930
                    EgonEvTrip.park_end
931
                    > (60 / int(meta_run_config.stepsize)) * 8760,
932
                ),
933
                EgonEvTrip.scenario == scenario_name,
934
            )
935
        invalid_trips = pd.read_sql(
936
            query.statement, query.session.bind, index_col=None
937
        )
938
        np.testing.assert_equal(
939
            invalid_trips.iloc[0].cnt,
940
            0,
941
            err_msg=(
942
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
943
                f"{EgonEvTrip.__table__} have invalid timesteps."
944
            ),
945
        )
946
947
        # Check if charging demand can be covered by available charging energy
948
        # while parking
949
        print("  Compare charging demand with available power...")
950
        with db.session_scope() as session:
951
            query = session.query(
952
                func.count(EgonEvTrip.event_id).label("cnt")
953
            ).filter(
954
                func.round(
955
                    cast(
956
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
957
                        * EgonEvTrip.charging_capacity_nominal
958
                        * (int(meta_run_config.stepsize) / 60),
959
                        Numeric,
960
                    ),
961
                    3,
962
                )
963
                < cast(EgonEvTrip.charging_demand, Numeric),
964
                EgonEvTrip.scenario == scenario_name,
965
            )
966
        invalid_trips = pd.read_sql(
967
            query.statement, query.session.bind, index_col=None
968
        )
969
        np.testing.assert_equal(
970
            invalid_trips.iloc[0].cnt,
971
            0,
972
            err_msg=(
973
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
974
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
975
                f"covered by available charging power."
976
            ),
977
        )
978
979
    def check_model_data():
980
        # Check if model components were fully created
981
        print("  Check if all model components were created...")
982
        # Get MVGDs which got EV allocated
983
        with db.session_scope() as session:
984
            query = (
985
                session.query(
986
                    EgonEvMvGridDistrict.bus_id,
987
                )
988
                .filter(
989
                    EgonEvMvGridDistrict.scenario == scenario_name,
990
                    EgonEvMvGridDistrict.scenario_variation
991
                    == scenario_var_name,
992
                )
993
                .group_by(EgonEvMvGridDistrict.bus_id)
994
            )
995
        mvgds_with_ev = (
996
            pd.read_sql(query.statement, query.session.bind, index_col=None)
997
            .bus_id.sort_values()
998
            .to_list()
999
        )
1000
1001
        # Load model components
1002
        with db.session_scope() as session:
1003
            query = (
1004
                session.query(
1005
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
1006
                    EgonPfHvLoad.bus.label("emob_bus_id"),
1007
                    EgonPfHvLoad.load_id.label("load_id"),
1008
                    EgonPfHvStore.store_id.label("store_id"),
1009
                )
1010
                .select_from(EgonPfHvLoad, EgonPfHvStore)
1011
                .join(
1012
                    EgonPfHvLoadTimeseries,
1013
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1014
                )
1015
                .join(
1016
                    EgonPfHvStoreTimeseries,
1017
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
1018
                )
1019
                .filter(
1020
                    EgonPfHvLoad.carrier == "land transport EV",
1021
                    EgonPfHvLoad.scn_name == scenario_name,
1022
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
1023
                    EgonPfHvStore.carrier == "battery storage",
1024
                    EgonPfHvStore.scn_name == scenario_name,
1025
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
1026
                    EgonPfHvLink.scn_name == scenario_name,
1027
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
1028
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
1029
                )
1030
            )
1031
        model_components = pd.read_sql(
1032
            query.statement, query.session.bind, index_col=None
1033
        )
1034
1035
        # Check number of buses with model components connected
1036
        mvgd_buses_with_ev = model_components.loc[
1037
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
1038
        ]
1039
        np.testing.assert_equal(
1040
            len(mvgds_with_ev),
1041
            len(mvgd_buses_with_ev),
1042
            err_msg=(
1043
                f"Number of Grid Districts with connected model components "
1044
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
1045
                f"differ from number of Grid Districts that got EVs "
1046
                f"allocated ({len(mvgds_with_ev)} in table "
1047
                f"{EgonEvMvGridDistrict.__table__})."
1048
            ),
1049
        )
1050
1051
        # Check if all required components exist (if no id is NaN)
1052
        np.testing.assert_equal(
1053
            model_components.drop_duplicates().isna().any().any(),
1054
            False,
1055
            err_msg=(
1056
                f"Some components are missing (see True values): "
1057
                f"{model_components.drop_duplicates().isna().any()}"
1058
            ),
1059
        )
1060
1061
        # Get all model timeseries
1062
        print("  Loading model timeseries...")
1063
        # Get all model timeseries
1064
        model_ts_dict = {
1065
            "Load": {
1066
                "carrier": "land transport EV",
1067
                "table": EgonPfHvLoad,
1068
                "table_ts": EgonPfHvLoadTimeseries,
1069
                "column_id": "load_id",
1070
                "columns_ts": ["p_set"],
1071
                "ts": None,
1072
            },
1073
            "Link": {
1074
                "carrier": "BEV charger",
1075
                "table": EgonPfHvLink,
1076
                "table_ts": EgonPfHvLinkTimeseries,
1077
                "column_id": "link_id",
1078
                "columns_ts": ["p_max_pu"],
1079
                "ts": None,
1080
            },
1081
            "Store": {
1082
                "carrier": "battery storage",
1083
                "table": EgonPfHvStore,
1084
                "table_ts": EgonPfHvStoreTimeseries,
1085
                "column_id": "store_id",
1086
                "columns_ts": ["e_min_pu", "e_max_pu"],
1087
                "ts": None,
1088
            },
1089
        }
1090
1091
        with db.session_scope() as session:
1092
            for node, attrs in model_ts_dict.items():
1093
                print(f"    Loading {node} timeseries...")
1094
                subquery = (
1095
                    session.query(getattr(attrs["table"], attrs["column_id"]))
1096
                    .filter(attrs["table"].carrier == attrs["carrier"])
1097
                    .filter(attrs["table"].scn_name == scenario_name)
1098
                    .subquery()
1099
                )
1100
1101
                cols = [
1102
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
1103
                ]
1104
                query = session.query(
1105
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
1106
                ).filter(
1107
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
1108
                        subquery
1109
                    ),
1110
                    attrs["table_ts"].scn_name == scenario_name,
1111
                )
1112
                attrs["ts"] = pd.read_sql(
1113
                    query.statement,
1114
                    query.session.bind,
1115
                    index_col=attrs["column_id"],
1116
                )
1117
1118
        # Check if all timeseries have 8760 steps
1119
        print("    Checking timeranges...")
1120
        for node, attrs in model_ts_dict.items():
1121
            for col in attrs["columns_ts"]:
1122
                ts = attrs["ts"]
1123
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
1124
                    col
1125
                ].apply(len)
1126
                np.testing.assert_equal(
1127
                    len(invalid_ts),
1128
                    0,
1129
                    err_msg=(
1130
                        f"{str(len(invalid_ts))} rows in timeseries do not "
1131
                        f"have 8760 timesteps. Table: "
1132
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
1133
                        f"{str(list(invalid_ts.index))}"
1134
                    ),
1135
                )
1136
1137
        # Compare total energy demand in model with some approximate values
1138
        # (per EV: 14,000 km/a, 0.17 kWh/km)
1139
        print("  Checking energy demand in model...")
1140
        total_energy_model = (
1141
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
1142
            / 1e6
1143
        )
1144
        print(f"    Total energy amount in model: {total_energy_model} TWh")
1145
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
1146
        print(
1147
            f"    Total approximated energy amount in scenario: "
1148
            f"{total_energy_scenario_approx} TWh"
1149
        )
1150
        np.testing.assert_allclose(
1151
            total_energy_model,
1152
            total_energy_scenario_approx,
1153
            rtol=0.1,
1154
            err_msg=(
1155
                "The total energy amount in the model deviates heavily "
1156
                "from the approximated value for current scenario."
1157
            ),
1158
        )
1159
1160
        # Compare total storage capacity
1161
        print("  Checking storage capacity...")
1162
        # Load storage capacities from model
1163
        with db.session_scope() as session:
1164
            query = session.query(
1165
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
1166
            ).filter(
1167
                EgonPfHvStore.scn_name == scenario_name,
1168
                EgonPfHvStore.carrier == "battery storage",
1169
            )
1170
        storage_capacity_model = (
1171
            pd.read_sql(
1172
                query.statement, query.session.bind, index_col=None
1173
            ).e_nom.sum()
1174
            / 1e3
1175
        )
1176
        print(
1177
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
1178
            f"{round(storage_capacity_model, 1)} GWh"
1179
        )
1180
1181
        # Load occurences of each EV
1182
        with db.session_scope() as session:
1183
            query = (
1184
                session.query(
1185
                    EgonEvMvGridDistrict.bus_id,
1186
                    EgonEvPool.type,
1187
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
1188
                        "count"
1189
                    ),
1190
                )
1191
                .join(
1192
                    EgonEvPool,
1193
                    EgonEvPool.ev_id
1194
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
1195
                )
1196
                .filter(
1197
                    EgonEvMvGridDistrict.scenario == scenario_name,
1198
                    EgonEvMvGridDistrict.scenario_variation
1199
                    == scenario_var_name,
1200
                    EgonEvPool.scenario == scenario_name,
1201
                )
1202
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
1203
            )
1204
        count_per_ev_all = pd.read_sql(
1205
            query.statement, query.session.bind, index_col="bus_id"
1206
        )
1207
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
1208
            meta_tech_data.battery_capacity
1209
        )
1210
        count_per_ev_all["bat_cap_total_MWh"] = (
1211
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
1212
        )
1213
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
1214
            1e3
1215
        ).sum()
1216
        print(
1217
            f"    Total storage capacity (simBEV): "
1218
            f"{round(storage_capacity_simbev, 1)} GWh"
1219
        )
1220
1221
        np.testing.assert_allclose(
1222
            storage_capacity_model,
1223
            storage_capacity_simbev,
1224
            rtol=0.01,
1225
            err_msg=(
1226
                "The total storage capacity in the model deviates heavily "
1227
                "from the input data provided by simBEV for current scenario."
1228
            ),
1229
        )
1230
1231
        # Check SoC storage constraint: e_min_pu < e_max_pu for all timesteps
1232
        print("  Validating SoC constraints...")
1233
        stores_with_invalid_soc = []
1234
        for idx, row in model_ts_dict["Store"]["ts"].iterrows():
1235
            ts = row[["e_min_pu", "e_max_pu"]]
1236
            x = np.array(ts.e_min_pu) > np.array(ts.e_max_pu)
1237
            if x.any():
1238
                stores_with_invalid_soc.append(idx)
1239
1240
        np.testing.assert_equal(
1241
            len(stores_with_invalid_soc),
1242
            0,
1243
            err_msg=(
1244
                f"The store constraint e_min_pu < e_max_pu does not apply "
1245
                f"for some storages in {EgonPfHvStoreTimeseries.__table__}. "
1246
                f"Invalid store_ids: {stores_with_invalid_soc}"
1247
            ),
1248
        )
1249
1250
    def check_model_data_lowflex_eGon2035():
1251
        # TODO: Add eGon100RE_lowflex
1252
        print("")
1253
        print("SCENARIO: eGon2035_lowflex")
1254
1255
        # Compare driving load and charging load
1256
        print("  Loading eGon2035 model timeseries: driving load...")
1257
        with db.session_scope() as session:
1258
            query = (
1259
                session.query(
1260
                    EgonPfHvLoad.load_id,
1261
                    EgonPfHvLoadTimeseries.p_set,
1262
                )
1263
                .join(
1264
                    EgonPfHvLoadTimeseries,
1265
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1266
                )
1267
                .filter(
1268
                    EgonPfHvLoad.carrier == "land transport EV",
1269
                    EgonPfHvLoad.scn_name == "eGon2035",
1270
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035",
1271
                )
1272
            )
1273
        model_driving_load = pd.read_sql(
1274
            query.statement, query.session.bind, index_col=None
1275
        )
1276
        driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0)
1277
1278
        print(
1279
            "  Loading eGon2035_lowflex model timeseries: dumb charging "
1280
            "load..."
1281
        )
1282
        with db.session_scope() as session:
1283
            query = (
1284
                session.query(
1285
                    EgonPfHvLoad.load_id,
1286
                    EgonPfHvLoadTimeseries.p_set,
1287
                )
1288
                .join(
1289
                    EgonPfHvLoadTimeseries,
1290
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1291
                )
1292
                .filter(
1293
                    EgonPfHvLoad.carrier == "land transport EV",
1294
                    EgonPfHvLoad.scn_name == "eGon2035_lowflex",
1295
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex",
1296
                )
1297
            )
1298
        model_charging_load_lowflex = pd.read_sql(
1299
            query.statement, query.session.bind, index_col=None
1300
        )
1301
        charging_load = np.array(
1302
            model_charging_load_lowflex.p_set.to_list()
1303
        ).sum(axis=0)
1304
1305
        # Ratio of driving and charging load should be 0.9 due to charging
1306
        # efficiency
1307
        print("  Compare cumulative loads...")
1308
        print(f"    Driving load (eGon2035): {driving_load.sum() / 1e6} TWh")
1309
        print(
1310
            f"    Dumb charging load (eGon2035_lowflex): "
1311
            f"{charging_load.sum() / 1e6} TWh"
1312
        )
1313
        driving_load_theoretical = (
1314
            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 1332 is not entered. Are you sure this can never be the case?
Loading history...
1315
        )
1316
        np.testing.assert_allclose(
1317
            driving_load.sum(),
1318
            driving_load_theoretical,
1319
            rtol=0.01,
1320
            err_msg=(
1321
                f"The driving load (eGon2035) deviates by more than 1% "
1322
                f"from the theoretical driving load calculated from charging "
1323
                f"load (eGon2035_lowflex) with an efficiency of "
1324
                f"{float(meta_run_config.eta_cp)}."
1325
            ),
1326
        )
1327
1328
    print("=====================================================")
1329
    print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===")
1330
    print("=====================================================")
1331
1332
    for scenario_name in ["eGon2035", "eGon100RE"]:
1333
        scenario_var_name = DATASET_CFG["scenario"]["variation"][scenario_name]
1334
1335
        print("")
1336
        print(f"SCENARIO: {scenario_name}, VARIATION: {scenario_var_name}")
1337
1338
        # Load scenario params for scenario and scenario variation
1339
        scenario_variation_parameters = get_sector_parameters(
1340
            "mobility", scenario=scenario_name
1341
        )["motorized_individual_travel"][scenario_var_name]
1342
1343
        # Load simBEV run config and tech data
1344
        meta_run_config = read_simbev_metadata_file(
1345
            scenario_name, "config"
1346
        ).loc["basic"]
1347
        meta_tech_data = read_simbev_metadata_file(scenario_name, "tech_data")
1348
1349
        print("")
1350
        print("Checking EV counts...")
1351
        ev_count_alloc = check_ev_allocation()
1352
1353
        print("")
1354
        print("Checking trip data...")
1355
        check_trip_data()
1356
1357
        print("")
1358
        print("Checking model data...")
1359
        check_model_data()
1360
1361
    print("")
1362
    check_model_data_lowflex_eGon2035()
1363
1364
    print("=====================================================")
1365