Scipy.optimize.minimize SLSQP con restricciones lineales falla

Considere el siguiente problema de optimización (convexo):

minimize 0.5 * yT * y st A*x - b == y 

donde las variables de optimización (vector) son x y y y A , b son una matriz y un vector, respectivamente, de dimensiones apropiadas.

El siguiente código encuentra una solución fácilmente utilizando el método SLSQP de Scipy:

 import numpy as np from scipy.optimize import minimize # problem dimensions: n = 10 # arbitrary integer set by user m = 2 * n # generate parameters A, b: np.random.seed(123) # for reproducibility of results A = np.random.randn(m,n) b = np.random.randn(m) # objective function: def obj(z): vy = z[n:] return 0.5 * vy.dot(vy) # constraint function: def cons(z): vx = z[:n] vy = z[n:] return A.dot(vx) - b - vy # constraints input for SLSQP: cons = ({'type': 'eq','fun': cons}) # generate a random initial estimate: z0 = np.random.randn(n+m) sol = minimize(obj, x0 = z0, constraints = cons, method = 'SLSQP', options={'disp': True}) 
 Optimization terminated successfully. (Exit mode 0) Current function value: 2.12236220865 Iterations: 6 Function evaluations: 192 Gradient evaluations: 6 

Tenga en cuenta que la función de restricción es una función conveniente ‘array-output’.

Ahora, en lugar de una función de salida de matriz para la restricción, en principio se podría usar un conjunto equivalente de funciones de restricción ‘salida escalar’ (en realidad, la documentación de scipy.optimize analiza solo este tipo de funciones de restricción como entrada para minimize ).

Aquí está el conjunto de restricciones equivalentes seguido de la salida de minimize (el mismo valor A , b y inicial que el listado anterior):

 # this is the i-th element of cons(z): def cons_i(z, i): vx = z[:n] vy = z[n:] return A[i].dot(vx) - b[i] - vy[i] # listable of scalar-output constraints input for SLSQP: cons_per_i = [{'type':'eq', 'fun': lambda z: cons_i(z, i)} for i in np.arange(m)] sol2 = minimize(obj, x0 = z0, constraints = cons_per_i, method = 'SLSQP', options={'disp': True}) 
 Singular matrix C in LSQ subproblem (Exit mode 6) Current function value: 6.87999270692 Iterations: 1 Function evaluations: 32 Gradient evaluations: 1 

Evidentemente, el algoritmo falla (el valor objective devuelto es en realidad el valor objective para la inicialización dada), lo cual me parece un poco extraño. Tenga en cuenta que ejecutar [cons_per_i[i]['fun'](sol.x) for i in np.arange(m)] muestra que sol.x , obtenido usando la formulación de restricción de matriz-salida, satisface todas las restricciones de salida escalar de cons_per_i como se esperaba (dentro de la tolerancia numérica).

Agradecería si alguien tiene alguna explicación para este problema.

Te has topado con los “cierres de enlace tardíos” gotcha . Todas las llamadas a cons_i se hacen con el segundo argumento igual a 19.

Una solución es utilizar el elemento de diccionario args en el diccionario que define las restricciones en lugar de los cierres de la función lambda:

 cons_per_i = [{'type':'eq', 'fun': cons_i, 'args': (i,)} for i in np.arange(m)] 

Con esto, la minimización funciona:

 In [417]: sol2 = minimize(obj, x0 = z0, constraints = cons_per_i, method = 'SLSQP', options={'disp': True}) Optimization terminated successfully. (Exit mode 0) Current function value: 2.1223622086 Iterations: 6 Function evaluations: 192 Gradient evaluations: 6 

También puede utilizar la sugerencia realizada en el artículo vinculado, que consiste en utilizar una expresión lambda con un segundo argumento que tiene el valor predeterminado deseado:

 cons_per_i = [{'type':'eq', 'fun': lambda z, i=i: cons_i(z, i)} for i in np.arange(m)]