stevengj / nlopt

library for nonlinear optimization, wrapping many algorithms for global and local, constrained or unconstrained, optimization
Other
1.89k stars 588 forks source link

SLSQP declares success even if constraints are not met within specified tolerance #552

Closed arvindv92 closed 3 weeks ago

arvindv92 commented 7 months ago

My use case is a chi-square minimization with multiple nonlinear equality based constraints. I started first with derivative free COBYLA, and then switched to SLSQP with analytical gradients implemented. In both cases, I saw the solvers declaring success even when the constraints had not been with within the specified tolerance, sometimes with very large violations.

To set up a toy test with a known function, I turned to the Rosenbrock function, with a single nonlinear equality constraint (x1^2 + x2^2 = 0), with the constraint intentionally scaled by 1e10 to give a large value. In the example below, I specify a constraint tolerance of 1e-6, and ftol_rel of 1e-4. But the solver exits with a successful status code of 3, with the constraint evaluated at the minimum being 3e-5, which is larger than my specified tolerance.

When I use LN_COBYLA instead of LD_SLSQP with all else being the same, the constraint violation seen on the successful exit (code 3) is even larger, equal to 246. I see that this has been reported before also.

Any advice on how to deal with these issues would be greatly appreciated. Thank you for the work that went into creating and maintaining NLopt!

test_NLopt_Rosenbrock.py:

import nlopt
import numpy as np

def rosenbrock(x, grad):
    if grad.size > 0:
        grad[0] = -400*x[0]*(x[1]-x[0]**2) - 2*(1-x[0])
        grad[1] = 200*(x[1]-x[0]**2)
    func = (100* (x[1]-x[0]**2)**2 + (1-x[0])**2)
    print("func = ", func, "at x[0] = ", x[0], " x[1] = ", x[1])
    return func

def myconstraint(x, grad):
    if grad.size > 0:
        grad[0] = (2*x[0])*1e10
        grad[1] = (2*x[1])*1e10
    return (x[0]**2 + x[1]**2 - 4)*1e10

opt = nlopt.opt(nlopt.LD_SLSQP, 2)
opt.set_min_objective(lambda x,grad: rosenbrock(x, grad))
opt.add_equality_constraint(lambda x,grad: myconstraint(x,grad), 1e-6)
opt.set_ftol_rel(1e-4)

x_fitted = opt.optimize([2.5, 5])
minf = opt.last_optimum_value()
print("optimum at ", x_fitted[0], x_fitted[1])
print("minimum value = ", minf)
print("result code = ", opt.last_optimize_result())

print("constraint at minimum = ", myconstraint(x_fitted, np.zeros(2)))

Output:

$ python -i test_NLopt_Rosenbrock.py 
func =  158.5 at x[0] =  2.5  x[1] =  5.0
func =  146803505608115.06 at x[0] =  -1100.990000000001  x[1] =  554.0200000000002
func =  13389984184.06146 at x[0] =  -107.8490000000001  x[1] =  59.90200000000002
func =  388897.0117672333 at x[0] =  -8.534900000000011  x[1] =  10.490200000000002
func =  1295.2788393920505 at x[0] =  1.3965099999999988  x[1] =  5.5490200000000005
func =  14.422721540390352 at x[0] =  2.189481326672275  x[1] =  5.154492530091245
func =  14.422721540390352 at x[0] =  2.189481326672275  x[1] =  5.154492530091245
func =  17.743208903111793 at x[0] =  1.759845600424561  x[1] =  2.6827397072793495
func =  18.12930562844732 at x[0] =  1.4594550668037019  x[1] =  1.706710064497841
func =  22.524318901599212 at x[0] =  1.107866374815913  x[1] =  1.701843233066463
func =  0.5491729153694156 at x[0] =  1.2788871605734926  x[1] =  1.7042105712502886
func =  0.5491729153694156 at x[0] =  1.2788871605734926  x[1] =  1.7042105712502886
func =  0.06463636845785697 at x[0] =  1.2524573232214695  x[1] =  1.5656465670957493
func =  0.06255671195089194 at x[0] =  1.2499208526432233  x[1] =  1.5613209173824814
func =  0.06226224100407627 at x[0] =  1.249481549024183  x[1] =  1.561664550958314
func =  0.0622534823683728 at x[0] =  1.2493919568480023  x[1] =  1.5617361337032474
func =  0.06225348172981736 at x[0] =  1.2493919133217883  x[1] =  1.561736164313974
optimum at  1.2493919133217883 1.561736164313974
minimum value =  0.06225348172981736
result code =  3
constraint at minimum =  3.552713678800501e-05
jonmaddock commented 3 months ago

I also have this exact issue: successful termination of SLSQP, but with constraints violated (above their tolerance value). I've also reproduced the example above.

jschueller commented 3 weeks ago

duplicate of #368, fixed in #465