-
Notifications
You must be signed in to change notification settings - Fork 57
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
Improvements to error handling #286
Comments
Improvements here are welcome. It would be helpful to first draft the failures that you want to address as tests so we can see clearly the issues. Does this one not return? if (np_iRow < 0).any() or (np_iRow >= m).any():
msg = b"Invalid row indices returned from jacobianstructure"
raise ValueError(msg)
return False For example:
|
As an example, there are 12 tests in your test suite that use def ensure_invalid_option(instance):
# -12: Invalid option
# Thrown in invalid Hessian because "hessian_approximation"
# is not chosen as "limited-memory"
ensure_solve_status(instance, -12)
def ensure_invalid_number(instance):
# -13: Invalid Number Detected
ensure_solve_status(instance, -13)
def ensure_unrecoverable_exception(instance):
# -100: Unrecoverable Exception
# *Should* be returned from errors in initialization
ensure_solve_status(instance, -100) Those tests indeed capture the actual behavior of cyipopt/Ipopt. That is, some errors in the definition of the hessian structure result in a status of -12, because Ipopt concludes there is no hessian provided when cyipopt thinks there is. But that's the actual behavior, not the desired behavior. In fact, if handled by raising the error instead of just logging it, Ipopt would end with status 5 (User Requested Stop) and then cyipopt would re-raise the ValueError, which I think is the desired behavior. Here's a modification of the hs071 test case to demonstrate. Choose the case you want to run. Depending on the case, a divide by zero exception is raised in one of the 7 user-defined callbacks. All but three cases produce a user requested stop then raise a division by zero error (as I would expect), but cases 5, 6, and 7 with exceptions in the hessian callback silence the error and they are never raised; instead you get a max iteration error. That's the insidious error I talked about above: It's instructive to run the cases and see the difference in behavior in case 4 (an exception in the jacobian) and case 6 (an exception in the hessian). Case 4 produces:
while case 6 produces
Getting back to the tests above, the right thing to do in the test is |
I'd like to point out some ways that error handling could be improved (I think substantially) fairly easily in cyipopt. If there's interest, I'd be willing to take a stab at a PR that accomplishes it. It's in some ways related to issue #210, that led to the adoption of
CyIpoptEvaluationError
to signal to cyipopt to returnFalse
from a callback evaluation.There's a few places where there are constructs like this:
An error that triggers this log message also leads to an Unrecoverable Exception (
status = -100
) from Ipopt, which isn't a very good clue to what the underlying problem is. Simply replacing that code bywould be a real improvement in error handling for the user. It leads to an Ipopt message
followed by a standard error trace with a
ValueError
message indicating that the error is withjacobianstructure
. There are about 10 instances where a log with levellogging.ERROR
(and some with levellogging.DEBUG
) could be replaced with aValueError
exception and result in better messaging.There's also a fairly insidious bug in the
hessiab_cb
function. Thetry
block hasThe last part should probably be
or maybe
which is a pattern seen in other callbacks. As is, the try-except block swallows an error in the Hessian evaluation silently, and doesn't re-raise it at all. I haven't worked out the details, but the result is that in problems where there's an error in the Hessian that raises an error, the problem of course doesn't converge, and there's no indication to the user that there was an issue.
General Principle. The general principle (I think) is this: The callback functions should always return
True
, unless specifically signalled byCyIpoptEvaluationError
to returnFalse
, which is to say, Ipopt will deal with the error. When there's an error that the user doesn't specifically catch, or when cyipopt detects invalid inputs, an exception should be allowed to be stored in the__exception
attribute, and thenFalse
returned to Ipopt by the intermediate callback to stop execution, and then the exception re-raised so the user can debug their code.As I said, I'm willing to draft a PR, with tests, if there's interest in this.
The text was updated successfully, but these errors were encountered: