Below I’ll write a function in pure python that I want to Cythonize.
def do_stuff(M_i, C_i):
return M_i.dot(C_i).dot(M_i)
def foo(M, C):
'''
M : np.array
N x J matrix
C : np.array
J x J matrix
'''
N = M.shape[0]
tot = 0
for i in range(N):
nonmiss = ~np.isnan(M[i, :])
M_i = M[i, nonmiss] # select non empty components of M
C_i = C[nonmiss, :] # select corresponding components of C
C_i = C_i[:, nonmiss] # select corresponding components of C
tot = tot + do_stuff(M_i, C_i)
return tot
Suppose that I know how to Cythonize the function do_stuff
. The actual do_stuff
function I’m interested in is more complicated than the one above but I thought I’d provide an example. The real do_stuff
function computes determinants and takes inverses, in addition to matrix multiplication.
My main problem has to do with creating the M_i
and C_i
subvector and submatrix. I’m not sure I can do the same sort of boolean indexing in Cython. And if I can, I don’t know how. But I can get started with the bit of Cython I know.
def foo_c(double[:, ::1] M, double[:, ::1] C):
cdef int N = M.shape[0]
cdef double tot = 0
for i in range(N):
tot = tot + do_stuff_c(M_i, C_i)
return tot
Advertisement
Answer
You probably won’t gain too much speed here, since boolean indexing in Numpy is implemented in C anyway, so should be fairly fast. The main thing you’d avoid is creating some unnecessary intermediates (which involves a memory allocation so is potentially slow).
What you want to do is create temporary arrays for M_i
and C_i
that are the largest possible size (i.e. J
or JxJ
). As you iterate through isnan(M_I)
you keep track of how many values you’ve actually stored. Then at the end you trim M_i
and C_i
to only the part that you’ve used:
Untested code:
for i in range(N):
filled_count_j = 0
M_i = np.empty((M.shape[1],))
C_i = np.empty((M.shape[1],M.shape[1]))
for j in range(M.shape[1]):
if ~isnan(M[i,j]):
M_i[filled_count] = M[i,j]
filled_count_k = 0
for k in range(M.shape[1]):
if ~isnan(M[i,k]):
C_i[filled_count_j,filled_count_k] = C[j,k]
filled_count_k += 1
filled_count_j += 1
M_i = M_i[:filled_count_j]
C_i = C_i[:filled_count_j,:filled_count_j]