-
Notifications
You must be signed in to change notification settings - Fork 553
[Depends on #3701] Add tests for trivial constraints in pyomo/contrib/solver #3703
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?
Conversation
…into observer_gurobi_refactor
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #3703 +/- ##
========================================
Coverage 85.94% 85.94%
========================================
Files 892 895 +3
Lines 103018 103738 +720
========================================
+ Hits 88535 89158 +623
- Misses 14483 14580 +97
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:
|
return self.var_map[self.v2id] | ||
|
||
|
||
class GurobiDirectQuadratic(GurobiDirectBase): |
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.
Shouldn't this class be named GurobiConsistentQuadratic? In GurobiObserver this is the used name.
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'm confused. Can you point me to to the place in GurobiObserver you are referring to?
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 referring to the field opt of the class _GurobiObserver.
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.
This class does not use the observer. This one is not persistent. Maybe I am missing something?
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.
Sorry, maybe I wasn’t clear enough. From what I see, in _GurobiObserver you are requesting opt as a GurobiPersistantQuadratic, but this class doesn’t appear to be defined. However, GurobiPersistant inherits from GurobiDirectQuadratic and is what gets passed to _GurobiObserver. So either I’m missing something, or there’s a naming issue.
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.
Good catch! Thank you. The observer is expecting GurobiPersistent
not GurobiPersistentQuadratic
. I will fix that.
self._solver_model.setObjective(gurobi_expr + repn_constant, sense=sense) | ||
|
||
|
||
class _GurobiObserver(Observer): |
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.
Since this class is just calling the "same" functions on the opt field. Why not just inherits Observer in GurobiPersistantSolver?
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 has to be done this way in order for "manual mode" of the persistent solvers to work. If someone calls opt.add_constraint
directly on the solver interface, we need the observer to be updated so things stay synchronized.
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.
You’re probably right, and I might be missing something. However, I don’t see why adding Observer as a superclass of GurobiPersistent wouldn't mean the observer is always updated, since it’s essentially the same as the persistent object.
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 important thing is that the _change_detector
methods get called. This makes my head hurt a little. Let me think on it.
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.
So if we did something like
class GurobiPersistent(GurobiDirectQuadratic, Observer):
def add_constraints(self, cons):
self._change_detector.add_constraints(cons)
Then there would not be a way to call _add_constraints
. It would just be an infinte loop. If GurobiPersistent.add_constraints
gets called, it would call the _change_detector
, and the _change_detector
would again call add_constraints
on the observer, which is GurobiPersistent.add_constraints
.
That is convoluted, but did it make any sense?
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.
With the current design, this will always happen: the solver calls the detector, which calls the observer, which in turn calls the solver (Opt) again. Or am I missing something?
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.
If GurobiPersistent.add_constraints
gets called, then that will call ModelChangeDetector.add_constraints
, which will then call _GurobiObserver.add_constraints
, which then calls GurobiPersistent._add_constraints
and not GurobiPersistent.add_constraints
. Very subtle but important difference there. However, this conversation is making me realize how convoluted this is. Maybe someone has a better idea?
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.
Okay, understood! One possible fix would be to give all Observer
functions a common prefix (e.g., on_*
) and make GurobiPersistantSolver a subclass of Observer
. We could then reimplement the functions accordingly.
raise NoSolutionError() | ||
|
||
def load_vars( | ||
self, vars_to_load: Sequence[VarData] | None = None, solution_id=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.
Shouldn't we use Optional here?
Summary/Motivation:
This PR adds a test for trivial constraints (both feasible and infeasible) to the new solver tests. It also fixes some related bugs.
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: