• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

rl-institut / multi-vector-simulator / 8870538658

28 Apr 2024 09:31PM UTC coverage: 75.582% (-1.4%) from 76.96%
8870538658

push

github

web-flow
Merge pull request #971 from rl-institut/fix/black-vulnerability

Fix/black vulnerability

26 of 29 new or added lines in 15 files covered. (89.66%)

826 existing lines in 21 files now uncovered.

5977 of 7908 relevant lines covered (75.58%)

0.76 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

6.58
/src/multi_vector_simulator/utils/data_parser.py
1
r"""
2
Module data_parser
3
==================
4

5
This module defines all functions to convert formats between EPA and MVS
6
- Define similar parameters mapping between the EPA and MVS in MAP_EPA_MVS and MAP_MVS_EPA
7
- Define which fields are expected in asset list of EPA for various assets' groups in EPA_ASSET_KEYS
8
- Convert MVS to EPA
9
- Convert EPA to MVS
10
"""
11

12
import pprint
1✔
13
import logging
1✔
14
import json
1✔
15
from copy import deepcopy
1✔
16

17
from multi_vector_simulator.utils import compare_input_parameters_with_reference
1✔
18

19

20
from multi_vector_simulator.utils.constants import (
1✔
21
    MISSING_PARAMETERS_KEY,
22
    EXTRA_PARAMETERS_KEY,
23
    DATA_TYPE_JSON_KEY,
24
    TYPE_SERIES,
25
    TYPE_NONE,
26
    TYPE_BOOL,
27
    KNOWN_EXTRA_PARAMETERS,
28
    DEFAULT_CONSTRAINT_VALUES,
29
    DEFAULT_VALUE,
30
)
31

32
from multi_vector_simulator.utils.constants_json_strings import (
1✔
33
    PROJECT_DATA,
34
    ECONOMIC_DATA,
35
    SIMULATION_SETTINGS,
36
    CONSTRAINTS,
37
    ENERGY_CONSUMPTION,
38
    ENERGY_CONVERSION,
39
    ENERGY_PRODUCTION,
40
    ENERGY_STORAGE,
41
    ENERGY_BUSSES,
42
    ENERGY_PROVIDERS,
43
    UNIT,
44
    LABEL,
45
    OEMOF_ASSET_TYPE,
46
    ENERGY_VECTOR,
47
    INFLOW_DIRECTION,
48
    CONNECTED_CONSUMPTION_SOURCE,
49
    CONNECTED_FEEDIN_SINK,
50
    ENERGY_PRICE,
51
    FEEDIN_TARIFF,
52
    PEAK_DEMAND_PRICING,
53
    PEAK_DEMAND_PRICING_PERIOD,
54
    RENEWABLE_SHARE_DSO,
55
    OUTFLOW_DIRECTION,
56
    DEVELOPMENT_COSTS,
57
    DISPATCH_PRICE,
58
    DISPATCHABILITY,
59
    INSTALLED_CAP,
60
    LIFETIME,
61
    MAXIMUM_CAP,
62
    MAXIMUM_ADD_CAP,
63
    OPTIMIZE_CAP,
64
    OPTIMIZED_ADD_CAP,
65
    SPECIFIC_COSTS,
66
    SPECIFIC_COSTS_OM,
67
    SPECIFIC_REPLACEMENT_COSTS_INSTALLED,
68
    SPECIFIC_REPLACEMENT_COSTS_OPTIMIZED,
69
    TIMESERIES,
70
    AGE_INSTALLED,
71
    RENEWABLE_ASSET_BOOL,
72
    EFFICIENCY,
73
    INPUT_POWER,
74
    OUTPUT_POWER,
75
    STORAGE_CAPACITY,
76
    PROJECT_ID,
77
    PROJECT_NAME,
78
    SCENARIO_ID,
79
    SCENARIO_NAME,
80
    START_DATE,
81
    EVALUATED_PERIOD,
82
    OUTPUT_LP_FILE,
83
    MINIMAL_RENEWABLE_FACTOR,
84
    MINIMAL_DEGREE_OF_AUTONOMY,
85
    FIX_COST,
86
    KPI,
87
    TIMESTEP,
88
    KPI_SCALARS_DICT,
89
    VALUE,
90
    EMISSION_FACTOR,
91
    MAXIMUM_EMISSIONS,
92
    FLOW,
93
    KPI_UNCOUPLED_DICT,
94
    KPI_COST_MATRIX,
95
    KPI_SCALAR_MATRIX,
96
    SOC_INITIAL,
97
    SCENARIO_DESCRIPTION,
98
    TIMESERIES_SOC,
99
    TYPE_ASSET,
100
    DSM,
101
    THERM_LOSSES_REL,
102
    THERM_LOSSES_ABS,
103
    NET_ZERO_ENERGY,
104
    COST_REPLACEMENT,
105
    ASSET_DICT,
106
)
107

