Python 使用Cython编程-具有共享代码的ndarray的常规类型

Python 使用Cython编程-具有共享代码的ndarray的常规类型,python,numpy,templates,cython,Python,Numpy,Templates,Cython,我正在尝试编写涉及Numpy ndarray的代码,但它们可能是不同类型的。我遇到的问题是,我不知道如何让Cython在处理所有输入输出类型组合的同时快速运行。这是我试图实现的MWE,当然,这不是编译,但它代表了我想要实现的目标: general_types.pyx: 本质上,我希望能够处理任何输入/输出类型,而无需在将数组类型传递给cywhere之前转换数组类型。a和b可以是彼此不同的任何整数类型,输出a_ind和b_ind也可以是完全不同的整数类型。要做到这一点,有几个障碍: 自动为cywh

我正在尝试编写涉及Numpy ndarray的代码,但它们可能是不同类型的。我遇到的问题是,我不知道如何让Cython在处理所有输入输出类型组合的同时快速运行。这是我试图实现的MWE,当然,这不是编译,但它代表了我想要实现的目标:

general_types.pyx:

本质上,我希望能够处理任何输入/输出类型,而无需在将数组类型传递给cywhere之前转换数组类型。a和b可以是彼此不同的任何整数类型,输出a_ind和b_ind也可以是完全不同的整数类型。要做到这一点,有几个障碍:

自动为cywhere\u wrapped选择正确的重载。 针对np.min\u scalar\u typea.shape[0]获取两个输出的正确类型,以避免分配额外内存。 为向量\u到\u np自动选择正确的重载 sizeofintegral应正确评估 有没有一种方法可以通过一个代码库实现这一点?如果愿意的话,我愿意去C++。 编辑:编译错误:

general_types.pyx:15:34: Can only index fused functions with types
general_types.pyx:29:24: Cannot coerce to a type that is not specialized
general_types.pyx:29:45: Cannot coerce to a type that is not specialized
general_types.pyx:34:44: Cannot coerce to a type that is not specialized
general_types.pyx:37:31: Type is not specialized
general_types.pyx:41:11: Type is not specialized
general_types.pyx:15:19: Invalid use of fused types, type cannot be specialized
general_types.pyx:34:9: Invalid use of fused types, type cannot be specialized
general_types.pyx:36:31: Invalid use of fused types, type cannot be specialized

这里的主要困难在于您使用两种类型的类型:

numpy dtype是运行时确定的Python对象,以及 Cython fused类型指定的类型,它是编译时确定的C类型。 在这两种类型之间进行转换并不特别容易,不能转换dtype->FUSSED type是有道理的,因为这将在运行时使用信息生成编译时所需的内容,但不能以其他方式进行转换是没有道理的。为了解决这个问题,我使用了伪参数——这些参数不被使用,但强制Cython为特定类型生成代码

第二个问题是,你希望你的a和b数组是不同的类型。我最初误读了你的评论,认为你想要相同的类型。。。。为此,您只需查看Cython>0.20.x所做更改的注释。这也意味着你必须自己完善积分-我也加入了int8_t。一个结果是,这最终定义了每个函数的4*4*4*4版本,这需要一段时间来编译,为您提供了一个大模块,并在您出错时为您提供每个错误消息的4*4*4*4副本

我做了以下修改:

从numpy数组切换到类型化MemoryView-我不是100%确定,但我认为numpy数组不能与融合类型一起工作

创建了两个新的保险丝类型uint_size_1/2。与内置整数不同,它包含对索引有意义的无符号整数,以及对节省空间有意义的8位整数。这将用于表示输出的类型

使内部函数CYU内部接受伪参数。这是一个使用非数组参数的数组参数不起作用,原因我不是100%清楚。我们的想法是确定存储索引所需的数据类型,使用该数据类型创建一个空数组,然后将该数组传递给cywhere_internal以选择函数的正确版本。cywhere_internal必须是def而不是cdef,因为选择是在运行时进行的

在vector_to_np中添加了一个伪uint_size参数。这有点像黑客,但如果没有伪参数,Cython似乎不会将其解释为融合类型函数。这个论点被忽略了

删除了大部分cython.boundscheckFalse和cython.wrapoundFalse装饰器。在这些问题上你什么都没做,所以我认为把它们排除在外更整洁

我将dtype参数传递到所有级别,因为我们需要在vector_to_np函数中使用它

其他一些微小的变化

代码:

很难确定这是否正确,因为它总是生成0长度的数组


我并不完全相信通过使用最小的索引类型来节省空间的价值——在运行时选择正确版本的cywhere\u internal有一点慢。只有当我真的没有空间时,我才会选择这样做。

你能说明它是如何编译失败的吗?我的感觉是,使用类型化内存视图而不是ndarray可能会起作用,但这是没有经过测试的…@DavidW尝试使用类型化内存视图不起作用,不幸的是。我添加了编译错误。不幸的是,这有两个问题。a和b不能是不同的类型,b。我想避免一个单独的类型。。。完全是通过首先分配正确的向量。它们是索引,但我更希望它们不会占用更多的空间。或者你会建议反对吗?a和b不能是不同的类型很容易修复-将它们都设置为np\u升级\u类型np。min\u scalar\u typea\u ind\u np,np.min\u scalar\u typeb\u ind\u np。我认为分配正确的向量类型可能很棘手-我会考虑如何做,但这并不容易
可能要过几天我才能有时间真正看一看。除非你知道你的空间快用完了,否则我看不到节省空间的巨大价值。这是一个很粗糙但相当棒的工作。我接受这个答案,但我真的希望a,b,a,b可以是不同的类型。非常感谢-最终编辑。。。a和b可以是不同的类型。这将生成256个不同的函数,因此编译实际上需要相当长的时间,并且会将任何错误消息复制256次,这使得调试非常困难。我不相信你真的应该用这个。。。
#!/usr/bin/env python
from setuptools import setup, Extension
from Cython.Build import cythonize
import numpy as np

cython_extensions = [
    Extension('*', ['**/*.pyx'], language='c++', extra_compile_args=['-std=c++11'])
]

setup(
    ext_modules=cythonize(cython_extensions, annotate=True),
    include_dirs=[np.get_include()],
)
general_types.pyx:15:34: Can only index fused functions with types
general_types.pyx:29:24: Cannot coerce to a type that is not specialized
general_types.pyx:29:45: Cannot coerce to a type that is not specialized
general_types.pyx:34:44: Cannot coerce to a type that is not specialized
general_types.pyx:37:31: Type is not specialized
general_types.pyx:41:11: Type is not specialized
general_types.pyx:15:19: Invalid use of fused types, type cannot be specialized
general_types.pyx:34:9: Invalid use of fused types, type cannot be specialized
general_types.pyx:36:31: Invalid use of fused types, type cannot be specialized
import numpy as np
cimport cython
from libcpp.vector cimport vector
from libc.stdint cimport (uint8_t, uint16_t, uint32_t, uint64_t,
                          int8_t, int16_t, int32_t, int64_t)

ctypedef fused uint_size_a:
    uint8_t
    uint16_t
    uint32_t
    uint64_t
ctypedef fused uint_size_b:
    uint8_t
    uint16_t
    uint32_t
    uint64_t

ctypedef fused integral_a:
    int8_t
    int16_t
    int32_t
    int64_t
ctypedef fused integral_b:
    int8_t
    int16_t
    int32_t
    int64_t

cdef extern from "<cstring>" namespace "std":
    void* memcpy (void* destination, const void* source, size_t num)

def cywhere(integral_a[::1] a not None, integral_b[::1] b not None):
    dtype_a = np.min_scalar_type(a.shape[0])
    dtype_b = np.min_scalar_type(b.shape[0])

    return cywhere_internal(np.zeros((0,),dtype=dtype_a),np.zeros((0,),dtype=dtype_b),
                            dtype_a,dtype_b,
                            a,b)

def cywhere_internal(uint_size_a[:] dummya, uint_size_b[:] dummyb, 
                     dtype_a, dtype_b,
                     integral_a[::1] a, integral_b[::1] b):
    cdef vector[uint_size_a] a_ind = vector[uint_size_a]()
    cdef vector[uint_size_b] b_ind = vector[uint_size_b]()
    cdef uint_size_a dummy2a = 0
    cdef uint_size_b dummy2b = 0

    a_ind_np = vector_to_np(dummy2a, dtype_a, a_ind)
    b_ind_np = vector_to_np(dummy2b, dtype_b, b_ind)
    return a_ind_np, b_ind_np

cdef vector_to_np(uint_size_a dummy, dtype, vector[uint_size_a]& vec):
    cdef uint_size_a[::1] arr = np.empty(vec.size(),dtype=dtype)

    cdef void *arr_p

    with cython.boundscheck(False):
        # we're fine doing this on a 0 length array
        arr_p = <void*> &arr[0]
    cdef void* vec_p = <void*> &vec[0]

    memcpy(arr_p, vec_p, sizeof(uint_size_a) * vec.size())

    return np.asarray(arr)