Passed
Pull Request — dev (#1129)
by
unknown
01:48
created

data.datasets.sanity_checks   F

Complexity

Total Complexity 99

Size/Duplication

Total Lines 2624
Duplicated Lines 3.47 %

Importance

Changes 0
Metric Value
wmc 99
eloc 1208
dl 91
loc 2624
rs 0.8
c 0
b 0
f 0

20 Functions

Rating   Name   Duplication   Size   Complexity  
C sanitycheck_pv_rooftop_buildings() 21 123 6
F etrago_eGon2035_electricity() 70 242 16
A sanity_check_H2_saltcavern_stores() 0 49 1
B sanity_check_gas_buses() 0 95 4
D sanity_check_gas_one_port() 0 189 12
A sanity_check_gas_links() 0 53 3
F sanitycheck_emobility_mit() 0 555 24
A sanity_check_CH4_stores() 0 56 3
A cts_electricity_demand_share() 0 25 2
A sanitycheck_home_batteries() 0 43 2
A sanity_check_CH4_grid() 0 74 3
B sanitycheck_dsm() 0 135 3
A residential_electricity_annual_sum() 0 37 1
B etrago_eGon2035_gas_abroad() 0 230 4
A cts_heat_demand_share() 0 25 2
B sanity_check_gas_generators_DE() 0 90 4
B etrago_eGon2035_heat() 0 222 1
A residential_electricity_hh_refinement() 0 37 1
B etrago_eGon2035_gas_DE() 0 105 3
A etrago_eGon100RE_gas_DE() 0 89 3

1 Method

Rating   Name   Duplication   Size   Complexity  
A SanityChecks.__init__() 0 19 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
import ast
11
12
from sqlalchemy import Numeric
13
from sqlalchemy.sql import and_, cast, func, or_
14
import matplotlib.pyplot as plt
15
import numpy as np
16
import pandas as pd
17
import seaborn as sns
18
19
from egon.data import config, db, logger
20
from egon.data.datasets import Dataset
21
from egon.data.datasets.electricity_demand_timeseries.cts_buildings import (
22
    EgonCtsElectricityDemandBuildingShare,
23
    EgonCtsHeatDemandBuildingShare,
24
)
25
from egon.data.datasets.emobility.motorized_individual_travel.db_classes import (  # noqa: E501
26
    EgonEvCountMunicipality,
27
    EgonEvCountMvGridDistrict,
28
    EgonEvCountRegistrationDistrict,
29
    EgonEvMvGridDistrict,
30
    EgonEvPool,
31
    EgonEvTrip,
32
)
33
from egon.data.datasets.emobility.motorized_individual_travel.helpers import (
34
    DATASET_CFG,
35
    read_simbev_metadata_file,
36
)
37
from egon.data.datasets.etrago_setup import (
38
    EgonPfHvLink,
39
    EgonPfHvLinkTimeseries,
40
    EgonPfHvLoad,
41
    EgonPfHvLoadTimeseries,
42
    EgonPfHvStore,
43
    EgonPfHvStoreTimeseries,
44
)
45
from egon.data.datasets.gas_grid import (
46
    define_gas_buses_abroad,
47
    define_gas_nodes_list,
48
    define_gas_pipeline_list,
49
)
50
from egon.data.datasets.gas_neighbours.eGon2035 import (
51
    calc_capacities,
52
    calc_ch4_storage_capacities,
53
    calc_global_ch4_demand,
54
    calc_global_power_to_h2_demand,
55
    calculate_ch4_grid_capacities,
56
    import_ch4_demandTS,
57
)
58
from egon.data.datasets.hydrogen_etrago.storage import (
59
    calculate_and_map_saltcavern_storage_potential,
60
)
61
from egon.data.datasets.industrial_gas_demand import (
62
    calculate_total_demand_100RE,
63
)
64
from egon.data.datasets.power_plants.pv_rooftop_buildings import (
65
    PV_CAP_PER_SQ_M,
66
    ROOF_FACTOR,
67
    SCENARIOS,
68
    load_building_data,
69
    scenario_data,
70
)
71
from egon.data.datasets.pypsaeursec import read_network
72
from egon.data.datasets.scenario_parameters import get_sector_parameters
73
from egon.data.datasets.storages.home_batteries import get_cbat_pbat_ratio
74
import egon.data
75
76
TESTMODE_OFF = (
77
    config.settings()["egon-data"]["--dataset-boundary"] == "Everything"
78
)
79
80
81
class SanityChecks(Dataset):
82
    #:
83
    name: str = "SanityChecks"
84
    #:
85
    version: str = "0.0.9"
86
87
    def __init__(self, dependencies):
88
        super().__init__(
89
            name=self.name,
90
            version=self.version,
91
            dependencies=dependencies,
92
            tasks={
93
                etrago_eGon2035_electricity,
94
                etrago_eGon2035_heat,
95
                residential_electricity_annual_sum,
96
                residential_electricity_hh_refinement,
97
                cts_electricity_demand_share,
98
                cts_heat_demand_share,
99
                sanitycheck_emobility_mit,
100
                sanitycheck_pv_rooftop_buildings,
101
                sanitycheck_home_batteries,
102
                etrago_eGon2035_gas_DE,
103
                etrago_eGon2035_gas_abroad,
104
                etrago_eGon100RE_gas_DE,
105
                sanitycheck_dsm,
106
            },
107
        )
108
109
110
def etrago_eGon2035_electricity():
111
    """Execute basic sanity checks.
112
113
    Returns print statements as sanity checks for the electricity sector in
114
    the eGon2035 scenario.
115
116
    Parameters
117
    ----------
118
    None
119
120
    Returns
121
    -------
122
    None
123
    """
124
125
    scn = "eGon2035"
126
127
    # Section to check generator capacities
128
    logger.info(f"Sanity checks for scenario {scn}")
129
    logger.info(
130
        "For German electricity generators the following deviations between "
131
        "the inputs and outputs can be observed:"
132
    )
133
134
    carriers_electricity = [
135
        "others",
136
        "reservoir",
137
        "run_of_river",
138
        "oil",
139
        "wind_onshore",
140
        "wind_offshore",
141
        "solar",
142
        "solar_rooftop",
143
        "biomass",
144
    ]
145
146
    for carrier in carriers_electricity:
147
148
        if carrier == "biomass":
149
            sum_output = db.select_dataframe(
150
                """SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
151
                    FROM grid.egon_etrago_generator
152
                    WHERE bus IN (
153
                        SELECT bus_id FROM grid.egon_etrago_bus
154
                        WHERE scn_name = 'eGon2035'
155
                        AND country = 'DE')
156
                    AND carrier IN ('biomass', 'industrial_biomass_CHP',
157
                    'central_biomass_CHP')
158
                    GROUP BY (scn_name);
159
                """,
160
                warning=False,
161
            )
162
163
        else:
164
            sum_output = db.select_dataframe(
165
                f"""SELECT scn_name,
166
                 SUM(p_nom::numeric) as output_capacity_mw
167
                         FROM grid.egon_etrago_generator
168
                         WHERE scn_name = '{scn}'
169
                         AND carrier IN ('{carrier}')
170
                         AND bus IN
171
                             (SELECT bus_id
172
                               FROM grid.egon_etrago_bus
173
                               WHERE scn_name = 'eGon2035'
174
                               AND country = 'DE')
175
                         GROUP BY (scn_name);
176
                    """,
177
                warning=False,
178
            )
179
180
        sum_input = db.select_dataframe(
181
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
182
                     FROM supply.egon_scenario_capacities
183
                     WHERE carrier= '{carrier}'
184
                     AND scenario_name ='{scn}'
185
                     GROUP BY (carrier);
186
                """,
187
            warning=False,
188
        )
189
190 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
191
            sum_output.output_capacity_mw.sum() == 0
192
            and sum_input.input_capacity_mw.sum() == 0
193
        ):
194
            logger.info(
195
                f"No capacity for carrier '{carrier}' needed to be"
196
                f" distributed. Everything is fine"
197
            )
198
199
        elif (
200
            sum_input.input_capacity_mw.sum() > 0
201
            and sum_output.output_capacity_mw.sum() == 0
202
        ):
203
            logger.info(
204
                f"Error: Capacity for carrier '{carrier}' was not distributed "
205
                f"at all!"
206
            )
207
208
        elif (
209
            sum_output.output_capacity_mw.sum() > 0
210
            and sum_input.input_capacity_mw.sum() == 0
211
        ):
212
            logger.info(
213
                f"Error: Eventhough no input capacity was provided for carrier"
214
                f"'{carrier}' a capacity got distributed!"
215
            )
216
217
        else:
218
            sum_input["error"] = (
219
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
220
                / sum_input.input_capacity_mw
221
            ) * 100
222
            g = sum_input["error"].values[0]
223
224
            logger.info(f"{carrier}: " + str(round(g, 2)) + " %")
225
226
    # Section to check storage units
227
228
    logger.info(f"Sanity checks for scenario {scn}")
229
    logger.info(
230
        "For German electrical storage units the following deviations between"
231
        "the inputs and outputs can be observed:"
232
    )
233
234
    carriers_electricity = ["pumped_hydro"]
235
236
    for carrier in carriers_electricity:
237
238
        sum_output = db.select_dataframe(
239
            f"""SELECT scn_name, SUM(p_nom::numeric) as output_capacity_mw
240
                         FROM grid.egon_etrago_storage
241
                         WHERE scn_name = '{scn}'
242
                         AND carrier IN ('{carrier}')
243
                         AND bus IN
244
                             (SELECT bus_id
245
                               FROM grid.egon_etrago_bus
246
                               WHERE scn_name = 'eGon2035'
247
                               AND country = 'DE')
248
                         GROUP BY (scn_name);
249
                    """,
250
            warning=False,
251
        )
252
253
        sum_input = db.select_dataframe(
254
            f"""SELECT carrier, SUM(capacity::numeric) as input_capacity_mw
255
                     FROM supply.egon_scenario_capacities
256
                     WHERE carrier= '{carrier}'
257
                     AND scenario_name ='{scn}'
258
                     GROUP BY (carrier);
259
                """,
260
            warning=False,
261
        )
262
263 View Code Duplication
        if (
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
264
            sum_output.output_capacity_mw.sum() == 0
265
            and sum_input.input_capacity_mw.sum() == 0
266
        ):
267
            print(
268
                f"No capacity for carrier '{carrier}' needed to be "
269
                f"distributed. Everything is fine"
270
            )
271
272
        elif (
273
            sum_input.input_capacity_mw.sum() > 0
274
            and sum_output.output_capacity_mw.sum() == 0
275
        ):
276
            print(
277
                f"Error: Capacity for carrier '{carrier}' was not distributed"
278
                f" at all!"
279
            )
280
281
        elif (
282
            sum_output.output_capacity_mw.sum() > 0
283
            and sum_input.input_capacity_mw.sum() == 0
284
        ):
285
            print(
286
                f"Error: Eventhough no input capacity was provided for carrier"
287
                f" '{carrier}' a capacity got distributed!"
288
            )
289
290
        else:
291
            sum_input["error"] = (
292
                (sum_output.output_capacity_mw - sum_input.input_capacity_mw)
293
                / sum_input.input_capacity_mw
294
            ) * 100
295
            g = sum_input["error"].values[0]
296
297
            print(f"{carrier}: " + str(round(g, 2)) + " %")
298
299
    # Section to check loads
300
301
    print(
302
        "For German electricity loads the following deviations between the"
303
        " input and output can be observed:"
304
    )
305
306
    output_demand = db.select_dataframe(
307
        """SELECT a.scn_name, a.carrier,  SUM((SELECT SUM(p)
308
        FROM UNNEST(b.p_set) p))/1000000::numeric as load_twh
309
            FROM grid.egon_etrago_load a
310
            JOIN grid.egon_etrago_load_timeseries b
311
            ON (a.load_id = b.load_id)
312
            JOIN grid.egon_etrago_bus c
313
            ON (a.bus=c.bus_id)
314
            AND b.scn_name = 'eGon2035'
315
            AND a.scn_name = 'eGon2035'
316
            AND a.carrier = 'AC'
317
            AND c.scn_name= 'eGon2035'
318
            AND c.country='DE'
319
            GROUP BY (a.scn_name, a.carrier);
320
321
    """,
322
        warning=False,
323
    )["load_twh"].values[0]
324
325
    input_cts_ind = db.select_dataframe(
326
        """SELECT scenario,
327
         SUM(demand::numeric/1000000) as demand_mw_regio_cts_ind
328
            FROM demand.egon_demandregio_cts_ind
329
            WHERE scenario= 'eGon2035'
330
            AND year IN ('2035')
331
            GROUP BY (scenario);
332
333
        """,
334
        warning=False,
335
    )["demand_mw_regio_cts_ind"].values[0]
336
337
    input_hh = db.select_dataframe(
338
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_regio_hh
339
            FROM demand.egon_demandregio_hh
340
            WHERE scenario= 'eGon2035'
341
            AND year IN ('2035')
342
            GROUP BY (scenario);
343
        """,
344
        warning=False,
345
    )["demand_mw_regio_hh"].values[0]
346
347
    input_demand = input_hh + input_cts_ind
348
349
    e = round((output_demand - input_demand) / input_demand, 2) * 100
350
351
    print(f"electricity demand: {e} %")
352
353
354
def etrago_eGon2035_heat():
355
    """Execute basic sanity checks.
356
357
    Returns print statements as sanity checks for the heat sector in
358
    the eGon2035 scenario.
359
360
    Parameters
361
    ----------
362
    None
363
364
    Returns
365
    -------
366
    None
367
    """
368
369
    # Check input and output values for the carriers "others",
370
    # "reservoir", "run_of_river" and "oil"
371
372
    scn = "eGon2035"
373
374
    # Section to check generator capacities
375
    print(f"Sanity checks for scenario {scn}")
376
    print(
377
        "For German heat demands the following deviations between the inputs"
378
        " and outputs can be observed:"
379
    )
380
381
    # Sanity checks for heat demand
382
383
    output_heat_demand = db.select_dataframe(
384
        """SELECT a.scn_name,
385
          (SUM(
386
          (SELECT SUM(p) FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
387
            FROM grid.egon_etrago_load a
388
            JOIN grid.egon_etrago_load_timeseries b
389
            ON (a.load_id = b.load_id)
390
            JOIN grid.egon_etrago_bus c
391
            ON (a.bus=c.bus_id)
392
            AND b.scn_name = 'eGon2035'
393
            AND a.scn_name = 'eGon2035'
394
            AND c.scn_name= 'eGon2035'
395
            AND c.country='DE'
396
            AND a.carrier IN ('rural_heat', 'central_heat')
397
            GROUP BY (a.scn_name);
398
        """,
399
        warning=False,
400
    )["load_twh"].values[0]
401
402
    input_heat_demand = db.select_dataframe(
403
        """SELECT scenario, SUM(demand::numeric/1000000) as demand_mw_peta_heat
404
            FROM demand.egon_peta_heat
405
            WHERE scenario= 'eGon2035'
406
            GROUP BY (scenario);
407
        """,
408
        warning=False,
409
    )["demand_mw_peta_heat"].values[0]
410
411
    e_demand = (
412
        round((output_heat_demand - input_heat_demand) / input_heat_demand, 2)
413
        * 100
414
    )
415
416
    logger.info(f"heat demand: {e_demand} %")
417
418
    # Sanity checks for heat supply
419
420
    logger.info(
421
        "For German heat supplies the following deviations between the inputs "
422
        "and outputs can be observed:"
423
    )
424
425
    # Comparison for central heat pumps
426
    heat_pump_input = db.select_dataframe(
427
        """SELECT carrier, SUM(capacity::numeric) as Urban_central_heat_pump_mw
428
            FROM supply.egon_scenario_capacities
429
            WHERE carrier= 'urban_central_heat_pump'
430
            AND scenario_name IN ('eGon2035')
431
            GROUP BY (carrier);
432
        """,
433
        warning=False,
434
    )["urban_central_heat_pump_mw"].values[0]
435
436
    heat_pump_output = db.select_dataframe(
437
        """SELECT carrier, SUM(p_nom::numeric) as Central_heat_pump_mw
438
            FROM grid.egon_etrago_link
439
            WHERE carrier= 'central_heat_pump'
440
            AND scn_name IN ('eGon2035')
441
            GROUP BY (carrier);
442
    """,
443
        warning=False,
444
    )["central_heat_pump_mw"].values[0]
445
446
    e_heat_pump = (
447
        round((heat_pump_output - heat_pump_input) / heat_pump_output, 2) * 100
448
    )
449
450
    logger.info(f"'central_heat_pump': {e_heat_pump} % ")
451
452
    # Comparison for residential heat pumps
453
454
    input_residential_heat_pump = db.select_dataframe(
455
        """SELECT carrier, SUM(capacity::numeric) as residential_heat_pump_mw
456
            FROM supply.egon_scenario_capacities
457
            WHERE carrier= 'residential_rural_heat_pump'
458
            AND scenario_name IN ('eGon2035')
459
            GROUP BY (carrier);
460
        """,
461
        warning=False,
462
    )["residential_heat_pump_mw"].values[0]
463
464
    output_residential_heat_pump = db.select_dataframe(
465
        """SELECT carrier, SUM(p_nom::numeric) as rural_heat_pump_mw
466
            FROM grid.egon_etrago_link
467
            WHERE carrier= 'rural_heat_pump'
468
            AND scn_name IN ('eGon2035')
469
            GROUP BY (carrier);
470
    """,
471
        warning=False,
472
    )["rural_heat_pump_mw"].values[0]
473
474
    e_residential_heat_pump = (
475
        round(
476
            (output_residential_heat_pump - input_residential_heat_pump)
477
            / input_residential_heat_pump,
478
            2,
479
        )
480
        * 100
481
    )
482
    logger.info(f"'residential heat pumps': {e_residential_heat_pump} %")
483
484
    # Comparison for resistive heater
485
    resistive_heater_input = db.select_dataframe(
486
        """SELECT carrier,
487
         SUM(capacity::numeric) as Urban_central_resistive_heater_MW
488
            FROM supply.egon_scenario_capacities
489
            WHERE carrier= 'urban_central_resistive_heater'
490
            AND scenario_name IN ('eGon2035')
491
            GROUP BY (carrier);
492
        """,
493
        warning=False,
494
    )["urban_central_resistive_heater_mw"].values[0]
495
496
    resistive_heater_output = db.select_dataframe(
497
        """SELECT carrier, SUM(p_nom::numeric) as central_resistive_heater_MW
498
            FROM grid.egon_etrago_link
499
            WHERE carrier= 'central_resistive_heater'
500
            AND scn_name IN ('eGon2035')
501
            GROUP BY (carrier);
502
        """,
503
        warning=False,
504
    )["central_resistive_heater_mw"].values[0]
505
506
    e_resistive_heater = (
507
        round(
508
            (resistive_heater_output - resistive_heater_input)
509
            / resistive_heater_input,
510
            2,
511
        )
512
        * 100
513
    )
514
515
    logger.info(f"'resistive heater': {e_resistive_heater} %")
516
517
    # Comparison for solar thermal collectors
518
519
    input_solar_thermal = db.select_dataframe(
520
        """SELECT carrier, SUM(capacity::numeric) as solar_thermal_collector_mw
521
            FROM supply.egon_scenario_capacities
522
            WHERE carrier= 'urban_central_solar_thermal_collector'
523
            AND scenario_name IN ('eGon2035')
524
            GROUP BY (carrier);
525
        """,
526
        warning=False,
527
    )["solar_thermal_collector_mw"].values[0]
528
529
    output_solar_thermal = db.select_dataframe(
530
        """SELECT carrier, SUM(p_nom::numeric) as solar_thermal_collector_mw
531
            FROM grid.egon_etrago_generator
532
            WHERE carrier= 'solar_thermal_collector'
533
            AND scn_name IN ('eGon2035')
534
            GROUP BY (carrier);
535
        """,
536
        warning=False,
537
    )["solar_thermal_collector_mw"].values[0]
538
539
    e_solar_thermal = (
540
        round(
541
            (output_solar_thermal - input_solar_thermal) / input_solar_thermal,
542
            2,
543
        )
544
        * 100
545
    )
546
    logger.info(f"'solar thermal collector': {e_solar_thermal} %")
547
548
    # Comparison for geothermal
549
550
    input_geo_thermal = db.select_dataframe(
551
        """SELECT carrier,
552
         SUM(capacity::numeric) as Urban_central_geo_thermal_MW
553
            FROM supply.egon_scenario_capacities
554
            WHERE carrier= 'urban_central_geo_thermal'
555
            AND scenario_name IN ('eGon2035')
556
            GROUP BY (carrier);
557
        """,
558
        warning=False,
559
    )["urban_central_geo_thermal_mw"].values[0]
560
561
    output_geo_thermal = db.select_dataframe(
562
        """SELECT carrier, SUM(p_nom::numeric) as geo_thermal_MW
563
            FROM grid.egon_etrago_generator
564
            WHERE carrier= 'geo_thermal'
565
            AND scn_name IN ('eGon2035')
566
            GROUP BY (carrier);
567
    """,
568
        warning=False,
569
    )["geo_thermal_mw"].values[0]
570
571
    e_geo_thermal = (
572
        round((output_geo_thermal - input_geo_thermal) / input_geo_thermal, 2)
573
        * 100
574
    )
575
    logger.info(f"'geothermal': {e_geo_thermal} %")
576
577
578
def residential_electricity_annual_sum(rtol=1e-5):
579
    """Sanity check for dataset electricity_demand_timeseries :
580
    Demand_Building_Assignment
581
582
    Aggregate the annual demand of all census cells at NUTS3 to compare
583
    with initial scaling parameters from DemandRegio.
584
    """
585
586
    df_nuts3_annual_sum = db.select_dataframe(
587
        sql="""
588
        SELECT dr.nuts3, dr.scenario, dr.demand_regio_sum, profiles.profile_sum
589
        FROM (
590
            SELECT scenario, SUM(demand) AS profile_sum, vg250_nuts3
591
            FROM demand.egon_demandregio_zensus_electricity AS egon,
592
             boundaries.egon_map_zensus_vg250 AS boundaries
593
            Where egon.zensus_population_id = boundaries.zensus_population_id
594
            AND sector = 'residential'
595
            GROUP BY vg250_nuts3, scenario
596
            ) AS profiles
597
        JOIN (
598
            SELECT nuts3, scenario, sum(demand) AS demand_regio_sum
599
            FROM demand.egon_demandregio_hh
600
            GROUP BY year, scenario, nuts3
601
              ) AS dr
602
        ON profiles.vg250_nuts3 = dr.nuts3 and profiles.scenario  = dr.scenario
603
        """
604
    )
605
606
    np.testing.assert_allclose(
607
        actual=df_nuts3_annual_sum["profile_sum"],
608
        desired=df_nuts3_annual_sum["demand_regio_sum"],
609
        rtol=rtol,
610
        verbose=False,
611
    )
612
613
    logger.info(
614
        "Aggregated annual residential electricity demand"
615
        " matches with DemandRegio at NUTS-3."
616
    )
617
618
619
def residential_electricity_hh_refinement(rtol=1e-5):
620
    """Sanity check for dataset electricity_demand_timeseries :
621
    Household Demands
622
623
    Check sum of aggregated household types after refinement method
624
    was applied and compare it to the original census values."""
625
626
    df_refinement = db.select_dataframe(
627
        sql="""
628
        SELECT refined.nuts3, refined.characteristics_code,
629
                refined.sum_refined::int, census.sum_census::int
630
        FROM(
631
            SELECT nuts3, characteristics_code, SUM(hh_10types) as sum_refined
632
            FROM society.egon_destatis_zensus_household_per_ha_refined
633
            GROUP BY nuts3, characteristics_code)
634
            AS refined
635
        JOIN(
636
            SELECT t.nuts3, t.characteristics_code, sum(orig) as sum_census
637
            FROM(
638
                SELECT nuts3, cell_id, characteristics_code,
639
                        sum(DISTINCT(hh_5types))as orig
640
                FROM society.egon_destatis_zensus_household_per_ha_refined
641
                GROUP BY cell_id, characteristics_code, nuts3) AS t
642
            GROUP BY t.nuts3, t.characteristics_code    ) AS census
643
        ON refined.nuts3 = census.nuts3
644
        AND refined.characteristics_code = census.characteristics_code
645
    """
646
    )
647
648
    np.testing.assert_allclose(
649
        actual=df_refinement["sum_refined"],
650
        desired=df_refinement["sum_census"],
651
        rtol=rtol,
652
        verbose=False,
653
    )
654
655
    logger.info("All Aggregated household types match at NUTS-3.")
656
657
658
def cts_electricity_demand_share(rtol=1e-5):
659
    """Sanity check for dataset electricity_demand_timeseries :
660
    CtsBuildings
661
662
    Check sum of aggregated cts electricity demand share which equals to one
663
    for every substation as the substation profile is linearly disaggregated
664
    to all buildings."""
665
666
    with db.session_scope() as session:
667
        cells_query = session.query(EgonCtsElectricityDemandBuildingShare)
668
669
    df_demand_share = pd.read_sql(
670
        cells_query.statement, cells_query.session.bind, index_col=None
671
    )
672
673
    np.testing.assert_allclose(
674
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
675
            "profile_share"
676
        ].sum(),
677
        desired=1,
678
        rtol=rtol,
679
        verbose=False,
680
    )
681
682
    logger.info("The aggregated demand shares equal to one!.")
683
684
685
def cts_heat_demand_share(rtol=1e-5):
686
    """Sanity check for dataset electricity_demand_timeseries
687
    : CtsBuildings
688
689
    Check sum of aggregated cts heat demand share which equals to one
690
    for every substation as the substation profile is linearly disaggregated
691
    to all buildings."""
692
693
    with db.session_scope() as session:
694
        cells_query = session.query(EgonCtsHeatDemandBuildingShare)
695
696
    df_demand_share = pd.read_sql(
697
        cells_query.statement, cells_query.session.bind, index_col=None
698
    )
699
700
    np.testing.assert_allclose(
701
        actual=df_demand_share.groupby(["bus_id", "scenario"])[
702
            "profile_share"
703
        ].sum(),
704
        desired=1,
705
        rtol=rtol,
706
        verbose=False,
707
    )
708
709
    logger.info("The aggregated demand shares equal to one!.")
710
711
712
def sanitycheck_pv_rooftop_buildings():
713
    def egon_power_plants_pv_roof_building():
714
        sql = """
715
        SELECT *
716
        FROM supply.egon_power_plants_pv_roof_building
717
        """
718
719
        return db.select_dataframe(sql, index_col="index")
720
721
    pv_roof_df = egon_power_plants_pv_roof_building()