108
from multi_vector_simulator.utils.exceptions import MissingParameterError
1✔
109

110
pp = pprint.PrettyPrinter(indent=4)
1✔
111

112
MAP_EPA_MVS = {
1✔
113
    "economic_data": ECONOMIC_DATA,
114
    "energy_providers": ENERGY_PROVIDERS,
115
    "energy_busses": ENERGY_BUSSES,
116
    "energy_consumption": ENERGY_CONSUMPTION,
117
    "energy_conversion": ENERGY_CONVERSION,
118
    "energy_production": ENERGY_PRODUCTION,
119
    "energy_storage": ENERGY_STORAGE,
120
    "project_data": PROJECT_DATA,
121
    "simulation_settings": SIMULATION_SETTINGS,
122
    "energy_vector": ENERGY_VECTOR,
123
    "installed_capacity": INSTALLED_CAP,
124
    "capacity": STORAGE_CAPACITY,
125
    # "input_bus_name": INFLOW_DIRECTION,
126
    # "output_bus_name": OUTFLOW_DIRECTION,
127
    "input_power": INPUT_POWER,
128
    "output_power": OUTPUT_POWER,
129
    "optimize_capacity": OPTIMIZE_CAP,
130
    "optimized_add_cap": OPTIMIZED_ADD_CAP,
131
    "maximum_capacity": MAXIMUM_CAP,
132
    "maximum_add_cap": MAXIMUM_ADD_CAP,
133
    "input_timeseries": TIMESERIES,
134
    "constraints": CONSTRAINTS,
135
    "renewable_asset": RENEWABLE_ASSET_BOOL,
136
    KPI: KPI,
137
    FIX_COST: FIX_COST,
138
    "time_step": TIMESTEP,
139
    "data": VALUE,
140
    "replacement_costs_during_project_lifetime": COST_REPLACEMENT,
141
    "specific_replacement_costs_of_installed_capacity": SPECIFIC_REPLACEMENT_COSTS_INSTALLED,
142
    "specific_replacement_costs_of_optimized_capacity": SPECIFIC_REPLACEMENT_COSTS_OPTIMIZED,
143
    "asset_type": TYPE_ASSET,
144
    "capex_fix": DEVELOPMENT_COSTS,
145
    "capex_var": SPECIFIC_COSTS,
146
    "opex_fix": SPECIFIC_COSTS_OM,
147
    "opex_var": DISPATCH_PRICE,
148
}
149

150
MAP_MVS_EPA = {value: key for (key, value) in MAP_EPA_MVS.items()}
1✔
151

152
# Fields expected for parameters of json returned to EPA, all assets will be returned
153
EPA_PARAM_KEYS = {
1✔
154
    PROJECT_DATA: [PROJECT_ID, PROJECT_NAME, SCENARIO_ID, SCENARIO_NAME],
155
    SIMULATION_SETTINGS: [START_DATE, EVALUATED_PERIOD, TIMESTEP, OUTPUT_LP_FILE],
156
    KPI: [
157
        KPI_SCALARS_DICT,
158
        KPI_UNCOUPLED_DICT,
159
        KPI_COST_MATRIX,
160
        KPI_SCALAR_MATRIX,
161
    ],
162
    "raw_results": ["index", "columns", "data"],
163
    "simulation_results": ["logs"],
164
}
165

