-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Features/Multiobjective Optimization #756
base: dev
Are you sure you want to change the base?
Conversation
Hello @lensum! Thanks for updating this PR. We checked the lines you've touched for PEP 8 issues, and found:
Comment last updated at 2021-07-08 14:07:12 UTC |
src/oemof/solph/models.py
Outdated
def _add_parent_block_sets(self): | ||
"""""" | ||
# set with all nodes | ||
self.NODES = po.Set(initialize=[n for n in self.es.nodes]) | ||
|
||
# pyomo set for timesteps of optimization problem | ||
self.TIMESTEPS = po.Set( | ||
initialize=range(len(self.es.timeindex)), ordered=True | ||
) | ||
|
||
# previous timesteps | ||
previous_timesteps = [x - 1 for x in self.TIMESTEPS] | ||
previous_timesteps[0] = self.TIMESTEPS.last() | ||
|
||
self.previous_timesteps = dict(zip(self.TIMESTEPS, previous_timesteps)) | ||
|
||
# pyomo set for all flows in the energy system graph | ||
self.FLOWS = po.Set( | ||
initialize=self.flows.keys(), ordered=True, dimen=2 | ||
) | ||
|
||
self.BIDIRECTIONAL_FLOWS = po.Set( | ||
initialize=[ | ||
k | ||
for (k, v) in self.flows.items() | ||
if hasattr(v, "bidirectional") | ||
], | ||
ordered=True, | ||
dimen=2, | ||
within=self.FLOWS, | ||
) | ||
|
||
self.UNIDIRECTIONAL_FLOWS = po.Set( | ||
initialize=[ | ||
k | ||
for (k, v) in self.flows.items() | ||
if not hasattr(v, "bidirectional") | ||
], | ||
ordered=True, | ||
dimen=2, | ||
within=self.FLOWS, | ||
) | ||
|
||
def _add_parent_block_variables(self): | ||
"""""" | ||
|
||
self.flow = po.Var(self.FLOWS, self.TIMESTEPS, within=po.Reals) | ||
|
||
for (o, i) in self.FLOWS: | ||
if self.flows[o, i].nominal_value is not None: | ||
if self.flows[o, i].fix[self.TIMESTEPS[1]] is not None: | ||
for t in self.TIMESTEPS: | ||
self.flow[o, i, t].value = ( | ||
self.flows[o, i].fix[t] | ||
* self.flows[o, i].nominal_value | ||
) | ||
self.flow[o, i, t].fix() | ||
else: | ||
for t in self.TIMESTEPS: | ||
self.flow[o, i, t].setub( | ||
self.flows[o, i].max[t] | ||
* self.flows[o, i].nominal_value | ||
) | ||
|
||
if not self.flows[o, i].nonconvex: | ||
for t in self.TIMESTEPS: | ||
self.flow[o, i, t].setlb( | ||
self.flows[o, i].min[t] | ||
* self.flows[o, i].nominal_value | ||
) | ||
elif (o, i) in self.UNIDIRECTIONAL_FLOWS: | ||
for t in self.TIMESTEPS: | ||
self.flow[o, i, t].setlb(0) | ||
else: | ||
if (o, i) in self.UNIDIRECTIONAL_FLOWS: | ||
for t in self.TIMESTEPS: | ||
self.flow[o, i, t].setlb(0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did you actually change something compared to the BaseModel class? If not, we would not need this code, because the methods are provided by the BaseClass, aren't they? (long time doing sub-classing coding in Python)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is an addition compared to the BaseModel class, yes, but not compared to the Model class. So I think the MultiObjectiveModel class should inherit from the Model class instead of the BaseModel class to reduce code duplication.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, quite a nice feature and a bit related to the pending multiperiod feature #750.
It seems, the only difference here is amending the CONSTRAINT_GROUPS list by block.MultiObjectiveFlow. So letting the class inherit from the Model class seems like a good idea and would save you redefining the methods already given there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependency is now fixed, together with a few linting errors. Codacy throws an error because of an import
statement which it should ignore. I don't really know what to do there, suggestions are highly appreciated.
Other than that, all pipelines are passed.
Altering the AUTHORS.rst
and CHANGELOG.rst
created a merge conflict, so I reverted that commit. I will try to alter those files one by one, to see whether only one of them creates a merge conflict and then go on from there.
…reduce code duplication
Dies reverted den Commit ce21ef0.
* Adapted test_multiobjective/test_multiobjective.py to adhere to pep8 regarding linebreaks around operators * removed unnecessary check for solver status
Removed unneeded imports and variables
* Corrected import order in tests * Added missing documentation in Flow class * Changed module docstrings for tests
Hey there, |
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 |
…estions. Merge conflict resulted from merging upstream repository into fork.
Thanks @jokochems, just did that. Seems to me, the only thing left is the approval of a maintainer 👍 |
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:
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? |
Corrected wrong parenthesis in MultiObjective.Objective class
Added pareto()-function in MultiObjectiveModel to automatically calculate pareto frontiert based on weighted sums of objectives. Calculated weight combinations can be used to find optimal multi-objective solution.
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 |
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! |
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 inoptions
.Overview:
To solve a multi-objective optimisation in oemof, these new elements are introduced:
MultiObjectiveModel
in modulemodels
_add_objective()
solve()
MultiObjectiveFlow
in moduleblocks
MultiObjective
with a comfort classObjective
in moduleoptions
_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 theObjective
nested class with the corresponding parameters.Example: Adding a flow with two different costs for ecological and financial objectives:
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 argumentoptimization_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:
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:
MultiObjectiveModel
_add_objective()
only collects objective functions and does not buildobjective
-attribute of model instance'_standard'
for backwards compabilityinvest
or normalvariable_costs
-attributes - are added to'_standard'
-objectivesolve()
due to there being different attribute sets etc. for each type of optimizationMultiObjectiveFlow
Flow
split into different objective functionsObjective
in classMultiObjective
multiobjective
attributeQuestions:
We chose to create a new class
MultiObjectiveModel
for this feature, but also discussed integrating it into theModel
class directly. Our reasoning was that our approach would not be API-breaking and therefore preferable. We also ensured, however, that the behaviour of theMultiObjectiveModel
class defaults to that of theModel
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.:
tox
(we worked on it with pytest)CHANGELOG.rst
AUTHORS.rst