8000 Add support for objective and constraint hessians and `jac=True` option for constraints in the scipy interface by jhelgert · Pull Request #121 · mechmotum/cyipopt · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Add support for objective and constraint hessians and jac=True option for constraints in the scipy interface #121

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

Merged
merged 25 commits into from
Aug 16, 2021

Conversation

jhelgert
Copy link
Contributor
@jhelgert jhelgert commented Jul 22, 2021

Implements #99 and #122.

  • Adds support for objective hessian and constraint Hessians through an additional hess field in the constraint dictionary. The hessian must have the signature hess(x,v) -> np.ndarray and return v[0] * Hg1_(x) + ... + v[m-1] * Hg_m(x). Here, v is the np.ndarray with shape m containing the lagrangian multipliers for the constraint and Hg_i denotes the hessian matrix of the constraint component g_i : R^N -> R.

Current limitations:

  • No support for NonlinearConstraint objects.
  • No support for objective hessian matrix-vector product hessp.
  • In case one passes the hessian for at least one constraint or the objective, we need the Hessians for all constraints and the objective.

@jhelgert jhelgert changed the title [WIP] Add support for objective and constraint hessians in the scipy interface Add support for objective and constraint hessians in the scipy interface Jul 24, 2021
@jhelgert jhelgert changed the title Add support for objective and constraint hessians in the scipy interface Add support for objective and constraint hessians and jac=True option for constraints in the scipy interface Jul 27, 2021
@moorepants
Copy link
Collaborator

@jhelgert Is this ready to be reviewed? Whenever you think it is done, let us know.

@jhelgert
Copy link
Contributor Author

@moorepants Yes, I think it's ready. However, there are some unintentional formatting changes because my code formatter seems to ignore the settings :/.

@jhelgert
Copy link
Contributor Author

@moorepants I think it's ready to be reviewed again. Thanks!

Copy link
Collaborator
@brocksam brocksam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mostly formatting changes, but a couple of questions that might warrant further discussion. Good job! Thanks!

@moorepants
Copy link
Collaborator

@jhelgert Can you merge in master so the tests at least run on linux?

jhelgert and others added 2 commits July 28, 2021 18:42
Co-authored-by: Sam Brockie <sgb39@cam.ac.uk>
@moorepants
Copy link
Collaborator

I guess appveyor is still running, just not webhooked into here anymore. Appveyor also failed: https://ci.appveyor.com/project/moorepants/cyipopt/builds/40165736

@jhelgert
Copy link
Contributor Author
jhelgert commented Jul 28, 2021

Thanks to both of you for the reviews so far! I will investigate why the last test test_minimize_ipopt_constraint_jac_true_if_scipy() fails on Linux while it passes on macOS.

PS: Appveyor fails with

Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "C:\projects\cyipopt\cyipopt\__init__.py", line 13, in <module>
    from .ipopt_wrapper import *
  File "C:\projects\cyipopt\cyipopt\ipopt_wrapper.py", line 8, in <module>
    from .scipy_interface import get_constraint_bounds_and_dimensions as cyipopt_get_constraint_bounds
ImportError: cannot import name 'get_constraint_bounds_and_dimensions'
Command exited with code 1

This was already fixed with the last commit.

@jhelgert
Copy link
Contributor Author

@moorepants All tests pass now on Linux. However, there's still one Appveyor job that fails:

https://ci.appveyor.com/project/moorepants/cyipopt/builds/40167487

@brocksam
Copy link
Collaborator

https://ci.appveyor.com/project/moorepants/cyipopt/builds/40167487

Looking at the Appveyor readout it is failing on assert np.isclose(res.get("fun"), 1.0828541) because res.get("fun") returns 1.003480116709976`. Unclear to me why this would be the case with this specific environment only.

@jhelgert
Copy link
Contributor Author
jhelgert commented Jul 29, 2021

@brocksam Thanks for the discussion! Can you please have another look? I think it should be much clearer now thanks to MemoizeJac. And as you already mentioned, we could replace evaluate_fun_with_grad() similarly in another PR.

Edit: Here's a small performance comparison (using jac=True vs evaluating the constraint function and the jacobian sequently):

import numpy as np
from scipy.optimize import rosen, rosen_der
from cyipopt import minimize_ipopt

# Tiny example

M = 2000
N = 3000

A = np.random.randn(M, N)
b = np.random.randn(M)

# Precalculate the matrix-vector products
AtA = A.T @ A
btA = b.T @ A
btb = b.T @ b
Atb = btA.T

# return the constraint value and the jacobian
def con_and_jac(x, rhs):
    AtAx = AtA @ x
    con_value = x.T @ AtAx - 2*btA @ x + btb
    jac = 2*AtAx - 2*Atb
    return con_value - rhs, jac


## Variant 1 (use jac=True in order to cache AtA @ x)
# x.T @ AtA @ x - 2*btA @ x + btb - 60000 >= 0
constr1 = {"type": "ineq", "fun": lambda x: con_and_jac(x, 60000), "jac": True}

## Variant 2 (no caching of intermediate results)
# x.T @ AtA @ x - 2*btA @ x + btb - 60000 >= 0
constr2 = {"type": "ineq", "fun": lambda x: x.T @ AtA @ x - 2*btA @ x + btb - 60000, "jac": lambda x: 2*AtA @ x - 2*Atb}

Timing

res = minimize_ipopt(rosen, x0=2.0*np.ones(N), constraints=constr1, options={'disp': 5, 'maxiter': 100})
res = minimize_ipopt(rosen, x0=2.0*np.ones(N), constraints=constr2, options={'disp': 5, 'maxiter': 100})

yields 11.2s and 15.1s on my machine.

brocksam
brocksam previously approved these changes Jul 29, 2021
@brocksam brocksam dismissed their stale review July 29, 2021 13:16

Appveryor still failing; Environment: PYTHON=C:\Miniconda37-x64, PYTHON_VERSION=3.8, PYTHON_ARCH=64, CONDA_PY=38, IPOPT=, IPOPTWINDIR=C:\projects\cyipopt\Ipopt-3.13.3-win64-msvs2019-md

@brocksam
Copy link
Collaborator

https://ci.appveyor.com/project/moorepants/cyipopt/builds/40167487

Looking at the Appveyor readout it is failing on assert np.isclose(res.get("fun"), 1.0828541) because res.get("fun") returns 1.003480116709976`. Unclear to me why this would be the case with this specific environment only.