166
# Fields expected for assets' parameters of json returned to EPA
167
EPA_ASSET_KEYS = {
1✔
168
    ENERGY_PROVIDERS: [
169
        "unique_id",
170
        "asset_type",
171
        LABEL,
172
        OEMOF_ASSET_TYPE,
173
        "energy_vector",
174
        INFLOW_DIRECTION,
175
        OUTFLOW_DIRECTION,
176
        CONNECTED_CONSUMPTION_SOURCE,
177
        CONNECTED_FEEDIN_SINK,
178
        DEVELOPMENT_COSTS,
179
        DISPATCH_PRICE,
180
        ENERGY_PRICE,
181
        FEEDIN_TARIFF,
182
        "installed_capacity",
183
        LIFETIME,
184
        "optimize_capacity",
185
        PEAK_DEMAND_PRICING,
186
        PEAK_DEMAND_PRICING_PERIOD,
187
        RENEWABLE_SHARE_DSO,
188
        SPECIFIC_COSTS,
189
        SPECIFIC_COSTS_OM,
190
        UNIT,
191
        FLOW,
192
    ],
193
    ENERGY_CONSUMPTION: [
194
        "unique_id",
195
        "asset_type",
196
        LABEL,
197
        INFLOW_DIRECTION,
198
        OEMOF_ASSET_TYPE,
199
        DEVELOPMENT_COSTS,
200
        DISPATCH_PRICE,
201
        "installed_capacity",
202
        LIFETIME,
203
        SPECIFIC_COSTS,
204
        SPECIFIC_COSTS_OM,
205
        "energy_vector",
206
        FLOW,
207
    ],
208
    ENERGY_CONVERSION: [
209
        "unique_id",
210
        "asset_type",
211
        LABEL,
212
        "energy_vector",
213
        OEMOF_ASSET_TYPE,
214
        INFLOW_DIRECTION,
215
        OUTFLOW_DIRECTION,
216
        AGE_INSTALLED,
217
        DEVELOPMENT_COSTS,
218
        DISPATCH_PRICE,
219
        EFFICIENCY,
220
        "installed_capacity",
221
        LIFETIME,
222
        "optimize_capacity",
223
        "optimized_add_cap",
224
        SPECIFIC_COSTS,
225
        SPECIFIC_COSTS_OM,
226
        FLOW,
227
    ],
228
    ENERGY_PRODUCTION: [
229
        "unique_id",
230
        "asset_type",
231
        LABEL,
232
        OEMOF_ASSET_TYPE,
233
        OUTFLOW_DIRECTION,
234
        DEVELOPMENT_COSTS,
235
        DISPATCH_PRICE,
236
        DISPATCHABILITY,
237
        "installed_capacity",
238
        LIFETIME,
239
        "maximum_capacity",
240
        "maximum_add_cap",
241
        "optimize_capacity",
242
        "optimized_add_cap",
243
        SPECIFIC_COSTS,
244
        SPECIFIC_COSTS_OM,
245
        AGE_INSTALLED,
246
        "renewable_asset",
247
        "energy_vector",
248
        FLOW,
249
    ],
250
    ENERGY_STORAGE: [
251
        "unique_id",
252
        "asset_type",
253
        LABEL,
254
        "energy_vector",
255
        INFLOW_DIRECTION,
256
        OUTFLOW_DIRECTION,
257
        OEMOF_ASSET_TYPE,
258
        INPUT_POWER,
259
        OUTPUT_POWER,
260
        STORAGE_CAPACITY,
261
        "optimize_capacity",
262
        "optimized_add_cap",
263
        TIMESERIES_SOC,
264
    ],
265
    ENERGY_BUSSES: [LABEL, "assets", "energy_vector"],
266
}
267

268

269
def convert_epa_params_to_mvs(epa_dict):
1✔
270
    """Convert the EPA output parameters to MVS input parameters
271

272
    Parameters
273
    ----------
274
    epa_dict: dict
275
        parameters from EPA user interface
276

277
    Returns
278
    -------
279
    dict_values: dict
280
        MVS json file, generated from EPA inputs, to be provided as MVS input
281

282
    Notes
283
    -----
284

285
    - For `simulation_settings`:
286
        - parameter `TIMESTEP` is parsed as unit-value pair
287
        - `OUTPUT_LP_FILE` is set to `False` by default
288
    - For `project_data`: parameter `SCENARIO_DESCRIPTION` is defined as placeholder string.
289
    - `fix_cost` is not required, default value will be set if it is not provided.
290
    - For missing asset group `CONSTRAINTS` following parameters are added:
291
        - MINIMAL_RENEWABLE_FACTOR: 0
292
        - MAXIMUM_EMISSIONS: None
293
        - MINIMAL_DEGREE_OF_AUTONOMY: 0
294
        - NET_ZERO_ENERGY: False
295
    - `ENERGY_STORAGE` assets:
296
        - Optimize cap written to main asset and removed from subassets
297
        - Units defined automatically (assumed: electricity system)
298
        - `SOC_INITIAL`: None
299
        - `THERM_LOSSES_REL`: 0
300
        - `THERM_LOSSES_ABS`: 0
301
    - If `TIMESERIES` parameter in asset dictionary: Redefine unit, value and label.
302
    - `ENERGY_PROVIDERS`:
303
        - Auto-define unit as kWh(el)
304
        - `INFLOW_DIRECTION=OUTFLOW_DIRECTION`
305
        - Default value for `EMISSION_FACTOR` added
306
    - `ENERGY_CONSUMPTION`:
307
        - `DSM` is `False`
308
        - `DISPATCHABILITY` is FALSE
309
    - `ENERGY_PRODUCTION`:
310
        - Default value for `EMISSION_FACTOR` added
311
        - `DISPATCHABILITY` is always `False`, as no dispatchable fuel assets possible right now. Must be tackeld by EPA.
312
    """
UNCOV
313
    epa_dict = deepcopy(epa_dict)
