//
// 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;
}