Passed
Pull Request — dev (#889)
by
unknown
01:56
created

sanitycheck_emobility_mit()   F

Complexity

Conditions 22

Size

Total Lines 549
Code Lines 355

Duplication

Lines 0
Ratio 0 %

Importance

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