722
723
    valid_buildings_gdf = load_building_data()
724
725
    valid_buildings_gdf = valid_buildings_gdf.assign(
726
        bus_id=valid_buildings_gdf.bus_id.astype(int),
727
        overlay_id=valid_buildings_gdf.overlay_id.astype(int),
728
        max_cap=valid_buildings_gdf.building_area.multiply(
729
            ROOF_FACTOR * PV_CAP_PER_SQ_M
730
        ),
731
    )
732
733
    merge_df = pv_roof_df.merge(
734
        valid_buildings_gdf[["building_area"]],
735
        how="left",
736
        left_on="building_id",
737
        right_index=True,
738
    )
739
740
    assert (
741
        len(merge_df.loc[merge_df.building_area.isna()]) == 0
742
    ), f"{len(merge_df.loc[merge_df.building_area.isna()])} != 0"
743
744
    scenarios = ["status_quo", "eGon2035"]
745
746
    base_path = Path(egon.data.__path__[0]).resolve()
747
748
    res_dir = base_path / "sanity_checks"
749
750
    res_dir.mkdir(parents=True, exist_ok=True)
751
752
    for scenario in scenarios:
753
        fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 8))
754
755
        scenario_df = merge_df.loc[merge_df.scenario == scenario]
756
757
        logger.info(
758
            scenario + " Capacity:\n" + str(scenario_df.capacity.describe())
759
        )
760
761
        small_gens_df = scenario_df.loc[scenario_df.capacity < 100]
762
763
        sns.histplot(data=small_gens_df, x="capacity", ax=ax1).set_title(
764
            scenario
765
        )
766
767
        sns.scatterplot(
768
            data=small_gens_df, x="capacity", y="building_area", ax=ax2
769
        ).set_title(scenario)
770
771
        plt.tight_layout()
772
773
        plt.savefig(
774
            res_dir / f"{scenario}_pv_rooftop_distribution.png",
775
            bbox_inches="tight",
776
        )
777
778
    for scenario in SCENARIOS:
779
        if scenario == "eGon2035":
780
            assert isclose(
781
                scenario_data(scenario=scenario).capacity.sum(),
782
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
783
                rel_tol=1e-02,
784
            ), (
785
                f"{scenario_data(scenario=scenario).capacity.sum()} != "
786
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
787
            )
788
        elif scenario == "eGon100RE":
789
            sources = config.datasets()["solar_rooftop"]["sources"]
790
791
            target = db.select_dataframe(
792
                f"""
793
                SELECT capacity
794
                FROM {sources['scenario_capacities']['schema']}.
795
                {sources['scenario_capacities']['table']} a
796
                WHERE carrier = 'solar_rooftop'
797
                AND scenario_name = '{scenario}'
798
                """
799
            ).capacity[0]
800
801
            dataset = config.settings()["egon-data"]["--dataset-boundary"]
802
803 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...
804
                sources = config.datasets()["scenario_input"]["sources"]
805
806
                path = Path(
807
                    f"./data_bundle_egon_data/nep2035_version2021/"
808
                    f"{sources['eGon2035']['capacities']}"
809
                ).resolve()
810
811
                total_2035 = (
812
                    pd.read_excel(
813
                        path,
814
                        sheet_name="1.Entwurf_NEP2035_V2021",
815
                        index_col="Unnamed: 0",
816
                    ).at["PV (Aufdach)", "Summe"]
817
                    * 1000
818
                )
819
                sh_2035 = scenario_data(scenario="eGon2035").capacity.sum()
820
821
                share = sh_2035 / total_2035
822
823
                target *= share
824
825
            assert isclose(
826
                target,
827
                merge_df.loc[merge_df.scenario == scenario].capacity.sum(),
828
                rel_tol=1e-02,
829
            ), (
830
                f"{target} != "
831
                f"{merge_df.loc[merge_df.scenario == scenario].capacity.sum()}"
832
            )
833
        else:
834
            raise ValueError(f"Scenario {scenario} is not valid.")
835
836
837
def sanitycheck_emobility_mit():
838
    """Execute sanity checks for eMobility: motorized individual travel
839
840
    Checks data integrity for eGon2035, eGon2035_lowflex and eGon100RE scenario
841
    using assertions:
842
      1. Allocated EV numbers and EVs allocated to grid districts
843
      2. Trip data (original inout data from simBEV)
844
      3. Model data in eTraGo PF tables (grid.egon_etrago_*)
845
846
    Parameters
847
    ----------
848
    None
849
850
    Returns
851
    -------
852
    None
853
    """
854
855
    def check_ev_allocation():
856
        # Get target number for scenario
857
        ev_count_target = scenario_variation_parameters["ev_count"]
858
        print(f"  Target count: {str(ev_count_target)}")
859
860
        # Get allocated numbers
861
        ev_counts_dict = {}
862
        with db.session_scope() as session:
863
            for table, level in zip(
864
                [
865
                    EgonEvCountMvGridDistrict,
866
                    EgonEvCountMunicipality,
867
                    EgonEvCountRegistrationDistrict,
868
                ],
869
                ["Grid District", "Municipality", "Registration District"],
870
            ):
871
                query = session.query(
872
                    func.sum(
873
                        table.bev_mini
874
                        + table.bev_medium
875
                        + table.bev_luxury
876
                        + table.phev_mini
877
                        + table.phev_medium
878
                        + table.phev_luxury
879
                    ).label("ev_count")
880
                ).filter(
881
                    table.scenario == scenario_name,
882
                    table.scenario_variation == scenario_var_name,
883
                )
884
885
                ev_counts = pd.read_sql(
886
                    query.statement, query.session.bind, index_col=None
887
                )
888
                ev_counts_dict[level] = ev_counts.iloc[0].ev_count
889
                print(
890
                    f"    Count table: Total count for level {level} "
891
                    f"(table: {table.__table__}): "
892
                    f"{str(ev_counts_dict[level])}"
893
                )
894
895
        # Compare with scenario target (only if not in testmode)
896
        if TESTMODE_OFF:
897
            for level, count in ev_counts_dict.items():
898
                np.testing.assert_allclose(
899
                    count,
900
                    ev_count_target,
901
                    rtol=0.0001,
902
                    err_msg=f"EV numbers in {level} seems to be flawed.",
903
                )
904
        else:
905
            print("    Testmode is on, skipping sanity check...")
906
907
        # Get allocated EVs in grid districts
908
        with db.session_scope() as session:
909
            query = session.query(
910
                func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
911
                    "ev_count"
912
                ),
913
            ).filter(
914
                EgonEvMvGridDistrict.scenario == scenario_name,
915
                EgonEvMvGridDistrict.scenario_variation == scenario_var_name,
916
            )
917
        ev_count_alloc = (
918
            pd.read_sql(query.statement, query.session.bind, index_col=None)
919
            .iloc[0]
920
            .ev_count
921
        )
922
        print(
923
            f"    EVs allocated to Grid Districts "
924
            f"(table: {EgonEvMvGridDistrict.__table__}) total count: "
925
            f"{str(ev_count_alloc)}"
926
        )
927
928
        # Compare with scenario target (only if not in testmode)
929
        if TESTMODE_OFF:
930
            np.testing.assert_allclose(
931
                ev_count_alloc,
932
                ev_count_target,
933
                rtol=0.0001,
934
                err_msg=(
935
                    "EV numbers allocated to Grid Districts seems to be "
936
                    "flawed."
937
                ),
938
            )
939
        else:
940
            print("    Testmode is on, skipping sanity check...")
941
942
        return ev_count_alloc
943
944
    def check_trip_data():
945
        # Check if trips start at timestep 0 and have a max. of 35040 steps
946
        # (8760h in 15min steps)
947
        print("  Checking timeranges...")
948
        with db.session_scope() as session:
949
            query = session.query(
950
                func.count(EgonEvTrip.event_id).label("cnt")
951
            ).filter(
952
                or_(
953
                    and_(
954
                        EgonEvTrip.park_start > 0,
955
                        EgonEvTrip.simbev_event_id == 0,
956
                    ),
957
                    EgonEvTrip.park_end
958
                    > (60 / int(meta_run_config.stepsize)) * 8760,
959
                ),
960
                EgonEvTrip.scenario == scenario_name,
961
            )
962
        invalid_trips = pd.read_sql(
963
            query.statement, query.session.bind, index_col=None
964
        )
965
        np.testing.assert_equal(
966
            invalid_trips.iloc[0].cnt,
967
            0,
968
            err_msg=(
969
                f"{str(invalid_trips.iloc[0].cnt)} trips in table "
970
                f"{EgonEvTrip.__table__} have invalid timesteps."
971
            ),
972
        )
973
974
        # Check if charging demand can be covered by available charging energy
975
        # while parking
976
        print("  Compare charging demand with available power...")
977
        with db.session_scope() as session:
978
            query = session.query(
979
                func.count(EgonEvTrip.event_id).label("cnt")
980
            ).filter(
981
                func.round(
982
                    cast(
983
                        (EgonEvTrip.park_end - EgonEvTrip.park_start + 1)
984
                        * EgonEvTrip.charging_capacity_nominal
985
                        * (int(meta_run_config.stepsize) / 60),
986
                        Numeric,
987
                    ),
988
                    3,
989
                )
990
                < cast(EgonEvTrip.charging_demand, Numeric),
991
                EgonEvTrip.scenario == scenario_name,
992
            )
993
        invalid_trips = pd.read_sql(
994
            query.statement, query.session.bind, index_col=None
995
        )
996
        np.testing.assert_equal(
997
            invalid_trips.iloc[0].cnt,
998
            0,
999
            err_msg=(
1000
                f"In {str(invalid_trips.iloc[0].cnt)} trips (table: "
1001
                f"{EgonEvTrip.__table__}) the charging demand cannot be "
1002
                f"covered by available charging power."
1003
            ),
1004
        )
1005
1006
    def check_model_data():
1007
        # Check if model components were fully created
1008
        print("  Check if all model components were created...")
1009
        # Get MVGDs which got EV allocated
1010
        with db.session_scope() as session:
1011
            query = (
1012
                session.query(
1013
                    EgonEvMvGridDistrict.bus_id,
1014
                )
1015
                .filter(
1016
                    EgonEvMvGridDistrict.scenario == scenario_name,
1017
                    EgonEvMvGridDistrict.scenario_variation
1018
                    == scenario_var_name,
1019
                )
1020
                .group_by(EgonEvMvGridDistrict.bus_id)
1021
            )
1022
        mvgds_with_ev = (
1023
            pd.read_sql(query.statement, query.session.bind, index_col=None)
1024
            .bus_id.sort_values()
1025
            .to_list()
1026
        )
1027
1028
        # Load model components
1029
        with db.session_scope() as session:
