• 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

94.23
/src/multi_vector_simulator/D0_modelling_and_optimization.py
1
"""
2
Module D0 - Model building
3
==========================
4

5
Functional requirements of module D0:
6
- measure time needed to build model
7
- measure time needed to solve model
8
- generate energy system model for oemof
9
- create dictionary of components so that they can be used for constraints and some
10
- raise warning if component not a (in mvs defined) oemof model type
11
- add all energy conversion, energy consumption, energy production, energy storage devices model
12
- plot network graph
13
- at constraints to remote model
14
- store lp file (optional)
15
- start oemof simulation
16
- process results by giving them to the next function
17
- dump oemof results
18
- add simulation parameters to dict values
19
"""
20

21
import logging
1✔
22
import os
1✔
23
import timeit
1✔
24
import warnings
1✔
25

26
from oemof.solph import processing
1✔
27
from oemof import solph
1✔
28

29
import multi_vector_simulator.D1_model_components as D1
1✔
30
import multi_vector_simulator.D2_model_constraints as D2
1✔
31

32
from multi_vector_simulator.utils.constants import (
1✔
33
    PATH_OUTPUT_FOLDER,
34
    ES_GRAPH,
35
    PATHS_TO_PLOTS,
36
    PLOT_SANKEY,
37
    PLOTS_ES,
38
    LP_FILE,
39
)
40
from multi_vector_simulator.utils.constants_json_strings import (
1✔
41
    ENERGY_BUSSES,
42
    ENERGY_VECTOR,
43
    OEMOF_ASSET_TYPE,
44
    ACCEPTED_ASSETS_FOR_ASSET_GROUPS,
45
    OEMOF_GEN_STORAGE,
46
    OEMOF_SINK,
47
    OEMOF_SOURCE,
48
    OEMOF_TRANSFORMER,
49
    OEMOF_BUSSES,
50
    OEMOF_ExtractionTurbineCHP,
51
    VALUE,
52
    SIMULATION_SETTINGS,
53
    LABEL,
54
    TIME_INDEX,
55
    OUTPUT_LP_FILE,
56
    SIMULATION_RESULTS,
57
    OBJECTIVE_VALUE,
58
    SIMULTATION_TIME,
59
    MODELLING_TIME,
60
)
61

62
from multi_vector_simulator.utils.exceptions import (
1✔
63
    MVSOemofError,
64
    WrongOemofAssetForGroupError,
65
    UnknownOemofAssetType,
66
)
67

68

69
def run_oemof(dict_values, save_energy_system_graph=False, return_les=False):
1✔
70
    """
71
    Creates and solves energy system model generated from excel template inputs.
72
    Each component is included by calling its constructor function in D1_model_components.
73

74
    Parameters
75
    ----------
76
    dict values: dict
77
        Includes all dictionary values describing the whole project, including costs,
78
        technical parameters and components. In C0_data_processing, each component was attributed
79
        with a certain in/output bus.
80

81
    save_energy_system_graph: bool
82
        if set to True, saves a local copy of the energy system's graph
83

84
    return_les: bool
85
        if set to True, the return also includes the local_energy_system in third position
86

87
    Returns
88
    -------
89
    saves and returns oemof simulation results
90
    """
91

92
    start = timer.initalize()
1✔
93

94
    model, dict_model = model_building.initialize(dict_values)
1✔
95

96
    model = model_building.adding_assets_to_energysystem_model(
1✔
97
        dict_values, dict_model, model
98
    )
99

100
    model_building.plot_networkx_graph(
1✔
101
        dict_values, model, save_energy_system_graph=save_energy_system_graph
102
    )
103

104
    logging.debug("Creating oemof model based on created components and busses...")
1✔
105
    local_energy_system = solph.Model(model)
1✔
106
    logging.debug("Created oemof model based on created components and busses.")
1✔
107

108
    local_energy_system = D2.add_constraints(
1✔
109
        local_energy_system, dict_values, dict_model
110
    )
111
    model_building.store_lp_file(dict_values, local_energy_system)
1✔
112

113
    model, results_main, results_meta = model_building.simulating(
1✔
114
        dict_values, model, local_energy_system
115
    )
116

117
    model_building.plot_sankey_diagramm(
1✔
118
        dict_values, model, save_energy_system_graph=save_energy_system_graph
119
    )
120

121
    timer.stop(dict_values, start)
1✔
122

123
    if return_les is True:
1✔
124
        return results_meta, results_main, local_energy_system
×
125
    else:
126
        return results_meta, results_main
1✔
127

128

129
class model_building:
1✔
130
    def initialize(dict_values):
1✔
131
        """
132
        Initalization of oemof model
133

134
        Parameters
135
        ----------
136
        dict_values: dict
137
            dictionary of simulation
138

139
        Returns
140
        -------
141
        oemof energy model (oemof.solph.network.EnergySystem), dict_model which gathers the assets added to this model later.
142
        """
143
        logging.info("Initializing oemof simulation.")
1✔
144
        model = solph.EnergySystem(
1✔
145
            timeindex=dict_values[SIMULATION_SETTINGS][TIME_INDEX],
146
            infer_last_interval=True,
147
        )
148

149
        # this dictionary will include all generated oemof objects
150
        dict_model = {
1✔
151
            OEMOF_BUSSES: {},
152
            OEMOF_SINK: {},
153
            OEMOF_SOURCE: {},
154
            OEMOF_TRANSFORMER: {},
155
            OEMOF_GEN_STORAGE: {},
156
            OEMOF_ExtractionTurbineCHP: {},
157
        }
158

159
        return model, dict_model
1✔
160

161
    def adding_assets_to_energysystem_model(dict_values, dict_model, model, **kwargs):
1✔
162
        """
163

164
        Parameters
165
        ----------
166
        dict_values: dict
167
            dict of simulation data
168

169
        dict_model:
170
            Updated list of assets in the oemof energy system model
171

172
        model: oemof.solph.network.EnergySystem
173
            Model of oemof energy system
174

175
        Returns
176
        -------
177

178
        """
179
        logging.info("Adding components to oemof energy system model...")
1✔
180

181
        # Busses have to be defined first
182
        for bus in dict_values[ENERGY_BUSSES]:
1✔
183
            D1.bus(
1✔
184
                model,
185
                dict_values[ENERGY_BUSSES][bus][LABEL],
186
                energy_vector=dict_values[ENERGY_BUSSES][bus][ENERGY_VECTOR],
187
                **dict_model,
188
            )
189

190
        # Adding step by step all assets defined within the asset groups
191
        for asset_group in ACCEPTED_ASSETS_FOR_ASSET_GROUPS:
1✔
192
            if asset_group in dict_values:
1✔
193
                for asset in dict_values[asset_group]:
1✔
194
                    type = dict_values[asset_group][asset][OEMOF_ASSET_TYPE]
1✔
195
                    # Checking if the asset type is one accepted for the asset group (security measure)
196
                    if type in ACCEPTED_ASSETS_FOR_ASSET_GROUPS[asset_group]:
1✔
197
                        # if so, then the appropriate function of D1 should be called
198
                        if type == OEMOF_TRANSFORMER:
1✔
199
                            D1.transformer(
1✔
200
                                model, dict_values[asset_group][asset], **dict_model
201
                            )
202
                        elif type == OEMOF_ExtractionTurbineCHP:
1✔
UNCOV
203
                            D1.chp(model, dict_values[asset_group][asset], **dict_model)
×
204
                        elif type == OEMOF_SINK:
1✔
205
                            D1.sink(
1✔
206
                                model, dict_values[asset_group][asset], **dict_model
207
                            )
208
                        elif type == OEMOF_SOURCE:
1✔
209
                            D1.source(
1✔
210
                                model, dict_values[asset_group][asset], **dict_model
211
                            )
212
                        elif type == OEMOF_GEN_STORAGE:
1✔
213
                            D1.storage(
1✔
214
                                model, dict_values[asset_group][asset], **dict_model
215
                            )
