diff --git a/include/xtensor-python/pyarray.hpp b/include/xtensor-python/pyarray.hpp index b4507ce..bad595c 100644 --- a/include/xtensor-python/pyarray.hpp +++ b/include/xtensor-python/pyarray.hpp @@ -346,6 +346,9 @@ namespace xt explicit pyarray(const shape_type& shape, const strides_type& strides, const_reference value); explicit pyarray(const shape_type& shape, const strides_type& strides); + template + static pyarray from_shape(S&& s); + pyarray(const self_type& rhs); self_type& operator=(const self_type& rhs); @@ -605,6 +608,18 @@ namespace xt { init_array(shape, strides); } + + /** + * Allocates and returns an pyarray with the specified shape. + * @param shape the shape of the pyarray + */ + template + template + inline pyarray pyarray::from_shape(S&& shape) + { + auto shp = xtl::forward_sequence(shape); + return self_type(shp); + } //@} /** @@ -726,6 +741,12 @@ namespace xt static_cast(PyArray_NDIM(this->python_array()))); m_strides = inner_strides_type(reinterpret_cast(PyArray_STRIDES(this->python_array())), static_cast(PyArray_NDIM(this->python_array()))); + + if (L != layout_type::dynamic && !do_strides_match(m_shape, m_strides, L)) + { + throw std::runtime_error("NumPy: passing container with bad strides for layout (is it a view?)."); + } + m_backstrides = backstrides_type(*this); m_storage = storage_type(reinterpret_cast(PyArray_DATA(this->python_array())), this->get_min_stride() * static_cast(PyArray_SIZE(this->python_array()))); diff --git a/include/xtensor-python/pycontainer.hpp b/include/xtensor-python/pycontainer.hpp index 7251fef..2904bb6 100644 --- a/include/xtensor-python/pycontainer.hpp +++ b/include/xtensor-python/pycontainer.hpp @@ -226,8 +226,7 @@ namespace xt bool check_array(const pybind11::handle& src) { using is_arithmetic_type = std::integral_constant::value)>; - return PyArray_Check(src.ptr()) && - check_array_type(src, is_arithmetic_type{}); + return PyArray_Check(src.ptr()) && check_array_type(src, is_arithmetic_type{}); } } @@ -277,9 +276,7 @@ namespace xt template inline bool pycontainer::check_(pybind11::handle h) { - auto dtype = pybind11::detail::npy_format_descriptor::dtype(); - return PyArray_Check(h.ptr()) && - PyArray_EquivTypes_(PyArray_TYPE(reinterpret_cast(h.ptr())), dtype.ptr()); + return detail::check_array(h); } template @@ -321,6 +318,30 @@ namespace xt return *static_cast(this); } + namespace detail + { + template + struct check_dims + { + static bool run(std::size_t) + { + return true; + } + }; + + template + struct check_dims> + { + static bool run(std::size_t new_dim) + { + if(new_dim != N) + { + throw std::runtime_error("Dims not matching."); + } + return new_dim == N; + } + }; + } /** * resizes the container. @@ -359,6 +380,7 @@ namespace xt template inline void pycontainer::resize(const S& shape, const strides_type& strides) { + detail::check_dims::run(shape.size()); derived_type tmp(xtl::forward_sequence(shape), strides); *static_cast(this) = std::move(tmp); } @@ -369,9 +391,9 @@ namespace xt { if (compute_size(shape) != this->size()) { - throw std::runtime_error("Cannot reshape with incorrect number of elements."); + throw std::runtime_error("Cannot reshape with incorrect number of elements (" + std::to_string(this->size()) + " vs " + std::to_string(compute_size(shape)) + ")"); } - + detail::check_dims::run(shape.size()); layout = default_assignable_layout(layout); NPY_ORDER npy_layout; @@ -388,7 +410,8 @@ namespace xt throw std::runtime_error("Cannot reshape with unknown layout_type."); } - PyArray_Dims dims = {reinterpret_cast(shape.data()), static_cast(shape.size())}; + using shape_ptr = typename std::decay_t::pointer; + PyArray_Dims dims = {reinterpret_cast(const_cast(shape.data())), static_cast(shape.size())}; auto new_ptr = PyArray_Newshape((PyArrayObject*) this->ptr(), &dims, npy_layout); auto old_ptr = this->ptr(); this->ptr() = new_ptr; diff --git a/include/xtensor-python/pytensor.hpp b/include/xtensor-python/pytensor.hpp index 1828154..0b73012 100644 --- a/include/xtensor-python/pytensor.hpp +++ b/include/xtensor-python/pytensor.hpp @@ -163,6 +163,9 @@ namespace xt explicit pytensor(const shape_type& shape, const strides_type& strides, const_reference value); explicit pytensor(const shape_type& shape, const strides_type& strides); + template + static pytensor from_shape(S&& shape); + pytensor(const self_type& rhs); self_type& operator=(const self_type& rhs); @@ -315,6 +318,19 @@ namespace xt { init_tensor(shape, strides); } + + /** + * Allocates and returns an pytensor with the specified shape. + * @param shape the shape of the pytensor + */ + template + template + inline pytensor pytensor::from_shape(S&& shape) + { + detail::check_dims::run(shape.size()); + auto shp = xtl::forward_sequence(shape); + return self_type(shp); + } //@} /** @@ -429,6 +445,12 @@ namespace xt std::transform(PyArray_STRIDES(this->python_array()), PyArray_STRIDES(this->python_array()) + N, m_strides.begin(), [](auto v) { return v / sizeof(T); }); adapt_strides(m_shape, m_strides, m_backstrides); + + if (L != layout_type::dynamic && !do_strides_match(m_shape, m_strides, L)) + { + throw std::runtime_error("NumPy: passing container with bad strides for layout (is it a view?)."); + } + m_storage = storage_type(reinterpret_cast(PyArray_DATA(this->python_array())), this->get_min_stride() * static_cast(PyArray_SIZE(this->python_array()))); } diff --git a/test/test_pyarray.cpp b/test/test_pyarray.cpp index 0c02abb..7dc8214 100644 --- a/test/test_pyarray.cpp +++ b/test/test_pyarray.cpp @@ -52,6 +52,15 @@ namespace xt } } + TEST(pyarray, from_shape) + { + auto arr = pyarray::from_shape({5, 2, 6}); + auto exp_shape = std::vector{5, 2, 6}; + EXPECT_TRUE(std::equal(arr.shape().begin(), arr.shape().end(), exp_shape.begin())); + EXPECT_EQ(arr.shape().size(), 3); + EXPECT_EQ(arr.size(), 5 * 2 * 6); + } + TEST(pyarray, strided_constructor) { central_major_result<> cmr; diff --git a/test/test_pytensor.cpp b/test/test_pytensor.cpp index 3dd0228..1dc4e77 100644 --- a/test/test_pytensor.cpp +++ b/test/test_pytensor.cpp @@ -51,6 +51,18 @@ namespace xt } } + TEST(pytensor, from_shape) + { + auto arr = pytensor::from_shape({5, 2, 6}); + auto exp_shape = std::vector{5, 2, 6}; + EXPECT_TRUE(std::equal(arr.shape().begin(), arr.shape().end(), exp_shape.begin())); + EXPECT_EQ(arr.shape().size(), 3); + EXPECT_EQ(arr.size(), 5 * 2 * 6); + using pyt3 = pytensor; + std::vector shp = std::vector{5, 2}; + EXPECT_THROW(pyt3::from_shape(shp), std::runtime_error); + } + TEST(pytensor, strided_constructor) { central_major_result cmr; @@ -211,8 +223,12 @@ namespace xt { pytensor a = {{1,2,3}, {4,5,6}}; auto ptr = a.data(); + a.reshape(a.shape()); // compilation check a.reshape({1, 6}); EXPECT_EQ(ptr, a.data()); - EXPECT_THROW(a.reshape({6}), std::runtime_error); + EXPECT_THROW(a.reshape(std::vector{6}), std::runtime_error); + // note this throws because std array has only 1 element initialized + // and the second element is `0`. + EXPECT_THROW(a.reshape({6, 5}), std::runtime_error); } } diff --git a/test_python/main.cpp b/test_python/main.cpp index 79c5253..3caf813 100644 --- a/test_python/main.cpp +++ b/test_python/main.cpp @@ -12,6 +12,7 @@ #include "xtensor/xarray.hpp" #define FORCE_IMPORT_ARRAY #include "xtensor-python/pyarray.hpp" +#include "xtensor-python/pytensor.hpp" #include "xtensor-python/pyvectorize.hpp" namespace py = pybind11; @@ -154,6 +155,22 @@ void char_array(xt::pyarray& carr) carr(2)[3] = '\0'; } +void row_major_tensor(xt::pytensor& arg) +{ + if (!std::is_same::value) + { + throw std::runtime_error("TEST FAILED"); + } +} + +void col_major_array(xt::pyarray& arg) +{ + if (!std::is_same()), double*>::value) + { + throw std::runtime_error("TEST FAILED"); + } +} + PYBIND11_MODULE(xtensor_python_test, m) { xt::import_numpy(); @@ -197,4 +214,7 @@ PYBIND11_MODULE(xtensor_python_test, m) m.def("dtype_to_python", dtype_to_python); m.def("dtype_from_python", dtype_from_python); m.def("char_array", char_array); + + m.def("col_major_array", col_major_array); + m.def("row_major_tensor", row_major_tensor); } diff --git a/test_python/setup.py b/test_python/setup.py index aea0f49..dc83c22 100644 --- a/test_python/setup.py +++ b/test_python/setup.py @@ -46,6 +46,7 @@ def __str__(self): ['main.cpp'], include_dirs=[ # Path to pybind11 headers + '../include/', get_pybind_include(), get_pybind_include(user=True), # Path to numpy headers diff --git a/test_python/test_pyarray.py b/test_python/test_pyarray.py index 2e8abcc..80a7a30 100644 --- a/test_python/test_pyarray.py +++ b/test_python/test_pyarray.py @@ -125,3 +125,24 @@ def test_char_array(self): self.assertEqual(var[0], b'hello') self.assertEqual(var[1], b'from') self.assertEqual(var[2], b'c++') + + def test_col_row_major(self): + var = np.arange(50, dtype=float).reshape(2, 5, 5) + + with self.assertRaises(RuntimeError): + xt.col_major_array(var) + + with self.assertRaises(RuntimeError): + xt.row_major_tensor(var.T) + + with self.assertRaises(RuntimeError): + xt.row_major_tensor(var[:, ::2, ::2]) + + with self.assertRaises(RuntimeError): + # raise for wrong dimension + xt.row_major_tensor(var[0, 0, :]) + + xt.row_major_tensor(var) + varF = np.arange(50, dtype=float).reshape(2, 5, 5, order='F') + xt.col_major_array(varF) + xt.col_major_array(varF[:, :, 0]) # still col major!