I am writing a code for a GUI application which works fine, however would like to bundle it into an exe/app self-contained file to make it as user-friendly as possible. As part of the code, I have written a Cython extension module which performs some linear algebra extra fast. This module uses Numpy to convert the input tuple of tuples into an array, then it performs all the calculations using a memoryview on the Numpy array. It all works fantastic and super fast, however, when I make the bundle, I need to import the whole Numpy package, which is anywhere in between 50 Mb to 200 Mb depending on the version, when I really only need numpy.ndarray and numpy.empty. My executable is 10 Mb without Numpy, and 50+ Mb with... I don't use any other function, and I have written all the linear algebra myself from basic operations. The Cython code is below.
from numpy import empty, array, int32
cimport cython
@cython.cdivision(True)
cdef int MCD(int a, int b):
cdef int r
if a > b:
a, b = b, a
r = a % b
while r > 0:
a, b = b, r
r = a % b
return b
@cython.wraparound(False)
@cython.cdivision(True)
cdef int GCD(int [:] iterable):
cdef int index, res
res = 0
for index in range(len(iterable)):
if iterable[index] != 0:
if res == 0:
res = abs(iterable[index])
else:
res = MCD(res, abs(iterable[index]))
if res == 0:
res = 1
return res
@cython.wraparound(False)
@cython.cdivision(True)
cdef int [:] C_Gauss(int [:, :] Arr):
cdef int [:, :] arr, arr_, new_arr
cdef int x_max, y_max, i, j, ii, det, g, c_
cdef int [:] temp, sol
arr_ = Arr
arr = arr_.T.copy()
x_max, y_max = arr.shape[0], arr.shape[1]
sol = arr_[:y_max-1, 0].copy()
sol [:] = 0
for j in range (y_max-1):
if arr[j,j] == 0:
for jj in range(j+1, x_max):
if arr[jj, j] != 0:
temp = arr[j, :].copy()
arr [j, :] = arr [jj, :]
arr [jj, : ] = temp
break
else:
break
for i in range(j+1, x_max):
if arr[i, j] != 0:
for jj in range(y_max-1, j-1, -1): #####
arr[i, jj] = arr[j,j]*arr [i, jj]- arr[i,j]*arr[j,jj]
g = GCD(arr[i, :])
if g != 1:
for jj in range(y_max-1, j, -1):
arr [i , jj] = arr [i , jj]//g
else:
for jj in range(y_max-1, x_max):
if arr[jj, y_max-1] != 0:
break
else:
new_arr = arr[:y_max-1, :]
det = 1
for j in range (y_max-2, 0, -1):
det *= new_arr[j,j]
for i in range(j):
if new_arr[i, j] != 0:
c_ = new_arr[i,j]
for jj in range(i, y_max):
new_arr[i, jj] = new_arr[i,jj]*new_arr[j, j] - new_arr[j,jj]*c_
g = GCD(new_arr[i, :])
for jj in range(i, y_max):
new_arr [i, jj] = new_arr [i, jj]//g
for j in range(y_max-1):
new_arr[j, y_max-1] *= det
new_arr[j, y_max-1] = new_arr[j, y_max-1]//new_arr[j, j]
sol = new_arr[:, y_max-1]
return sol
@cython.wraparound(False)
@cython.cdivision(True)
def Screen(tuple families, tuple solution_vector, Py_ssize_t solution_code, Py_ssize_t no_triplets):
cdef int [:, :] input_data, vectors, Augmented
cdef int [:] solution, combinations, codes, multiplicities, indices, ratios, sol_indices
cdef int N, I, J, K, CombinedAminoAcids, indexing, is_negative, half_mark, mcd
cdef list output = []
input_data = array(families, order = 'F', dtype = int32)
solution = array(solution_vector, dtype = int32)
combinations = empty(shape = (no_triplets,), dtype = int32)
codes = input_data[:, 0]
indices = input_data[:, 1]
multiplicities = input_data[:, 2]
vectors = input_data[:, 3:]
N = solution.shape[0]
M = input_data.shape[0]
Augmented = empty(shape = (no_triplets+1, N), dtype = int32)
Augmented[no_triplets, :] = solution
for I in range(no_triplets):
combinations[I] = M -1 - I
combinations[no_triplets - 1] += 1
indexing = 0
half_mark = no_triplets // 2 + 1
while combinations[0] > no_triplets - 1:
K = no_triplets - 1
for J in range(no_triplets - 1, -1, -1):
if combinations[J] > no_triplets - 1 - J:
K = J
break
combinations[K] -= 1
for J in range(1, no_triplets - K):
combinations[K + J] = combinations[K] - J
CombinedAminoAcids = 0
for I in range(no_triplets):
CombinedAminoAcids |= codes[combinations[I]]
if CombinedAminoAcids == solution_code:
for I in range(no_triplets):
Augmented[I, :] = vectors[combinations[I], :]
indexing += 1
ratios = C_Gauss(Augmented)
sol_indices = ratios.copy()
is_negative = 0
for I in range(no_triplets):
if ratios[I] == 0:
break
elif ratios[I] < 0:
is_negative += 1
ratios[I] *= multiplicities[combinations[I]]
sol_indices[I] = indices[combinations[I]]
else:
mcd = GCD(ratios)
if is_negative >= half_mark:
mcd *= -1
for I in range(no_triplets):
ratios[I] /= mcd
output.append( (tuple(sol_indices), tuple(ratios)) )
return output
Is there any way for me to avoid importing the whole Numpy package? I exclusively need to convert the input tuple of tuples of the function 'Screen' into an object which can be accessed as a memoryview. The C and C++ arrays do not support declaration of their size at run-time, but only at compile-time, and I don't know the size of the tuple beforehand, hence the use of numpy arrays. If I can avoid numpy altogether, I'd be pretty happy.
Any suggestions?
Thank you.