GeezerVison is showing github.com — add pybind casters for strided_views, array_adaptor, and tensor_adaptor · lavaux/xtensor-python@fa97cf0
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: CMakeLists.txt +10-9Lines changed: 10 additions & 9 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -62,15 +62,16 @@ message(STATUS "Found numpy: ${NUMPY_INCLUDE_DIRS}") | 62 | 62 | # ===== | 63 | 63 |
| 64 | 64 | set(XTENSOR_PYTHON_HEADERS | 65 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pyarray.hpp | 66 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pyarray_backstrides.hpp | 67 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pycontainer.hpp | 68 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pystrides_adaptor.hpp | 69 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pytensor.hpp | 70 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pyvectorize.hpp | 71 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/xtensor_python_config.hpp | 72 | | - ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/xtensor_type_caster_base.hpp | 73 | | -) | | 65 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pyarray.hpp | | 66 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pyarray_backstrides.hpp | | 67 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pycontainer.hpp | | 68 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pynative_casters.hpp | | 69 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pystrides_adaptor.hpp | | 70 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pytensor.hpp | | 71 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/pyvectorize.hpp | | 72 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/xtensor_python_config.hpp | | 73 | + ${XTENSOR_PYTHON_INCLUDE_DIR}/xtensor-python/xtensor_type_caster_base.hpp | | 74 | + ) | 74 | 75 |
| 75 | 76 | add_library(xtensor-python INTERFACE) | 76 | 77 | target_include_directories(xtensor-python INTERFACE | |
Collapse file Copy file name to clipboardExpand all lines: include/xtensor-python/pyarray.hpp +1-5Lines changed: 1 addition & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 21 | 21 | #include "pyarray_backstrides.hpp" | 22 | 22 | #include "pycontainer.hpp" | 23 | 23 | #include "pystrides_adaptor.hpp" | | 24 | +#include "pynative_casters.hpp" | 24 | 25 | #include "xtensor_type_caster_base.hpp" | 25 | 26 |
| 26 | 27 | namespace xt | @@ -91,11 +92,6 @@ namespace pybind11 | 91 | 92 | } | 92 | 93 | }; | 93 | 94 |
| 94 | | - // Type caster for casting xarray to ndarray | 95 | | - template <class T, xt::layout_type L> | 96 | | - struct type_caster<xt::xarray<T, L>> : xtensor_type_caster_base<xt::xarray<T, L>> | 97 | | - { | 98 | | - }; | 99 | 95 | } | 100 | 96 | } | 101 | 97 |
| |
Collapse file Copy file name to clipboard +52Lines changed: 52 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| | 1 | +/*************************************************************************** | | 2 | +* Copyright (c) Wolf Vollprecht, Johan Mabille and Sylvain Corlay * | | 3 | +* Copyright (c) QuantStack * | | 4 | +* * | | 5 | +* Distributed under the terms of the BSD 3-Clause License. * | | 6 | +* * | | 7 | +* The full license is in the file LICENSE, distributed with this software. * | | 8 | +****************************************************************************/ | | 9 | + | | 10 | +#ifndef PYNATIVE_CASTERS_HPP | | 11 | +#define PYNATIVE_CASTERS_HPP | | 12 | + | | 13 | +#include "xtensor_type_caster_base.hpp" | | 14 | + | | 15 | + | | 16 | +namespace pybind11 | | 17 | +{ | | 18 | + namespace detail | | 19 | + { | | 20 | + // Type caster for casting xarray to ndarray | | 21 | + template <class T, xt::layout_type L> | | 22 | + struct type_caster<xt::xarray<T, L>> : xtensor_type_caster_base<xt::xarray<T, L>> | | 23 | + { | | 24 | + }; | | 25 | + | | 26 | + // Type caster for casting xt::xtensor to ndarray | | 27 | + template <class T, std::size_t N, xt::layout_type L> | | 28 | + struct type_caster<xt::xtensor<T, N, L>> : xtensor_type_caster_base<xt::xtensor<T, N, L>> | | 29 | + { | | 30 | + }; | | 31 | + | | 32 | + // Type caster for casting xt::xstrided_view to ndarray | | 33 | + template <class CT, class S, xt::layout_type L, class FST> | | 34 | + struct type_caster<xt::xstrided_view<CT, S, L, FST>> : xtensor_type_caster_base<xt::xstrided_view<CT, S, L, FST>> | | 35 | + { | | 36 | + }; | | 37 | + | | 38 | + // Type caster for casting xt::xarray_adaptor to ndarray | | 39 | + template <class EC, xt::layout_type L, class SC, class Tag> | | 40 | + struct type_caster<xt::xarray_adaptor<EC, L, SC, Tag>> : xtensor_type_caster_base<xt::xarray_adaptor<EC, L, SC, Tag>> | | 41 | + { | | 42 | + }; | | 43 | + | | 44 | + // Type caster for casting xt::xtensor_adaptor to ndarray | | 45 | + template <class EC, std::size_t N, xt::layout_type L, class Tag> | | 46 | + struct type_caster<xt::xtensor_adaptor<EC, N, L, Tag>> : xtensor_type_caster_base<xt::xtensor_adaptor<EC, N, L, Tag>> | | 47 | + { | | 48 | + }; | | 49 | + } | | 50 | +} | | 51 | + | | 52 | +#endif |
Collapse file Copy file name to clipboardExpand all lines: include/xtensor-python/pytensor.hpp +1-5Lines changed: 1 addition & 5 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 21 | 21 |
| 22 | 22 | #include "pycontainer.hpp" | 23 | 23 | #include "pystrides_adaptor.hpp" | | 24 | +#include "pynative_casters.hpp" | 24 | 25 | #include "xtensor_type_caster_base.hpp" | 25 | 26 |
| 26 | 27 | namespace xt | @@ -99,11 +100,6 @@ namespace pybind11 | 99 | 100 | } | 100 | 101 | }; | 101 | 102 |
| 102 | | - // Type caster for casting xt::xtensor to ndarray | 103 | | - template <class T, std::size_t N, xt::layout_type L> | 104 | | - struct type_caster<xt::xtensor<T, N, L>> : xtensor_type_caster_base<xt::xtensor<T, N, L>> | 105 | | - { | 106 | | - }; | 107 | 103 | } | 108 | 104 | } | 109 | 105 |
| |
Collapse file Copy file name to clipboardExpand all lines: include/xtensor-python/xtensor_type_caster_base.hpp +6-6Lines changed: 6 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -23,7 +23,7 @@ namespace pybind11 | 23 | 23 | { | 24 | 24 | namespace detail | 25 | 25 | { | 26 | | - // Casts an xtensor (or xarray) type to numpy array.If given a base, | | 26 | + // Casts a strided expression type to numpy array.If given a base, | 27 | 27 | // the numpy array references the src data, otherwise it'll make a copy. | 28 | 28 | // The writeable attributes lets you specify writeable flag for the array. | 29 | 29 | template <typename Type> | @@ -39,7 +39,7 @@ namespace pybind11 | 39 | 39 | std::vector<std::size_t> python_shape(src.shape().size()); | 40 | 40 | std::copy(src.shape().begin(), src.shape().end(), python_shape.begin()); | 41 | 41 |
| 42 | | - array a(python_shape, python_strides, src.begin(), base); | | 42 | + array a(python_shape, python_strides, &*(src.begin()), base); | 43 | 43 |
| 44 | 44 | if (!writeable) | 45 | 45 | { | @@ -49,8 +49,8 @@ namespace pybind11 | 49 | 49 | return a.release(); | 50 | 50 | } | 51 | 51 |
| 52 | | - // Takes an lvalue ref to some xtensor (or xarray) type and a (python) base object, creating a numpy array that | 53 | | - // reference the xtensor object's data with `base` as the python-registered base class (if omitted, | | 52 | + // Takes an lvalue ref to some strided expression type and a (python) base object, creating a numpy array that | | 53 | + // reference the expression object's data with `base` as the python-registered base class (if omitted, | 54 | 54 | // the base will be set to None, and lifetime management is up to the caller). The numpy array is | 55 | 55 | // non-writeable if the given type is const. | 56 | 56 | template <typename Type, typename CType> | @@ -59,7 +59,7 @@ namespace pybind11 | 59 | 59 | return xtensor_array_cast<Type>(src, parent, !std::is_const<CType>::value); | 60 | 60 | } | 61 | 61 |
| 62 | | - // Takes a pointer to xtensor (or xarray), builds a capsule around it, then returns a numpy | | 62 | + // Takes a pointer to a strided expression, builds a capsule around it, then returns a numpy | 63 | 63 | // array that references the encapsulated data with a python-side reference to the capsule to tie | 64 | 64 | // its destruction to that of any dependent python objects. Const-ness is determined by whether or | 65 | 65 | // not the CType of the pointer given is const. | @@ -70,7 +70,7 @@ namespace pybind11 | 70 | 70 | return xtensor_ref_array<Type>(*src, base); | 71 | 71 | } | 72 | 72 |
| 73 | | - // Base class of type_caster for xtensor and xarray | | 73 | + // Base class of type_caster for strided expressions | 74 | 74 | template <class Type> | 75 | 75 | struct xtensor_type_caster_base | 76 | 76 | { | |
Collapse file Copy file name to clipboardExpand all lines: test_python/main.cpp +56Lines changed: 56 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 15 | 15 | #include "xtensor-python/pyarray.hpp" | 16 | 16 | #include "xtensor-python/pytensor.hpp" | 17 | 17 | #include "xtensor-python/pyvectorize.hpp" | | 18 | +#include "xtensor/xadapt.hpp" | | 19 | +#include "xtensor/xstrided_view.hpp" | 18 | 20 |
| 19 | 21 | namespace py = pybind11; | 20 | 22 | using complex_t = std::complex<double>; | @@ -133,6 +135,49 @@ class C | 133 | 135 | array_type m_array; | 134 | 136 | }; | 135 | 137 |
| | 138 | +struct test_native_casters | | 139 | +{ | | 140 | + using array_type = xt::xarray<double>; | | 141 | + array_type a = xt::ones<double>({50, 50}); | | 142 | + | | 143 | + const auto & get_array() | | 144 | + { | | 145 | + return a; | | 146 | + } | | 147 | + | | 148 | + auto get_strided_view() | | 149 | + { | | 150 | + return xt::strided_view(a, {xt::range(0, 1), xt::range(0, 3, 2)}); | | 151 | + } | | 152 | + | | 153 | + auto get_array_adapter() | | 154 | + { | | 155 | + using shape_type = std::vector<size_t>; | | 156 | + shape_type shape = {2, 2}; | | 157 | + shape_type stride = {3, 2}; | | 158 | + return xt::adapt(a.data(), 4, xt::no_ownership(), shape, stride); | | 159 | + } | | 160 | + | | 161 | + auto get_tensor_adapter() | | 162 | + { | | 163 | + using shape_type = std::array<size_t, 2>; | | 164 | + shape_type shape = {2, 2}; | | 165 | + shape_type stride = {3, 2}; | | 166 | + return xt::adapt(a.data(), 4, xt::no_ownership(), shape, stride); | | 167 | + } | | 168 | + | | 169 | + auto get_owning_array_adapter() | | 170 | + { | | 171 | + size_t size = 100; | | 172 | + int * data = new int[size]; | | 173 | + std::fill(data, data + size, 1); | | 174 | + | | 175 | + using shape_type = std::vector<size_t>; | | 176 | + shape_type shape = {size}; | | 177 | + return xt::adapt(std::move(data), size, xt::acquire_ownership(), shape); | | 178 | + } | | 179 | +}; | | 180 | + | 136 | 181 | xt::pyarray<A> dtype_to_python() | 137 | 182 | { | 138 | 183 | A a1{123, 321, 'a', {1, 2, 3}}; | @@ -257,4 +302,15 @@ PYBIND11_MODULE(xtensor_python_test, m) | 257 | 302 |
| 258 | 303 | m.def("diff_shape_overload", [](xt::pytensor<int, 1> a) { return 1; }); | 259 | 304 | m.def("diff_shape_overload", [](xt::pytensor<int, 2> a) { return 2; }); | | 305 | + | | 306 | + py::class_<test_native_casters>(m, "test_native_casters") | | 307 | + .def(py::init<>()) | | 308 | + .def("get_array", &test_native_casters::get_array, py::return_value_policy::reference_internal) // memory managed by the class instance | | 309 | + .def("get_strided_view", &test_native_casters::get_strided_view, py::keep_alive<0, 1>()) // keep_alive<0, 1>() => do not free "self" before the returned view | | 310 | + .def("get_array_adapter", &test_native_casters::get_array_adapter, py::keep_alive<0, 1>()) // keep_alive<0, 1>() => do not free "self" before the returned adapter | | 311 | + .def("get_tensor_adapter", &test_native_casters::get_tensor_adapter, py::keep_alive<0, 1>()) // keep_alive<0, 1>() => do not free "self" before the returned adapter | | 312 | + .def("get_owning_array_adapter", &test_native_casters::get_owning_array_adapter) // auto memory management as the adapter owns its memory | | 313 | + .def("view_keep_alive_member_function", [](test_native_casters & self, xt::pyarray<double> & a) // keep_alive<0, 2>() => do not free second parameter before the returned view | | 314 | + {return xt::reshape_view(a, {a.size(), });}, | | 315 | + py::keep_alive<0, 2>()); | 260 | 316 | } |
Collapse file Copy file name to clipboardExpand all lines: test_python/test_pyarray.py +67Lines changed: 67 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -166,6 +166,73 @@ def test_diff_shape_overload(self): | 166 | 166 | # FIXME: the TypeError information is not informative | 167 | 167 | xt.diff_shape_overload(np.ones((2, 2, 2))) | 168 | 168 |
| | 169 | + def test_native_casters(self): | | 170 | + import gc | | 171 | + | | 172 | + # check keep alive policy for get_strided_view() | | 173 | + gc.collect() | | 174 | + obj = xt.test_native_casters() | | 175 | + a = obj.get_strided_view() | | 176 | + obj = None | | 177 | + gc.collect() | | 178 | + _ = np.zeros((100, 100)) | | 179 | + self.assertEqual(a.sum(), a.size) | | 180 | + | | 181 | + # check keep alive policy for get_array_adapter() | | 182 | + gc.collect() | | 183 | + obj = xt.test_native_casters() | | 184 | + a = obj.get_array_adapter() | | 185 | + obj = None | | 186 | + gc.collect() | | 187 | + _ = np.zeros((100, 100)) | | 188 | + self.assertEqual(a.sum(), a.size) | | 189 | + | | 190 | + # check keep alive policy for get_array_adapter() | | 191 | + gc.collect() | | 192 | + obj = xt.test_native_casters() | | 193 | + a = obj.get_tensor_adapter() | | 194 | + obj = None | | 195 | + gc.collect() | | 196 | + _ = np.zeros((100, 100)) | | 197 | + self.assertEqual(a.sum(), a.size) | | 198 | + | | 199 | + # check keep alive policy for get_owning_array_adapter() | | 200 | + gc.collect() | | 201 | + obj = xt.test_native_casters() | | 202 | + a = obj.get_owning_array_adapter() | | 203 | + gc.collect() | | 204 | + _ = np.zeros((100, 100)) | | 205 | + self.assertEqual(a.sum(), a.size) | | 206 | + | | 207 | + # check keep alive policy for view_keep_alive_member_function() | | 208 | + gc.collect() | | 209 | + a = np.ones((100, 100)) | | 210 | + b = obj.view_keep_alive_member_function(a) | | 211 | + obj = None | | 212 | + a = None | | 213 | + gc.collect() | | 214 | + _ = np.zeros((100, 100)) | | 215 | + self.assertEqual(b.sum(), b.size) | | 216 | + | | 217 | + # check shared buffer (insure that no copy is done) | | 218 | + obj = xt.test_native_casters() | | 219 | + arr = obj.get_array() | | 220 | + | | 221 | + strided_view = obj.get_strided_view() | | 222 | + strided_view[0, 1] = -1 | | 223 | + self.assertEqual(strided_view.shape, (1, 2)) | | 224 | + self.assertEqual(arr[0, 2], -1) | | 225 | + | | 226 | + adapter = obj.get_array_adapter() | | 227 | + self.assertEqual(adapter.shape, (2, 2)) | | 228 | + adapter[1, 1] = -2 | | 229 | + self.assertEqual(arr[0, 5], -2) | | 230 | + | | 231 | + adapter = obj.get_tensor_adapter() | | 232 | + self.assertEqual(adapter.shape, (2, 2)) | | 233 | + adapter[1, 1] = -3 | | 234 | + self.assertEqual(arr[0, 5], -3) | | 235 | + | 169 | 236 |
| 170 | 237 | class AttributeTest(TestCase): | 171 | 238 |
| |
|