I am new to python so there's a chance I could be doing something very simple wrong without knowing it. I am using optimize.fmin from scipy to optimize the transformation matrix between two points. I have written an objective function to multiply my 'guess' transformation matrix and the initial point and then find the difference between each element of the resulting matrix and my second point. I keep getting the error: "matmul: Input operand 1 has a mismatch in its core dimension 0, with gufunc signature (n?,k),(k,m?)->(n?,m?) (size 4 is different from 16)" when I call optimize.fmin.
It seems somehow my guess matrix is being changed from a 4x4 to a 1x16 matrix when optimize.fmin is called.
I have added print statements to print the size of my guess and initial point matrices which gives that they are both 4x4. I then call my objective function, which executes fine. But I still get the error when I call optimize.fmin. I have included my code below. My guess transformation matrix is currently the actual transformation matrix to keep it simple.
import numpy as np
from scipy import optimize
import math
rows,cols = 4,4 #number of rows and columns in the matrices
model = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0],
[0, 0, 0, 1]
])
#4x4 identity matix, guess and inputArray are multiplied to create the model of goalArray
guess = np.array([
[1, -1, 2, 0],
[0, 2, 0, 0],
[1, 0, 0, 1],
[0, 1, 2, 0]
])
#4x4 identity matrix, changed by optimize.fmin to find the transformation matrix that gives goalArray when multiplied with inputArray
inputArray = np.array([
[2, 4, 6, 9],
[2, 3, 1, 0],
[7, 2, 6, 4],
[1, 5, 2, 1]
])
#4x4 matrix provided by generatePoints([2 9 2 pi -pi pi], 4) xyz_in
goalArray = np.array([
[14, 5, 17, 17],
[4, 6, 2, 0],
[3, 9, 8, 10],
[16, 7, 13, 8]
])
#4x4 matrix provided by generatePoints([2 9 2 pi -pi pi], 4) xyz_out + eye(4) for noise
#objective function passed to optimize.fmin
#multiplies inputArray by the guess transition matrix
#then finds the difference between each value in the goalArray and the model
#and adds them to find the error
def objfunc(guess, inputArray, goalArray):
sum = 0
model = guess @ inputArray
for i in range(rows):
for j in range(cols):
sum = sum + math.sqrt((goalArray[i][j] - model[i][j]) ** 2)
return sum
print(guess.shape) #prints (4,4)
print(inputArray.shape) #prints (4,4)
print(objfunc(guess, inputArray, goalArray)) #prints 0.0
minimum = optimize.fmin(objfunc, guess, args=(inputArray, goalArray)) #minimize error to find the guess transition matrix between inputArray and goalArray
#print("minimum value:", minimum[0])
#print("error:", minimum[1])
#print(guess)
It seems somehow my guess matrix is being changed from a 4x4 to a 1x16 matrix when optimize.fmin is called.
Yes.
The short answer is that scipy.optimize.fmin
ravels (converts to 1D) your input array guess
before calling objfun
. The minimum thing you need to do to make the code work is to reshape guess to (4, 4)
at the beginning of objfunc
.
def objfunc(guess, inputArray, goalArray):
guess = guess.reshape((4, 4)) # add this
... # the rest is unchanged
and after you extract your result.
There are several other things you should do. For example, you don't need the for
loop in the objective function. That is,
for i in range(rows):
for j in range(cols):
sum = sum + math.sqrt((goalArray[i][j] - model[i][j]) ** 2)
can be replaced with
sum = np.sum(np.sqrt((goalArray - model) ** 2)
because arithmetic operations are performed elementwise on NumPy arrays.
But this makes it easy to see that you are squaring each term and then immediately taking the square root. If you mean to take the absolute value, use abs
.
sum = np.sum(np.abs(goalArray - model))
If you mean to square root after taking the sum:
sum = np.sqrt(np.sum((goalArray - model) ** 2))
Either way, this the intent is more clear if you use numpy.linalg.norm
:
sum = np.linalg.norm((goalArray - model).ravel(), ord=2)
# use ord=1 for the sum of the absolute values
# use ord=2 for the root of the sum of the squares
# If you don't `ravel`, you can instead get a matrix norm
Also, optimize.fmin
is a legacy function. For new code, use optimize.minimize
. (Note that the documentation of minimize
is explicit about the requirement that your guess must be 1D and the argument x
passed into your objective function will be 1D.)
minimum = optimize.minimize(objfunc, guess.ravel(), args=(inputArray, goalArray))
print(minimum.fun)
print(minimum.message)
print(minimum.x.reshape((4, 4)))
This way you can easily use a different optimization method if the default one doesn't give you a good enough result.
minimum = optimize.minimize(objfunc, guess.ravel(), args=(inputArray, goalArray), method='slsqp')
# default method is BFGS
And most might not give you great results if you use the sum of absolute values, since the objective function would not be smooth. Technically, method='Nelder-Mead'
(which is essentially what fmin
does) is the only one that doesn't rely on gradients, but that still doesn't guarantee it will do well on your problem.
But I'm assuming you want to perform nonlinear optimization on a real, nonlinear problem. This particular problem can be solved with linear algebra if inputArray
is nonsingular.
np.linalg.solve(inputArray.T, goalArray.T).T