×
UNCOV
314
    dict_values = {}
×
315

316
    # Loop though one-dimensional energy system data (parameters directly in group)
317
    # Warnings for missing param_groups, will result in fatal error (except for fix_cost) as they can not be replaced with default values
UNCOV
318
    for param_group in [
×
319
        PROJECT_DATA,
320
        ECONOMIC_DATA,
321
        SIMULATION_SETTINGS,
322
        CONSTRAINTS,
323
        FIX_COST,
324
    ]:
325

326
        if MAP_MVS_EPA[param_group] in epa_dict:
×
327
            # Write entry of EPA to MVS json file
UNCOV
328
            dict_values[param_group] = epa_dict[MAP_MVS_EPA[param_group]]
×
329

330
            # convert fields names from EPA convention to MVS convention, if applicable
331
            keys_list = list(dict_values[param_group].keys())
×
332
            for k in keys_list:
×
UNCOV
333
                if k in MAP_EPA_MVS:
×
UNCOV
334
                    dict_values[param_group][MAP_EPA_MVS[k]] = dict_values[
×
335
                        param_group
336
                    ].pop(k)
337

338
            if param_group == SIMULATION_SETTINGS:
×
339
                timestep = dict_values[param_group].get(TIMESTEP)
×
UNCOV
340
                if timestep is not None:
×
UNCOV
341
                    dict_values[param_group][TIMESTEP] = {
×
342
                        UNIT: "min",
343
                        VALUE: timestep,
344
                    }
345
                # by default the lp file will not be outputted
346
                output_lp_file = dict_values[param_group].get(OUTPUT_LP_FILE)
×
UNCOV
347
                if output_lp_file is None:
×
UNCOV
348
                    dict_values[param_group][OUTPUT_LP_FILE] = {
×
349
                        UNIT: TYPE_BOOL,
350
                        VALUE: False,
351
                    }
352
                else:
UNCOV
353
                    dict_values[param_group][OUTPUT_LP_FILE] = {
×
354
                        UNIT: TYPE_BOOL,
355
                        VALUE: True if output_lp_file == "true" else False,
356
                    }
357

358
            if param_group == PROJECT_DATA:
×
UNCOV
359
                if SCENARIO_DESCRIPTION not in dict_values[param_group]:
×
UNCOV
360
                    dict_values[param_group][
×
361
                        SCENARIO_DESCRIPTION
362
                    ] = "[No scenario description available]"
363

364
        else:
UNCOV
365
            logging.warning(
×
366
                f"The parameters group '{MAP_MVS_EPA[param_group]}' is not present in the EPA parameters to be parsed into MVS json format"
367
            )
368

369
    # Loop through energy system asset groups and their assets
370
    # Logging warning message for missing asset groups, will not raise error if an asset group does not contain any assets
UNCOV
371
    for asset_group in [
×
372
        ENERGY_CONSUMPTION,
373
        ENERGY_CONVERSION,
374
        ENERGY_PRODUCTION,
375
        ENERGY_STORAGE,
376
        ENERGY_BUSSES,
377
        ENERGY_PROVIDERS,
378
    ]:
379
        if MAP_MVS_EPA[asset_group] in epa_dict:
×
UNCOV
380
            dict_asset = {}
×
381
            for asset in epa_dict[MAP_MVS_EPA[asset_group]]:
×
382

383
                asset_label = asset[LABEL]
×
UNCOV
384
                dict_asset[asset_label] = asset
×
UNCOV
385
                asset_keys = list(dict_asset[asset_label].keys())
×
386

387
                # change EPA style keys of an asset to MVS style ones
388
                for k in asset_keys:
×
389

UNCOV
390
                    if k in MAP_EPA_MVS:
×
UNCOV
391
                        dict_asset[asset_label][MAP_EPA_MVS[k]] = dict_asset[
×
392
                            asset_label
393
                        ].pop(k)
394

395
                    # for energy_storage there is an extra indentation level
UNCOV
396
                    if asset_group == ENERGY_STORAGE:
×
UNCOV
397
                        if k in (
×
398
                            MAP_MVS_EPA[STORAGE_CAPACITY],
399
                            MAP_MVS_EPA[INPUT_POWER],
400
                            MAP_MVS_EPA[OUTPUT_POWER],
401
                        ):
UNCOV
402
                            subasset = dict_asset[asset_label][MAP_EPA_MVS[k]]
×
403
                            subasset_keys = list(subasset.keys())
×
404

405
                            for sk in subasset_keys:
×
UNCOV
406
                                if sk in MAP_EPA_MVS:
×
UNCOV
407
                                    subasset[MAP_EPA_MVS[sk]] = subasset.pop(sk)
×
408

409
                            # add unit if not provided
