Say I have a non-convex objective function loss that takes a np.ndarray named X whose shape is (n,) and returns a float number. The objective has many many many local minima, since it's essentially a function of np.round(X * c, 2) where c is another constant array of shape (n,).
You can imagine something like this:
def loss(X: np.ndarray) -> float:
c = np.array([0.1, 0.5, -0.8, 7.0, 0.0])
X_rounded = np.round(X * c, 2)
return rosen(X_rounded)
The linear constraint is expressed with two constant matrices (also stored as numpy's ndarray), A whose shape is (m, n) and b whose shape is (m,). I need to minimize loss with respect to X while keeping A.dot(X) == b. In addition, each element of X must be subject to 0 <= X_i <= 2, and I have a decent initial guess X0 = [1, 1, ..., 1].
I don't need a global minimum, the search can stop as soon as loss(X) <= 1. The objective is mostly written in SQL and thus absurdly slow, so I also want the optimization to terminate when loss has been evaluated ~200 times. (This is not a hard requirement, you can also terminate after the optimization has been running for say 5 minutes.)
With scipy, I can do
rv = minimize(loss,
initial_guess,
method='SLSQP',
bounds=[(0, 2)] * n,
constraints={
'type': 'eq',
'fun': lambda x: A.dot(x) - b
},
options={
'maxiter': 5
})
I'm not satisfied with this solution because the results are worse than artificial initial guesses (which are sampled around the global minimum as a smoke test), presumable due to the abundance of local minima and some numerical issues? In addition, I cannot estimate the number of objective invocations per iteration (otherwise I can bound the number of innovations by setting maxiter).
How can I do better with mystic, which is presumably more flexible?
Since I don't know what
Aandbare, I'm going to improvise. So it's not going to be exactly the same as your problem, but should be close enough.Let's set up the problem by building the loss function and the constraints. There may be a better way to build the constraint, but the following is pretty general (albeit a bit ugly):
Then try to solve for the global minimum:
That works (it's probably still not the global minimum)... but it takes some time. So, let's try a faster local solver.
Not bad. You however wanted to limit the number of evaluations of
loss, and also to be able to stop iflossis close to the minimum... so let's say stop whenloss(x) <= 120. I'll also limit the number of function evaluations to200.There's even more flexibility if you use the class interface to the solvers, but I'll leave that for another time.