-
Notifications
You must be signed in to change notification settings - Fork 526
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
Add the support of GreyBox in MindtPy #2988
Conversation
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 work, Zedong! I request some changes and also some revisions from our friends that motivated this development! @adowling2 !
tmp=True, | ||
ignore_infeasible=False, | ||
tolerance=config.constraint_tolerance, | ||
) |
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 use of this transformation is new. Is it motivated by our new tests? Is the transformation well-supported? @emma58
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.
After MindtPy rewrite, we repeatedly use the fixed-nlp subproblem. Therefore, we will call 'contrib.deactivate_trivial_constraints'
transformation and revert it after solving. I missed the feasibility subproblem and this might be a bug.
- (slack_var if config.add_slack else 0) | ||
<= 0 | ||
) | ||
# TODO: gurobi_persistent currently does not support greybox model. |
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 become an 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.
Gurobi
will fail even when we deactivate the greybox
block. Therefore, we cannot use Gurobi
as the mip solver.
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 a bug in gurobi_persistent
? Why does it behave differently?
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 will submit an issue related to this.
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.
@emma58 Could you provide me a reference
example?
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.
from pyomo.environ import *
m = ConcreteModel()
m.b = Block()
m.b.x = Var(bounds=(2, 5))
m.y = Var(domain=NonNegativeReals)
m.c = Constraint(expr=m.b.x - m.y <= 4)
m.obj = Objective(expr=m.b.x + m.y)
m.b.deactivate()
m.somewhere_safe_to_keep_things = Block()
m.somewhere_safe_to_keep_things.x = Reference(m.b.x)
opt = SolverFactory('gurobi_persistent')
opt.set_instance(m)
opt.solve(m, tee=True)
This is the main idea. You'll want to figure out how to do your own accounting in terms of finding the Vars you need to reference and naming the references, etc.
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.
#3000 Issue opened
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.
@emma58 . I don't think Reference
is an elegant way to resolve this issue. I will comment the code of adding Gurobi
lazy constraints and only support greybox
in multi-tree implementation this time.
Btw, since persistent solver will be totally replaced by appsi_solver
, does appsi_solver support both CPLEX
and Gurobi
callbacks as persistent solver
now? @michaelbynum
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.
appsi_gurobi does support callbacks. appsi_cplex will, but does not currently.
The tests are failing because: |
This PR is ready for review. @emma58 |
I don't think we're ready yet, Zedong.
We are responsible for the first, and scipy taking so much longer seems to be a new thing. Let's aim to fix the first error and then we can keep trying. |
The scipy error needs to be resolved, too: it indicates that something is causing scipy to be imported when we |
Hi @jsiirola . Import code Output
|
@ZedongPeng, you want to do something like: import pyomo.common.dependencies.scipy.sparse as scipy_sparse
# then later...
return scipy_sparse.coo_matrix((data, (row, col)), shape=(1, 5)) |
The error is not inside
|
Ahh - the issue is that mindtpy is importing PyNumero (in You can see this by running
You can resolve things by deferring the import of pynumero using the from pyomo.common.dependencies import attempt_import
egb = attempt_import('pyomo.contrib.pynumero.interfaces.external_grey_box')[0] and then replace instances of |
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.
There seem to be some test failing still.
E pyomo.common.errors.DeferredImportError: The pyomo.contrib.pynumero.interfaces.external_grey_box module (an optional Pyomo dependency) failed to import: ModuleNotFoundError: No module named 'scipy' ------- generated xml file: /home/runner/work/pyomo/pyomo/TEST-pyomo.xml ------- =========================== short test summary info ============================ ERROR pyomo/contrib/mindtpy/tests/test_mindtpy.py ERROR pyomo/contrib/mindtpy/tests/test_mindtpy_ECP.py ERROR pyomo/contrib/mindtpy/tests/test_mindtpy_feas_pump.py ERROR pyomo/contrib/mindtpy/tests/test_mindtpy_grey_box.py ERROR pyomo/contrib/mindtpy/tests/test_mindtpy_lp_nlp.py !!!!!!!!!!!!!!!!!!! Interrupted: 5 errors during collection !!!!!!!!!!!!!!!!!!!! ================== 30 skipped, 5 errors in 100.53s (0:01:40) ===================
These tests fail on the slim verrsion and pypy versions (those without numpy). We are importing numpy instead of attempting that import
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 looks pretty good. A few questions/comments, and you still need to get the tests passing, which will just involve making sure you're never accidentally importing numpy or scipy via pynumero when they're not actually available.
@@ -802,18 +810,21 @@ def init_rNLP(self, add_oa_cuts=True): | |||
MindtPy unable to handle the termination condition of the relaxed NLP. | |||
""" | |||
config = self.config | |||
m = self.working_model.clone() | |||
self.rnlp = self.working_model.clone() |
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.
Do you really want to store this publicly on the solver object? If your users can find it and it doesn't start with underscore, they might start to rely on it. That's not necessarily bad--just something to think through.
# related to https://github.com/Pyomo/pyomo/issues/2363 | ||
if ( | ||
'appsi' in config.mip_solver | ||
or 'appsi' in config.nlp_solver | ||
or ( | ||
config.mip_regularization_solver is not None | ||
and 'appsi' in config.mip_regularization_solver | ||
) | ||
): | ||
config.load_solutions = 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.
This makes me think that load_solutions
shouldn't be up to the user then, since there are cases where you need to force it to be 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.
Yes. This option should not be up to the users. Is there a way to define an option (not open to users) instead of config (open to users) in Pyomo?
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's not really an option at all, just something you calculate based on some of the config options, right? You could probably store it privately on the algorithm class. Just make sure to restore state afterwards if you do keep it on the class.
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 it a good choice to change load_solutions
to the attribute of the Solver 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.
That sounds fine to me. You should probably make it private as well. The only risk of doing this is that you should be careful to restore the state after the solve.
CONFIG.declare( | ||
'load_solutions', | ||
ConfigValue( | ||
default=True, | ||
description='Whether to load solutions in solve() function', | ||
domain=bool, | ||
), | ||
) |
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.
See my comment above, but I'm not sure of why this should be up to the user. I think it would be fine (and perhaps easier to support in the future) to keep this detail locally, or maybe privately on the solver object, and manage it yourself. Especially since it looks users don't actually have a choice if they want to use appsi solvers.
for index, output in enumerate(target_model_grey_box.outputs.values()): | ||
dual_value = jacobians_model.dual[jacobian_model_grey_box][ | ||
output.name.replace("outputs", "output_constraints") | ||
] | ||
target_model.MindtPy_utils.cuts.oa_cuts.add( | ||
expr=copysign(1, sign_adjust * dual_value) | ||
* ( | ||
sum( | ||
jacobian_matrix[index][var_index] * (var - value(var)) | ||
for var_index, var in enumerate( | ||
target_model_grey_box.inputs.values() | ||
) | ||
) | ||
) | ||
- (output - value(output)) | ||
- (slack_var if config.add_slack else 0) | ||
<= 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.
Are you sure that this is always going to create a correct cut, even if greybox changes its behavior? The part that looks potentially scary to me is calling enumerate
over getting the values()
from dictionaries coming from greybox. You will get deterministic ordering from values()
as long as the dictionary is constructed in the same order every time, but if that changes, is this still right? Or does greybox make a promise about consistency with that order and the jacobian? (This is me wondering out of ignorance, but it may be worth adding a comment even if it is fine.)
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 we don't call enumerate
over values
, any suggestions about how to implement it? Directly enumerate
over variable
?
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 don't really know--you would know better than me. It's completely possible that this is fine. My concern is that you will end up with the wrong coefficient (jacobian_matrix[index][var_index]
) paired with the wrong var - value(var)
term if the target_model_greyboox.inputs
dictionary is every constructed in a different order that doesn't correspond to the indices in jacobian_matrix
. I don't know greybox well at all, so maybe it makes a promise that what I just said will never happen, in which case your code is totally fine. I'm just asking if you know if that's the case or not. It's always worth thinking carefully when you are relying on the order things come up when iterating over a dictionary...
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.
Currently, I cannot come up with a good way to improve this since the evaluate_jacobian_outputs
returns a scipy.sparse._coo.coo_matrix
.
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.
Maybe just put in a comment that you are relying on the order in which items are added to the target_model_greybox.inputs
dictionary, so that future you (or someone else) will have a hint if this ever doesn't work.
Test failures point to import of scipy when it is not available. A conditional attempt import should resolve it. |
Now all the tests passed except the Jenkins one. Could you show me the output of Jenkins? @emma58 Thanks. |
@ZedongPeng, I'm not convinced those failures are actually your fault... I just merged main, and we'll see what happens. It looks like you still have a few comments above to address though? |
Codecov ReportAttention:
Additional details and impacted files@@ Coverage Diff @@
## main #2988 +/- ##
=======================================
Coverage 87.96% 87.96%
=======================================
Files 770 770
Lines 90162 90187 +25
=======================================
+ Hits 79310 79332 +22
- Misses 10852 10855 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
I think this PR is ready to merge if all the tests pass. |
This is ready for a second review. @jsiirola, @mrmundt, @blnicho, @michaelbynum, any volunteers? |
Fixes #2767
Summary/Motivation:
This Pull Request adds the support of the Greybox in MindtPy.
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: