-
Notifications
You must be signed in to change notification settings - Fork 560
Observer gurobi refactor #3698
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
base: main
Are you sure you want to change the base?
Observer gurobi refactor #3698
Conversation
…into observer_gurobi_refactor
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3698 +/- ##
==========================================
- Coverage 89.40% 86.20% -3.21%
==========================================
Files 909 911 +2
Lines 105541 105552 +11
==========================================
- Hits 94364 90996 -3368
- Misses 11177 14556 +3379
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
…into observer_gurobi_refactor
jsiirola
left a comment
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.
Some initial comments and questions. I haven't gotten through the persistent interface yet. I have lots of design questions there -- it might be helpful to discuss offline / at a dev call.
| from .gurobi_direct import GurobiDirect | ||
| from .gurobi_persistent import GurobiPersistent | ||
| from .gurobi_direct_minlp import GurobiDirectMINLP |
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.
We have had issues with relative imports; it would probably be best to convert these to absolute imports...
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.
Will do.
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.
Oh, I think this has to be a relative import. This is in the __init__.py file.
| GurobiDirectBase._gurobipy_env = None | ||
|
|
||
| @staticmethod | ||
| def env(): |
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.
We should provide a mechanism for users to construct their own Env and pass it to us. This is necessary for thing like working with a trial license.
| def env(): | |
| def env(env=None): |
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.
I agree.
| self._gurobi_vars = None | ||
| self._pyomo_vars = None |
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.
Why are these being stored on the solver interface? Direct is supposed to be non-persistent, and adding this seems to violate that principle.
This seems to only be needed for _pyomo_gurobi_var_iter(), which is in turn only needed by _mipstart. I propose that both the attributes and _pyomo_gurobi)var_iter() be removed, and instead _mipstart be updated to accept the SolutionLoader as an argument. As the SolutionLoader is responsible for remembering the mapping between Pyomo and Gurobi, that feels like the natural place to query to get the mapping within _mipstart.
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.
I like that suggestion. Will do.
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.
Is this going to work for callbacks too? They have the same issue, that they need this state (and actually more) to work... So I think there may be a bigger design question lurking here: For example, they need the gurobipy model to be somewhere they can get to: In persistent, that is currently also sitting on the solver interface.
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.
(I was originally thinking that this kind of state could be added in a contextmanager kind of style, and removed before solve is over, regardless of what happens. But that certainly might not be the right answer.)
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.
Excellent points, @emma58. I think this is worth further discussion.
| ) | ||
|
|
||
|
|
||
| def _load_suboptimal_mip_solution(solver_model, var_map, vars_to_load, solution_number): |
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.
Should this (and other loaders) be updated so that instead of var_map, it accepts an iterable of (pyomo_var, gurobi_var) tuples? That way interfaces that do not generate the mapping can still make use of this method?
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.
I think I have to iterate over it twice...
| original_solution_number = solver_model.getParamInfo('SolutionNumber')[2] | ||
| solver_model.setParam('SolutionNumber', solution_number) | ||
| gurobi_vars_to_load = [var_map[v] for v in vars_to_load] | ||
| vals = solver_model.getAttr("Xn", gurobi_vars_to_load) | ||
| res = ComponentMap(zip(vars_to_load, vals)) | ||
| solver_model.setParam('SolutionNumber', original_solution_number) |
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.
These implementations all seem to be geared around "partial loads", and are actually quite inefficient for "full loads". As I would expect that the vast majority of users / use cases will actually be doing a full load, I would suggest redesigning these methods so that full loads are efficient, and partial loads are efficient if the number of objects to load is small.
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.
What makes you say this implementation is inefficient?
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.
At least in the case of the direct interface, we already have the list of gurobi vars and pyomo vars in the order in which they were given to Gurobi, so if you are getting everything you could get away with:
return ComponentMap(zip(pyomo_vars, solver_model.getAttr('Xn')))and avoid iterating over things twice, and forming an extra list, and performing N COmponentMap lookups (plus unknown lookups within Gurobi).
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.
Let me do some profiling and see if this even matters.
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.
A good model to use in profiling is @emma58's 400M var LP.
| def invalidate(self): | ||
| self._valid = False |
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.
Doesn't the Direct interface also need to worry about invalidating the last loader before the next solve? Does this really need to be a specialization for the persistent interface?
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 direct interface solution loader should still be valid after subsequent solves, I think???
Summary/Motivation:
This PR refactors the gurobi interfaces to achieve two goals:
Changes proposed in this PR:
Legal Acknowledgement
By contributing to this software project, I have read the contribution guide and agree to the following terms and conditions for my contribution: