3

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.

Daniele
  • 31
  • 1
  • cython.view.array is probably what you are looking for https://cython.readthedocs.io/en/latest/src/userguide/memoryviews.html#cython-arrays – ead Jun 29 '20 at 14:07
  • To my understanding, cython array has no clue what to do with a tuple of tuples as input and the shape needs to be declared at compile time, which I cannot do since I don't know it in the first place...am I wrong? – Daniele Jun 29 '20 at 19:20
  • I would be surprised if shape must be known at compile time. You can write two nested for loops or have dependency on numpy - it is your choice, but maybe somebody will come up with something better. – ead Jun 29 '20 at 19:30

0 Answers0