@jhelgert this is still a problem.

@jhelgert
Copy link
Contributor Author
jhelgert commented Aug 1, 2021

I think we are getting closer. I removed the moody test_minimize_ipopt_jac_and_hessians_constraints_monotone_strategy_if_scipy() test that failed due to different results on macOS/Linux/Windows:

# macOS: 1.087746, 1.143973, 1.306229, 1.710728, 2.880089
#   Win: 1.087746, 1.143977, 1.306237, 1.710748, 2.880159
# Linux: 1.087745, 1.143968, 1.306218, 1.710698, 2.879984

Both test_minimize_ipopt_jac_and_hessians_constraints_if_scipy() and test_minimize_ipopt_hs071() also use the new constraint Hessians and the tests pass on Linux and Windows (Appveyor).

@brocksam
Copy link
Collaborator
brocksam commented Aug 1, 2021

I think we are getting closer. I removed the moody test_minimize_ipopt_jac_and_hessians_constraints_monotone_strategy_if_scipy() test that failed due to different results on macOS/Linux/Windows:

# macOS: 1.087746, 1.143973, 1.306229, 1.710728, 2.880089
#   Win: 1.087746, 1.143977, 1.306237, 1.710748, 2.880159
# Linux: 1.087745, 1.143968, 1.306218, 1.710698, 2.879984

Both test_minimize_ipopt_jac_and_hessians_constraints_if_scipy() and test_minimize_ipopt_hs071() also use the new constraint Hessians and the tests pass on Linux and Windows (Appveyor).

To make sure I've understood this correctly, you're still operating under the assumption that there is a corner case where the different mu-strategy options result in different solutions? If this is the case, should we ensure that this is indeed a quirk of Ipopt and not a quirk of CyIpopt by double checking using the C++ API before committing these changes?

@jhelgert
Copy link
Contributor Author
jhelgert commented Aug 1, 2021

To make sure I've understood this correctly, you're still operating under the assumption that there is a corner case where the different mu-strategy options result in different solutions?

Yes, exactly.

If this is the case, should we ensure that this is indeed a quirk of Ipopt and not a quirk of CyIpopt by double checking using the C++ API before committing these changes?

Before writing it in C++, I have done it quickly with CasADi which computes all gradients, jacobians and Hessians by algorithmic differentiation and interfaces different solvers including Ipopt. Here, I can reproduce the problem:

import casadi as cas

opti = cas.Opti()
x = opti.variable(5)
rosen = sum(100*(x[i+1]-x[i]**2)**2 + (1-x[i])**2 for i in range(4))
opti.minimize(rosen)
opti.subject_to(rosen - 1.0 >= 0)

opti.solver("ipopt", {}, {"mu_strategy": "adaptive"})
opti.set_initial(x, [1.3, 0.7, 0.8, 1.9, 1.2])
sol = opti.solve()

print(sol.value(x))
print(sol.value(rosen))

which gives me

[1.09022019 1.14568612 1.30965198 1.71962945 2.90683522]
1.0828541437101658

Using the default 'mu_strategy' the objective value is again 1.0000000578559098 as expected. Therefore, it should indeed be a quirk of Ipopt. Should I still double-check it again with Ipopt's C++ API?

@brocksam
Copy link
Collaborator
brocksam commented Aug 1, 2021

Excellent idea to use CasADi! As you've confirmed it's an Ipopt quirk independent of CyIpopt, that's definitely sufficient.

10000

Copy link
Collaborator
@brocksam brocksam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other than the docstring on the new test, I'm very happy with this. Thanks so much for all of your work! I'll defer to @moorepants to give final approval and make the merge.

@jhelgert
Copy link
Contributor Author

@moorepants Any objections from your side?

@moorepants
Copy link
Collaborator

I'm on vacation right now and haven't had time to do a final check. If Sam has time for a final review and merge, he can do that. Otherwise, I'll try to review in the next week if I can find a bit of time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants
0