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

data.datasets.sanity_checks   B

Complexity

Total Complexity 51

Size/Duplication

Total Lines 1305
Duplicated Lines 5.36 %

Importance

Changes 0
Metric Value
wmc 51
eloc 680
dl 70
loc 1305
rs 7.84
c 0
b 0
f 0

8 Functions

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