216
                        else:
217
                            raise UnknownOemofAssetType(
1✔
218
                                f"Asset {asset} has type {type}, "
219
                                f"but this type is not a defined oemof asset type."
220
                            )
221

222
                    else:
223
                        raise WrongOemofAssetForGroupError(
1✔
224
                            f"Asset {asset} has type {type}, "
225
                            f"but this type is not an asset type attributed to asset group {asset_group}"
226
                            f" for oemof model generation."
227
                        )
228

229
        logging.debug("All components added.")
1✔
230
        return model
1✔
231

232
    def plot_networkx_graph(dict_values, model, save_energy_system_graph=False):
1✔
233
        """
234
        Plots a graph of the energy system if that graph is to be displayed or stored.
235

236
        Parameters
237
        ----------
238
        dict_values: dict
239
            All simulation inputs
240

241
        model: `oemof.solph.network.EnergySystem`
242
            oemof-solph object for energy system model
243

244
        save_energy_system_graph: bool
245
            if True, save the graph in the mvs output folder
246
            Default: False
247

248
        Returns
249
        -------
250
        None
251
        """
252
        if save_energy_system_graph is True:
1✔
253
            from multi_vector_simulator.F1_plotting import ESGraphRenderer
1✔
254

255
            fpath = os.path.join(
1✔
256
                dict_values[SIMULATION_SETTINGS][PATH_OUTPUT_FOLDER], ES_GRAPH
257
            )
258
            dict_values[PATHS_TO_PLOTS][PLOTS_ES] += str(fpath)
1✔
259

260
            # Draw the energy system model
261
            graph = ESGraphRenderer(model, filepath=fpath)
1✔
262
            logging.debug("Created graph of the energy system model.")
1✔
263

264
            graph.render()
1✔
265

266
    def plot_sankey_diagramm(dict_values, model, save_energy_system_graph=False):
1✔
267
        """
268
        Prepare a sankey diagram of the simulated energy model
269

270
        Parameters
271
        ----------
272
        dict_values: dict
273
            All simulation inputs
274

275
         model: `oemof.solph.network.EnergySystem`
276
            oemof-solph object for energy system model
277

278
        save_energy_system_graph: bool
279
            if True, save the graph in the mvs output folder
280
            Default: False
281

282
        Returns
283
        -------
284

285
        """
286
        if save_energy_system_graph is True:
1✔
UNCOV
287
            from multi_vector_simulator.F1_plotting import ESGraphRenderer
×
288

289
            graph = ESGraphRenderer(model)
×
UNCOV
290
            dict_values[PATHS_TO_PLOTS][PLOT_SANKEY] = graph.sankey(
×
291
                model.results["main"]
292
            )
293

294
    def store_lp_file(dict_values, local_energy_system):
1✔
295
        """
296
        Stores linear equation system generated with pyomo as an "lp file".
297

298
        Parameters
299
        ----------
300
        dict_values: dict
301
            All simulation input data
302

303
        local_energy_system: object
304
            pyomo object including all constraints of the energy system
305

306
        Returns
307
        -------
308
        Nothing.
309
        """
310
        if dict_values[SIMULATION_SETTINGS][OUTPUT_LP_FILE][VALUE] is True:
1✔
311
            path_lp_file = os.path.join(
1✔
312
                dict_values[SIMULATION_SETTINGS][PATH_OUTPUT_FOLDER], LP_FILE
313
            )
314
            logging.debug("Saving to lp-file.")
1✔
315
            local_energy_system.write(
1✔
316
                path_lp_file,
317
                io_options={"symbolic_solver_labels": True},
318
            )
319

320
    def simulating(dict_values, model, local_energy_system):
