// // python_multi_array.cpp // python-multi-array // // Copyright (C) 2017 Rue Yokaze // Distributed under the MIT License. // #include #include #include #include #include #include // Using from python is avoided because many definitions conflict with names from std. namespace python = boost::python; using boost::extents; using boost::multi_array; using std::shared_ptr; using std::vector; static python::object main_module = python::import("__main__"); static python::object builtin_module = main_module.attr("__builtins__"); static python::object python_hasattr = builtin_module.attr("hasattr"); static python::object python_int = builtin_module.attr("int"); static python::object numpy = python::import("numpy"); static python::object bool8 = numpy.attr("bool8"); static python::object uint8 = numpy.attr("uint8"); static python::object uint16 = numpy.attr("uint16"); static python::object uint32 = numpy.attr("uint32"); static python::object uint64 = numpy.attr("uint64"); static python::object int8 = numpy.attr("int8"); static python::object int16 = numpy.attr("int16"); static python::object int32 = numpy.attr("int32"); static python::object int64 = numpy.attr("int64"); static python::object float32 = numpy.attr("float32"); static python::object float64 = numpy.attr("float64"); namespace python_multi_array { namespace impl { size_t extract_size_t(python::object obj) { // numpy.int32 cannot be converted directly into C++ integer types. // therefore, the value is first converted to python integer type // and then converted to size_t. return python::extract(python_int(obj)); } vector extract_index(python::object index) { if (python::extract(python_hasattr(index, "__len__")) == false) { return { extract_size_t(index) }; } else { vector ret; size_t length = python::len(index); for (size_t i = 0; i < length; ++i) { ret.push_back(extract_size_t(index[i])); } return ret; } } } // // [Python] // [array_type] multi_array.make(shape, dtype) // // allocate a boost::multi_array of expected shape and data type. // // shape: int, list or tuple // dtype: bool8, int8, int16, int32, int64, uint8, uint16, uint32, uint64, // float32 or float64, all defined in numpy // // return: a smart-pointer of the array // python::object make(python::object shape, python::object dtype); namespace impl { template python::object make_typed_sized(const size_t* s, size_t ndim) { auto reset = [](const auto& arr) { std::fill(arr->origin(), arr->origin() + arr->num_elements(), 0); return arr; }; switch (ndim) { case 1: return python::object(reset(std::make_shared>(extents[s[0]]))); case 2: return python::object(reset(std::make_shared>(extents[s[0]][s[1]]))); case 3: return python::object(reset(std::make_shared>(extents[s[0]][s[1]][s[2]]))); case 4: return python::object(reset(std::make_shared>(extents[s[0]][s[1]][s[2]][s[3]]))); case 5: return python::object(reset(std::make_shared>(extents[s[0]][s[1]][s[2]][s[3]][s[4]]))); case 6: return python::object(reset(std::make_shared>(extents[s[0]][s[1]][s[2]][s[3]][s[4]][s[5]]))); case 7: return python::object(reset(std::make_shared>(extents[s[0]][s[1]][s[2]][s[3]][s[4]][s[5]][s[6]]))); case 8: return python::object(reset(std::make_shared>(extents[s[0]][s[1]][s[2]][s[3]][s[4]][s[5]][s[6]][s[7]]))); default: throw std::invalid_argument("shape"); } } template python::object make_typed(python::object shape) { vector shape_vector = extract_index(shape); return make_typed_sized(shape_vector.data(), shape_vector.size()); } } python::object make(python::object shape, python::object dtype) { if (dtype == bool8) { return impl::make_typed(shape); } else if (dtype == int8) { return impl::make_typed(shape); } else if (dtype == int16) { return impl::make_typed(shape); } else if (dtype == int32) { return impl::make_typed(shape); } else if (dtype == int64) { return impl::make_typed(shape); } else if (dtype == uint8) { return impl::make_typed(shape); } else if (dtype == uint16) { return impl::make_typed(shape); } else if (dtype == uint32) { return impl::make_typed(shape); } else if (dtype == uint64) { return impl::make_typed(shape); } else if (dtype == float32) { return impl::make_typed(shape); } else if (dtype == float64) { return impl::make_typed(shape); } else { throw std::invalid_argument("dtype"); } } // // [Python] // T x[index] // x[index] = T // // get and set one element via index operator. // Example: // x[2, 4] = 2.0 // template T getitem(const shared_ptr>& This, python::object index_object) { if (This == nullptr) { throw std::invalid_argument("self"); } vector index = impl::extract_index(index_object); if (index.size() != N) { // index has an invalid dimensionality throw std::invalid_argument("index"); } T* ptr = This->origin(); for (size_t i = 0; i < N; ++i) { if (This->shape()[i] <= index[i]) { // index exceeds boundary throw std::invalid_argument("index"); } ptr += This->strides()[i] * index[i]; } return *ptr; } template void setitem(const shared_ptr>& This, python::object index_object, T value) { if (This == nullptr) { throw std::invalid_argument("self"); } vector index = impl::extract_index(index_object); if (index.size() != N) { // index has an invalid dimensionality throw std::invalid_argument("index"); } T* ptr = This->origin(); for (size_t i = 0; i < N; ++i) { if (This->shape()[i] <= index[i]) { throw std::invalid_argument("index"); } ptr += This->strides()[i] * index[i]; } *ptr = value; } // // [Python] // x.reset() // // This function resets every elements of the array with zero. // template void reset(const shared_ptr>& This) { if (This == nullptr) { throw std::invalid_argument("self"); } std::fill(This->origin(), This->origin() + This->num_elements(), 0); } // // [Python] // dtype x.element() // // return: data type of the array. possible values are bool8, uint8, // uint16, uint32, uint64, int8, int16, int32, int64, float32, // float64, all defined in numpy. // template python::object element(const shared_ptr>& This) { if (This == nullptr) { throw std::invalid_argument("self"); } return python::numpy::dtype::get_builtin(); } // // [Python] // tuple x.shape() // // return: the shape of the array. // template python::object shape(const shared_ptr>& This) { if (This == nullptr) { throw std::invalid_argument("self"); } const size_t* s = This->shape(); switch (N) { case 1: return python::make_tuple(s[0]); case 2: return python::make_tuple(s[0], s[1]); case 3: return python::make_tuple(s[0], s[1], s[2]); case 4: return python::make_tuple(s[0], s[1], s[2], s[3]); case 5: return python::make_tuple(s[0], s[1], s[2], s[3], s[4]); case 6: return python::make_tuple(s[0], s[1], s[2], s[3], s[4], s[5]); case 7: return python::make_tuple(s[0], s[1], s[2], s[3], s[4], s[5], s[6]); case 8: return python::make_tuple(s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]); default: throw std::invalid_argument("self"); } } // // [Python] // int x.num_dimensions() // // return: the number of dimensions of the array. // template size_t num_dimensions(const shared_ptr>& This) { if (This == nullptr) { throw std::invalid_argument("self"); } return N; } // // [Python] // int x.num_elements() // // return: the total number of elements of the array. // example: // It returns 8 for an array with shape (2, 4). // template size_t num_elements(const shared_ptr>& This) { if (This == nullptr) { throw std::invalid_argument("self"); } return This->num_elements(); } // // [Python] // numpy.ndarray x.get() // // return: a copy of the array stored in numpy.ndarray. // template python::object get(const shared_ptr>& This) { if (This == nullptr) { throw std::invalid_argument("self"); } size_t s[N]; size_t d[N]; std::copy(This->shape(), This->shape() + N, s); std::transform(This->strides(), This->strides() + N, d, [](auto input) { return input * sizeof(T); }); auto make_tuple_from_array = [](const size_t* a) { switch (N) { case 1: return python::make_tuple(a[0]); case 2: return python::make_tuple(a[0], a[1]); case 3: return python::make_tuple(a[0], a[1], a[2]); case 4: return python::make_tuple(a[0], a[1], a[2], a[3]); case 5: return python::make_tuple(a[0], a[1], a[2], a[3], a[4]); case 6: return python::make_tuple(a[0], a[1], a[2], a[3], a[4], a[5]); case 7: return python::make_tuple(a[0], a[1], a[2], a[3], a[4], a[5], a[6]); case 8: return python::make_tuple(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7]); } throw std::invalid_argument("this"); }; python::numpy::dtype dt = python::numpy::dtype::get_builtin(); python::tuple shape = make_tuple_from_array(s); python::tuple strides = make_tuple_from_array(d); return boost::python::numpy::from_data(This->origin(), dt, shape, strides, boost::python::object()); } // // [Python] // x.set(numpy.ndarray nd) // // Reset the array with values from nd. // nd.dtype may be different from x.element() but the values are implicitly // converted to x.element(). // template void set(const shared_ptr>& This, python::numpy::ndarray nd); namespace impl { template void set_typed(shared_ptr> This, python::numpy::ndarray nd) { if (N != nd.get_nd()) { throw std::invalid_argument("nd"); } size_t s[N]; std::copy(This->shape(), This->shape() + N, s); size_t ix[N]; std::fill(ix, ix + N, 0); size_t boost_strides[N]; std::copy(This->strides(), This->strides() + N, boost_strides); size_t numpy_strides[N]; std::transform(nd.get_strides(), nd.get_strides() + N, numpy_strides, [](auto input) { return input / sizeof(S); }); T* p_boost_origin = This->origin(); const S* p_numpy_origin = reinterpret_cast(nd.get_data()); while (ix[0] < s[0]) { T* p_boost_element = p_boost_origin; const S* p_numpy_element = p_numpy_origin; for (size_t d = 0; d < (N - 1); ++d) { p_boost_element += ix[d] * boost_strides[d]; p_numpy_element += ix[d] * numpy_strides[d]; } while (ix[N - 1] < s[N - 1]) { *p_boost_element = static_cast(*p_numpy_element); p_boost_element += boost_strides[N - 1]; p_numpy_element += numpy_strides[N - 1]; ++(ix[N - 1]); } for (size_t d = N - 1; d > 0; --d) { if (s[d] <= ix[d]) { ix[d] = 0; ++(ix[d - 1]); } } } } } template void set(const shared_ptr>& This, python::numpy::ndarray nd) { if (This == nullptr) { throw std::invalid_argument("self"); } python::numpy::dtype dt = nd.get_dtype(); if (dt == bool8) { impl::set_typed(This, nd); } else if (dt == uint8) { impl::set_typed(This, nd); } else if (dt == uint16) { impl::set_typed(This, nd); } else if (dt == uint32) { impl::set_typed(This, nd); } else if (dt == uint64) { impl::set_typed(This, nd); } else if (dt == int8) { impl::set_typed(This, nd); } else if (dt == int16) { impl::set_typed(This, nd); } else if (dt == int32) { impl::set_typed(This, nd); } else if (dt == int64) { impl::set_typed(This, nd); } else if (dt == float32) { impl::set_typed(This, nd); } else if (dt == float64) { impl::set_typed(This, nd); } else { throw std::invalid_argument("nd"); } } // // [Internal-usage only] // let python interpreter to export types from this module. // class array_template { public: template static void declare(const char* name) { python::class_>>(name) .def("__getitem__", &getitem) .def("__setitem__", &setitem) .def("reset", &reset) .def("element", &element) .def("shape", &shape) .def("num_dimensions", &num_dimensions) .def("num_elements", &num_elements) .def("get", &get) .def("set", &set); } }; } BOOST_PYTHON_MODULE(multi_array) { using namespace python_multi_array; using boost::python::def; boost::python::numpy::initialize(); array_template::declare("shared_bool_vector"); array_template::declare("shared_bool_matrix"); array_template::declare("shared_bool_tensor"); array_template::declare("shared_bool_tensor4"); array_template::declare("shared_bool_tensor5"); array_template::declare("shared_bool_tensor6"); array_template::declare("shared_bool_tensor7"); array_template::declare("shared_bool_tensor8"); array_template::declare("shared_uint8_vector"); array_template::declare("shared_uint8_matrix"); array_template::declare("shared_uint8_tensor"); array_template::declare("shared_uint8_tensor4"); array_template::declare("shared_uint8_tensor5"); array_template::declare("shared_uint8_tensor6"); array_template::declare("shared_uint8_tensor7"); array_template::declare("shared_uint8_tensor8"); array_template::declare("shared_uint16_vector"); array_template::declare("shared_uint16_matrix"); array_template::declare("shared_uint16_tensor"); array_template::declare("shared_uint16_tensor4"); array_template::declare("shared_uint16_tensor5"); array_template::declare("shared_uint16_tensor6"); array_template::declare("shared_uint16_tensor7"); array_template::declare("shared_uint16_tensor8"); array_template::declare("shared_uint32_vector"); array_template::declare("shared_uint32_matrix"); array_template::declare("shared_uint32_tensor"); array_template::declare("shared_uint32_tensor4"); array_template::declare("shared_uint32_tensor5"); array_template::declare("shared_uint32_tensor6"); array_template::declare("shared_uint32_tensor7"); array_template::declare("shared_uint32_tensor8"); array_template::declare("shared_uint64_vector"); array_template::declare("shared_uint64_matrix"); array_template::declare("shared_uint64_tensor"); array_template::declare("shared_uint64_tensor4"); array_template::declare("shared_uint64_tensor5"); array_template::declare("shared_uint64_tensor6"); array_template::declare("shared_uint64_tensor7"); array_template::declare("shared_uint64_tensor8"); array_template::declare("shared_int8_vector"); array_template::declare("shared_int8_matrix"); array_template::declare("shared_int8_tensor"); array_template::declare("shared_int8_tensor4"); array_template::declare("shared_int8_tensor5"); array_template::declare("shared_int8_tensor6"); array_template::declare("shared_int8_tensor7"); array_template::declare("shared_int8_tensor8"); array_template::declare("shared_int16_vector"); array_template::declare("shared_int16_matrix"); array_template::declare("shared_int16_tensor"); array_template::declare("shared_int16_tensor4"); array_template::declare("shared_int16_tensor5"); array_template::declare("shared_int16_tensor6"); array_template::declare("shared_int16_tensor7"); array_template::declare("shared_int16_tensor8"); array_template::declare("shared_int32_vector"); array_template::declare("shared_int32_matrix"); array_template::declare("shared_int32_tensor"); array_template::declare("shared_int32_tensor4"); array_template::declare("shared_int32_tensor5"); array_template::declare("shared_int32_tensor6"); array_template::declare("shared_int32_tensor7"); array_template::declare("shared_int32_tensor8"); array_template::declare("shared_int64_vector"); array_template::declare("shared_int64_matrix"); array_template::declare("shared_int64_tensor"); array_template::declare("shared_int64_tensor4"); array_template::declare("shared_int64_tensor5"); array_template::declare("shared_int64_tensor6"); array_template::declare("shared_int64_tensor7"); array_template::declare("shared_int64_tensor8"); array_template::declare("shared_float_vector"); array_template::declare("shared_float_matrix"); array_template::declare("shared_float_tensor"); array_template::declare("shared_float_tensor4"); array_template::declare("shared_float_tensor5"); array_template::declare("shared_float_tensor6"); array_template::declare("shared_float_tensor7"); array_template::declare("shared_float_tensor8"); array_template::declare("shared_double_vector"); array_template::declare("shared_double_matrix"); array_template::declare("shared_double_tensor"); array_template::declare("shared_double_tensor4"); array_template::declare("shared_double_tensor5"); array_template::declare("shared_double_tensor6"); array_template::declare("shared_double_tensor7"); array_template::declare("shared_double_tensor8"); def("make", make); // define aliases of numpy data types python::scope This; This.attr("bool8") = bool8; This.attr("uint8") = uint8; This.attr("uint16") = uint16; This.attr("uint32") = uint32; This.attr("uint64") = uint64; This.attr("int8") = int8; This.attr("int16") = int16; This.attr("int32") = int32; This.attr("int64") = int64; This.attr("float32") = float32; This.attr("float64") = float64; }