Passed
Push — dev ( 55b615...5a2bf5 )
by
unknown
01:51 queued 12s
created

sanitycheck_emobility_mit()   F

Complexity

Conditions 24

Size

Total Lines 556
Code Lines 359

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 359
dl 0
loc 556
rs 0
c 0
b 0
f 0
cc 24
nop 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

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

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

1
"""
2
This module does sanity checks for both the eGon2035 and the eGon100RE scenario seperately where a percentage
3
error is given to showcase difference in output and input values. Please note that there are missing input technologies in the supply tables.
4
 Authors: @ALonso, @dana
5
"""
6
7
from sqlalchemy import Numeric
8
from sqlalchemy.sql import and_, cast, func, or_
9
import numpy as np
10
import pandas as pd
11
12
from egon.data import config, db
13
from egon.data.datasets import Dataset
14
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (
15
    EgonEvCountMunicipality,
16
    EgonEvCountMvGridDistrict,
17
    EgonEvCountRegistrationDistrict,
18
    EgonEvMvGridDistrict,
19
    EgonEvPool,
20
    EgonEvTrip,
21
)
22
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
23
    DATASET_CFG,
24
    read_simbev_metadata_file,
25
)
26
from egon.data.datasets.etrago_setup import (
27
    EgonPfHvLink,
28
    EgonPfHvLinkTimeseries,
29
    EgonPfHvLoad,
30
    EgonPfHvLoadTimeseries,
31
    EgonPfHvStore,
32
    EgonPfHvStoreTimeseries,
33
)
34
from egon.data.datasets.scenario_parameters import get_sector_parameters
35
36
TESTMODE_OFF = (
37
    config.settings()["egon-data"]["--dataset-boundary"] == "Everything"
38
)
39
40
41
class SanityChecks(Dataset):
42
    def __init__(self, dependencies):
43
        super().__init__(
44
            name="SanityChecks",
45
            version="0.0.4",
46
            dependencies=dependencies,
47
            tasks={
48
                sanitycheck_eGon2035_electricity,
49
                sanitycheck_eGon2035_heat,
50
                sanitycheck_emobility_mit,
51
            },
52
        )
53
54
55
def sanitycheck_eGon2035_electricity():
56
    """Execute basic sanity checks.
57
58
    Returns print statements as sanity checks for the electricity sector in
59
    the eGon2035 scenario.
60
61
    Parameters
62
    ----------
63
    None
64
65
    Returns
66
    -------
67
    None
68
    """
69
70
    scn = "eGon2035"
71
72
    # Section to check generator capacities
73
    print(f"Sanity checks for scenario {scn}")
74
    print(
75
        "For German electricity generators the following deviations between the inputs and outputs can be observed:"
76
    )
77
78
    carriers_electricity = [
79
        "other_non_renewable",
80
        "other_renewable",
81
        "reservoir",
82
        "run_of_river",
83
        "oil",
84
        "wind_onshore",
85
        "wind_offshore",
86
        "solar",
87
        "solar_rooftop",
88
        "biomass",
89
    ]
90
91
    for carrier in carriers_electricity:
92
93
        if carrier == "biomass":
94
            sum_output = db.select_dataframe(
95
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
96
                    FROM grid.egon_etrago_generator
97
                    WHERE bus IN (
98
                        SELECT bus_id FROM grid.egon_etrago_bus
99
                        WHERE scn_name = 'eGon2035'
100
                        AND country = 'DE')
101
                    AND carrier IN ('biomass', 'industrial_biomass_CHP', 'central_biomass_CHP')
102
                    GROUP BY (scn_name);
103
                """,
104
                warning=False,
105
            )
106
107
        else:
108
            sum_output = db.select_dataframe(
109
                f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
110
                         FROM grid.egon_etrago_generator
111
                         WHERE scn_name = '{scn}'
112
                         AND carrier IN ('{carrier}')
113
                         AND bus IN
114
                             (SELECT bus_id
115
                               FROM grid.egon_etrago_bus
116
                               WHERE scn_name = 'eGon2035'
117
                               AND country = 'DE')
118
                         GROUP BY (scn_name);
119
                    """,
120
                warning=False,
121
            )
