pythonoptimizationscipyscipy-optimizeminimization

Scipy Minimize throwing bounds error when constraint is added


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.


Solution

  • 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)