1030
            query = (
1031
                session.query(
1032
                    EgonPfHvLink.bus0.label("mvgd_bus_id"),
1033
                    EgonPfHvLoad.bus.label("emob_bus_id"),
1034
                    EgonPfHvLoad.load_id.label("load_id"),
1035
                    EgonPfHvStore.store_id.label("store_id"),
1036
                )
1037
                .select_from(EgonPfHvLoad, EgonPfHvStore)
1038
                .join(
1039
                    EgonPfHvLoadTimeseries,
1040
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1041
                )
1042
                .join(
1043
                    EgonPfHvStoreTimeseries,
1044
                    EgonPfHvStoreTimeseries.store_id == EgonPfHvStore.store_id,
1045
                )
1046
                .filter(
1047
                    EgonPfHvLoad.carrier == "land_transport_EV",
1048
                    EgonPfHvLoad.scn_name == scenario_name,
1049
                    EgonPfHvLoadTimeseries.scn_name == scenario_name,
1050
                    EgonPfHvStore.carrier == "battery_storage",
1051
                    EgonPfHvStore.scn_name == scenario_name,
1052
                    EgonPfHvStoreTimeseries.scn_name == scenario_name,
1053
                    EgonPfHvLink.scn_name == scenario_name,
1054
                    EgonPfHvLink.bus1 == EgonPfHvLoad.bus,
1055
                    EgonPfHvLink.bus1 == EgonPfHvStore.bus,
1056
                )
1057
            )
1058
        model_components = pd.read_sql(
1059
            query.statement, query.session.bind, index_col=None
1060
        )
1061
1062
        # Check number of buses with model components connected
1063
        mvgd_buses_with_ev = model_components.loc[
1064
            model_components.mvgd_bus_id.isin(mvgds_with_ev)
1065
        ]
1066
        np.testing.assert_equal(
1067
            len(mvgds_with_ev),
1068
            len(mvgd_buses_with_ev),
1069
            err_msg=(
1070
                f"Number of Grid Districts with connected model components "
1071
                f"({str(len(mvgd_buses_with_ev))} in tables egon_etrago_*) "
1072
                f"differ from number of Grid Districts that got EVs "
1073
                f"allocated ({len(mvgds_with_ev)} in table "
1074
                f"{EgonEvMvGridDistrict.__table__})."
1075
            ),
1076
        )
1077
1078
        # Check if all required components exist (if no id is NaN)
1079
        np.testing.assert_equal(
1080
            model_components.drop_duplicates().isna().any().any(),
1081
            False,
1082
            err_msg=(
1083
                f"Some components are missing (see True values): "
1084
                f"{model_components.drop_duplicates().isna().any()}"
1085
            ),
1086
        )
1087
1088
        # Get all model timeseries
1089
        print("  Loading model timeseries...")
1090
        # Get all model timeseries
1091
        model_ts_dict = {
1092
            "Load": {
1093
                "carrier": "land_transport_EV",
1094
                "table": EgonPfHvLoad,
1095
                "table_ts": EgonPfHvLoadTimeseries,
1096
                "column_id": "load_id",
1097
                "columns_ts": ["p_set"],
1098
                "ts": None,
1099
            },
1100
            "Link": {
1101
                "carrier": "BEV_charger",
1102
                "table": EgonPfHvLink,
1103
                "table_ts": EgonPfHvLinkTimeseries,
1104
                "column_id": "link_id",
1105
                "columns_ts": ["p_max_pu"],
1106
                "ts": None,
1107
            },
1108
            "Store": {
1109
                "carrier": "battery_storage",
1110
                "table": EgonPfHvStore,
1111
                "table_ts": EgonPfHvStoreTimeseries,
1112
                "column_id": "store_id",
1113
                "columns_ts": ["e_min_pu", "e_max_pu"],
1114
                "ts": None,
1115
            },
1116
        }
1117
1118
        with db.session_scope() as session:
1119
            for node, attrs in model_ts_dict.items():
1120
                print(f"    Loading {node} timeseries...")
1121
                subquery = (
1122
                    session.query(getattr(attrs["table"], attrs["column_id"]))
1123
                    .filter(attrs["table"].carrier == attrs["carrier"])
1124
                    .filter(attrs["table"].scn_name == scenario_name)
1125
                    .subquery()
1126
                )
1127
1128
                cols = [
1129
                    getattr(attrs["table_ts"], c) for c in attrs["columns_ts"]
1130
                ]
1131
                query = session.query(
1132
                    getattr(attrs["table_ts"], attrs["column_id"]), *cols
1133
                ).filter(
1134
                    getattr(attrs["table_ts"], attrs["column_id"]).in_(
1135
                        subquery
1136
                    ),
1137
                    attrs["table_ts"].scn_name == scenario_name,
1138
                )
1139
                attrs["ts"] = pd.read_sql(
1140
                    query.statement,
1141
                    query.session.bind,
1142
                    index_col=attrs["column_id"],
1143
                )
1144
1145
        # Check if all timeseries have 8760 steps
1146
        print("    Checking timeranges...")
1147
        for node, attrs in model_ts_dict.items():
1148
            for col in attrs["columns_ts"]:
1149
                ts = attrs["ts"]
1150
                invalid_ts = ts.loc[ts[col].apply(lambda _: len(_)) != 8760][
1151
                    col
1152
                ].apply(len)
1153
                np.testing.assert_equal(
1154
                    len(invalid_ts),
1155
                    0,
1156
                    err_msg=(
1157
                        f"{str(len(invalid_ts))} rows in timeseries do not "
1158
                        f"have 8760 timesteps. Table: "
1159
                        f"{attrs['table_ts'].__table__}, Column: {col}, IDs: "
1160
                        f"{str(list(invalid_ts.index))}"
1161
                    ),
1162
                )
1163
1164
        # Compare total energy demand in model with some approximate values
1165
        # (per EV: 14,000 km/a, 0.17 kWh/km)
1166
        print("  Checking energy demand in model...")
1167
        total_energy_model = (
1168
            model_ts_dict["Load"]["ts"].p_set.apply(lambda _: sum(_)).sum()
1169
            / 1e6
1170
        )
1171
        print(f"    Total energy amount in model: {total_energy_model} TWh")
1172
        total_energy_scenario_approx = ev_count_alloc * 14000 * 0.17 / 1e9
1173
        print(
1174
            f"    Total approximated energy amount in scenario: "
1175
            f"{total_energy_scenario_approx} TWh"
1176
        )
1177
        np.testing.assert_allclose(
1178
            total_energy_model,
1179
            total_energy_scenario_approx,
1180
            rtol=0.1,
1181
            err_msg=(
1182
                "The total energy amount in the model deviates heavily "
1183
                "from the approximated value for current scenario."
1184
            ),
1185
        )
1186
1187
        # Compare total storage capacity
1188
        print("  Checking storage capacity...")
1189
        # Load storage capacities from model
1190
        with db.session_scope() as session:
1191
            query = session.query(
1192
                func.sum(EgonPfHvStore.e_nom).label("e_nom")
1193
            ).filter(
1194
                EgonPfHvStore.scn_name == scenario_name,
1195
                EgonPfHvStore.carrier == "battery_storage",
1196
            )
1197
        storage_capacity_model = (
1198
            pd.read_sql(
1199
                query.statement, query.session.bind, index_col=None
1200
            ).e_nom.sum()
1201
            / 1e3
1202
        )
1203
        print(
1204
            f"    Total storage capacity ({EgonPfHvStore.__table__}): "
1205
            f"{round(storage_capacity_model, 1)} GWh"
1206
        )
1207
1208
        # Load occurences of each EV
1209
        with db.session_scope() as session:
1210
            query = (
1211
                session.query(
1212
                    EgonEvMvGridDistrict.bus_id,
1213
                    EgonEvPool.type,
1214
                    func.count(EgonEvMvGridDistrict.egon_ev_pool_ev_id).label(
1215
                        "count"
1216
                    ),
1217
                )
1218
                .join(
1219
                    EgonEvPool,
1220
                    EgonEvPool.ev_id
1221
                    == EgonEvMvGridDistrict.egon_ev_pool_ev_id,
1222
                )
1223
                .filter(
1224
                    EgonEvMvGridDistrict.scenario == scenario_name,
1225
                    EgonEvMvGridDistrict.scenario_variation
1226
                    == scenario_var_name,
1227
                    EgonEvPool.scenario == scenario_name,
1228
                )
1229
                .group_by(EgonEvMvGridDistrict.bus_id, EgonEvPool.type)
1230
            )
1231
        count_per_ev_all = pd.read_sql(
1232
            query.statement, query.session.bind, index_col="bus_id"
1233
        )
1234
        count_per_ev_all["bat_cap"] = count_per_ev_all.type.map(
1235
            meta_tech_data.battery_capacity
1236
        )
1237
        count_per_ev_all["bat_cap_total_MWh"] = (
1238
            count_per_ev_all["count"] * count_per_ev_all.bat_cap / 1e3
1239
        )
1240
        storage_capacity_simbev = count_per_ev_all.bat_cap_total_MWh.div(
1241
            1e3
1242
        ).sum()
1243
        print(
1244
            f"    Total storage capacity (simBEV): "
1245
            f"{round(storage_capacity_simbev, 1)} GWh"
1246
        )
1247
1248
        np.testing.assert_allclose(
1249
            storage_capacity_model,
1250
            storage_capacity_simbev,
1251
            rtol=0.01,
1252
            err_msg=(
1253
                "The total storage capacity in the model deviates heavily "
1254
                "from the input data provided by simBEV for current scenario."
1255
            ),
1256
        )
1257
1258
        # Check SoC storage constraint: e_min_pu < e_max_pu for all timesteps
1259
        print("  Validating SoC constraints...")
1260
        stores_with_invalid_soc = []
1261
        for idx, row in model_ts_dict["Store"]["ts"].iterrows():
1262
            ts = row[["e_min_pu", "e_max_pu"]]
1263
            x = np.array(ts.e_min_pu) > np.array(ts.e_max_pu)
1264
            if x.any():
1265
                stores_with_invalid_soc.append(idx)
1266
1267
        np.testing.assert_equal(
1268
            len(stores_with_invalid_soc),
1269
            0,
1270
            err_msg=(
1271
                f"The store constraint e_min_pu < e_max_pu does not apply "
1272
                f"for some storages in {EgonPfHvStoreTimeseries.__table__}. "
1273
                f"Invalid store_ids: {stores_with_invalid_soc}"
1274
            ),
1275
        )
1276
1277
    def check_model_data_lowflex_eGon2035():
1278
        # TODO: Add eGon100RE_lowflex
1279
        print("")
1280
        print("SCENARIO: eGon2035_lowflex")
1281
1282
        # Compare driving load and charging load
1283
        print("  Loading eGon2035 model timeseries: driving load...")
1284
        with db.session_scope() as session:
1285
            query = (
1286
                session.query(
1287
                    EgonPfHvLoad.load_id,
1288
                    EgonPfHvLoadTimeseries.p_set,
1289
                )
1290
                .join(
1291
                    EgonPfHvLoadTimeseries,
1292
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1293
                )
1294
                .filter(
1295
                    EgonPfHvLoad.carrier == "land_transport_EV",
1296
                    EgonPfHvLoad.scn_name == "eGon2035",
1297
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035",
1298
                )
1299
            )
1300
        model_driving_load = pd.read_sql(
1301
            query.statement, query.session.bind, index_col=None
1302
        )
1303
        driving_load = np.array(model_driving_load.p_set.to_list()).sum(axis=0)
1304
1305
        print(
1306
            "  Loading eGon2035_lowflex model timeseries: dumb charging "
1307
            "load..."
1308
        )
1309
        with db.session_scope() as session:
1310
            query = (
1311
                session.query(
1312
                    EgonPfHvLoad.load_id,
1313
                    EgonPfHvLoadTimeseries.p_set,
1314
                )
1315
                .join(
1316
                    EgonPfHvLoadTimeseries,
1317
                    EgonPfHvLoadTimeseries.load_id == EgonPfHvLoad.load_id,
1318
                )
1319
                .filter(
1320
                    EgonPfHvLoad.carrier == "land_transport_EV",
1321
                    EgonPfHvLoad.scn_name == "eGon2035_lowflex",
1322
                    EgonPfHvLoadTimeseries.scn_name == "eGon2035_lowflex",
1323
                )
1324
            )
1325
        model_charging_load_lowflex = pd.read_sql(
1326
            query.statement, query.session.bind, index_col=None
1327
        )
1328
        charging_load = np.array(
1329
            model_charging_load_lowflex.p_set.to_list()
1330
        ).sum(axis=0)
1331
1332
        # Ratio of driving and charging load should be 0.9 due to charging
1333
        # efficiency
1334
        print("  Compare cumulative loads...")
1335
        print(f"    Driving load (eGon2035): {driving_load.sum() / 1e6} TWh")
