I have two numpy matrices with with different shapes:
vertices = np.array([[ 100, 101, 102, 103, -1],
[ 200, 201, 202, 203, 204],
[ 300, 301, 302, 303, 104],
[ 505, 506, 507, -1, -1]])
faces = np.array([[ 104, 102, 100],
[1202, 203, 2000],
[ 303, 505, 104],
[ 101, 102, 104]])
I want to link the index of each row in faces to rows in vertices in order to compute the correspdoning area for each vertex. The area computation is not in this post because it is irrelevant.
Each vertex can correspond to any number of faces. Each face can only be attached to one vertex.
Link rule: For each row in vertices, if 2 or more elements exist in a faces row, those rows are linked.
Expected result is a dictionary with keys that are indices into vertices, and values that are the count of linked faces for the corresponding vertex.
Expected result:
{0: 2.0, 2: 1.0}
I wrote a working algorithm but I am looking for a more performant implementation:
def area(faces, corresponding_vertices_id):
skeleton_node_corresponding_area = defaultdict(lambda: 0.)
for face in faces:
for skeleton_node, vectex_id in enumerate(corresponding_vertices_id):
if np.count_nonzero(np.in1d(face, vectice_id)) >=2 :
area = 1 # Mock action. real area will be compute later
skeleton_node_corresponding_area[skeleton_node] = skeleton_node_corresponding_area[skeleton_node] + area
break
return skeleton_node_corresponding_area
area(faces, vertices)
defaultdict(<function __main__.area.<locals>.<lambda>()>, {0: 2.0, 2: 1.0})
in1d(a, b)is essentially doing this (but likely without the intermediate arrays, and some short circuiting):You can speed this up a bit by pre-sorting the arrays. This should be valid as long as you only care that two numbers match:
Now you can do something like
This works by doing a binary search of each element of
facesin each row ofvertices. Sincesearchsortedreturns insertion indices, you have to check which locations actually match the value. The modulo operator ensures that elements with insertion index past the end of the array do not cause anIndexError. The proper way to handle that would be a conditional, but setting their indices to zero is faster, and works out OK since they don't match either way. Thebreakspeeds things up since you mention that each face can only belong to a single vertex.This is still not terribly fast. Assuming a pair of arrays of shape
(M, N), your algorithm isO(M^2 * N^2). This one performs anO(M * N * log(N))sort, followed by a nested loop that isO(M * M * N * log(N))(each search isO(log(N)), but it has to be done for each element of the row), for total ofO(M^2 * N * log(N)).A more performant approach (once you get past the point where the extra overhead matters) would be to use python
sets, since search for an individual element isO(1):If
areadepends on the original value offaceandvertexbefore conversion toset, modify your loops to run over the index only, and access what you need by index.This is
O(M * M * N)sinceface & vertexis anO(N)operation. If you had a non-random distribution of values, you could probably do something that would allow you to reduce the complexity of the outer loops, e.g., by sorting the rows or so.