410
                            # TODO deal with other vectors than electricity
411
                            if UNIT not in subasset:
×
UNCOV
412
                                if k == MAP_MVS_EPA[STORAGE_CAPACITY]:
×
413
                                    subasset[UNIT] = "kWh"
×
414
                                else:
415
                                    subasset[UNIT] = "kW"
×
416
                            # set the initial value of the state of charge to None
UNCOV
417
                            if k == MAP_MVS_EPA[STORAGE_CAPACITY]:
×
418
                                subasset[SOC_INITIAL] = {VALUE: None, UNIT: TYPE_NONE}
×
419
                                # move the optimize cap property from STORAGE_CAPACITY to the asset level
UNCOV
420
                                if OPTIMIZE_CAP in subasset:
×
NEW
421
                                    dict_asset[asset_label][OPTIMIZE_CAP] = (
×
422
                                        subasset.pop(OPTIMIZE_CAP)
423
                                    )
424

425
                # move the unit outside the timeseries dict
426
                if TIMESERIES in dict_asset[asset_label]:
×
427
                    unit = dict_asset[asset_label][TIMESERIES].pop(UNIT)
×
UNCOV
428
                    data = dict_asset[asset_label][TIMESERIES].pop(VALUE)
×
UNCOV
429
                    dict_asset[asset_label][
×
430
                        UNIT
431
                    ] = unit  # todo this is a trick, as "UNIT" was not given
UNCOV
432
                    dict_asset[asset_label][TIMESERIES][VALUE] = data
×
UNCOV
433
                    dict_asset[asset_label][TIMESERIES][
×
434
                        DATA_TYPE_JSON_KEY
435
                    ] = TYPE_SERIES
436

437
                if asset_group == ENERGY_CONVERSION:
×
UNCOV
438
                    if DISPATCH_PRICE not in dict_asset[asset_label]:
×
UNCOV
439
                        dict_asset[asset_label].update(
×
440
                            {DISPATCH_PRICE: {VALUE: 0, UNIT: "factor"}}
441
                        )
442
                    if DEVELOPMENT_COSTS not in dict_asset[asset_label]:
×
443
                        dict_asset[asset_label].update(
×
444
                            {DEVELOPMENT_COSTS: {VALUE: 0, UNIT: "factor"}}
445
                        )
446

447
                # TODO remove this when change has been made on EPA side
UNCOV
448
                if asset_group == ENERGY_PRODUCTION:
×
UNCOV
449
                    dict_asset[asset_label].update({DISPATCHABILITY: False})
×
450

451
                # typically DSO
UNCOV
452
                if asset_group == ENERGY_PROVIDERS:
×
453
                    # unit is not provided, so default is kWh
UNCOV
454
                    if UNIT not in dict_asset[asset_label]:
×
UNCOV
455
                        dict_asset[asset_label][UNIT] = "kWh"
×
456
                    # if inflow direction is not provided, the same as outflow direction is used
457
                    if INFLOW_DIRECTION not in dict_asset[asset_label]:
×
UNCOV
458
                        dict_asset[asset_label][INFLOW_DIRECTION] = dict_asset[
×
459
                            asset_label
460
                        ][OUTFLOW_DIRECTION]
461
                    # format the energy price and feedin tariffs as timeseries
UNCOV
462
                    for asset_param in (ENERGY_PRICE, FEEDIN_TARIFF):
×
UNCOV
463
                        param_value = dict_asset[asset_label][asset_param][VALUE]
×
UNCOV
464
                        if isinstance(param_value, list):
×
465
                            dict_asset[asset_label][asset_param][VALUE] = {
×
466
                                VALUE: param_value,
467
                                DATA_TYPE_JSON_KEY: TYPE_SERIES,
468
                            }
469

470
                # TODO remove this when change has been made on EPA side
471
                if asset_group == ENERGY_STORAGE:
×
472

UNCOV
473
                    if (
×
474
                        THERM_LOSSES_REL
475
                        not in dict_asset[asset_label][STORAGE_CAPACITY]
476
                    ):
UNCOV
477
                        dict_asset[asset_label][STORAGE_CAPACITY][THERM_LOSSES_REL] = {
×
478
                            UNIT: "factor",
479
                            VALUE: 0,
480
                        }
UNCOV
481
                    if (
×
482
                        THERM_LOSSES_ABS
483
                        not in dict_asset[asset_label][STORAGE_CAPACITY]
484
                    ):
485
                        dict_asset[asset_label][STORAGE_CAPACITY][THERM_LOSSES_ABS] = {
×
486
                            UNIT: "kWh",
487
                            VALUE: 0,
488
                        }
489

UNCOV
490
                    if OPTIMIZE_CAP not in dict_asset[asset_label]:
×
491
                        dict_asset[asset_label][OPTIMIZE_CAP] = {
×
492
                            UNIT: TYPE_BOOL,
493
                            VALUE: False,
494
                        }
495
                    else:
UNCOV
496
                        logging.warning(
×
497
                            "The optimized cap has been updated on EPA side so you can look for "
498
                            "this warning in data_parser.py and remove the warning and the 7 "
499
                            "lines of code above it as well"
500
                        )
501

502
                if asset_group == ENERGY_CONSUMPTION:
×
503
                    # DSM not used parameters, but to be sure it will be defined as False
UNCOV
504
                    if DSM not in dict_asset[asset_label]:
×
505
                        dict_asset[asset_label][DSM] = False
×
506
                    # Dispatchability of energy consumption assets always False
UNCOV
507
                    dict_asset[asset_label].update(
×
508
                        {
509
                            DISPATCHABILITY: {UNIT: TYPE_BOOL, VALUE: False},
510
                        }
511
                    )
512

UNCOV
513
                if asset_group == ENERGY_PRODUCTION or ENERGY_PROVIDERS:
×
514
                    # Emission factor only applicable for energy production assets and energy providers
515
                    if EMISSION_FACTOR not in dict_asset[asset_label]:
×
516
                        dict_asset[asset_label][EMISSION_FACTOR] = {
×
517
                            VALUE: KNOWN_EXTRA_PARAMETERS[EMISSION_FACTOR][
518
                                DEFAULT_VALUE
519
                            ]
520
                        }
521

522
            dict_values[asset_group] = dict_asset
×
523
        else:
524
            logging.info(
×
525
                f"The assets parameters '{MAP_MVS_EPA[asset_group]}' is not present in the EPA parameters to be parsed into MVS json format"
526
            )
527
            epa_dict.update({asset_group: {}})
×
528
            dict_values.update({asset_group: {}})
×
529

530
    # Check if all necessary input parameters are provided
531
    comparison = compare_input_parameters_with_reference(dict_values, set_default=True)
×
532

533
    # ToDo compare_input_parameters_with_reference() does not identify excess/missing parameters in the subassets of energyStorages.
534
    if EXTRA_PARAMETERS_KEY in comparison:
×
UNCOV
535
        warning_extra_parameters = "Following parameters are provided to the MVS that may be excess information: \n"
×
UNCOV
536
        for group in comparison[EXTRA_PARAMETERS_KEY]:
×
UNCOV
537
            warning_extra_parameters += f"- {group} ("
×
538
            for parameter in comparison[EXTRA_PARAMETERS_KEY][group]:
×
UNCOV
539
                if parameter not in [LABEL, "unique_id"]:
×
540
                    warning_extra_parameters += f"{parameter}, "
×
541
            warning_extra_parameters = warning_extra_parameters[:-2] + ") \n"
×
UNCOV
542
        logging.warning(warning_extra_parameters)
×
543

UNCOV
544
    if MISSING_PARAMETERS_KEY in comparison:
×
545
        error_msg = []
×
546

UNCOV
547
        missing_params = comparison[MISSING_PARAMETERS_KEY]
×
UNCOV
548
        if CONSTRAINTS in missing_params:
×
549

UNCOV
550
            if CONSTRAINTS not in dict_values:
×
551
                dict_values[CONSTRAINTS] = {}
×
552

553
            for missing_constraint in missing_params[CONSTRAINTS]:
×
NEW
554
                dict_values[CONSTRAINTS][missing_constraint] = (
×
555
                    DEFAULT_CONSTRAINT_VALUES[missing_constraint]
556
                )
557

UNCOV
558
            missing_params.pop(CONSTRAINTS)
×
559

UNCOV
560
        if SIMULATION_SETTINGS in missing_params:
×
561
            if (
×
562
                OUTPUT_LP_FILE in missing_params[SIMULATION_SETTINGS]
563
                and len(missing_params[SIMULATION_SETTINGS]) == 1
564
            ):
565
                dict_values[SIMULATION_SETTINGS][OUTPUT_LP_FILE] = {
×
566
                    UNIT: TYPE_BOOL,
567
                    VALUE: False,
568
                }
569
                missing_params.pop(SIMULATION_SETTINGS)
×
570

571
        if FIX_COST in missing_params:
×
UNCOV
572
            dict_values[FIX_COST] = {}
×
573
            missing_params.pop(FIX_COST)
×
574

UNCOV
575
        error_msg.append(" ")
×
UNCOV
576
        error_msg.append(" ")
×
UNCOV
577
        error_msg.append(
×
578
            "The following parameter groups and sub parameters are missing from input parameters:"
579
        )
580

UNCOV
581
        if len(missing_params.keys()) > 0:
×
582

UNCOV
583
            for asset_group in missing_params.keys():
×
584
                # Only raise an error about missing parameter if an asset group contains assets
UNCOV
585
                if len(dict_values[asset_group].keys()) > 0:
×
UNCOV
586
                    error_msg.append(asset_group)
×
UNCOV
587
                if missing_params[asset_group] is not None:
×
UNCOV
588
                    for k in missing_params[asset_group]:
×
UNCOV
589
                        error_msg.append(f"\t`{k}` parameter")
×
590

591
            raise (MissingParameterError("\n".join(error_msg)))
×
592

UNCOV
593
    return dict_values
×
594

595

596
def convert_mvs_params_to_epa(mvs_dict, verbatim=False):
1✔
597
    """Convert the MVS output parameters to EPA format
598

599
    Parameters
600
    ----------
601
    mvs_dict: dict
602
        output parameters from MVS
603

604
    Returns
605
    -------
606
    epa_dict: dict
607
        epa parameters
608

609
    """
610

611
    epa_dict = {}
×
612

613
    # manage which parameters are kept and which one are removed in epa_dict
UNCOV
614
    for param_group in EPA_PARAM_KEYS:
×
615

616
        # translate field name from mvs to epa
UNCOV
617
        param_group_epa = MAP_MVS_EPA.get(param_group, param_group)
×
618

619
        # assign the whole MVS value to the EPA field
620
        epa_dict[param_group_epa] = mvs_dict[param_group]
×
UNCOV
621
        if isinstance(epa_dict[param_group_epa], str):
×
622
            pass
×
623
        else:
UNCOV
624
            keys_list = list(epa_dict[param_group_epa].keys())
×
UNCOV
625
            for k in keys_list:
×
626
                # ditch all subfields which are not present in the EPA_PARAM_KEYS value corresponding
627
                # to the parameter group (except for CONSTRAINTS)
UNCOV
628
                if k not in EPA_PARAM_KEYS[param_group] or param_group in (
×
629
                    CONSTRAINTS,
630
                ):
UNCOV
631
                    epa_dict[param_group_epa].pop(k)
×
632
                else:
633
                    # convert fields names from MVS convention to EPA convention, if applicable
634
                    if k in MAP_MVS_EPA:
×
635
                        epa_dict[param_group_epa][MAP_MVS_EPA[k]] = epa_dict[
×
636
                            param_group_epa
637
                        ].pop(k)
638

639
                    if k == KPI_UNCOUPLED_DICT:
×
640
                        epa_dict[param_group_epa][k] = json.loads(
×
641
                            epa_dict[param_group_epa][k].to_json(orient="index")
642
                        )
643

644
                    if k in (KPI_SCALAR_MATRIX, KPI_COST_MATRIX):
×
645

UNCOV
646
                        cols = epa_dict[param_group_epa][k].columns
×
647
                        epa_dict[param_group_epa][k].columns = [
×
648
                            MAP_MVS_EPA.get(k, k) for k in cols
649
                        ]
650
                        epa_dict[param_group_epa][k] = json.loads(
×
651
                            epa_dict[param_group_epa][k]
652
                            .set_index("label")
653
                            .to_json(orient="index")
654
                        )
655

656
                    # if the parameter is of type
657
                    if k == OUTPUT_LP_FILE:
×
658
                        if epa_dict[param_group_epa][k][UNIT] == TYPE_BOOL:
×
659
                            epa_dict[param_group_epa].pop(k)
×
660

661
    # manage which assets parameters are kept and which one are removed in epa_dict
UNCOV
662
    for asset_group in EPA_ASSET_KEYS:
×
663
        list_asset = []
×
664
        for asset_label in mvs_dict[asset_group]:
×
665
            # mvs[asset_group] is a dict we want to change into a list
666

667
            # each asset is also a dict
668
            asset = mvs_dict[asset_group][asset_label]
×
669

670
            # if the asset possesses a unit field
671
            if UNIT in asset:
×
672
                unit = asset.pop(UNIT)
×
673
            else:
674
                unit = None
×
675

676
            unit_soc = None
×
677

678
            # keep the information about the dict key, but move it into the dict value
679
            asset[LABEL] = asset_label
×
680

681
            asset_keys = list(asset.keys())
×
UNCOV
682
            for k in asset_keys:
×
UNCOV
683
                if k in MAP_MVS_EPA:
×
684
                    # convert some keys MVS to EPA style according to the mapping
685
                    asset[MAP_MVS_EPA[k]] = asset.pop(k)
×
686
                # TODO change energy busses from dict to list in MVS
UNCOV
687
                if asset_group == ENERGY_BUSSES and k == ASSET_DICT:
