Passed
Pull Request — dev (#1052)
by
unknown
02:51 queued 01:09
created

data.datasets.sanity_checks   F

Complexity

Total Complexity 59

Size/Duplication

Total Lines 1542
Duplicated Lines 5.9 %

Importance

Changes 0
Metric Value
wmc 59
eloc 807
dl 91
loc 1542
rs 3.873
c 0
b 0
f 0

10 Functions

Rating   Name   Duplication   Size   Complexity  
C sanitycheck_pv_rooftop_buildings() 21 123 6
F etrago_eGon2035_electricity() 70 242 16
F sanitycheck_emobility_mit() 0 555 24
A sanitycheck_home_batteries() 0 43 2
A cts_electricity_demand_share() 0 25 2
A residential_electricity_annual_sum() 0 37 1
B sanitycheck_dsm() 0 132 3
A cts_heat_demand_share() 0 25 2
B etrago_eGon2035_heat() 0 222 1
A residential_electricity_hh_refinement() 0 37 1

1 Method

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