In a complex-valued array a
with nsel = ~750000
elements, I repeatedly (>~10^6
iterations) update nchange < ~1000
elements. After each iteration, in the absolute-squared, real-valued array b
, I need to find the indices of the K
largest values (K
can be assumed to be small, for sure K <= ~50
, in practice likely K <= ~10
). The K
indices do not need to be sorted.
The updated values and their indices change in each iteration and they depend on the (a priori) unknown elements of a
corresponding to the largest values of b
and their indices. Nonetheless, let us assume they are essentially random, with exception that one specific element (typically (one of) the largest value(s)) is always included among the updated values. Important: After an update, the new largest value(s) might be among the non-updated elements.
Below is a minimal example. For simplicity, it demonstrates only one of the 10^6 (looped) iterations. We can find the indices of the K
largest values using b.argmax()
(for K = 1
) or b.argpartition()
(arbitrary K
, general case, see https://stackoverflow.com/a/23734295/5269892). However, due to the large size of b
(nsel
), going over the full arrays to find the indices of the largest values is very slow. Combined with the large number of iterations, this forms the bottleneck of a larger code (the nonlinear deconvolution algorithm CLEAN) I am using into which this step is embedded.
I have already asked the question how to find the largest value (the case K = 1
) most efficiently, see Python most efficient way to find index of maximum in partially changed array. The accepted solution relies on accessing b
only partially by splitting the data into chunks and (re-)computing the maxima of only the chunks for which some elements were updated. A speed-up of > 7x
is thus achieved.
According to the author @Jérôme Richard (thanks for your help!), this solution can unfortunately not be easily generalized to K > 1
. As suggested by him, a possible alternative may be a binary search tree. Now my
Questions: How is such a binary tree implemented in practice and how do we then find the indices of the largest values most efficiently (and if possible, easily)? Do you have other solutions for the fastest way to repeatedly find the indices of the K
largest values in the partially updated array?
Note: In each iteration I will need b
(or a copy of it) later again as a numpy array. If possible, the solution should be mostly python-based, calling C from python or using Cython or numba
is ok. I currently use python 3.7.6, numpy 1.21.2
.
import numpy as np
# some array shapes ('nnu_use' and 'nm'), number of total values ('nvals'), number of selected values ('nsel';
# here 'nsel' == 'nvals'; in general 'nsel' <= 'nvals') and number of values to be changed ('nchange' << 'nsel')
nnu_use, nm = 10418//2 + 1, 144
nvals = nnu_use * nm
nsel = nvals
nchange = 1000
# number of largest peaks to be found
K = 10
# fix random seed, generate random 2D 'Fourier transform' ('a', complex-valued), compute power ('b', real-valued),
# and two 2D arrays for indices of axes 0 and 1
np.random.seed(100)
a = np.random.rand(nsel) + 1j * np.random.rand(nsel)
b = a.real ** 2 + a.imag ** 2
inu_2d = np.tile(np.arange(nnu_use)[:,None], (1,nm))
im_2d = np.tile(np.arange(nm)[None,:], (nnu_use,1))
# select 'nsel' random indices and get 1D arrays of the selected 2D indices
isel = np.random.choice(nvals, nsel, replace=False)
inu_sel, im_sel = inu_2d.flatten()[isel], im_2d.flatten()[isel]
def do_update_iter(a, b):
# find index of maximum, choose 'nchange' indices of which 'nchange - 1' are random and the remaining one is the
# index of the maximum, generate random complex numbers, update 'a' and compute updated 'b'
imax = b.argmax()
ichange = np.concatenate(([imax],np.random.choice(nsel, nchange-1, replace=False)))
a_change = np.random.rand(nchange) + 1j*np.random.rand(nchange)
a[ichange] = a_change
b[ichange] = a_change.real ** 2 + a_change.imag ** 2
return a, b, ichange
# do an update iteration on 'a' and 'b'
a, b, ichange = do_update_iter(a, b)
# find indices of largest K values
ilarge = b.argpartition(-K)[-K:]