• 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

94.17
/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, network
1✔
27
import oemof.solph as 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
        )
147

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

158
        return model, dict_model
1✔
159

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

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

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

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

174
        Returns
175
        -------
176

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

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

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

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

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

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

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

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

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

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

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

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

263
            graph.render()
1✔
264

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

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

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

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

281
        Returns
282
        -------
283

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

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

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

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

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

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

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

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

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

330

331
        Parameters
332
        ----------
333
        dict_values: dict
334
            All simulation inputs
335

336
        model: object
337
            oemof-solph object for energy system model
338

339
        local_energy_system: object
340
            pyomo object storing all constraints of the energy system model
341

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

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

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

383
        model.results["main"] = results_main
1✔
384
        model.results["meta"] = results_meta
1✔
385

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

401

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

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

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

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