Passed
Push — dev ( ea1eb9...5107fe )
by
unknown
05:55 queued 04:17
created

data.datasets.sanity_checks   C

Complexity

Total Complexity 56

Size/Duplication

Total Lines 1407
Duplicated Lines 6.47 %

Importance

Changes 0
Metric Value
wmc 56
eloc 735
dl 91
loc 1407
rs 5.385
c 0
b 0
f 0

9 Functions

Rating   Name   Duplication   Size   Complexity  
F etrago_eGon2035_electricity() 70 242 16
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
C sanitycheck_pv_rooftop_buildings() 21 123 6
F sanitycheck_emobility_mit() 0 555 24
A sanitycheck_home_batteries() 0 43 2

1 Method

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