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

rl-institut / multi-vector-simulator / 4084543790

pending completion
4084543790

push

github

GitHub
Merge pull request #952 from rl-institut/fix/chp_component

152 of 152 new or added lines in 9 files covered. (100.0%)

5899 of 7665 relevant lines covered (76.96%)

0.77 hits per line

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

6.91
/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
    "input_bus_name": INFLOW_DIRECTION,  # TODO remove this when it is updated on EPA side
122
    "output_bus_name": OUTFLOW_DIRECTION,  # TODO remove this when it is updated on EPA side
123
    "simulation_settings": SIMULATION_SETTINGS,
124
    "energy_vector": ENERGY_VECTOR,
125
    "installed_capacity": INSTALLED_CAP,
126
    "capacity": STORAGE_CAPACITY,
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: [KPI_SCALARS_DICT, KPI_UNCOUPLED_DICT, KPI_COST_MATRIX, KPI_SCALAR_MATRIX],
157
}
158

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

261

262
def convert_epa_params_to_mvs(epa_dict):
1✔
263
    """Convert the EPA output parameters to MVS input parameters
264

265
    Parameters
266
    ----------
267
    epa_dict: dict
268
        parameters from EPA user interface
269

270
    Returns
271
    -------
272
    dict_values: dict
273
        MVS json file, generated from EPA inputs, to be provided as MVS input
274

275
    Notes
276
    -----
277

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

309
    # Loop though one-dimensional energy system data (parameters directly in group)
310
    # Warnings for missing param_groups, will result in fatal error (except for fix_cost) as they can not be replaced with default values
311
    for param_group in [
×
312
        PROJECT_DATA,
313
        ECONOMIC_DATA,
314
        SIMULATION_SETTINGS,
315
        CONSTRAINTS,
316
        FIX_COST,
317
    ]:
318

319
        if MAP_MVS_EPA[param_group] in epa_dict:
×
320
            # Write entry of EPA to MVS json file
321
            dict_values[param_group] = epa_dict[MAP_MVS_EPA[param_group]]
×
322

323
            # convert fields names from EPA convention to MVS convention, if applicable
324
            keys_list = list(dict_values[param_group].keys())
×
325
            for k in keys_list:
×
326
                if k in MAP_EPA_MVS:
×
327
                    dict_values[param_group][MAP_EPA_MVS[k]] = dict_values[
×
328
                        param_group
329
                    ].pop(k)
330

331
            if param_group == SIMULATION_SETTINGS:
×
332
                timestep = dict_values[param_group].get(TIMESTEP)
×
333
                if timestep is not None:
×
334
                    dict_values[param_group][TIMESTEP] = {
×
335
                        UNIT: "min",
336
                        VALUE: timestep,
337
                    }
338
                # by default the lp file will not be outputted
339
                output_lp_file = dict_values[param_group].get(OUTPUT_LP_FILE)
×
340
                if output_lp_file is None:
×
341
                    dict_values[param_group][OUTPUT_LP_FILE] = {
×
342
                        UNIT: TYPE_BOOL,
343
                        VALUE: False,
344
                    }
345
                else:
346
                    dict_values[param_group][OUTPUT_LP_FILE] = {
×
347
                        UNIT: TYPE_BOOL,
348
                        VALUE: True if output_lp_file == "true" else False,
349
                    }
350

351
            if param_group == PROJECT_DATA:
×
352
                if SCENARIO_DESCRIPTION not in dict_values[param_group]:
×
353
                    dict_values[param_group][
×
354
                        SCENARIO_DESCRIPTION
355
                    ] = "[No scenario description available]"
356

357
        else:
358
            logging.warning(
×
359
                f"The parameters group '{MAP_MVS_EPA[param_group]}' is not present in the EPA parameters to be parsed into MVS json format"
360
            )
361

362
    # Loop through energy system asset groups and their assets
363
    # Logging warning message for missing asset groups, will not raise error if an asset group does not contain any assets
364
    for asset_group in [
×
365
        ENERGY_CONSUMPTION,
366
        ENERGY_CONVERSION,
367
        ENERGY_PRODUCTION,
368
        ENERGY_STORAGE,
369
        ENERGY_BUSSES,
370
        ENERGY_PROVIDERS,
371
    ]:
372
        if MAP_MVS_EPA[asset_group] in epa_dict:
×
373
            dict_asset = {}
×
374
            for asset in epa_dict[MAP_MVS_EPA[asset_group]]:
×
375

376
                asset_label = asset[LABEL]
×
377
                dict_asset[asset_label] = asset
×
378
                asset_keys = list(dict_asset[asset_label].keys())
×
379

380
                # change EPA style keys of an asset to MVS style ones
381
                for k in asset_keys:
×
382

383
                    if k in MAP_EPA_MVS:
×
384
                        dict_asset[asset_label][MAP_EPA_MVS[k]] = dict_asset[
×
385
                            asset_label
386
                        ].pop(k)
387

388
                    # for energy_storage there is an extra indentation level
389
                    if asset_group == ENERGY_STORAGE:
×
390
                        if k in (
×
391
                            MAP_MVS_EPA[STORAGE_CAPACITY],
392
                            MAP_MVS_EPA[INPUT_POWER],
393
                            MAP_MVS_EPA[OUTPUT_POWER],
394
                        ):
395
                            subasset = dict_asset[asset_label][MAP_EPA_MVS[k]]
×
396
                            subasset_keys = list(subasset.keys())
×
397

398
                            for sk in subasset_keys:
×
399
                                if sk in MAP_EPA_MVS:
×
400
                                    subasset[MAP_EPA_MVS[sk]] = subasset.pop(sk)
×
401

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

418
                # move the unit outside the timeseries dict
419
                if TIMESERIES in dict_asset[asset_label]:
×
420
                    unit = dict_asset[asset_label][TIMESERIES].pop(UNIT)
×
421
                    data = dict_asset[asset_label][TIMESERIES].pop(VALUE)
×
422
                    dict_asset[asset_label][
×
423
                        UNIT
424
                    ] = unit  # todo this is a trick, as "UNIT" was not given
425
                    dict_asset[asset_label][TIMESERIES][VALUE] = data
×
426
                    dict_asset[asset_label][TIMESERIES][
×
427
                        DATA_TYPE_JSON_KEY
428
                    ] = TYPE_SERIES
429

430
                # TODO remove this when change has been made on EPA side
431
                if asset_group == ENERGY_PRODUCTION:
×
432
                    dict_asset[asset_label].update({DISPATCHABILITY: False})
×
433

434
                # typically DSO
435
                if asset_group == ENERGY_PROVIDERS:
×
436
                    # unit is not provided, so default is kWh
437
                    if UNIT not in dict_asset[asset_label]:
×
438
                        dict_asset[asset_label][UNIT] = "kWh"
×
439
                    # if inflow direction is not provided, the same as outflow direction is used
440
                    if INFLOW_DIRECTION not in dict_asset[asset_label]:
×
441
                        dict_asset[asset_label][INFLOW_DIRECTION] = dict_asset[
×
442
                            asset_label
443
                        ][OUTFLOW_DIRECTION]
444

445
                # TODO remove this when change has been made on EPA side
446
                if asset_group == ENERGY_STORAGE:
×
447

448
                    if (
×
449
                        THERM_LOSSES_REL
450
                        not in dict_asset[asset_label][STORAGE_CAPACITY]
451
                    ):
452
                        dict_asset[asset_label][STORAGE_CAPACITY][THERM_LOSSES_REL] = {
×
453
                            UNIT: "factor",
454
                            VALUE: 0,
455
                        }
456
                    if (
×
457
                        THERM_LOSSES_ABS
458
                        not in dict_asset[asset_label][STORAGE_CAPACITY]
459
                    ):
460
                        dict_asset[asset_label][STORAGE_CAPACITY][THERM_LOSSES_ABS] = {
×
461
                            UNIT: "kWh",
462
                            VALUE: 0,
463
                        }
464

465
                    if OPTIMIZE_CAP not in dict_asset[asset_label]:
×
466
                        dict_asset[asset_label][OPTIMIZE_CAP] = {
×
467
                            UNIT: TYPE_BOOL,
468
                            VALUE: False,
469
                        }
470
                    else:
471
                        logging.warning(
×
472
                            "The optimized cap has been updated on EPA side so you can look for "
473
                            "this warning in data_parser.py and remove the warning and the 7 "
474
                            "lines of code above it as well"
475
                        )
476

477
                if asset_group == ENERGY_CONSUMPTION:
×
478
                    # DSM not used parameters, but to be sure it will be defined as False
479
                    if DSM not in dict_asset[asset_label]:
×
480
                        dict_asset[asset_label][DSM] = False
×
481
                    # Dispatchability of energy consumption assets always False
482
                    dict_asset[asset_label].update(
×
483
                        {DISPATCHABILITY: {UNIT: TYPE_BOOL, VALUE: False},}
484
                    )
485

486
                if asset_group == ENERGY_PRODUCTION or ENERGY_PROVIDERS:
×
487
                    # Emission factor only applicable for energy production assets and energy providers
488
                    if EMISSION_FACTOR not in dict_asset[asset_label]:
×
489
                        dict_asset[asset_label][EMISSION_FACTOR] = {
×
490
                            VALUE: KNOWN_EXTRA_PARAMETERS[EMISSION_FACTOR][
491
                                DEFAULT_VALUE
492
                            ]
493
                        }
494

495
            dict_values[asset_group] = dict_asset
×
496
        else:
497
            logging.info(
×
498
                f"The assets parameters '{MAP_MVS_EPA[asset_group]}' is not present in the EPA parameters to be parsed into MVS json format"
499
            )
500
            epa_dict.update({asset_group: {}})
×
501
            dict_values.update({asset_group: {}})
×
502

503
    # Check if all necessary input parameters are provided
504
    comparison = compare_input_parameters_with_reference(dict_values, set_default=True)
×
505

506
    # ToDo compare_input_parameters_with_reference() does not identify excess/missing parameters in the subassets of energyStorages.
507
    if EXTRA_PARAMETERS_KEY in comparison:
×
508
        warning_extra_parameters = "Following parameters are provided to the MVS that may be excess information: \n"
×
509
        for group in comparison[EXTRA_PARAMETERS_KEY]:
×
510
            warning_extra_parameters += f"- {group} ("
×
511
            for parameter in comparison[EXTRA_PARAMETERS_KEY][group]:
×
512
                if parameter not in [LABEL, "unique_id"]:
×
513
                    warning_extra_parameters += f"{parameter}, "
×
514
            warning_extra_parameters = warning_extra_parameters[:-2] + ") \n"
×
515
        logging.warning(warning_extra_parameters)
×
516

517
    if MISSING_PARAMETERS_KEY in comparison:
×
518
        error_msg = []
×
519

520
        missing_params = comparison[MISSING_PARAMETERS_KEY]
×
521
        if CONSTRAINTS in missing_params:
×
522

523
            if CONSTRAINTS not in dict_values:
×
524
                dict_values[CONSTRAINTS] = {}
×
525

526
            for missing_constraint in missing_params[CONSTRAINTS]:
×
527
                dict_values[CONSTRAINTS][
×
528
                    missing_constraint
529
                ] = DEFAULT_CONSTRAINT_VALUES[missing_constraint]
530

531
            missing_params.pop(CONSTRAINTS)
×
532

533
        if SIMULATION_SETTINGS in missing_params:
×
534
            if (
×
535
                OUTPUT_LP_FILE in missing_params[SIMULATION_SETTINGS]
536
                and len(missing_params[SIMULATION_SETTINGS]) == 1
537
            ):
538
                dict_values[SIMULATION_SETTINGS][OUTPUT_LP_FILE] = {
×
539
                    UNIT: TYPE_BOOL,
540
                    VALUE: False,
541
                }
542
                missing_params.pop(SIMULATION_SETTINGS)
×
543

544
        if FIX_COST in missing_params:
×
545
            dict_values[FIX_COST] = {}
×
546
            missing_params.pop(FIX_COST)
×
547

548
        error_msg.append(" ")
×
549
        error_msg.append(" ")
×
550
        error_msg.append(
×
551
            "The following parameter groups and sub parameters are missing from input parameters:"
552
        )
553

554
        if len(missing_params.keys()) > 0:
×
555

556
            for asset_group in missing_params.keys():
×
557
                # Only raise an error about missing parameter if an asset group contains assets
558
                if len(dict_values[asset_group].keys()) > 0:
×
559
                    error_msg.append(asset_group)
×
560
                if missing_params[asset_group] is not None:
×
561
                    for k in missing_params[asset_group]:
×
562
                        error_msg.append(f"\t`{k}` parameter")
×
563

564
            raise (MissingParameterError("\n".join(error_msg)))
×
565

566
    return dict_values
×
567

568

569
def convert_mvs_params_to_epa(mvs_dict, verbatim=False):
1✔
570
    """Convert the MVS output parameters to EPA format