122
123
        sum_input = db.select_dataframe(
124
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
125
                     FROM supply.egon_scenario_capacities
126
                     WHERE carrier= '{carrier}'
127
                     AND scenario_name ='{scn}'
128
                     GROUP BY (carrier);
129
                """,
130
            warning=False,
131
        )
132
133 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
134
            sum_output.output_capacity_mw.sum() == 0
135
            and sum_input.input_capacity_mw.sum() == 0
136
        ):
137
            print(
138
                f"No capacity for carrier '{carrier}' needed to be distributed. Everything is fine"
139
            )
140
141
        elif (
142
            sum_input.input_capacity_mw.sum() > 0
143
            and sum_output.output_capacity_mw.sum() == 0
144
        ):
145
            print(
146
                f"Error: Capacity for carrier '{carrier}' was not distributed at all!"
147
            )
148
149
        elif (
150
            sum_output.output_capacity_mw.sum() > 0
151
            and sum_input.input_capacity_mw.sum() == 0
152
        ):
153
            print(
154
                f"Error: Eventhough no input capacity was provided for carrier '{carrier}' a capacity got distributed!"
155
            )
156
157
        else:
158
            sum_input["error"] = (
159
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
160
                / sum_input.input_capacity_mw
161
            ) * 100
162
            g = sum_input["error"].values[0]
163
164
            print(f"{carrier}: " + str(round(g, 2)) + " %")
165
166
    # Section to check storage units
167
168
    print(f"Sanity checks for scenario {scn}")
169
    print(
170
        "For German electrical storage units the following deviations between the inputs and outputs can be observed:"
171
    )
172
173
    carriers_electricity = ["pumped_hydro"]
174
175
    for carrier in carriers_electricity:
176
177
        sum_output = db.select_dataframe(
178
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
179
                         FROM grid.egon_etrago_storage
180
                         WHERE scn_name = '{scn}'
181
                         AND carrier IN ('{carrier}')
182
                         AND bus IN
183
                             (SELECT bus_id
184
                               FROM grid.egon_etrago_bus
185
                               WHERE scn_name = 'eGon2035'
186
                               AND country = 'DE')
187
                         GROUP BY (scn_name);
188
                    """,
189
            warning=False,
190
        )
191
192
        sum_input = db.select_dataframe(
193
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
194
                     FROM supply.egon_scenario_capacities
195
                     WHERE carrier= '{carrier}'
196
                     AND scenario_name ='{scn}'
197
                     GROUP BY (carrier);
198
                """,
199
            warning=False,
200
        )
201
202 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
203
            sum_output.output_capacity_mw.sum() == 0
204
            and sum_input.input_capacity_mw.sum() == 0
205
        ):
206
            print(
207
                f"No capacity for carrier '{carrier}' needed to be distributed. Everything is fine"
208
            )
209
210
        elif (
211
            sum_input.input_capacity_mw.sum() > 0
212
            and sum_output.output_capacity_mw.sum() == 0
213
        ):
214
            print(
215
                f"Error: Capacity for carrier '{carrier}' was not distributed at all!"
216
            )
217
218
        elif (
219
            sum_output.output_capacity_mw.sum() > 0
220
            and sum_input.input_capacity_mw.sum() == 0
221
        ):
222
            print(
223
                f"Error: Eventhough no input capacity was provided for carrier '{carrier}' a capacity got distributed!"
224
            )
225
226
        else:
227
            sum_input["error"] = (
228
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
229
                / sum_input.input_capacity_mw
230
            ) * 100
231
            g = sum_input["error"].values[0]
232
233
            print(f"{carrier}: " + str(round(g, 2)) + " %")
234
235
    # Section to check loads
236
237
    print(
238
        "For German electricity loads the following deviations between the input and output can be observed:"
239
    )
240
241
    output_demand = db.select_dataframe(
242
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
243
            FROM grid.egon_etrago_load a
244
            JOIN grid.egon_etrago_load_timeseries b
245
            ON (a.load_id = b.load_id)
246
            JOIN grid.egon_etrago_bus c
247
            ON (a.bus=c.bus_id)
248
            AND b.scn_name = 'eGon2035'
249
            AND a.scn_name = 'eGon2035'
250
            AND a.carrier = 'AC'
251
            AND c.scn_name= 'eGon2035'
252
            AND c.country='DE'
253
            GROUP BY (a.scn_name, a.carrier);
254
255
    """,
256
        warning=False,
257
    )["load_twh"].values[0]
