I am trying to optimize a matrix given some bounds and constraints. When I run the minimize function with only the bounds everything works fine, but when adding the constraints I get the following error:
Exception has occurred: IndexError
SLSQP Error: the length of bounds is not compatible with that of x0.
Below is a nonsense, but minimal, example of the code not working:
from scipy.linalg import norm
from scipy.optimize import minimize
import numpy as np
def objectiveFunction(guess, targetVector) -> float:
guess.shape = (int(np.sqrt(guess.size)), int(np.sqrt(guess.size)))
attempt = np.matmul(np.array(targetVector).transpose(), guess)
assert(type(norm(attempt - targetVector)) == float)
return norm(attempt - targetVector)
def rowSumConstraint(guess) -> float:
guess.shape = (int(np.sqrt(guess.size)), int(np.sqrt(guess.size)))
guess = guess.sum(axis = 1)
ones = np.ones(guess.size)
assert(type(norm(guess - ones)) == float)
return norm(guess - ones)
size = 3
target = np.random.rand(size)
initialGuess = np.random.rand(size, size).flatten()
_bounds = tuple([(0,1) for _ in range(len(initialGuess))])
_constraints = [{'type': 'eq', 'fun': rowSumConstraint}]
print(len(_bounds))
print(len(initialGuess))
res = minimize(fun = objectiveFunction, x0 = initialGuess, args = (target), bounds = _bounds, constraints = _constraints)
The console output from the print statements is reasonably both 9. The code runs fine using only the bounds, but when the constraints are added, the bounds throw an error. I am at a loss, thank you in advance.
Edit: This was solved by letting all inputs to minimize be lists, and then converting them to arrays as necessary in the objective and constraint functions. I have no idea why this works or why it would matter to minimize.
The minimize()
function generally assumes that when it evaluates f(x)
, then x
is not modified. If you modify it, you may get strange behavior.
This is a problem, because this modifies the argument passed to it:
guess.shape = (int(np.sqrt(guess.size)), int(np.sqrt(guess.size)))
This is why removing the constraint helps - it evaluates the constraint while setting up the problem, which changes the value of x0. It's probably why converting the arrays to lists and back helps, because that introduces a copy.
A simple way to fix this is to copy the array before modifying it.
def objectiveFunction(guess, targetVector) -> float:
guess = guess.copy()
guess.shape = (int(np.sqrt(guess.size)), int(np.sqrt(guess.size)))
attempt = np.matmul(np.array(targetVector).transpose(), guess)
assert(type(norm(attempt - targetVector)) == float)
return norm(attempt - targetVector)
def rowSumConstraint(guess) -> float:
guess = guess.copy()
guess.shape = (int(np.sqrt(guess.size)), int(np.sqrt(guess.size)))
guess = guess.sum(axis = 1)
ones = np.ones(guess.size)
assert(type(norm(guess - ones)) == float)
return norm(guess - ones)