1336
        print(
1337
            f"    Dumb charging load (eGon2035_lowflex): "
1338
            f"{charging_load.sum() / 1e6} TWh"
1339
        )
1340
        driving_load_theoretical = (
1341
            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 1359 is not entered. Are you sure this can never be the case?
Loading history...
1342
        )
1343
        np.testing.assert_allclose(
1344
            driving_load.sum(),
1345
            driving_load_theoretical,
1346
            rtol=0.01,
1347
            err_msg=(
1348
                f"The driving load (eGon2035) deviates by more than 1% "
1349
                f"from the theoretical driving load calculated from charging "
1350
                f"load (eGon2035_lowflex) with an efficiency of "
1351
                f"{float(meta_run_config.eta_cp)}."
1352
            ),
1353
        )
1354
1355
    print("=====================================================")
1356
    print("=== SANITY CHECKS FOR MOTORIZED INDIVIDUAL TRAVEL ===")
1357
    print("=====================================================")
1358
1359
    for scenario_name in ["eGon2035", "eGon100RE"]:
1360
        scenario_var_name = DATASET_CFG["scenario"]["variation"][scenario_name]
1361
1362
        print("")
1363
        print(f"SCENARIO: {scenario_name}, VARIATION: {scenario_var_name}")
1364
1365
        # Load scenario params for scenario and scenario variation
1366
        scenario_variation_parameters = get_sector_parameters(
1367
            "mobility", scenario=scenario_name
1368
        )["motorized_individual_travel"][scenario_var_name]
1369
1370
        # Load simBEV run config and tech data
1371
        meta_run_config = read_simbev_metadata_file(
1372
            scenario_name, "config"
1373
        ).loc["basic"]
1374
        meta_tech_data = read_simbev_metadata_file(scenario_name, "tech_data")
1375
1376
        print("")
1377
        print("Checking EV counts...")
1378
        ev_count_alloc = check_ev_allocation()
1379
1380
        print("")
1381
        print("Checking trip data...")
1382
        check_trip_data()
1383
1384
        print("")
1385
        print("Checking model data...")
1386
        check_model_data()
1387
1388
    print("")
1389
    check_model_data_lowflex_eGon2035()
1390
1391
    print("=====================================================")
1392
1393
1394
def sanitycheck_home_batteries():
1395
    # get constants
1396
    constants = config.datasets()["home_batteries"]["constants"]
1397
    scenarios = constants["scenarios"]
1398
    cbat_pbat_ratio = get_cbat_pbat_ratio()
1399
1400
    sources = config.datasets()["home_batteries"]["sources"]
1401
    targets = config.datasets()["home_batteries"]["targets"]
1402
1403
    for scenario in scenarios:
1404
        # get home battery capacity per mv grid id
1405
        sql = f"""
1406
        SELECT el_capacity as p_nom, bus_id FROM
1407
        {sources["storage"]["schema"]}
1408
        .{sources["storage"]["table"]}
1409
        WHERE carrier = 'home_battery'
1410
        AND scenario = '{scenario}'
1411
        """
1412
1413
        home_batteries_df = db.select_dataframe(sql, index_col="bus_id")
1414
1415
        home_batteries_df = home_batteries_df.assign(
1416
            capacity=home_batteries_df.p_nom * cbat_pbat_ratio
1417
        )
1418
1419
        sql = f"""
1420
        SELECT * FROM
1421
        {targets["home_batteries"]["schema"]}
1422
        .{targets["home_batteries"]["table"]}
1423
        WHERE scenario = '{scenario}'
1424
        """
1425
1426
        home_batteries_buildings_df = db.select_dataframe(
1427
            sql, index_col="index"
1428
        )
1429
1430
        df = (
1431
            home_batteries_buildings_df[["bus_id", "p_nom", "capacity"]]
1432
            .groupby("bus_id")
1433
            .sum()
1434
        )
1435
1436
        assert (home_batteries_df.round(6) == df.round(6)).all().all()
1437
1438
1439
def sanity_check_gas_buses(scn):
1440
    """Execute sanity checks for the gas buses in Germany
1441
1442
    Returns print statements as sanity checks for the CH4, H2_grid and
1443
    H2_saltcavern buses.
1444
      * For all of them, it is checked if they are not isolated.
1445
      * For the grid buses, the deviation is calculated between the
1446
        number of gas grid buses in the database and the original
1447
        Scigrid_gas number of gas buses in Germany.
1448
1449
    Parameters
1450
    ----------
1451
    scn_name : str
1452
        Name of the scenario
1453
1454
    """
1455
    logger.info("BUSES")
1456
1457
    # Are gas buses isolated?
1458
    corresponding_carriers = {
1459
        "eGon2035": {
1460
            "CH4": "CH4",
1461
            "H2_grid": "H2_feedin",
1462
            "H2_saltcavern": "power_to_H2",
1463
        },
1464
        "eGon100RE": {
1465
            "CH4": "CH4",
1466
            "H2_grid": "H2_retrofit",
1467
            "H2_saltcavern": "H2_gridextension",
1468
        },
1469
    }
1470
    for key in corresponding_carriers[scn]:
1471
        isolated_gas_buses = db.select_dataframe(
1472
            f"""
1473
            SELECT bus_id, carrier, country
1474
            FROM grid.egon_etrago_bus
1475
            WHERE scn_name = '{scn}'
1476
            AND carrier = '{key}'
1477
            AND country = 'DE'
1478
            AND bus_id NOT IN
1479
                (SELECT bus0
1480
                FROM grid.egon_etrago_link
1481
                WHERE scn_name = '{scn}'
1482
                AND carrier = '{corresponding_carriers[scn][key]}')
1483
            AND bus_id NOT IN
1484
                (SELECT bus1
1485
                FROM grid.egon_etrago_link
1486
                WHERE scn_name = '{scn}'
1487
                AND carrier = '{corresponding_carriers[scn][key]}')
1488
            ;
1489
            """,
1490
            warning=False,
1491
        )
1492
        if not isolated_gas_buses.empty:
1493
            logger.info(f"Isolated {key} buses:")
1494
            logger.info(isolated_gas_buses)
1495
1496
    # Deviation of the gas grid buses number
1497
    target_file = (
1498
        Path(".") / "datasets" / "gas_data" / "data" / "IGGIELGN_Nodes.csv"
1499
    )
1500
1501
    Grid_buses_list = pd.read_csv(
1502
        target_file,
1503
        delimiter=";",
1504
        decimal=".",
1505
        usecols=["country_code"],
1506
    )
1507
1508
    Grid_buses_list = Grid_buses_list[
1509
        Grid_buses_list["country_code"].str.match("DE")
1510
    ]
1511
    input_grid_buses = len(Grid_buses_list.index)
1512
1513
    for carrier in ["CH4", "H2_grid"]:
1514
        output_grid_buses_df = db.select_dataframe(
1515
            f"""
1516
            SELECT bus_id
1517
            FROM grid.egon_etrago_bus
1518
            WHERE scn_name = '{scn}'
1519
            AND country = 'DE'
1520
            AND carrier = '{carrier}';
1521
            """,
1522
            warning=False,
1523
        )
1524
        output_grid_buses = len(output_grid_buses_df.index)
1525
1526
        e_grid_buses = (
1527
            round(
1528
                (output_grid_buses - input_grid_buses) / input_grid_buses,
1529
                2,
1530
            )
1531
            * 100
1532
        )
1533
        logger.info(f"Deviation {carrier} buses: {e_grid_buses} %")
1534
1535
1536
def sanity_check_CH4_stores(scn):
1537
    """Execute sanity checks for the CH4 stores in Germany
1538
1539
    Returns print statements as sanity checks for the CH4 stores
1540
    capacity in Germany. The deviation is calculated between:
1541
      * the sum of the capacities of the stores with carrier 'CH4'
1542
        in the database (for one scenario) and
1543
      * the sum of:
1544
          * the capacity the gas grid allocated to CH4 (total capacity
1545
            in eGon2035 and capacity reduced the share of the grid
1546
            allocated to H2 in eGon100RE)
1547
          * the total capacity of the CH4 stores in Germany (source: GIE)
1548
1549
    Parameters
1550
    ----------
1551
    scn_name : str
1552
        Name of the scenario
1553
1554
    """
1555
    output_CH4_stores = db.select_dataframe(
1556
        f"""SELECT SUM(e_nom::numeric) as e_nom_germany
1557
                FROM grid.egon_etrago_store
1558
                WHERE scn_name = '{scn}'
1559
                AND carrier = 'CH4'
1560
                AND bus IN
1561
                    (SELECT bus_id
1562
                    FROM grid.egon_etrago_bus
1563
                    WHERE scn_name = '{scn}'
1564
                    AND country = 'DE'
1565
                    AND carrier = 'CH4');
1566
                """,
1567
        warning=False,
1568
    )["e_nom_germany"].values[0]
1569
1570
    if scn == "eGon2035":
1571
        grid_cap = 130000
1572
    elif scn == "eGon100RE":
1573
        grid_cap = 13000 * (
1574
            1
1575
            - get_sector_parameters("gas", "eGon100RE")[
1576
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1577
            ]
1578
        )
1579
1580
    stores_cap_D = 266424202  # MWh GIE https://www.gie.eu/transparency/databases/storage-database/
1581
1582
    input_CH4_stores = stores_cap_D + grid_cap