258
259
    input_cts_ind = db.select_dataframe(
260
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
261
            FROM demand.egon_demandregio_cts_ind
262
            WHERE scenario= 'eGon2035'
263
            AND year IN ('2035')
264
            GROUP BY (scenario);
265
266
        """,
267
        warning=False,
268
    )["demand_mw_regio_cts_ind"].values[0]
269
270
    input_hh = db.select_dataframe(
271
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
272
            FROM demand.egon_demandregio_hh
273
            WHERE scenario= 'eGon2035'
274
            AND year IN ('2035')
275
            GROUP BY (scenario);
276
        """,
277
        warning=False,
278
    )["demand_mw_regio_hh"].values[0]
279
280
    input_demand = input_hh + input_cts_ind
281
282
    e = round((output_demand - input_demand) / input_demand, 2) * 100
283
284
    print(f"electricity demand: {e} %")
285
286
287
def sanitycheck_eGon2035_heat():
288
    """Execute basic sanity checks.
289
290
    Returns print statements as sanity checks for the heat sector in
291
    the eGon2035 scenario.
292
293
    Parameters
294
    ----------
295
    None
296
297
    Returns
298
    -------
299
    None
300
    """
301
302
    # Check input and output values for the carriers "other_non_renewable",
303
    # "other_renewable", "reservoir", "run_of_river" and "oil"
304
305
    scn = "eGon2035"
306
307
    # Section to check generator capacities
308
    print(f"Sanity checks for scenario {scn}")
309
    print(
310
        "For German heat demands the following deviations between the inputs and outputs can be observed:"
311
    )
312
313
    # Sanity checks for heat demand
314
315
    output_heat_demand = db.select_dataframe(
316
        """SELECT a.scn_name,  (SUM((SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
317
            FROM grid.egon_etrago_load a
318
            JOIN grid.egon_etrago_load_timeseries b
319
            ON (a.load_id = b.load_id)
320
            JOIN grid.egon_etrago_bus c
321
            ON (a.bus=c.bus_id)
322
            AND b.scn_name = 'eGon2035'
323
            AND a.scn_name = 'eGon2035'
324
            AND c.scn_name= 'eGon2035'
325
            AND c.country='DE'
326
            AND a.carrier IN ('rural_heat', 'central_heat')
327
            GROUP BY (a.scn_name);
328
        """,
329
        warning=False,
330
    )["load_twh"].values[0]
331
332
    input_heat_demand = db.select_dataframe(
333
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
334
            FROM demand.egon_peta_heat
335
            WHERE scenario= 'eGon2035'
336
            GROUP BY (scenario);
337
        """,
338
        warning=False,
339
    )["demand_mw_peta_heat"].values[0]
340
341
    e_demand = (
342
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
343
        * 100
344
    )
345
346
    print(f"heat demand: {e_demand} %")
347
348
    # Sanity checks for heat supply
349
350
    print(
351
        "For German heat supplies the following deviations between the inputs and outputs can be observed:"
352
    )
353
354
    # Comparison for central heat pumps
355
    heat_pump_input = db.select_dataframe(
356
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
357
            FROM supply.egon_scenario_capacities
358
            WHERE carrier= 'urban_central_heat_pump'
359
            AND scenario_name IN ('eGon2035')
360
            GROUP BY (carrier);
361
        """,
362
        warning=False,
363
    )["urban_central_heat_pump_mw"].values[0]
364
365
    heat_pump_output = db.select_dataframe(
366
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
367
            FROM grid.egon_etrago_link
368
            WHERE carrier= 'central_heat_pump'
369
            AND scn_name IN ('eGon2035')
370
            GROUP BY (carrier);
371
    """,
372
        warning=False,
373
    )["central_heat_pump_mw"].values[0]
374
375
    e_heat_pump = (
376
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
377
    )
378
379
    print(f"'central_heat_pump': {e_heat_pump} % ")
380
381
    # Comparison for residential heat pumps
382
383
    input_residential_heat_pump = db.select_dataframe(
384
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
385
            FROM supply.egon_scenario_capacities
386
            WHERE carrier= 'residential_rural_heat_pump'
387
            AND scenario_name IN ('eGon2035')
388
            GROUP BY (carrier);
389
        """,
390
        warning=False,
391
    )["residential_heat_pump_mw"].values[0]
392
393
    output_residential_heat_pump = db.select_dataframe(
394
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
395
            FROM grid.egon_etrago_link
396
            WHERE carrier= 'rural_heat_pump'
397
            AND scn_name IN ('eGon2035')
398
            GROUP BY (carrier);
399
    """,
400
        warning=False,
401
    )["rural_heat_pump_mw"].values[0]
402
403
    e_residential_heat_pump = (
404
        round(
405
            (output_residential_heat_pump - input_residential_heat_pump)
406
            / input_residential_heat_pump,
407
            2,
408
        )
409
        * 100
410
    )
411
    print(f"'residential heat pumps': {e_residential_heat_pump} %")
412
413
    # Comparison for resistive heater
414
    resistive_heater_input = db.select_dataframe(
415
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_resistive_heater_MW
416
            FROM supply.egon_scenario_capacities
417
            WHERE carrier= 'urban_central_resistive_heater'
418
            AND scenario_name IN ('eGon2035')
419
            GROUP BY (carrier);
420
        """,
421
        warning=False,
422
    )["urban_central_resistive_heater_mw"].values[0]
423
424
    resistive_heater_output = db.select_dataframe(
425
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
426
            FROM grid.egon_etrago_link
427
            WHERE carrier= 'central_resistive_heater'
428
            AND scn_name IN ('eGon2035')
429
            GROUP BY (carrier);
430
        """,
431
        warning=False,
432
    )["central_resistive_heater_mw"].values[0]
433
434
    e_resistive_heater = (
435
        round(
436
            (resistive_heater_output - resistive_heater_input)
437
            / resistive_heater_input,
438
            2,
439
        )
440
        * 100
441
    )
442
443
    print(f"'resistive heater': {e_resistive_heater} %")
444
445
    # Comparison for solar thermal collectors
446
447
    input_solar_thermal = db.select_dataframe(
448
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
449
            FROM supply.egon_scenario_capacities
450
            WHERE carrier= 'urban_central_solar_thermal_collector'
451
            AND scenario_name IN ('eGon2035')
452
            GROUP BY (carrier);
453
        """,
454
        warning=False,
455
    )["solar_thermal_collector_mw"].values[0]
456
457
    output_solar_thermal = db.select_dataframe(
458
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
459
            FROM grid.egon_etrago_generator
460
            WHERE carrier= 'solar_thermal_collector'
461
            AND scn_name IN ('eGon2035')
462
            GROUP BY (carrier);
463
        """,
464
        warning=False,
465
    )["solar_thermal_collector_mw"].values[0]
466
467
    e_solar_thermal = (
468
        round(
469
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
470
            2,
471
        )
472
        * 100
473
    )
474
    print(f"'solar thermal collector': {e_solar_thermal} %")
475
476
    # Comparison for geothermal
477
478
    input_geo_thermal = db.select_dataframe(
479
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_geo_thermal_MW
480
            FROM supply.egon_scenario_capacities
481
            WHERE carrier= 'urban_central_geo_thermal'
482
            AND scenario_name IN ('eGon2035')
483
            GROUP BY (carrier);
484
        """,
485
        warning=False,
486
    )["urban_central_geo_thermal_mw"].values[0]
