oemof-solph
oemof-solph copied to clipboard
Features/Multiobjective Optimization
Hey there,
we have integrated a feature to solve an oemof-model using a multi-objective approach. For now only a weighted objective function approach is implemented, but other multi-objective algorithms could be integrated as well. The implementation is similar to that of the investment and nonconvex methods in using an additional attribute of Flow
instances with an associated class in options
.
Overview:
To solve a multi-objective optimisation in oemof, these new elements are introduced:
- new class
MultiObjectiveModel
in modulemodels
- with new
_add_objective()
- with new
solve()
- with new
- new class
MultiObjectiveFlow
in moduleblocks
- new class
MultiObjective
with a comfort classObjective
in moduleoptions
- new function
_multiobjective_grouping
and corresponding grouping in modulegroupings
The main idea is that flows can have different costs for different objectives by setting the multiobjective
attribute of the Flow. This consists of key-value-pairs with the name for the partial objective function and an instance of the Objective
nested class with the corresponding parameters.
Example: Adding a flow with two different costs for ecological and financial objectives:
import oemof.solph as solph
from oemof.solph.options import MultiObjective as mo
# create and add electrical source with two different prices
el_source = solph.Source(
label='el_source',
outputs={el_bus: solph.Flow(multiobjective=mo(
ecological=mo.Objective(
variable_costs=15),
financial=mo.Objective(
variable_costs=10)))})
energy_system.add(el_source_fix)
The implementation is therefore similar to the investment- and nonconvex-methods and extends the standard solph.GROUPINGS
to aggregate all objectives with the same objective function handle.
The solve()
function currently differentiates between single-objective and multi-objective optimization through the keyword argument optimization_type
. Further attributes then depend on the chosen type, e.g. objective_weights
.
Example: creating a mulitobjective Model and solving it with a weighted objective function:
# create a pyomo optimization problem
optimisation_model = solph.MultiObjectiveModel(energy_system)
# solve problem using glpk
solver_results = optimisation_model.solve(
solver='glpk',
optimization_type='weighted',
objective_weights={'ecological': 0.4,
'financial': 0.5},)
As written above: Currently only weighted objective functions are implemented, but other multi-objective algorithms can be integrated in the future as well.
Insights:
Some further technical insights:
- class
MultiObjectiveModel
- function
_add_objective()
only collects objective functions and does not buildobjective
-attribute of model instance- this faciliates solving the same model instance with e.g. different weightings or even different objectives
- adds default handle
'_standard'
for backwards compability - not previously collected objectives - e.g. from
invest
or normalvariable_costs
-attributes - are added to'_standard'
-objective
- the objective function is only build when calling
solve()
due to there being different attribute sets etc. for each type of optimization - the weighted sum approach currently uses the given weights directly without considerations for normalization or possible numeric problems
- function
- class
MultiObjectiveFlow
- adds no new constraints
- uses same calculation for objectives as
Flow
split into different objective functions
- nested class
Objective
in classMultiObjective
- currently used only to faciliate setting parameters
- could possibly be extended to include investment parameters for different objectives
- the additional grouping works the same as that for nonconvex or investment flows by checking the
multiobjective
attribute
Questions:
We chose to create a new class MultiObjectiveModel
for this feature, but also discussed integrating it into the Model
class directly. Our reasoning was that our approach would not be API-breaking and therefore preferable. We also ensured, however, that the behaviour of the MultiObjectiveModel
class defaults to that of the Model
class. Which implementation would you prefer?
Best regards,
Contributors: west-a, esske, lensum, matvanbeek, matnpy
A little heads up: This contribution is still work in progress. The code performs as expected, but we do not fulfill your guidelines on Pull-Requests yet. E.g.:
- We have not yet checked if the tests run with
tox
(we worked on it with pytest) - The documentation needs updating
- Changes must still be mentioned in
CHANGELOG.rst
- We did not yet add our names to
AUTHORS.rst
Hello @lensum! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:
- In the file
src/oemof/solph/models.py
:
Line 604:9: E303 too many blank lines (2) Line 648:10: E225 missing whitespace around operator Line 679:55: E251 unexpected spaces around keyword / parameter equals Line 679:57: E251 unexpected spaces around keyword / parameter equals Line 684:49: E225 missing whitespace around operator
Comment last updated at 2021-07-08 14:07:12 UTC
@lensum
Hey there,
I think this PR is almost ready, so I removed the "WIP" in the title. A maintainer would need to approve the running workflows. Codacy does not like the import statement for the new MultiObjectiveFlow, I think this also requires action from a maintainer. Due to bad style from our site, we cannot change the CHANGELOG.rst
or the AUTHORS.rst
without creating a merge conflict. Is there anything we need to do additionally?
Hi @lensum,
I'm sorry, I'm not a maintainer myself. But what you can / should do beforehand is merge the current version of the oemof/dev branch into your project and resolve the merge conflicts. If you are a PyCharm user, this is pretty straightforward and you can just "click" together a combined version in the merge view. For the network/flow.py
file, it seems that you are just lacking the latest additions.
Thanks @jokochems, just did that. Seems to me, the only thing left is the approval of a maintainer :+1:
Thanks for your effort.
I do understand you approach, but to represent the current feature level, other choices would be easier to maintain. In the end, it comes down to one sentence in your introduction:
other multi-objective algorithms could be integrated as well.
If you are going to implement something like this, it makes sense to explicitly add different types of cost. If this is not the case, I would opt for a solution that just adds the weighted cost:
import oemof.solph as solph
weights = {
"ecological" 0.4,
"financial": 0.6,
}
# create and add electrical source with two different prices
el_source = solph.Source(
label='el_source',
outputs={el_bus: solph.Flow(
variable_costs=(weights["ecological"] * 15
+ weights["financial"] * 10))})
energy_system.add(el_source_fix)
Thanks for your effort.
I do understand you approach, but to represent the current feature level, other choices would be easier to maintain. In the end, it comes down to one sentence in your introduction:
other multi-objective algorithms could be integrated as well.
If you are going to implement something like this, it makes sense to explicitly add different types of cost. If this is not the case, I would opt for a solution that just adds the weighted cost:
import oemof.solph as solph weights = { "ecological" 0.4, "financial": 0.6, } # create and add electrical source with two different prices el_source = solph.Source( label='el_source', outputs={el_bus: solph.Flow( variable_costs=(weights["ecological"] * 15 + weights["financial"] * 10))}) energy_system.add(el_source_fix)
Thank you for the reply, @p-snft. True, the above method would also work. Then I guess the sensible thing to do is to put this MR on hold until we've added another method?
There hasn't been any activity on this pull request in quite a while. Nonetheless, I think, it would be a good improvement to include after the release of v0.5.0
resp. v0.5.1
.
Thank you @jokochems for the reminder! I also think it would be best to wait for the release of v.0.5.0 and maybe include it in the release of v.0.5.1. However, so far the development has been a group effort (and I would like to keep it that way), so it might be difficult to keep up with the schedule for the release of v.0.5.1. We'll try!