From ee660a02b4346d8da92a51bcf3ffa8e69e325839 Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Tue, 20 Mar 2018 16:55:29 -0400 Subject: [PATCH 1/2] numpy: Add test for explicit dtype checks. At present, int64 + uint64 do not exactly match dtype(...).num --- tests/test_numpy_array.cpp | 81 ++++++++++++++++++++++++++++++++++++++ tests/test_numpy_array.py | 15 +++++++ 2 files changed, 96 insertions(+) diff --git a/tests/test_numpy_array.cpp b/tests/test_numpy_array.cpp index 5702594932..41cba662e2 100644 --- a/tests/test_numpy_array.cpp +++ b/tests/test_numpy_array.cpp @@ -14,6 +14,67 @@ #include +// Size / dtype checks. +struct DtypeCheck { + py::dtype numpy{}; + py::dtype pybind11{}; +}; + +template +DtypeCheck get_dtype_check(const char* name) { + py::module np = py::module::import("numpy"); + DtypeCheck check{}; + check.numpy = np.attr("dtype")(np.attr(name)); + check.pybind11 = py::dtype::of(); + return check; +} + +std::vector get_concrete_dtype_checks() { + return { + // Normalization + get_dtype_check("int8"), + get_dtype_check("uint8"), + get_dtype_check("int16"), + get_dtype_check("uint16"), + get_dtype_check("int32"), + get_dtype_check("uint32"), + get_dtype_check("int64"), + get_dtype_check("uint64") + }; +} + +struct DtypeSizeCheck { + std::string name{}; + int size_cpp{}; + int size_numpy{}; + // For debugging. + py::dtype dtype{}; +}; + +template +DtypeSizeCheck get_dtype_size_check() { + DtypeSizeCheck check{}; + check.name = py::type_id(); + check.size_cpp = sizeof(T); + check.dtype = py::dtype::of(); + check.size_numpy = check.dtype.attr("itemsize").template cast(); + return check; +} + +std::vector get_platform_dtype_size_checks() { + return { + get_dtype_size_check(), + get_dtype_size_check(), + get_dtype_size_check(), + get_dtype_size_check(), + get_dtype_size_check(), + get_dtype_size_check(), + get_dtype_size_check(), + get_dtype_size_check(), + }; +} + +// Arrays. using arr = py::array; using arr_t = py::array_t; static_assert(std::is_same::value, ""); @@ -72,6 +133,26 @@ TEST_SUBMODULE(numpy_array, sm) { try { py::module::import("numpy"); } catch (...) { return; } + // test_dtypes + py::class_(sm, "DtypeCheck") + .def_readonly("numpy", &DtypeCheck::numpy) + .def_readonly("pybind11", &DtypeCheck::pybind11) + .def("__repr__", [](const DtypeCheck& self) { + return py::str("").format( + self.numpy, self.pybind11); + }); + sm.def("get_concrete_dtype_checks", &get_concrete_dtype_checks); + + py::class_(sm, "DtypeSizeCheck") + .def_readonly("name", &DtypeSizeCheck::name) + .def_readonly("size_cpp", &DtypeSizeCheck::size_cpp) + .def_readonly("size_numpy", &DtypeSizeCheck::size_numpy) + .def("__repr__", [](const DtypeSizeCheck& self) { + return py::str("").format( + self.name, self.size_cpp, self.size_numpy, self.dtype); + }); + sm.def("get_platform_dtype_size_checks", &get_platform_dtype_size_checks); + // test_array_attributes sm.def("ndim", [](const arr& a) { return a.ndim(); }); sm.def("shape", [](const arr& a) { return arr(a.ndim(), a.shape()); }); diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 8ac0e66fb3..454235b007 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -7,6 +7,21 @@ import numpy as np +def test_dtypes(): + # See issue #1328. + # - Platform-dependent sizes. + for size_check in m.get_platform_dtype_size_checks(): + print(size_check) + assert size_check.size_cpp == size_check.size_numpy, size_check + # - Concrete sizes. + for check in m.get_concrete_dtype_checks(): + print(check) + assert check.numpy == check.pybind11, check + if check.numpy.num != check.pybind11.num: + print("NOTE: typenum mismatch for {}: {} != {}".format( + check, check.numpy.num, check.pybind11.num)) + + @pytest.fixture(scope='function') def arr(): return np.array([[1, 2, 3], [4, 5, 6]], '=u2') From 8f28b8e7ea96fcb3a32f871f0e7f53ee5180247f Mon Sep 17 00:00:00 2001 From: Eric Cousineau Date: Sat, 1 Dec 2018 07:31:44 -0500 Subject: [PATCH 2/2] numpy: Provide concrete size aliases Test for dtype checks now succeed without warnings --- include/pybind11/numpy.h | 35 ++++++++++++++++++++++++++++++++--- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index bdc3a5ddd8..7f38f189de 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -108,6 +109,18 @@ inline numpy_internals& get_numpy_internals() { return *ptr; } +template struct same_size { + template using as = bool_constant; +}; + +// Lookup a type according to its size, and return a value corresponding to the NumPy typenum. +template +constexpr int platform_lookup(Int... codes) { + using code_index = std::integral_constant::template as, Check...>()>; + static_assert(code_index::value != sizeof...(Check), "Unable to match type on this platform"); + return std::get(std::make_tuple(codes...)); +} + struct npy_api { enum constants { NPY_ARRAY_C_CONTIGUOUS_ = 0x0001, @@ -126,7 +139,23 @@ struct npy_api { NPY_FLOAT_, NPY_DOUBLE_, NPY_LONGDOUBLE_, NPY_CFLOAT_, NPY_CDOUBLE_, NPY_CLONGDOUBLE_, NPY_OBJECT_ = 17, - NPY_STRING_, NPY_UNICODE_, NPY_VOID_ + NPY_STRING_, NPY_UNICODE_, NPY_VOID_, + // Platform-dependent normalization + NPY_INT8_ = NPY_BYTE_, + NPY_UINT8_ = NPY_UBYTE_, + NPY_INT16_ = NPY_SHORT_, + NPY_UINT16_ = NPY_USHORT_, + // `npy_common.h` defines the integer aliases. In order, it checks: + // NPY_BITSOF_LONG, NPY_BITSOF_LONGLONG, NPY_BITSOF_INT, NPY_BITSOF_SHORT, NPY_BITSOF_CHAR + // and assigns the alias to the first matching size, so we should check in this order. + NPY_INT32_ = platform_lookup( + NPY_LONG_, NPY_INT_, NPY_SHORT_), + NPY_UINT32_ = platform_lookup( + NPY_ULONG_, NPY_UINT_, NPY_USHORT_), + NPY_INT64_ = platform_lookup( + NPY_LONG_, NPY_LONGLONG_, NPY_INT_), + NPY_UINT64_ = platform_lookup( + NPY_ULONG_, NPY_ULONGLONG_, NPY_UINT_), }; typedef struct { @@ -1004,8 +1033,8 @@ struct npy_format_descriptor