1✔
321
        """
322
        Initiates the oemof-solph simulation, accesses results and writes main results into dict
323

324
        If an error is encountered in the oemof solver, mvs should not be allowed to continue,
325
        otherwise other errors related to the uncomplete simulation result might occur and it will
326
        be more obscure to the endusers what went wrong.
327

328
        A MVS error is raised if the omoef solver warning states explicitely that
329
        "termination condition infeasible", otherwise the oemof solver warning is re-raised as
330
        an error.
331

332

333
        Parameters
334
        ----------
335
        dict_values: dict
336
            All simulation inputs
337

338
        model: object
339
            oemof-solph object for energy system model
340

341
        local_energy_system: object
342
            pyomo object storing all constraints of the energy system model
343

344
        Returns
345
        -------
346
        Updated model with results, main results (flows, assets) and meta results (simulation)
347
        """
348

349
        logging.info("Starting simulation.")
1✔
350
        # turn warnings into errors
351
        warnings.filterwarnings("error")
1✔
352
        warnings.filterwarnings("always", category=FutureWarning)
1✔
353
        try:
1✔
354
            local_energy_system.solve(
1✔
355
                solver="cbc",
356
                solve_kwargs={
357
                    "tee": False
358
                },  # if tee_switch is true solver messages will be displayed
359
                cmdline_options={"ratioGap": str(0.03)},
360
            )  # ratioGap allowedGap mipgap
361
        except UserWarning as e:
1✔
362
            error_message = str(e)
1✔
363
            compare_message = "termination condition infeasible"
1✔
364
            if compare_message in error_message:
1✔
365
                error_message = (
1✔
366
                    f"The following error occurred during the mvs solver: {error_message}\n\n "
367
                    f"There are several reasons why this could have happened."
368
                    "\n\t- the energy system is not properly connected. "
369
                    "\n\t- the capacity of some assets might not have been optimized. "
370
                    "\n\t- the demands might not be supplied with the installed capacities in "
371
                    "current energy system. Check your maximum power demand and if your energy "
372
                    "production assets and/or energy conversion assets have enough capacity to "
373
                    "meet the total demand"
374
                )
375
                logging.error(error_message)
1✔
376
                raise MVSOemofError(error_message) from None
1✔
377
            else:
UNCOV
378
                raise e
×
379
        # stop turning warnings into errors
380
        warnings.resetwarnings()
1✔
381

382
        # add results to the energy system to make it possible to store them.
383
        results_main = processing.results(local_energy_system)
1✔
384
        results_meta = processing.meta_results(local_energy_system)
1✔
385

386
        model.results["main"] = results_main
1✔
387
        model.results["meta"] = results_meta
1✔
388

389
        dict_values.update(
1✔
390
            {
391
                SIMULATION_RESULTS: {
392
                    LABEL: SIMULATION_RESULTS,
393
                    OBJECTIVE_VALUE: results_meta["objective"],
394
                    SIMULTATION_TIME: round(results_meta["solver"]["Time"], 2),
395
                }
396
            }
397
        )
398
        logging.info(
1✔
399
            "Simulation time: %s minutes.",
400
            round(dict_values[SIMULATION_RESULTS][SIMULTATION_TIME] / 60, 2),
401
        )
402
        return model, results_main, results_main
1✔
403

404

405
class timer:
1✔
406
    def initalize():
1✔
407
        """
408
        Starts a timer
409
        Returns
410
        -------
411
        """
412
        # Start clock to determine total simulation time
413
        start = timeit.default_timer()
1✔
414
        return start
1✔
415

416
    def stop(dict_values, start):
1✔
417
        """
418
        Ends timer and adds duration of simulation to dict_values
419
        Parameters
420
        ----------
421
        dict_values: dict
422
            Dict of simulation including SIMULATION_RESULTS key
423
        start: timestamp
424
            start time of timer
425

426
        Returns
427
        -------
428
        Simulation time in dict_values
429
        """
430
        duration = timeit.default_timer() - start
1✔
431
        dict_values[SIMULATION_RESULTS].update({MODELLING_TIME: round(duration, 2)})
1✔
432

433
        logging.info(
1✔
434
            "Modeling time: %s minutes.",
435
            round(dict_values[SIMULATION_RESULTS][MODELLING_TIME] / 60, 2),
436
        )
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