487
488
    output_geo_thermal = db.select_dataframe(
489
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
490
            FROM grid.egon_etrago_generator
491
            WHERE carrier= 'geo_thermal'
492
            AND scn_name IN ('eGon2035')
493
            GROUP BY (carrier);
494
    """,
495
        warning=False,
496
    )["geo_thermal_mw"].values[0]
497
498
    e_geo_thermal = (
499
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
500
        * 100
501
    )
502
    print(f"'geothermal': {e_geo_thermal} %")
503
504
505
def sanitycheck_emobility_mit():
506
    """Execute sanity checks for eMobility: motorized individual travel
507
508
    Checks data integrity for eGon2035, eGon2035_lowflex and eGon100RE scenario
509
    using assertions:
510
      1. Allocated EV numbers and EVs allocated to grid districts
511
      2. Trip data (original inout data from simBEV)
512
      3. Model data in eTraGo PF tables (grid.egon_etrago_*)
513
514
    Parameters
515
    ----------
516
    None
517
518
    Returns
519
    -------
520
    None
521
    """
522
523
    def check_ev_allocation():
524
        # Get target number for scenario
525
        ev_count_target = scenario_variation_parameters["ev_count"]
526
        print(f"  Target count: {str(ev_count_target)}")
527
528
        # Get allocated numbers
529
        ev_counts_dict = {}
530
        with db.session_scope() as session:
531
            for table, level in zip(
532
                [
533
                    EgonEvCountMvGridDistrict,
534
                    EgonEvCountMunicipality,
535
                    EgonEvCountRegistrationDistrict,
536
                ],
537
                ["Grid District", "Municipality", "Registration District"],
538
            ):
539
                query = session.query(
540
                    func.sum(
541
                        table.bev_mini
542
                        + table.bev_medium
543
                        + table.bev_luxury
544
                        + table.phev_mini
545
                        + table.phev_medium
546
                        + table.phev_luxury
547
                    ).label("ev_count")
548
                ).filter(
549
                    table.scenario == scenario_name,
550
                    table.scenario_variation == scenario_var_name,
551
                )
552
553
                ev_counts = pd.read_sql(
554
                    query.statement, query.session.bind, index_col=None
555
                )
556
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
557
                print(
558
                    f"    Count table: Total count for level {level} "
559
                    f"(table: {table.__table__}): "
560
                    f"{str(ev_counts_dict[level])}"
561
                )
562
563
        # Compare with scenario target (only if not in testmode)
564
        if TESTMODE_OFF:
565
            for level, count in ev_counts_dict.items():
566
                np.testing.assert_allclose(
567
                    count,
568
                    ev_count_target,
569
                    rtol=0.0001,
570
                    err_msg=f"EV numbers in {level} seems to be flawed.",
571
                )
572
        else:
573
            print("    Testmode is on, skipping sanity check...")
574
575
        # Get allocated EVs in grid districts
576
        with db.session_scope() as session:
577
            query = session.query(
578
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
579
                    "ev_count"
580
                ),
581
            ).filter(
582
                EgonEvMvGridDistrict.scenario == scenario_name,
583
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
584
            )
585
        ev_count_alloc = (
586
            pd.read_sql(query.statement, query.session.bind, index_col=None)
587
            .iloc[0]
588
            .ev_count
589
        )
590
        print(
591
            f"    EVs allocated to Grid Districts "
592
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
593
            f"{str(ev_count_alloc)}"
594
        )
595
596
        # Compare with scenario target (only if not in testmode)
597
        if TESTMODE_OFF:
598
            np.testing.assert_allclose(
599
                ev_count_alloc,
600
                ev_count_target,
601
                rtol=0.0001,
602
                err_msg=(
603
                    "EV numbers allocated to Grid Districts seems to be flawed."
604
                ),
605
            )
606
        else:
607
            print("    Testmode is on, skipping sanity check...")
608
609
        return ev_count_alloc
610
611
    def check_trip_data():
612
        # Check if trips start at timestep 0 and have a max. of 35040 steps
613
        # (8760h in 15min steps)
614
        print("  Checking timeranges...")
615
        with db.session_scope() as session:
616
            query = session.query(
617
                func.count(EgonEvTrip.event_id).label("cnt")
618
            ).filter(
619
                or_(
620
                    and_(
621
                        EgonEvTrip.park_start > 0,
622
                        EgonEvTrip.simbev_event_id == 0,
623
                    ),
624
                    EgonEvTrip.park_end
625
                    > (60 / int(meta_run_config.stepsize)) * 8760,
626
                ),
627
                EgonEvTrip.scenario == scenario_name,
628
            )
629
        invalid_trips = pd.read_sql(
630
            query.statement, query.session.bind, index_col=None
631
        )
632
        np.testing.assert_equal(
633
            invalid_trips.iloc[0].cnt,
634
            0,
635
            err_msg=(
636
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
637
                f"{EgonEvTrip.__table__} have invalid timesteps."
638
            ),
639
        )
640
641
        # Check if charging demand can be covered by available charging energy
642
        # while parking
643
        print("  Compare charging demand with available power...")
644
        with db.session_scope() as session:
645
            query = session.query(
646
                func.count(EgonEvTrip.event_id).label("cnt")
647
            ).filter(
648
                func.round(
649
                    cast(
650
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
651
                        * EgonEvTrip.charging_capacity_nominal
652
                        * (int(meta_run_config.stepsize) / 60),
653
                        Numeric,
654
                    ),
655
                    3,
656
                )
657
                < cast(EgonEvTrip.charging_demand, Numeric),
658
                EgonEvTrip.scenario == scenario_name,
659
            )
660
        invalid_trips = pd.read_sql(
661
            query.statement, query.session.bind, index_col=None
662
        )
663
        np.testing.assert_equal(
664
            invalid_trips.iloc[0].cnt,
665
            0,
666
            err_msg=(
667
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
668
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
669
                f"covered by available charging power."
670
            ),
671
        )
672
673
    def check_model_data():
674
        # Check if model components were fully created
675
        print("  Check if all model components were created...")
676
        # Get MVGDs which got EV allocated
677
        with db.session_scope() as session:
678
            query = (
679
                session.query(
680
                    EgonEvMvGridDistrict.bus_id,
681
                )
682
                .filter(
683
                    EgonEvMvGridDistrict.scenario == scenario_name,
684
                    EgonEvMvGridDistrict.scenario_variation
685
                    == scenario_var_name,
686
                )
687
                .group_by(EgonEvMvGridDistrict.bus_id)
688
            )
689
        mvgds_with_ev = (
690
            pd.read_sql(query.statement, query.session.bind, index_col=None)
691
            .bus_id.sort_values()
692
            .to_list()
693
        )
694
695
        # Load model components
696
        with db.session_scope() as session:
697
            query = (
698
                session.query(
699
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
700
                    EgonPfHvLoad.bus.label("emob_bus_id"),
701
                    EgonPfHvLoad.load_id.label("load_id"),
702
                    EgonPfHvStore.store_id.label("store_id"),
703
                )
704
                .select_from(EgonPfHvLoad, EgonPfHvStore)
705
                .join(
706
                    EgonPfHvLoadTimeseries,
707
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
708
                )
709
                .join(
710
                    EgonPfHvStoreTimeseries,
711
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
712
                )
713
                .filter(
714
                    EgonPfHvLoad.carrier == "land transport EV",
715
                    EgonPfHvLoad.scn_name == scenario_name,
716
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
717
                    EgonPfHvStore.carrier == "battery storage",
718
                    EgonPfHvStore.scn_name == scenario_name,
719
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
720
                    EgonPfHvLink.scn_name == scenario_name,
721
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
722
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
723
                )
724
            )
725
        model_components = pd.read_sql(
726
            query.statement, query.session.bind, index_col=None
727
        )
728
729
        # Check number of buses with model components connected
730
        mvgd_buses_with_ev = model_components.loc[
731
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
732
        ]
733
        np.testing.assert_equal(
734
            len(mvgds_with_ev),
735
            len(mvgd_buses_with_ev),
736
            err_msg=(
737
                f"Number of Grid Districts with connected model components "
738
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
739
                f"differ from number of Grid Districts that got EVs "
740
                f"allocated ({len(mvgds_with_ev)} in table "
741
                f"{EgonEvMvGridDistrict.__table__})."
742
            ),
743
        )
744
745
        # Check if all required components exist (if no id is NaN)
746
        np.testing.assert_equal(
747
            model_components.drop_duplicates().isna().any().any(),
748
            False,
749
            err_msg=(
750
                f"Some components are missing (see True values): "
751
                f"{model_components.drop_duplicates().isna().any()}"
752
            ),
753
        )
754
755
        # Get all model timeseries
756
        print("  Loading model timeseries...")
757
        # Get all model timeseries
758
        model_ts_dict = {
759
            "Load": {
760
                "carrier": "land transport EV",
761
                "table": EgonPfHvLoad,
762
                "table_ts": EgonPfHvLoadTimeseries,
763
                "column_id": "load_id",
764
                "columns_ts": ["p_set"],
765
                "ts": None,
766
            },
767
            "Link": {
768
                "carrier": "BEV charger",
769
                "table": EgonPfHvLink,
770
                "table_ts": EgonPfHvLinkTimeseries,
771
                "column_id": "link_id",
772
                "columns_ts": ["p_max_pu"],
773
                "ts": None,
774
            },
775
            "Store": {
776
                "carrier": "battery storage",
777
                "table": EgonPfHvStore,
778
                "table_ts": EgonPfHvStoreTimeseries,
779
                "column_id": "store_id",
780
                "columns_ts": ["e_min_pu", "e_max_pu"],
781
                "ts": None,
782
            },
783
        }
784
785
        with db.session_scope() as session:
786
            for node, attrs in model_ts_dict.items():
787
                print(f"    Loading {node} timeseries...")
788
                subquery = (
789
                    session.query(
790
                        getattr(attrs["table"], attrs["column_id"])
791
                    )
792
                    .filter(attrs["table"].carrier == attrs["carrier"])
793
                    .filter(attrs["table"].scn_name == scenario_name)
794
                    .subquery()
795
                )
796
797
                cols = [
798
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
799
                ]
800
                query = session.query(
801
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
802
                ).filter(
803
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
804
                        subquery
805
                    ),
806
                    attrs["table_ts"].scn_name == scenario_name,
807
                )
808
                attrs["ts"] = pd.read_sql(
809
                    query.statement,
810
                    query.session.bind,
811
                    index_col=attrs["column_id"],
812
                )
813
814
        # Check if all timeseries have 8760 steps
815
        print("    Checking timeranges...")
816
        for node, attrs in model_ts_dict.items():
817
            for col in attrs["columns_ts"]:
818
                ts = attrs["ts"]
819
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
820
                    col
821
                ].apply(len)
822
                np.testing.assert_equal(
823
                    len(invalid_ts),
824
                    0,
825
                    err_msg=(
826
                        f"{str(len(invalid_ts))} rows in timeseries do not "
827
                        f"have 8760 timesteps. Table: "
828
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
829
                        f"{str(list(invalid_ts.index))}"
830
                    ),
831
                )
832
833
        # Compare total energy demand in model with some approximate values
834
        # (per EV: 14,000 km/a, 0.17 kWh/km)
835
        print("  Checking energy demand in model...")
836
        total_energy_model = (
837
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
838
            / 1e6
839
        )
840
        print(f"    Total energy amount in model: {total_energy_model} TWh")
841
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
842
        print(
843
            f"    Total approximated energy amount in scenario: "
844
            f"{total_energy_scenario_approx} TWh"
845
        )
846
        np.testing.assert_allclose(
847
            total_energy_model,
848
            total_energy_scenario_approx,
849
            rtol=0.1,
850
            err_msg=(
851
                "The total energy amount in the model deviates heavily "
852
                "from the approximated value for current scenario."
853
            ),
854
        )
855
856
        # Compare total storage capacity
857
        print("  Checking storage capacity...")
858
        # Load storage capacities from model
859
        with db.session_scope() as session:
860
            query = session.query(
861
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
862
            ).filter(
863
                EgonPfHvStore.scn_name == scenario_name,
864
                EgonPfHvStore.carrier == "battery storage",
865
            )
866
        storage_capacity_model = (
867
            pd.read_sql(
868
                query.statement, query.session.bind, index_col=None
869
            ).e_nom.sum()
870
            / 1e3
871
        )
872
        print(
873
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
874
            f"{round(storage_capacity_model, 1)} GWh"
875
        )
876
877
        # Load occurences of each EV
878
        with db.session_scope() as session:
879
            query = (
880
                session.query(
881
                    EgonEvMvGridDistrict.bus_id,
882
                    EgonEvPool.type,
883
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
884
                        "count"
885
                    ),
886
                )
887
                .join(
888
                    EgonEvPool,
889
                    EgonEvPool.ev_id
890
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
891
                )
892
                .filter(
893
                    EgonEvMvGridDistrict.scenario == scenario_name,
894
                    EgonEvMvGridDistrict.scenario_variation
895
                    == scenario_var_name,
896
                    EgonEvPool.scenario == scenario_name,
897
                )
898
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
899
            )
900
        count_per_ev_all = pd.read_sql(
901
            query.statement, query.session.bind, index_col="bus_id"
902
        )
903
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
904
            meta_tech_data.battery_capacity
905
        )
906
        count_per_ev_all["bat_cap_total_MWh"] = (
907
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
908
        )
909
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
910
            1e3
911
        ).sum()
912
        print(
913
            f"    Total storage capacity (simBEV): "
914
            f"{round(storage_capacity_simbev, 1)} GWh"
915
        )
916
917
        np.testing.assert_allclose(
918
            storage_capacity_model,
919
            storage_capacity_simbev,
920
            rtol=0.01,
921
            err_msg=(
922
                "The total storage capacity in the model deviates heavily "
923
                "from the input data provided by simBEV for current scenario."
924
            ),
925
        )
926
927
        # Check SoC storage constraint: e_min_pu < e_max_pu for all timesteps
928
        print("  Validating SoC constraints...")
929
        stores_with_invalid_soc = []
930
        for idx, row in model_ts_dict["Store"]["ts"].iterrows():
931
            ts = row[["e_min_pu", "e_max_pu"]]
932
            x = np.array(ts.e_min_pu) > np.array(ts.e_max_pu)
933
            if x.any():
934
                stores_with_invalid_soc.append(idx)
935
936
        np.testing.assert_equal(
937
            len(stores_with_invalid_soc),
938
            0,
939
            err_msg=(
940
                f"The store constraint e_min_pu < e_max_pu does not apply "
941
                f"for some storages in {EgonPfHvStoreTimeseries.__table__}. "
942
                f"Invalid store_ids: {stores_with_invalid_soc}"
943
            ),
944
        )
945
946
    def check_model_data_lowflex_eGon2035():
947
        # TODO: Add eGon100RE_lowflex
948
        print("")
949
        print("SCENARIO: eGon2035_lowflex")
950
951
        # Compare driving load and charging load
952
        print("  Loading eGon2035 model timeseries: driving load...")
953
        with db.session_scope() as session:
954
            query = (
955
                session.query(
956
                    EgonPfHvLoad.load_id,
957
                    EgonPfHvLoadTimeseries.p_set,
958
                )
959
                .join(
960
                    EgonPfHvLoadTimeseries,
961
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
962
                )
963
                .filter(
964
                    EgonPfHvLoad.carrier == "land transport EV",
965
                    EgonPfHvLoad.scn_name == "eGon2035",
966
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035",
967
                )
968
            )
969
        model_driving_load = pd.read_sql(
970
            query.statement, query.session.bind, index_col=None
971
        )
972
        driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0)
973
974
        print(
975
            "  Loading eGon2035_lowflex model timeseries: dumb charging "
976
            "load..."
977
        )
978
        with db.session_scope() as session:
979
            query = (
980
                session.query(
981
                    EgonPfHvLoad.load_id,
982
                    EgonPfHvLoadTimeseries.p_set,
983
                )
984
                .join(
985
                    EgonPfHvLoadTimeseries,
986
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
987
                )
988
                .filter(
989
                    EgonPfHvLoad.carrier == "land transport EV",
990
                    EgonPfHvLoad.scn_name == "eGon2035_lowflex",
991
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex",
992
                )
993
            )
994
        model_charging_load_lowflex = pd.read_sql(
995
            query.statement, query.session.bind, index_col=None
996
        )
997
        charging_load = np.array(
998
            model_charging_load_lowflex.p_set.to_list()
999
        ).sum(axis=0)
1000
1001
        # Ratio of driving and charging load should be 0.9 due to charging
1002
        # efficiency
1003
        print("  Compare cumulative loads...")
1004
        print(f"    Driving load (eGon2035): {driving_load.sum() / 1e6} TWh")
1005
        print(
1006
            f"    Dumb charging load (eGon2035_lowflex): "
1007
            f"{charging_load.sum() / 1e6} TWh"
1008
        )
1009
        driving_load_theoretical = (
1010
            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 1028 is not entered. Are you sure this can never be the case?
Loading history...
1011
        )
1012
        np.testing.assert_allclose(
1013
            driving_load.sum(),
1014
            driving_load_theoretical,
1015
            rtol=0.01,
1016
            err_msg=(
1017
                f"The driving load (eGon2035) deviates by more than 1% "
1018
                f"from the theoretical driving load calculated from charging "
1019
                f"load (eGon2035_lowflex) with an efficiency of "
1020
                f"{float(meta_run_config.eta_cp)}."
1021
            ),
1022
        )
1023
1024
    print("=====================================================")
1025
    print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===")
1026
    print("=====================================================")
1027
1028
    for scenario_name in ["eGon2035", "eGon100RE"]:
1029
        scenario_var_name = DATASET_CFG["scenario"]["variation"][scenario_name]
1030
1031
        print("")
1032
        print(f"SCENARIO: {scenario_name}, VARIATION: {scenario_var_name}")
1033
1034
        # Load scenario params for scenario and scenario variation
1035
        scenario_variation_parameters = get_sector_parameters(
1036
            "mobility", scenario=scenario_name
1037
        )["motorized_individual_travel"][scenario_var_name]
1038
1039
        # Load simBEV run config and tech data
1040
        meta_run_config = read_simbev_metadata_file(
1041
            scenario_name, "config"
1042
        ).loc["basic"]
1043
        meta_tech_data = read_simbev_metadata_file(scenario_name, "tech_data")
1044
1045
        print("")
1046
        print("Checking EV counts...")
1047
        ev_count_alloc = check_ev_allocation()
1048
1049
        print("")
1050
        print("Checking trip data...")
1051
        check_trip_data()
1052
1053
        print("")
1054
        print("Checking model data...")
1055
        check_model_data()
1056
1057
    print("")
1058
    check_model_data_lowflex_eGon2035()
1059
1060
    print("=====================================================")
1061