0

I'm trying to create a simple cython module and have the following problem. I would like to create a function like:

cdef float calc(float[:] a1, float[:] a2):
    cdef float res = 0
    cdef int l = len(a2)
    cdef float item_a2
    cdef float item_a1

    for idx in range(l):
        if a2[idx] > 0:
            item_a2 = a2[idx]
            item_a1 = a1[idx]
            res += item_a2 * item_a1

    return res

When the function is being executed, a1 and a2 params are python lists. Therefore I get the error:

TypeError: a bytes-like object is required, not 'list'

I just need to make such calculations and nothing more. But how shall I define input params float[:] a1 and float[:] a2 if I need to maximize speed up using C? Probably it's necessary to convert lists to arrays manually?

P.S. would appreciate also if you can also explain to me whether it's necessary to declare cdef float item_a2 explicitly to perform multiplication (in terms of performance) or it is equally to result += a2[idx] * a1[idx]

user1820686
  • 2,008
  • 5
  • 25
  • 44
  • 1
    There are some similar questions around: https://stackoverflow.com/q/47005382/5769463 or https://stackoverflow.com/q/11689967/5769463 – ead Jul 10 '18 at 17:56

2 Answers2

3
cdef float calc(float[:] a1, float[:] a2):

a1 and a2 can be any object that supports the buffer protocol and has a float type. The most common examples would be either a numpy array or the standard library array module. They will not accept Python lists because a Python list is not a single homogeneous C type packed efficiently into memory, but instead a collection of Python objects.

To create a suitable object from a Python list you can do either:

numpy.array([1.0,2.0],dtype=numpy.float32)
array.array('f',[1.0,2.0])

(You may want to consider using double/float64 instead of float for extra precision, but that's your choice)

If you don't want to create array objects like this then Cython will not help you much since there is not much speed up possible with plain lists.

The np.ndarray[FLOAT, ndim=1] a1 syntax suggested in the other answer an outdated version of the memoryview syntax you're already using. There are no advantages (and a few small disadvantages) to using it.


result += a2[idx] * a1[idx]

is fine - Cython knows the types of a1 and a2 so there is no need to create temporary intermediate variables. You can get a html highlighted file with cython -a filename.pyx to inspect that will help indicate where the non-accelerated parts are.

DavidW
  • 29,336
  • 6
  • 55
  • 86
1

Cython answer

One way you can do this (if you're open to using numpy):

import numpy as np
cimport numpy as np

ctypedef np.npy_float FLOAT
ctypedef np.npy_intp INTP

cdef FLOAT calc(np.ndarray[FLOAT, ndim=1, mode='c'] a1, 
                np.ndarray[FLOAT, ndim=1, mode='c'] a2):
    cdef FLOAT res = 0
    cdef INTP l = a2.shape[0]
    cdef FLOAT item_a2
    cdef FLOAT item_a1

    for idx in range(l):
        if a2[idx] > 0:
            item_a2 = a2[idx]
            item_a1 = a1[idx]
            res += item_a2 * item_a1

    return res

This will require a np.float32 dtype for your array. If you wanted a np.float64, you can redefine FLOAT as np.float64_t.

One unsolicited piece of advice... l is a bad name for a variable, since it looks like a digit. Consider renaming it length, or something of the like.

Pure python with Numpy

Finally, it looks like you're trying to compute the dot product between two vectors where elements in one array are positive. You could use Numpy here pretty efficiently to get the same result.

>>> import numpy as np
>>> a1 = np.array([0, 1, 2, 3, 4, 5, 6])
>>> a2 = np.array([1, 2, 0, 3, -1])
>>> a1[:a2.shape[0]].dot(np.maximum(a2, 0))
11

Note, I added the a1 slice since you didn't check for length equality in your Cython function, but used a2's length. So I assumed the lengths may differ.

TayTay
  • 6,882
  • 4
  • 44
  • 65
  • 1
    I don't think (the first part of) this is useful advice. You've changed the newer, more general typed memoryview syntax to the old numpy syntax. This won't change the speed meaningfully, but means that the code actually works with _fewer_ input types. – DavidW Jul 10 '18 at 16:29
  • @DavidW, could you show how it could be represented in memoryview syntax, please? – user1820686 Jul 10 '18 at 17:28
  • @Tgsmith61591, thanks for the detailed answer. Could you explain why did you use `cimport numpy as np` right after python import, please? What's the reason for that? – user1820686 Jul 10 '18 at 17:30
  • @user1820686 The `float[:] a1` that OP uses in the question is memoryview syntax. (To match your `mode="C"` you can change it to `float[::1]`) This will accept a range of types including numpy arrays, the standard library `array` library, and any other type that follows the Python "buffer protocol". – DavidW Jul 10 '18 at 18:08
  • Thanks @DavidW. The memory view syntax is new to me. I've always written Cython with (what is apparently) the old syntax. I appreciate your clarification! – TayTay Jul 10 '18 at 18:56