Efficient double iteration over an array
I have the following code where dots are many rows across a list of 3 cols, coorRadius is the radius within which I want to find the local maximums of the coordinates, and localCoordinateMaxima is the array in which I store these maximums:
for i,x in enumerate(points):
check = 1
for j,y in enumerate(points):
if linalg.norm(x-y) <= coorRadius and x[2] < y[2]:
check = 0
if check == 1:
localCoordinateMaxima.append(i)
print localCoordinateMaxima
Unfortunately it lasts forever, when I have several thousand points, I am looking for a way to speed it up. I tried to do it with the all () condition, however I failed with it and I'm not even sure if it would be more efficient. Could you guys suggest a way to do this faster?
Best!
source to share
Your search for neighbors is best done with KDTree.
from scipy.spatial import cKDTree tree = cKDTree(points) pairs = tree.query_pairs(coorRadius)
Now pairs
is a set of two sets of elements (i, j)
, wherein i < j
and points[i]
and points[j]
are within coorRadius
each other. Now you can just len(points)**2
iterate over them, which is likely to be much smaller than what you are currently iterating over:
is_maximum = [True] * len(points)
for i, j in pairs:
if points[i][2] < points[j][2]:
is_maximum[i] = False
elif points[j][2] < points[i][2]:
is_maximum[j] = False
localCoordinateMaxima, = np.nonzero(is_maximum)
This can be further accelerated by vectorizing it:
pairs = np.array(list(pairs)) pairs = np.vstack((pairs, pairs[:, ::-1])) pairs = pairs[np.argsort(pairs[:, 0])] is_z_smaller = points[pairs[:, 0], 2] < points[pairs[:, 1], 2] bins, = np.nonzero(pairs[:-1, 0] != pairs[1:, 0]) bins = np.concatenate(([0], bins+1)) is_maximum = np.logical_and.reduceat(is_z_smaller, bins) localCoordinateMaxima, = np.nonzero(is_maximum)
The above code assumes that each point has at least one neighbor within coorRadius
. If not, you need to complicate things a little:
pairs = np.array(list(pairs)) pairs = np.vstack((pairs, pairs[:, ::-1])) pairs = pairs[np.argsort(pairs[:, 0])] is_z_smaller = points[pairs[:, 0], 2] < points[pairs[:, 1], 2] bins, = np.nonzero(pairs[:-1, 0] != pairs[1:, 0]) has_neighbors = pairs[np.concatenate(([True], bins)), 0] bins = np.concatenate(([0], bins+1)) is_maximum = np.ones((len(points),), bool) is_maximum[has_neighbors] &= np.logical_and.reduceat(is_z_smaller, bins) localCoordinateMaxima, = np.nonzero(is_maximum)
source to share
Here's a slightly drawn-out version of your code:
for i, x in enumerate(points):
x2 = x[2]
for y in points:
if linalg.norm(x-y) <= coorRadius and x2 < y[2]:
break
else:
localCoordinateMaxima.append(i)
print localCoordinateMaxima
Changes:
- Change your search
x[2]
. - The j variable was not used.
- Add an early exit break
- Use a for-else construct instead of a flag variable
source to share
With numpy, it's not too hard. You can do it with one (long) expression if you like:
import numpy as np points = np.array(points) localCoordinateMaxima = np.where(np.all((np.linalg.norm(points-points[None,:], axis=-1) > coorRadius) | (points[:,2] >= points[:,None,2]), axis=-1))
The algorithm that your current code implements is essential where(not(any(w <= x and y < z)))
. If you allocate not
via logical operations within it (using the Demonator's laws), you can avoid one level of nesting by reversing the inequalities to get where(all(w > x or y >= z)))
.
w
is a matrix of norms applied to the differences between the points passed together. x
is a constant. y
and z
- both arrays with third point coordinates, formed so that they pass together in the same shape as w
.
source to share