0 ignored issues
show
introduced by
The variable grid_cap does not seem to be defined for all execution paths.
Loading history...
1583
1584
    e_CH4_stores = (
1585
        round(
1586
            (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
1587
            2,
1588
        )
1589
        * 100
1590
    )
1591
    logger.info(f"Deviation CH4 stores: {e_CH4_stores} %")
1592
1593
1594
def sanity_check_H2_saltcavern_stores(scn):
1595
    """Execute sanity checks for the H2 saltcavern stores in Germany
1596
1597
    Returns print as sanity checks for the H2 saltcavern potential
1598
    storage capacity in Germany. The deviation is calculated between:
1599
      * the sum of the of the H2 saltcavern potential storage capacity
1600
        (e_nom_max) in the database and
1601
      * the sum of the H2 saltcavern potential storage capacity
1602
        assumed to be the ratio of the areas of 500 m radius around
1603
        substations in each german federal state and the estimated
1604
        total hydrogen storage potential of the corresponding federal
1605
        state (data from InSpEE-DS report).
1606
    This test works also in test mode.
1607
1608
    Parameters
1609
    ----------
1610
    scn_name : str
1611
        Name of the scenario
1612
1613
    """
1614
    output_H2_stores = db.select_dataframe(
1615
        f"""SELECT SUM(e_nom_max::numeric) as e_nom_max_germany
1616
                FROM grid.egon_etrago_store
1617
                WHERE scn_name = '{scn}'
1618
                AND carrier = 'H2_underground'
1619
                AND bus IN
1620
                    (SELECT bus_id
1621
                    FROM grid.egon_etrago_bus
1622
                    WHERE scn_name = '{scn}'
1623
                    AND country = 'DE'
1624
                    AND carrier = 'H2_saltcavern');
1625
                """,
1626
        warning=False,
1627
    )["e_nom_max_germany"].values[0]
1628
1629
    storage_potentials = calculate_and_map_saltcavern_storage_potential()
1630
    storage_potentials["storage_potential"] = (
1631
        storage_potentials["area_fraction"] * storage_potentials["potential"]
1632
    )
1633
    input_H2_stores = sum(storage_potentials["storage_potential"].to_list())
1634
1635
    e_H2_stores = (
1636
        round(
1637
            (output_H2_stores - input_H2_stores) / input_H2_stores,
1638
            2,
1639
        )
1640
        * 100
1641
    )
1642
    logger.info(f"Deviation H2 saltcavern stores: {e_H2_stores} %")
1643
1644
1645
def sanity_check_gas_one_port(scn):
1646
    """Check connections of gas one-port components
1647
1648
    Verify that gas one-port component (loads, generators, stores) are
1649
    all connected to a bus (of the right carrier) present in the data
1650
    base. Return print statements if this is not the case.
1651
    These sanity checks are not specific to Germany, they also include
1652
    the neighbouring countries.
1653
1654
    Parameters
1655
    ----------
1656
    scn_name : str
1657
        Name of the scenario
1658
1659
    """
1660
    # Loads
1661
    if scn == "eGon2035":
1662
        ## CH4_for_industry Germany
1663
        isolated_one_port_c = db.select_dataframe(
1664
            f"""
1665
            SELECT load_id, bus, carrier, scn_name
1666
                FROM grid.egon_etrago_load
1667
                WHERE scn_name = '{scn}'
1668
                AND carrier = 'CH4_for_industry'
1669
                AND bus NOT IN
1670
                    (SELECT bus_id
1671
                    FROM grid.egon_etrago_bus
1672
                    WHERE scn_name = '{scn}'
1673
                    AND country = 'DE'
1674
                    AND carrier = 'CH4')
1675
            ;
1676
            """,
1677
            warning=False,
1678
        )
1679
        if not isolated_one_port_c.empty:
1680
            logger.info("Isolated loads:")
1681
            logger.info(isolated_one_port_c)
1682
1683
        ## CH4_for_industry abroad
1684
        isolated_one_port_c = db.select_dataframe(
1685
            f"""
1686
            SELECT load_id, bus, carrier, scn_name
1687
                FROM grid.egon_etrago_load
1688
                WHERE scn_name = '{scn}'
1689
                AND carrier = 'CH4'
1690
                AND bus NOT IN
1691
                    (SELECT bus_id
1692
                    FROM grid.egon_etrago_bus
1693
                    WHERE scn_name = '{scn}'
1694
                    AND country != 'DE'
1695
                    AND carrier = 'CH4')
1696
            ;
1697
            """,
1698
            warning=False,
1699
        )
1700
        if not isolated_one_port_c.empty:
1701
            logger.info("Isolated loads:")
1702
            logger.info(isolated_one_port_c)
1703
1704
        ## H2_for_industry
1705
        isolated_one_port_c = db.select_dataframe(
1706
            f"""
1707
            SELECT load_id, bus, carrier, scn_name
1708
                FROM grid.egon_etrago_load
1709
                WHERE scn_name = '{scn}'
1710
                AND carrier = 'H2_for_industry'
1711
                AND (bus NOT IN
1712
                    (SELECT bus_id
1713
                    FROM grid.egon_etrago_bus
1714
                    WHERE scn_name = '{scn}'
1715
                    AND country = 'DE'
1716
                    AND carrier = 'H2_grid')
1717
                AND bus NOT IN
1718
                    (SELECT bus_id
1719
                    FROM grid.egon_etrago_bus
1720
                    WHERE scn_name = '{scn}'
1721
                    AND country != 'DE'
1722
                    AND carrier = 'AC'))
1723
            ;
1724
            """,
1725
            warning=False,
1726
        )
1727
        if not isolated_one_port_c.empty:
1728
            logger.info("Isolated loads:")
1729
            logger.info(isolated_one_port_c)
1730
1731
    elif scn == "eGon100RE":
1732
        # Loads
1733
        load_carriers = {
1734
            "CH4_for_industry": "CH4",
1735
            "H2_for_industry": "H2_grid",
1736
            "CH4_system_boundary": "CH4",
1737
            "H2_system_boundary": "H2_grid",
1738
        }
1739
        for key in load_carriers:
1740
            isolated_one_port_c = db.select_dataframe(
1741
                f"""
1742
                SELECT load_id, bus, carrier, scn_name
1743
                    FROM grid.egon_etrago_load
1744
                    WHERE scn_name = '{scn}'
1745
                    AND carrier = '{key}'
1746
                    AND bus NOT IN
1747
                        (SELECT bus_id
1748
                        FROM grid.egon_etrago_bus
1749
                        WHERE scn_name = '{scn}'
1750
                        AND carrier = '{load_carriers[key]}')
1751
                ;
1752
                """,
1753
                warning=False,
1754
            )
1755
            if not isolated_one_port_c.empty:
1756
                logger.info("Isolated loads:")
1757
                logger.info(isolated_one_port_c)
1758
1759
    # Stores
1760
    ## CH4 and H2_underground
1761
    corresponding_carriers = {
1762
        "CH4": "CH4",
1763
        "H2_saltcavern": "H2_underground",
1764
    }
1765
    for key in corresponding_carriers:
1766
        isolated_one_port_c = db.select_dataframe(
1767
            f"""
1768
            SELECT store_id, bus, carrier, scn_name
1769
                FROM grid.egon_etrago_store
1770
                WHERE scn_name = '{scn}'
1771
                AND carrier = '{corresponding_carriers[key]}'
1772
                AND bus NOT IN
1773
                    (SELECT bus_id
1774
                    FROM grid.egon_etrago_bus
1775
                    WHERE scn_name = '{scn}'
1776
                    AND carrier = '{key}')
1777
                AND bus NOT IN
1778
                    (SELECT bus_id
1779
                    FROM grid.egon_etrago_bus
1780
                    WHERE scn_name = '{scn}'
1781
                    AND carrier = 'H2_grid')
1782
            ;
1783
            """,
1784
            warning=False,
1785
        )
1786
        if not isolated_one_port_c.empty:
1787
            logger.info("Isolated stores:")
1788
            logger.info(isolated_one_port_c)
1789
1790
    ## H2_overground
1791
    isolated_one_port_c = db.select_dataframe(
1792
        f"""
1793
        SELECT store_id, bus, carrier, scn_name
1794
            FROM grid.egon_etrago_store
1795
            WHERE scn_name = '{scn}'
1796
            AND carrier = 'H2_overground'
1797
            AND bus NOT IN
1798
                (SELECT bus_id
1799
                FROM grid.egon_etrago_bus
1800
                WHERE scn_name = '{scn}'
1801
                AND carrier = 'H2_saltcavern')
1802
            AND bus NOT IN
1803
                (SELECT bus_id
1804
                FROM grid.egon_etrago_bus
1805
                WHERE scn_name = '{scn}'
1806
                AND carrier = 'H2_grid')
1807
        ;
1808
        """,
1809
        warning=False,
1810
    )
1811
    if not isolated_one_port_c.empty:
1812
        logger.info("Isolated stores:")
1813
        logger.info(isolated_one_port_c)
1814
1815
    # Generators
1816
    isolated_one_port_c = db.select_dataframe(
1817
        f"""
1818
        SELECT generator_id, bus, carrier, scn_name
1819
            FROM grid.egon_etrago_generator
1820
            WHERE scn_name = '{scn}'
1821
            AND carrier = 'CH4'
1822
            AND bus NOT IN
1823
                (SELECT bus_id
1824
                FROM grid.egon_etrago_bus
1825
                WHERE scn_name = '{scn}'
1826
                AND carrier = 'CH4');
1827
        ;
1828
        """,
1829
        warning=False,
1830
    )
1831
    if not isolated_one_port_c.empty:
1832
        logger.info("Isolated generators:")
1833
        logger.info(isolated_one_port_c)
1834
1835
1836
def sanity_check_CH4_grid(scn):
1837
    """Execute sanity checks for the gas grid capacity in Germany
1838
1839
    Returns print statements as sanity checks for the CH4 links
1840
    (pipelines) in Germany. The deviation is calculated between
1841
    the sum of the power (p_nom) of all the CH4 pipelines in Germany
1842
    for one scenario in the database and the sum of the powers of the
1843
    imported pipelines.
1844
    In eGon100RE, the sum is reduced by the share of the grid that is
1845
    allocated to hydrogen (share calculated by PyPSA-eur-sec).
1846
    This test works also in test mode.
1847
1848
    Parameters
1849
    ----------
1850
    scn_name : str
1851
        Name of the scenario
1852
1853
    Returns
1854
    -------
1855
    scn_name : float
1856
        Sum of the power (p_nom) of all the pipelines in Germany
1857
1858
    """
1859
    grid_carrier = "CH4"
1860
    output_gas_grid = db.select_dataframe(
1861
        f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1862
            FROM grid.egon_etrago_link
1863
            WHERE scn_name = '{scn}'
1864
            AND carrier = '{grid_carrier}'
1865
            AND bus0 IN
1866
                (SELECT bus_id
1867
                FROM grid.egon_etrago_bus
1868
                WHERE scn_name = '{scn}'
1869
                AND country = 'DE'
1870
                AND carrier = '{grid_carrier}')
1871
            AND bus1 IN
1872
                (SELECT bus_id
1873
                FROM grid.egon_etrago_bus
1874
                WHERE scn_name = '{scn}'
1875
                AND country = 'DE'
1876
                AND carrier = '{grid_carrier}')
1877
                ;
1878
            """,
1879
        warning=False,
1880
    )["p_nom_germany"].values[0]
1881
1882
    gas_nodes_list = define_gas_nodes_list()
1883
    abroad_gas_nodes_list = define_gas_buses_abroad()
1884
    gas_grid = define_gas_pipeline_list(gas_nodes_list, abroad_gas_nodes_list)
1885
    gas_grid_germany = gas_grid[
1886
        (gas_grid["country_0"] == "DE") & (gas_grid["country_1"] == "DE")
1887
    ]
1888
    p_nom_total = sum(gas_grid_germany["p_nom"].to_list())
1889
1890
    if scn == "eGon2035":
1891
        input_gas_grid = p_nom_total
1892
    if scn == "eGon100RE":
1893
        input_gas_grid = p_nom_total * (
1894
            1
1895
            - get_sector_parameters("gas", "eGon100RE")[
1896
                "retrofitted_CH4pipeline-to-H2pipeline_share"
1897
            ]
1898
        )
1899
1900
    e_gas_grid = (
1901
        round(
1902
            (output_gas_grid - input_gas_grid) / input_gas_grid,
0 ignored issues
show
introduced by
The variable input_gas_grid does not seem to be defined in case scn == "eGon2035" on line 1890 is False. Are you sure this can never be the case?
Loading history...
1903
            2,
1904
        )
1905
        * 100
1906
    )
1907
    logger.info(f"Deviation of the capacity of the CH4 grid: {e_gas_grid} %")
1908
1909
    return p_nom_total
1910
1911
1912
def sanity_check_gas_links(scn):
1913
    """Check connections of gas links
1914
1915
    Verify that gas links are all connected to buses present in the data
1916
    base. Return print statements if this is not the case.
1917
    This sanity check is not specific to Germany, it also includes
1918
    the neighbouring countries.
1919
1920
    Parameters
1921
    ----------
1922
    scn_name : str
1923
        Name of the scenario
1924
1925
    """
1926
    carriers = [
1927
        "central_gas_boiler",
1928
        "central_gas_CHP",
1929
        "central_gas_CHP_heat",
1930
        "CH4",
1931
        "CH4_to_H2",
1932
        "H2_feedin",
1933
        "H2_grid_extension",
1934
        "H2_retrofit",
1935
        "H2_to_CH4",
1936
        "H2_to_power",
1937
        "industrial_gas_CHP",
1938
        "OCGT",
1939
        "power_to_H2",
1940
        "urban_central_gas_CHP",
1941
        "urban_central_gas_CHP_CC",
1942
    ]
1943
    for c in carriers:
1944
        link_with_missing_bus = db.select_dataframe(
1945
            f"""
1946
            SELECT link_id, bus0, bus1, carrier, scn_name
1947
                FROM grid.egon_etrago_link
1948
                WHERE scn_name = '{scn}'
1949
                AND carrier = '{c}'
1950
                AND (bus0 NOT IN
1951
                    (SELECT bus_id
1952
                    FROM grid.egon_etrago_bus
1953
                    WHERE scn_name = '{scn}')
1954
                OR bus1 NOT IN
1955
                    (SELECT bus_id
1956
                    FROM grid.egon_etrago_bus
1957
                    WHERE scn_name = '{scn}'))
1958
            ;
1959
            """,
1960
            warning=False,
1961
        )
1962
        if not link_with_missing_bus.empty:
1963
            logger.info("Links with missing bus:")
1964
            logger.info(link_with_missing_bus)
1965
1966
1967
def sanity_check_gas_generators_DE(scn):
1968
    """Execute sanity checks for the gas production capacity in Germany
1969
1970
    The deviation is calculated between the sums of the nominal powers
1971
    of the gas generators in the database and of the ones in the sources
1972
    documents:
1973
      * Biogaspartner Einspeiseatlas Deutschland from the dena for the
1974
        biogas and Productions from the SciGRID_gas data for the natural
1975
        gas for eGon2035
1976
      * Biogaspartner Einspeiseatlas Deutschland from the dena for the
1977
        biogas only for eGon10RE
1978
1979
    Parameters
1980
    ----------
1981
    scn_name : str
1982
        Name of the scenario
1983
1984
    """
1985
1986
    logger.info("GENERATORS")
1987
    carrier_generator = "CH4"
1988
1989
    output_gas_generation = db.select_dataframe(
1990
        f"""SELECT SUM(p_nom::numeric) as p_nom_germany
1991
                FROM grid.egon_etrago_generator
1992
                WHERE scn_name = '{scn}'
1993
                AND carrier = '{carrier_generator}'
1994
                AND bus IN
1995
                    (SELECT bus_id
1996
                    FROM grid.egon_etrago_bus
1997
                    WHERE scn_name = '{scn}'
1998
                    AND country = 'DE'
1999
                    AND carrier = '{carrier_generator}');
2000
                """,
2001
        warning=False,
2002
    )["p_nom_germany"].values[0]
2003
2004
    basename = "Biogaspartner_Einspeiseatlas_Deutschland_2021.xlsx"
2005
    target_file = Path(".") / "datasets" / "gas_data" / basename
2006
2007
    conversion_factor_b = 0.010830  # m^3/h to MWh/h
2008
    p_biogas = (
2009
        pd.read_excel(
2010
            target_file,
2011
            usecols=["Einspeisung Biomethan [(N*m^3)/h)]"],
2012
        )["Einspeisung Biomethan [(N*m^3)/h)]"].sum()
2013
        * conversion_factor_b
2014
    )
2015
2016
    if scn == "eGon2035":
2017
        target_file = (
2018
            Path(".")
2019
            / "datasets"
2020
            / "gas_data"
2021
            / "data"
2022
            / "IGGIELGN_Productions.csv"
2023
        )
2024
2025
        NG_generators_list = pd.read_csv(
2026
            target_file,
2027
            delimiter=";",
2028
            decimal=".",
2029
            usecols=["country_code", "param"],
2030
        )
2031
2032
        NG_generators_list = NG_generators_list[
2033
            NG_generators_list["country_code"].str.match("DE")
2034
        ]
2035
2036
        p_NG = 0
2037
        for index, row in NG_generators_list.iterrows():
2038
            param = ast.literal_eval(row["param"])
2039
            p_NG = p_NG + param["max_supply_M_m3_per_d"]
2040
        conversion_factor = 437.5  # MCM/day to MWh/h
2041
        p_NG = p_NG * conversion_factor
2042
2043
        input_gas_generation = p_NG + p_biogas
2044
2045
    elif scn == "eGon100RE":
2046
        input_gas_generation = p_biogas
2047
2048
    e_generation = (
2049
        round(
2050
            (output_gas_generation - input_gas_generation)
0 ignored issues
show
introduced by
The variable input_gas_generation does not seem to be defined for all execution paths.
Loading history...
2051
            / input_gas_generation,
2052
            2,
2053
        )
2054
        * 100
2055
    )
2056
    logger.info(f"Deviation {carrier_generator} generation: {e_generation} %")
2057
2058
2059
def etrago_eGon2035_gas_DE():
2060
    """Execute basic sanity checks for the gas sector in eGon2035
2061
2062
    Returns print statements as sanity checks for the gas sector in
2063
    the eGon2035 scenario for the following components in Germany:
2064
      * Buses: with the function :py:func:`sanity_check_gas_buses`
2065
      * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry'
2066
        the deviation is calculated between the sum of the loads in the
2067
        database and the sum the loads in the sources document
2068
        (opendata.ffe database)
2069
      * Generators: with the function :py:func:`sanity_check_gas_generators_DE`
2070
      * Stores: deviations for stores with following carriers are
2071
        calculated:
2072
          * 'CH4': with the function :py:func:`sanity_check_CH4_stores`
2073
          * 'H2_underground': with the function :py:func:`sanity_check_H2_saltcavern_stores`
2074
      * One-port components (loads, generators, stores): verification
2075
        that they are all connected to a bus present in the database
2076
        with the function :py:func:`sanity_check_gas_one_port`
2077
      * Links: verification:
2078
          * that the gas links are all connected to buses present in
2079
            the database with the function :py:func:`sanity_check_gas_links`
2080
          * of the capacity of the gas grid with the function
2081
            :py:func:`sanity_check_CH4_grid`
2082
2083
    """
2084
    scn = "eGon2035"
2085
2086
    if TESTMODE_OFF:
2087
        logger.info(f"Gas sanity checks for scenario {scn}")
2088
2089
        # Buses
2090
        sanity_check_gas_buses(scn)
2091
2092
        # Loads
2093
        logger.info("LOADS")
2094
2095
        path = Path(".") / "datasets" / "gas_data" / "demand"
2096
        corr_file = path / "region_corr.json"
2097
        df_corr = pd.read_json(corr_file)
2098
        df_corr = df_corr.loc[:, ["id_region", "name_short"]]
2099
        df_corr.set_index("id_region", inplace=True)
2100
2101
        for carrier in ["CH4_for_industry", "H2_for_industry"]:
2102
2103
            output_gas_demand = db.select_dataframe(
2104
                f"""SELECT (SUM(
2105
                    (SELECT SUM(p)
2106
                    FROM UNNEST(b.p_set) p))/1000000)::numeric as load_twh
2107
                    FROM grid.egon_etrago_load a
2108
                    JOIN grid.egon_etrago_load_timeseries b
2109
                    ON (a.load_id = b.load_id)
2110
                    JOIN grid.egon_etrago_bus c
2111
                    ON (a.bus=c.bus_id)
2112
                    AND b.scn_name = '{scn}'
2113
                    AND a.scn_name = '{scn}'
2114
                    AND c.scn_name = '{scn}'
2115
                    AND c.country = 'DE'
2116
                    AND a.carrier = '{carrier}';
2117
                """,
2118
                warning=False,
2119
            )["load_twh"].values[0]
2120
2121
            input_gas_demand = pd.read_json(
2122
                path / (carrier + "_eGon2035.json")
2123
            )
2124
            input_gas_demand = input_gas_demand.loc[:, ["id_region", "value"]]
2125
            input_gas_demand.set_index("id_region", inplace=True)
2126
            input_gas_demand = pd.concat(
2127
                [input_gas_demand, df_corr], axis=1, join="inner"
2128
            )
2129
            input_gas_demand["NUTS0"] = (input_gas_demand["name_short"].str)[
2130
                0:2
2131
            ]
2132
            input_gas_demand = input_gas_demand[
2133
                input_gas_demand["NUTS0"].str.match("DE")
2134
            ]
2135
            input_gas_demand = sum(input_gas_demand.value.to_list()) / 1000000
2136
2137
            e_demand = (
2138
                round(
2139
                    (output_gas_demand - input_gas_demand) / input_gas_demand,
2140
                    2,
2141
                )
2142
                * 100
2143
            )
2144
            logger.info(f"Deviation {carrier}: {e_demand} %")
2145
2146
        # Generators
2147
        sanity_check_gas_generators_DE(scn)
2148
2149
        # Stores
2150
        logger.info("STORES")
2151
        sanity_check_CH4_stores(scn)
2152
        sanity_check_H2_saltcavern_stores(scn)
2153
2154
        # One-port components
2155
        sanity_check_gas_one_port(scn)
2156
2157
        # Links
2158
        logger.info("LINKS")
2159
        sanity_check_CH4_grid(scn)
2160
        sanity_check_gas_links(scn)
2161
2162
    else:
2163
        print("Testmode is on, skipping sanity check.")
2164
2165
2166
def etrago_eGon2035_gas_abroad():
2167
    """Execute basic sanity checks for the gas sector in eGon2035 abroad
2168
2169
    Returns print statements as sanity checks for the gas sector in
2170
    the eGon2035 scenario for the following components in Germany:
2171
      * Buses
2172
      * Loads: for the carriers 'CH4' and 'H2_for_industry'
2173
        the deviation is calculated between the sum of the loads in the
2174
        database and the sum in the sources document (TYNDP)
2175
      * Generators: the deviation is calculated between the sums of the
2176
        nominal powers of the methane generators abroad in the database
2177
        and of the ones in the sources document (TYNDP)
2178
      * Stores: the deviation for methane stores abroad is calculated
2179
        between the sum of the capacities in the data base and the one
2180
        of the source document (SciGRID_gas data)
2181
      * Links: verification of the capacity of the crossbordering gas
2182
        grid pipelines.
2183
2184
    """
2185
    scn = "eGon2035"
2186
2187
    if TESTMODE_OFF:
2188
        logger.info(f"Gas sanity checks abroad for scenario {scn}")
2189
2190
        # Buses
2191
        logger.info("BUSES")
2192
2193
        # Are gas buses isolated?
2194
        corresponding_carriers = {
2195
            "eGon2035": {
2196
                "CH4": "CH4",
2197
            },
2198
            # "eGon100RE": {
2199
            #     "CH4": "CH4",
2200
            #     "H2_grid": "H2_retrofit",
2201
            # }
2202
        }
2203
        for key in corresponding_carriers[scn]:
2204
            isolated_gas_buses_abroad = db.select_dataframe(
2205
                f"""
2206
                SELECT bus_id, carrier, country
2207
                FROM grid.egon_etrago_bus
2208
                WHERE scn_name = '{scn}'
2209
                AND carrier = '{key}'
2210
                AND country != 'DE'
2211
                AND bus_id NOT IN
2212
                    (SELECT bus0
2213
                    FROM grid.egon_etrago_link
2214
                    WHERE scn_name = '{scn}'
2215
                    AND carrier = '{corresponding_carriers[scn][key]}')
2216
                AND bus_id NOT IN
2217
                    (SELECT bus1
2218
                    FROM grid.egon_etrago_link
2219
                    WHERE scn_name = '{scn}'
2220
                    AND carrier = '{corresponding_carriers[scn][key]}')
2221
                ;
2222
                """,
2223
                warning=False,
2224
            )
2225
            if not isolated_gas_buses_abroad.empty:
2226
                logger.info(f"Isolated {key} buses abroad:")
2227
                logger.info(isolated_gas_buses_abroad)
2228
2229
        # Loads
2230
        logger.info("LOADS")
2231
2232
        (
2233
            Norway_global_demand_1y,
2234
            normalized_ch4_demandTS,
2235
        ) = import_ch4_demandTS()
2236
        input_CH4_demand_abroad = calc_global_ch4_demand(
2237
            Norway_global_demand_1y
2238
        )
2239
        input_CH4_demand = input_CH4_demand_abroad["GlobD_2035"].sum()
2240
2241
        ## CH4
2242
        output_CH4_demand = db.select_dataframe(
2243
            f"""SELECT (SUM(
2244
                (SELECT SUM(p)
2245
                FROM UNNEST(b.p_set) p)))::numeric as load_mwh
2246
                FROM grid.egon_etrago_load a
2247
                JOIN grid.egon_etrago_load_timeseries b
2248
                ON (a.load_id = b.load_id)
2249
                JOIN grid.egon_etrago_bus c
2250
                ON (a.bus=c.bus_id)
2251
                AND b.scn_name = '{scn}'
2252
                AND a.scn_name = '{scn}'
2253
                AND c.scn_name = '{scn}'
2254
                AND c.country != 'DE'
2255
                AND a.carrier = 'CH4';
2256
            """,
2257
            warning=False,
2258
        )["load_mwh"].values[0]
2259
2260
        e_demand_CH4 = (
2261
            round(
2262
                (output_CH4_demand - input_CH4_demand) / input_CH4_demand,
2263
                2,
2264
            )
2265
            * 100
2266
        )
2267
        logger.info(f"Deviation CH4 load: {e_demand_CH4} %")
2268
2269
        ## H2_for_industry
2270
        input_power_to_h2_demand_abroad = calc_global_power_to_h2_demand()
2271
        input_H2_demand = input_power_to_h2_demand_abroad["GlobD_2035"].sum()
2272
2273
        output_H2_demand = db.select_dataframe(
2274
            f"""SELECT SUM(p_set::numeric) as p_set_abroad
2275
                    FROM grid.egon_etrago_load
2276
                    WHERE scn_name = '{scn}'
2277
                    AND carrier = 'H2_for_industry'
2278
                    AND bus IN
2279
                        (SELECT bus_id
2280
                        FROM grid.egon_etrago_bus
2281
                        WHERE scn_name = '{scn}'
2282
                        AND country != 'DE'
2283
                        AND carrier = 'AC');
2284
                    """,
2285
            warning=False,
2286
        )["p_set_abroad"].values[0]
2287
2288
        e_demand_H2 = (
2289
            round(
2290
                (output_H2_demand - input_H2_demand) / input_H2_demand,
2291
                2,
2292
            )
2293
            * 100
2294
        )
2295
        logger.info(f"Deviation H2_for_industry load: {e_demand_H2} %")
2296
2297
        # Generators
2298
        logger.info("GENERATORS ")
2299
        CH4_gen = calc_capacities()
2300
        input_CH4_gen = CH4_gen["cap_2035"].sum()
2301
2302
        output_CH4_gen = db.select_dataframe(
2303
            f"""SELECT SUM(p_nom::numeric) as p_nom_abroad
2304
                    FROM grid.egon_etrago_generator
2305
                    WHERE scn_name = '{scn}'
2306
                    AND carrier = 'CH4'
2307
                    AND bus IN
2308
                        (SELECT bus_id
2309
                        FROM grid.egon_etrago_bus
2310
                        WHERE scn_name = '{scn}'
2311
                        AND country != 'DE'
2312
                        AND carrier = 'CH4');
2313
                    """,
2314
            warning=False,
2315
        )["p_nom_abroad"].values[0]
2316
2317
        e_gen = (
2318
            round(
2319
                (output_CH4_gen - input_CH4_gen) / input_CH4_gen,
2320
                2,
2321
            )
2322
            * 100
2323
        )
2324
        logger.info(f"Deviation CH4 generators: {e_gen} %")
2325
2326
        # Stores
2327
        logger.info("STORES")
2328
        ch4_input_capacities = calc_ch4_storage_capacities()
2329
        input_CH4_stores = ch4_input_capacities["e_nom"].sum()
2330
2331
        output_CH4_stores = db.select_dataframe(
2332
            f"""SELECT SUM(e_nom::numeric) as e_nom_abroad
2333
                    FROM grid.egon_etrago_store
2334
                    WHERE scn_name = '{scn}'
2335
                    AND carrier = 'CH4'
2336
                    AND bus IN
2337
                        (SELECT bus_id
2338
                        FROM grid.egon_etrago_bus
2339
                        WHERE scn_name = '{scn}'
2340
                        AND country != 'DE'
2341
                        AND carrier = 'CH4');
2342
                    """,
2343
            warning=False,
2344
        )["e_nom_abroad"].values[0]
2345
2346
        e_stores = (
2347
            round(
2348
                (output_CH4_stores - input_CH4_stores) / input_CH4_stores,
2349
                2,
2350
            )
2351
            * 100
2352
        )
2353
        logger.info(f"Deviation CH4 stores: {e_stores} %")
2354
2355
        # Links
2356
        logger.info("LINKS")
2357
        ch4_grid_input_capacities = calculate_ch4_grid_capacities()
2358
        input_CH4_grid = ch4_grid_input_capacities["p_nom"].sum()
2359
2360
        grid_carrier = "CH4"
2361
        output_gas_grid = db.select_dataframe(
2362
            f"""SELECT SUM(p_nom::numeric) as p_nom
2363
            FROM grid.egon_etrago_link
2364
            WHERE scn_name = '{scn}'
2365
            AND carrier = '{grid_carrier}'
2366
            AND (bus0 IN
2367
                (SELECT bus_id
2368
                FROM grid.egon_etrago_bus
2369
                WHERE scn_name = '{scn}'
2370
                AND country != 'DE'
2371
                AND carrier = '{grid_carrier}')
2372
            OR bus1 IN
2373
                (SELECT bus_id
2374
                FROM grid.egon_etrago_bus
2375
                WHERE scn_name = '{scn}'
2376
                AND country != 'DE'
2377
                AND carrier = '{grid_carrier}'))
2378
                ;
2379
            """,
2380
            warning=False,
2381
        )["p_nom"].values[0]
2382
2383
        e_gas_grid = (
2384
            round(
2385
                (output_gas_grid - input_CH4_grid) / input_CH4_grid,
2386
                2,
2387
            )
2388
            * 100
2389
        )
2390
        logger.info(
2391
            f"Deviation of the capacity of the crossbordering CH4 grid: {e_gas_grid} %"
2392
        )
2393
2394
    else:
2395
        print("Testmode is on, skipping sanity check.")
2396
2397
2398
def etrago_eGon100RE_gas_DE():
2399
    """Execute basic sanity checks for the gas sector in eGon100RE
2400
2401
    Returns print statements as sanity checks for the gas sector in
2402
    the eGon100RE scenario for the following components in Germany:
2403
      * Buses: with the function :py:func:`sanity_check_gas_buses`
2404
      * Loads: for the carriers 'CH4_for_industry' and 'H2_for_industry'
2405
        the deviation is calculated between the sum of the loads in the
2406
        database and the loads calcultaed by p-e-s
2407
      * Generators: with the function :py:func:`sanity_check_gas_generators_DE`
2408
      * Stores: deviations for stores with following carriers are
2409
        calculated:
2410
          * 'CH4': with the function :py:func:`sanity_check_CH4_stores`
2411
          * 'H2_underground': with the function :py:func:`sanity_check_H2_saltcavern_stores`
2412
          * 'H2': the deviation is calculated between the sum of the
2413
            capacities of the stores with carrier 'H2' in the database
2414
            and the sum of the capacity the gas grid allocated to H2
2415
      * One-port components (loads, generators, stores): verification
2416
        that they are all connected to a bus present in the database
2417
        with the function :py:func:`sanity_check_gas_one_port`
2418
      * Links: verification:
2419
          * that the gas links are all connected to buses present in
2420
            the data base with the function :py:func:`sanity_check_gas_links`
2421
          * of the capacity of the gas grid with the function
2422
            :py:func:`sanity_check_CH4_grid`
2423
2424
    """
2425
    scn = "eGon100RE"
2426
2427
    if TESTMODE_OFF:
2428
        logger.info(f"Gas sanity checks for scenario {scn}")
2429
2430
        # Buses
2431
        sanity_check_gas_buses(scn)
2432
2433
        # Loads
2434
        logger.info("LOADS")
2435
2436
        H2_d, CH4_d = calculate_total_demand_100RE()
2437
2438
        for carrier, total_ind_gas_d in zip(
2439
            ["H2_for_industry", "CH4_for_industry"], [H2_d, CH4_d]
2440
        ):
2441
2442
            output_gas_demand = db.select_dataframe(
2443
                f"""SELECT (SUM(
2444
                    (SELECT SUM(p)
2445
                    FROM UNNEST(b.p_set) p)))::numeric as load
2446
                    FROM grid.egon_etrago_load a
2447
                    JOIN grid.egon_etrago_load_timeseries b
2448
                    ON (a.load_id = b.load_id)
2449
                    JOIN grid.egon_etrago_bus c
2450
                    ON (a.bus=c.bus_id)
2451
                    AND b.scn_name = '{scn}'
2452
                    AND a.scn_name = '{scn}'
2453
                    AND c.scn_name = '{scn}'
2454
                    AND c.country = 'DE'
2455
                    AND a.carrier = '{carrier}';
2456
                """,
2457
                warning=False,
2458
            )["load"].values[0]
2459
2460
            input_gas_demand = total_ind_gas_d
2461
2462
            e_demand = (
2463
                round(
2464
                    (output_gas_demand - input_gas_demand) / input_gas_demand,
2465
                    2,
2466
                )
2467
                * 100
2468
            )
2469
            logger.info(f"Deviation {carrier}: {e_demand} %")
2470
2471
        # Generators
2472
        sanity_check_gas_generators_DE(scn)
2473
2474
        # Stores
2475
        logger.info("STORES")
2476
        sanity_check_CH4_stores(scn)
2477
        sanity_check_H2_saltcavern_stores(scn)
2478
        # Check for H2 should be implemented when H2 stores are in dev
2479
2480
        # One-port components
2481
        sanity_check_gas_one_port(scn)
2482
2483
        # Links
2484
        logger.info("LINKS")
2485
        sanity_check_CH4_grid(scn)
2486
        sanity_check_gas_links(scn)
2487
2488
2489
def sanitycheck_dsm():
2490
    def df_from_series(s: pd.Series):
2491
        return pd.DataFrame.from_dict(dict(zip(s.index, s.values)))
2492
2493
    for scenario in ["eGon2035", "eGon100RE"]:
2494
        # p_min and p_max
2495
        sql = f"""
2496
        SELECT link_id, bus0 as bus, p_nom FROM grid.egon_etrago_link
2497
        WHERE carrier = 'dsm'
2498
        AND scn_name = '{scenario}'
2499
        ORDER BY link_id
2500
        """
2501
2502
        meta_df = db.select_dataframe(sql, index_col="link_id")
2503
        link_ids = str(meta_df.index.tolist())[1:-1]
2504
2505
        sql = f"""
2506
        SELECT link_id, p_min_pu, p_max_pu
2507
        FROM grid.egon_etrago_link_timeseries
2508
        WHERE scn_name = '{scenario}'
2509
        AND link_id IN ({link_ids})
2510
        ORDER BY link_id
2511
        """
2512
2513
        ts_df = db.select_dataframe(sql, index_col="link_id")
2514
2515
        p_max_df = df_from_series(ts_df.p_max_pu).mul(meta_df.p_nom)
2516
        p_min_df = df_from_series(ts_df.p_min_pu).mul(meta_df.p_nom)
2517
2518
        p_max_df.columns = meta_df.bus.tolist()
2519
        p_min_df.columns = meta_df.bus.tolist()
2520
2521
        targets = config.datasets()["DSM_CTS_industry"]["targets"]
2522
2523
        tables = [
2524
            "cts_loadcurves_dsm",
2525
            "ind_osm_loadcurves_individual_dsm",
2526
            "demandregio_ind_sites_dsm",
2527
            "ind_sites_loadcurves_individual",
2528
        ]
2529
2530
        df_list = []
2531
2532
        for table in tables:
2533
            target = targets[table]
2534
            sql = f"""
2535
            SELECT bus, p_min, p_max, e_max, e_min
2536
            FROM {target["schema"]}.{target["table"]}
2537
            WHERE scn_name = '{scenario}'
2538
            ORDER BY bus
2539
            """
2540
2541
            df_list.append(db.select_dataframe(sql))
2542
2543
        individual_ts_df = pd.concat(df_list, ignore_index=True)
2544
2545
        groups = individual_ts_df[["bus"]].reset_index().groupby("bus").groups
2546
2547
        individual_p_max_df = df_from_series(individual_ts_df.p_max)
2548
2549
        individual_p_max_df = pd.DataFrame(
2550
            [
2551
                individual_p_max_df[idxs].sum(axis=1)
2552
                for idxs in groups.values()
2553
            ],
2554
            index=groups.keys(),
2555
        ).T
2556
2557
        individual_p_min_df = df_from_series(individual_ts_df.p_min)
2558
2559
        individual_p_min_df = pd.DataFrame(
2560
            [
2561
                individual_p_min_df[idxs].sum(axis=1)
2562
                for idxs in groups.values()
2563
            ],
2564
            index=groups.keys(),
2565
        ).T
2566
2567
        # due to the fact that time series are clipped at zero (either
2568
        # direction) there is a little difference between the sum of the
2569
        # individual time series and the aggregated time series as the second
2570
        # is generated independent of the others. This makes atol=1e-01
2571
        # necessary.
2572
        atol = 1e-01
2573
        assert np.allclose(p_max_df, individual_p_max_df, atol=atol)
2574
        assert np.allclose(p_min_df, individual_p_min_df, atol=atol)
2575
2576
        # e_min and e_max
2577
        sql = f"""
2578
        SELECT store_id, bus, e_nom FROM grid.egon_etrago_store
2579
        WHERE carrier = 'dsm'
2580
        AND scn_name = '{scenario}'
2581
        ORDER BY store_id
2582
        """
2583
2584
        meta_df = db.select_dataframe(sql, index_col="store_id")
2585
        store_ids = str(meta_df.index.tolist())[1:-1]
2586
2587
        sql = f"""
2588
        SELECT store_id, e_min_pu, e_max_pu
2589
        FROM grid.egon_etrago_store_timeseries
2590
        WHERE scn_name = '{scenario}'
2591
        AND store_id IN ({store_ids})
2592
        ORDER BY store_id
2593
        """
2594
2595
        ts_df = db.select_dataframe(sql, index_col="store_id")
2596
2597
        e_max_df = df_from_series(ts_df.e_max_pu).mul(meta_df.e_nom)
2598
        e_min_df = df_from_series(ts_df.e_min_pu).mul(meta_df.e_nom)
2599
2600
        e_max_df.columns = meta_df.bus.tolist()
2601
        e_min_df.columns = meta_df.bus.tolist()
2602
2603
        individual_e_max_df = df_from_series(individual_ts_df.e_max)
2604
2605
        individual_e_max_df = pd.DataFrame(
2606
            [
2607
                individual_e_max_df[idxs].sum(axis=1)
2608
                for idxs in groups.values()
2609
            ],
2610
            index=groups.keys(),
2611
        ).T
2612
        individual_e_min_df = df_from_series(individual_ts_df.e_min)
2613
2614
        individual_e_min_df = pd.DataFrame(
2615
            [
2616
                individual_e_min_df[idxs].sum(axis=1)
2617
                for idxs in groups.values()
2618
            ],
2619
            index=groups.keys(),
2620
        ).T
2621
2622
        assert np.allclose(e_max_df, individual_e_max_df)
2623
        assert np.allclose(e_min_df, individual_e_min_df)
2624