571

572
    Parameters
573
    ----------
574
    mvs_dict: dict
575
        output parameters from MVS
576

577
    Returns
578
    -------
579
    epa_dict: dict
580
        epa parameters
581

582
    """
583

584
    epa_dict = {}
×
585

586
    # manage which parameters are kept and which one are removed in epa_dict
587
    for param_group in EPA_PARAM_KEYS:
×
588

589
        # translate field name from mvs to epa
590
        param_group_epa = MAP_MVS_EPA[param_group]
×
591

592
        # assign the whole MVS value to the EPA field
593
        epa_dict[param_group_epa] = mvs_dict[param_group]
×
594

595
        keys_list = list(epa_dict[param_group_epa].keys())
×
596
        for k in keys_list:
×
597
            # ditch all subfields which are not present in the EPA_PARAM_KEYS value corresponding
598
            # to the parameter group (except for CONSTRAINTS)
599
            if k not in EPA_PARAM_KEYS[param_group] or param_group in (CONSTRAINTS,):
×
600
                epa_dict[param_group_epa].pop(k)
×
601
            else:
602
                # convert fields names from MVS convention to EPA convention, if applicable
603
                if k in MAP_MVS_EPA:
×
604
                    epa_dict[param_group_epa][MAP_MVS_EPA[k]] = epa_dict[
×
605
                        param_group_epa
606
                    ].pop(k)
607

608
                if k == KPI_UNCOUPLED_DICT:
×
609
                    epa_dict[param_group_epa][k] = json.loads(
×
610
                        epa_dict[param_group_epa][k].to_json(orient="index")
611
                    )
612

613
                if k in (KPI_SCALAR_MATRIX, KPI_COST_MATRIX):
×
614

615
                    cols = epa_dict[param_group_epa][k].columns
×
616
                    epa_dict[param_group_epa][k].columns = [
×
617
                        MAP_MVS_EPA.get(k, k) for k in cols
618
                    ]
619
                    epa_dict[param_group_epa][k] = json.loads(
×
620
                        epa_dict[param_group_epa][k]
621
                        .set_index("label")
622
                        .to_json(orient="index")
623
                    )
624

625
                # if the parameter is of type
626
                if k == OUTPUT_LP_FILE:
×
627
                    if epa_dict[param_group_epa][k][UNIT] == TYPE_BOOL:
×
628
                        epa_dict[param_group_epa].pop(k)
×
629

630
    # manage which assets parameters are kept and which one are removed in epa_dict
631
    for asset_group in EPA_ASSET_KEYS:
×
632
        list_asset = []
×
633
        for asset_label in mvs_dict[asset_group]:
×
634
            # mvs[asset_group] is a dict we want to change into a list
635

636
            # each asset is also a dict
637
            asset = mvs_dict[asset_group][asset_label]
×
638

639
            # if the asset possesses a unit field
640
            if UNIT in asset:
×
641
                unit = asset.pop(UNIT)
×
642
            else:
643
                unit = None
×
644

645
            unit_soc = None
×
646

647
            # keep the information about the dict key, but move it into the dict value
648
            asset[LABEL] = asset_label
×
649

650
            asset_keys = list(asset.keys())
×
651
            for k in asset_keys:
×
652
                if k in MAP_MVS_EPA:
×
653
                    # convert some keys MVS to EPA style according to the mapping
654
                    asset[MAP_MVS_EPA[k]] = asset.pop(k)
×
655
                # TODO change energy busses from dict to list in MVS
656
                if asset_group == ENERGY_BUSSES and k == ASSET_DICT:
×
657
                    asset["assets"] = list(asset.pop(k).keys())
×
658
                if asset_group == ENERGY_STORAGE:
×
659
                    if k in (INPUT_POWER, OUTPUT_POWER, STORAGE_CAPACITY):
×
660
                        asset[k] = mvs_dict[asset_group][asset_label][MAP_MVS_EPA[k]]
×
661
                        subasset_keys = list(asset[k].keys())
×
662

663
                        # if the asset possesses a unit field
664
                        if UNIT in asset[k]:
×
665
                            subunit = asset[k].pop(UNIT)
×
666
                            if k == STORAGE_CAPACITY:
×
667
                                unit_soc = subunit
×
668
                        else:
669
                            subunit = None
×
670

671
                        for sk in subasset_keys:
×
672
                            if sk in MAP_MVS_EPA:
×
673
                                # convert some keys MVS to EPA style according to the mapping
674
                                asset[k][MAP_MVS_EPA[sk]] = asset[k].pop(sk)
×
675
                        # convert pandas.Series to a timeseries dict with key DATA value list,
676
                        # move the unit inside the timeseries dict under key UNIT
677
                        if FLOW in asset[k]:
×
678
                            timeseries = asset[k][FLOW].to_list()
×
679
                            asset[k][FLOW] = {UNIT: subunit, VALUE: timeseries}
×
680

681
            if MAP_MVS_EPA[TIMESERIES] in asset:
×
682
                asset.pop(MAP_MVS_EPA[TIMESERIES])
×
683

684
            # convert pandas.Series to a timeseries dict with key DATA value list,
685
            # move the unit inside the timeseries dict under key UNIT
686
            if FLOW in asset:
×
687
                if isinstance(asset.get(MAP_MVS_EPA[OUTFLOW_DIRECTION], None), list):
×
688
                    timeseries = {}
×
689
                    for bus in asset[MAP_MVS_EPA[OUTFLOW_DIRECTION]]:
×
690
                        timeseries[bus] = asset[FLOW][bus].to_list()
×
691
                    asset[FLOW] = {UNIT: unit, VALUE: timeseries}
×
692
                else:
693
                    timeseries = asset[FLOW].to_list()
×
694
                    asset[FLOW] = {UNIT: unit, VALUE: timeseries}
×
695

696
            if TIMESERIES_SOC in asset:
×
697
                timeseries = asset[TIMESERIES_SOC].to_list()
×
698
                asset[TIMESERIES_SOC] = {UNIT: unit_soc, VALUE: timeseries}
×
699

700
            # Excess sinks should not be provided to the EPA
701
            if "_excess" not in asset_label:
×
702
                list_asset.append(asset)
×
703

704
        epa_dict[MAP_MVS_EPA[asset_group]] = list_asset
×
705

706
    # verify that there are extra keys, besides the one expected by EPA data structure
707
    extra_keys = {}
×
708
    # verify that there are keys expected by the EPA which are not filled
709
    missing_keys = {}
×
710
    for asset_group in EPA_ASSET_KEYS:
×
711
        extra_keys[asset_group] = []
×
712
        missing_keys[asset_group] = []
×
713
        for asset in epa_dict[MAP_MVS_EPA[asset_group]]:
×
714
            asset_keys = list(asset.keys())
×
715
            # loop over the actual fields of the asset
716
            for k in asset_keys:
×
717
                # remove any field which is not listed under the asset_group in EPA_ASSET_KEYS
718
                if k not in EPA_ASSET_KEYS[asset_group]:
×
719
                    asset.pop(k)
×
720
                    # keep trace of this extra key
721
                    if k not in extra_keys[asset_group]:
×
722
                        extra_keys[asset_group].append((asset[LABEL], k))
×
723
            # loop over the expected fields of the asset_group in EPA_ASSET_KEYS
724
            for k in EPA_ASSET_KEYS[asset_group]:
×
725
                # if a field is missing in the actual asset, keep trace of it
726
                if k not in asset:
×
727
                    missing_keys[asset_group].append((asset[LABEL], k))
×
728

729
    if verbatim is True:
×
730
        print("#" * 10 + " Missing values " + "#" * 10)
×
731
        pp.pprint(missing_keys)
×
732

733
        print("#" * 10 + " Extra values " + "#" * 12)
×
734
        pp.pprint(extra_keys)
×
735

736
    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