×
688
                    asset["assets"] = list(asset.pop(k).keys())
×
689
                if asset_group == ENERGY_STORAGE:
×
UNCOV
690
                    if k in (INPUT_POWER, OUTPUT_POWER, STORAGE_CAPACITY):
×
UNCOV
691
                        asset[k] = mvs_dict[asset_group][asset_label][MAP_MVS_EPA[k]]
×
UNCOV
692
                        subasset_keys = list(asset[k].keys())
×
693

694
                        # if the asset possesses a unit field
695
                        if UNIT in asset[k]:
×
696
                            subunit = asset[k].pop(UNIT)
×
697
                            if k == STORAGE_CAPACITY:
×
698
                                unit_soc = subunit
×
699
                        else:
700
                            subunit = None
×
701

UNCOV
702
                        for sk in subasset_keys:
×
703
                            if sk in MAP_MVS_EPA:
×
704
                                # convert some keys MVS to EPA style according to the mapping
705
                                asset[k][MAP_MVS_EPA[sk]] = asset[k].pop(sk)
×
706
                        # convert pandas.Series to a timeseries dict with key DATA value list,
707
                        # move the unit inside the timeseries dict under key UNIT
708
                        if FLOW in asset[k]:
×
709
                            timeseries = asset[k][FLOW].to_list()
×
UNCOV
710
                            asset[k][FLOW] = {UNIT: subunit, VALUE: timeseries}
×
711

UNCOV
712
            if MAP_MVS_EPA[TIMESERIES] in asset:
×
UNCOV
713
                asset.pop(MAP_MVS_EPA[TIMESERIES])
×
714

715
            # convert pandas.Series to a timeseries dict with key DATA value list,
716
            # move the unit inside the timeseries dict under key UNIT
717
            if FLOW in asset:
×
718
                if isinstance(asset.get(OUTFLOW_DIRECTION, None), list):
×
719
                    timeseries = {}
×
720
                    for bus in asset[OUTFLOW_DIRECTION]:
×
721
                        timeseries[bus] = asset[FLOW][bus].to_list()
×
UNCOV
722
                    asset[FLOW] = {UNIT: unit, VALUE: timeseries}
×
723
                else:
UNCOV
724
                    timeseries = asset[FLOW].to_list()
×
725
                    asset[FLOW] = {UNIT: unit, VALUE: timeseries}
×
726

UNCOV
727
            if TIMESERIES_SOC in asset:
×
728
                timeseries = asset[TIMESERIES_SOC].to_list()
×
729
                asset[TIMESERIES_SOC] = {UNIT: unit_soc, VALUE: timeseries}
×
730

731
            # Excess sinks should not be provided to the EPA
UNCOV
732
            if "_excess" not in asset_label:
×
733
                list_asset.append(asset)
×
734

UNCOV
735
        epa_dict[MAP_MVS_EPA[asset_group]] = list_asset
×
736

737
    # verify that there are extra keys, besides the one expected by EPA data structure
738
    extra_keys = {}
×
739
    # verify that there are keys expected by the EPA which are not filled
740
    missing_keys = {}
×
741
    for asset_group in EPA_ASSET_KEYS:
×
UNCOV
742
        extra_keys[asset_group] = []
×
743
        missing_keys[asset_group] = []
×
UNCOV
744
        for asset in epa_dict[MAP_MVS_EPA[asset_group]]:
×
UNCOV
745
            asset_keys = list(asset.keys())
×
746
            # loop over the actual fields of the asset
UNCOV
747
            for k in asset_keys:
×
748
                # remove any field which is not listed under the asset_group in EPA_ASSET_KEYS
UNCOV
749
                if k not in EPA_ASSET_KEYS[asset_group]:
×
UNCOV
750
                    asset.pop(k)
×
751
                    # keep trace of this extra key
UNCOV
752
                    if k not in extra_keys[asset_group]:
×
UNCOV
753
                        extra_keys[asset_group].append((asset[LABEL], k))
×
754
            # loop over the expected fields of the asset_group in EPA_ASSET_KEYS
UNCOV
755
            for k in EPA_ASSET_KEYS[asset_group]:
×
756
                # if a field is missing in the actual asset, keep trace of it
UNCOV
757
                if k not in asset:
×
UNCOV
758
                    missing_keys[asset_group].append((asset[LABEL], k))
×
759

UNCOV
760
    if verbatim is True:
×
UNCOV
761
        print("#" * 10 + " Missing values " + "#" * 10)
×
UNCOV
762
        pp.pprint(missing_keys)
×
763

UNCOV
764
        print("#" * 10 + " Extra values " + "#" * 12)
×
UNCOV
765
        pp.pprint(extra_keys)
×
766

UNCOV
767
    return epa_dict
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc