Python Matrix Diagonal Of Inf Without Fill_diagonal
Solution 1:
Approach #1
The magic you are looking for is in NumPy strides that gets us a view of the array without the diagonal elements and as such doesn't occupy anymore of the memory space. Here's the implementation to get such a view -
def nodiag_view(a):
m = a.shape[0]
p,q = a.strides
return np.lib.stride_tricks.as_strided(a[:,1:], (m-1,m), (p+q,q))
Let's take a look at a sample run to verify that its a view -
In [124]: a # Input array
Out[124]:
array([[ 0, 61, 43, 26, 21],
[20, 0, 78, 29, 64],
[34, 49, 0, 64, 60],
[36, 96, 67, 0, 75],
[36, 85, 40, 74, 0]])
# Get the no-diag view
In [125]: a_nodiag = nodiag_view(a)
# Lets's verify by changing elements in the view and that should change# elements in the original array too
In [126]: a_nodiag[:] = 999
In [127]: a
Out[127]:
array([[ 0, 999, 999, 999, 999],
[999, 0, 999, 999, 999],
[999, 999, 0, 999, 999],
[999, 999, 999, 0, 999],
[999, 999, 999, 999, 0]])
Finally, let's see how we can setup it up to solve your entire problem -
def argmin_without_diag(a):
a_nodiag = nodiag_view(a)
idx_nodiag = np.argmin(a_nodiag)
m = a.shape[0]
idx = idx_nodiag + np.unravel_index(idx_nodiag, (m-1,m))[0]+1
return np.unravel_index(idx, a.shape)
Sample run -
In[142]: aOut[142]:
array([[ 0, 60, 79, 55, 77],
[62, 0, 86, 84, 25],
[32, 96, 0, 74, 89],
[24, 33, 64, 0, 93],
[14, 74, 30, 44, 0]])
In[143]: argmin_without_diag(a)
Out[143]: (4, 0)
Approach #2
If you worry about both memory and performance, you could temporarily set the diagonal ones as infnite, then get the argmin index and then put back the original diagonal values. Thus, effectively the input array is unchanged. The implementation would look something like this -
defargmin_without_diag_replacement(a):
# Store diagonal values
vals = a.ravel()[::a.shape[1]+1].copy()
# Set diag ones as infinites
a.ravel()[::a.shape[1]+1] = np.inf
# Get argmin index
idx = np.argmin(a)
# Put back the original diag values
a.ravel()[::a.shape[1]+1] = vals
return np.unravel_index(idx, a.shape)
Thus, for (n x n) shaped array, the temporary array would have just n elements.
Sample run -
In[237]: aOut[237]:
array([[ 0., 95., 57., 75., 92.],
[ 37., 0., 69., 71., 62.],
[ 42., 72., 0., 30., 57.],
[ 41., 80., 94., 0., 26.],
[ 36., 45., 71., 76., 0.]])
In[238]: argmin_without_diag_replacement(a)
Out[238]: (3, 4)
Runtime test
In [271]: a = np.random.randint(11,99,(1000,1000)).astype(float)
In [272]: np.fill_diagonal(a,0)
In [273]: %timeit argmin_without_diag(a)
1000 loops, best of 3: 1.76 ms per loop
In [274]: %timeit argmin_without_diag_replacement(a)
1000 loops, best of 3: 688 µs per loop
Solution 2:
orig_mat = np.array([[1.2,2,3],[4,5,6],[7,8,9]])
#set diagonal to inf without making a copy of the array.
orig_mat + np.where(np.eye(orig_mat.shape[0])>0,np.inf,0)
array([[ inf, 2., 3.],
[ 4., inf, 6.],
[ 7., 8., inf]])
#the original array remains untorched.
print(orig_mat)
[[ 1.2 2. 3. ]
[ 4. 5. 6. ]
[ 7. 8. 9. ]]Solution 3:
From what I understand you want to find the index of the min of the matrix, but not include the diagonal elements:
orig_mat = np.array([[1.2,2,3],[4,5,6],[7,8,9]])
min_idx = np.argmin(orig_mat+np.multiply(np.eye(orig_mat.shape[0]),1e9))
Solution 4:
My nomination is the explicit copy and fill, wrapped in a function
def foo(mat):
a = mat.copy()
np.fill_diagonal(a,np.inf)
return a
This does make a copy, but only one.
orig_mat + np.where(np.eye(orig_mat.shape[0])>0,np.inf,0))
returns a new array as well,but in the process has to make two added temporary ones. So it ends up being slower:
for orig_mat=np.ones((1000,1000))
In [67]: timeit np.argmin(orig_mat + np.where(np.eye(orig_mat.shape[0])>0, np.inf, 0))
100 loops, best of 3: 18.7 ms per loop
In [68]: timeit np.argmin(foo(orig_mat))
100 loops, best of 3: 6.94 ms per loop
Surprisingly @Divakar's solution isn't much faster:
In [69]: timeit argmin_without_diag(orig_mat)
100 loops, best of 3: 5.97 ms per loop
Post a Comment for "Python Matrix Diagonal Of Inf Without Fill_diagonal"