test_numpy_array.cpp revision 12391
1/* 2 tests/test_numpy_array.cpp -- test core array functionality 3 4 Copyright (c) 2016 Ivan Smirnov <i.s.smirnov@gmail.com> 5 6 All rights reserved. Use of this source code is governed by a 7 BSD-style license that can be found in the LICENSE file. 8*/ 9 10#include "pybind11_tests.h" 11 12#include <pybind11/numpy.h> 13#include <pybind11/stl.h> 14 15#include <cstdint> 16 17using arr = py::array; 18using arr_t = py::array_t<uint16_t, 0>; 19static_assert(std::is_same<arr_t::value_type, uint16_t>::value, ""); 20 21template<typename... Ix> arr data(const arr& a, Ix... index) { 22 return arr(a.nbytes() - a.offset_at(index...), (const uint8_t *) a.data(index...)); 23} 24 25template<typename... Ix> arr data_t(const arr_t& a, Ix... index) { 26 return arr(a.size() - a.index_at(index...), a.data(index...)); 27} 28 29template<typename... Ix> arr& mutate_data(arr& a, Ix... index) { 30 auto ptr = (uint8_t *) a.mutable_data(index...); 31 for (ssize_t i = 0; i < a.nbytes() - a.offset_at(index...); i++) 32 ptr[i] = (uint8_t) (ptr[i] * 2); 33 return a; 34} 35 36template<typename... Ix> arr_t& mutate_data_t(arr_t& a, Ix... index) { 37 auto ptr = a.mutable_data(index...); 38 for (ssize_t i = 0; i < a.size() - a.index_at(index...); i++) 39 ptr[i]++; 40 return a; 41} 42 43template<typename... Ix> ssize_t index_at(const arr& a, Ix... idx) { return a.index_at(idx...); } 44template<typename... Ix> ssize_t index_at_t(const arr_t& a, Ix... idx) { return a.index_at(idx...); } 45template<typename... Ix> ssize_t offset_at(const arr& a, Ix... idx) { return a.offset_at(idx...); } 46template<typename... Ix> ssize_t offset_at_t(const arr_t& a, Ix... idx) { return a.offset_at(idx...); } 47template<typename... Ix> ssize_t at_t(const arr_t& a, Ix... idx) { return a.at(idx...); } 48template<typename... Ix> arr_t& mutate_at_t(arr_t& a, Ix... idx) { a.mutable_at(idx...)++; return a; } 49 50#define def_index_fn(name, type) \ 51 sm.def(#name, [](type a) { return name(a); }); \ 52 sm.def(#name, [](type a, int i) { return name(a, i); }); \ 53 sm.def(#name, [](type a, int i, int j) { return name(a, i, j); }); \ 54 sm.def(#name, [](type a, int i, int j, int k) { return name(a, i, j, k); }); 55 56template <typename T, typename T2> py::handle auxiliaries(T &&r, T2 &&r2) { 57 if (r.ndim() != 2) throw std::domain_error("error: ndim != 2"); 58 py::list l; 59 l.append(*r.data(0, 0)); 60 l.append(*r2.mutable_data(0, 0)); 61 l.append(r.data(0, 1) == r2.mutable_data(0, 1)); 62 l.append(r.ndim()); 63 l.append(r.itemsize()); 64 l.append(r.shape(0)); 65 l.append(r.shape(1)); 66 l.append(r.size()); 67 l.append(r.nbytes()); 68 return l.release(); 69} 70 71TEST_SUBMODULE(numpy_array, sm) { 72 try { py::module::import("numpy"); } 73 catch (...) { return; } 74 75 // test_array_attributes 76 sm.def("ndim", [](const arr& a) { return a.ndim(); }); 77 sm.def("shape", [](const arr& a) { return arr(a.ndim(), a.shape()); }); 78 sm.def("shape", [](const arr& a, ssize_t dim) { return a.shape(dim); }); 79 sm.def("strides", [](const arr& a) { return arr(a.ndim(), a.strides()); }); 80 sm.def("strides", [](const arr& a, ssize_t dim) { return a.strides(dim); }); 81 sm.def("writeable", [](const arr& a) { return a.writeable(); }); 82 sm.def("size", [](const arr& a) { return a.size(); }); 83 sm.def("itemsize", [](const arr& a) { return a.itemsize(); }); 84 sm.def("nbytes", [](const arr& a) { return a.nbytes(); }); 85 sm.def("owndata", [](const arr& a) { return a.owndata(); }); 86 87 // test_index_offset 88 def_index_fn(index_at, const arr&); 89 def_index_fn(index_at_t, const arr_t&); 90 def_index_fn(offset_at, const arr&); 91 def_index_fn(offset_at_t, const arr_t&); 92 // test_data 93 def_index_fn(data, const arr&); 94 def_index_fn(data_t, const arr_t&); 95 // test_mutate_data, test_mutate_readonly 96 def_index_fn(mutate_data, arr&); 97 def_index_fn(mutate_data_t, arr_t&); 98 def_index_fn(at_t, const arr_t&); 99 def_index_fn(mutate_at_t, arr_t&); 100 101 // test_make_c_f_array 102 sm.def("make_f_array", [] { return py::array_t<float>({ 2, 2 }, { 4, 8 }); }); 103 sm.def("make_c_array", [] { return py::array_t<float>({ 2, 2 }, { 8, 4 }); }); 104 105 // test_wrap 106 sm.def("wrap", [](py::array a) { 107 return py::array( 108 a.dtype(), 109 {a.shape(), a.shape() + a.ndim()}, 110 {a.strides(), a.strides() + a.ndim()}, 111 a.data(), 112 a 113 ); 114 }); 115 116 // test_numpy_view 117 struct ArrayClass { 118 int data[2] = { 1, 2 }; 119 ArrayClass() { py::print("ArrayClass()"); } 120 ~ArrayClass() { py::print("~ArrayClass()"); } 121 }; 122 py::class_<ArrayClass>(sm, "ArrayClass") 123 .def(py::init<>()) 124 .def("numpy_view", [](py::object &obj) { 125 py::print("ArrayClass::numpy_view()"); 126 ArrayClass &a = obj.cast<ArrayClass&>(); 127 return py::array_t<int>({2}, {4}, a.data, obj); 128 } 129 ); 130 131 // test_cast_numpy_int64_to_uint64 132 sm.def("function_taking_uint64", [](uint64_t) { }); 133 134 // test_isinstance 135 sm.def("isinstance_untyped", [](py::object yes, py::object no) { 136 return py::isinstance<py::array>(yes) && !py::isinstance<py::array>(no); 137 }); 138 sm.def("isinstance_typed", [](py::object o) { 139 return py::isinstance<py::array_t<double>>(o) && !py::isinstance<py::array_t<int>>(o); 140 }); 141 142 // test_constructors 143 sm.def("default_constructors", []() { 144 return py::dict( 145 "array"_a=py::array(), 146 "array_t<int32>"_a=py::array_t<std::int32_t>(), 147 "array_t<double>"_a=py::array_t<double>() 148 ); 149 }); 150 sm.def("converting_constructors", [](py::object o) { 151 return py::dict( 152 "array"_a=py::array(o), 153 "array_t<int32>"_a=py::array_t<std::int32_t>(o), 154 "array_t<double>"_a=py::array_t<double>(o) 155 ); 156 }); 157 158 // test_overload_resolution 159 sm.def("overloaded", [](py::array_t<double>) { return "double"; }); 160 sm.def("overloaded", [](py::array_t<float>) { return "float"; }); 161 sm.def("overloaded", [](py::array_t<int>) { return "int"; }); 162 sm.def("overloaded", [](py::array_t<unsigned short>) { return "unsigned short"; }); 163 sm.def("overloaded", [](py::array_t<long long>) { return "long long"; }); 164 sm.def("overloaded", [](py::array_t<std::complex<double>>) { return "double complex"; }); 165 sm.def("overloaded", [](py::array_t<std::complex<float>>) { return "float complex"; }); 166 167 sm.def("overloaded2", [](py::array_t<std::complex<double>>) { return "double complex"; }); 168 sm.def("overloaded2", [](py::array_t<double>) { return "double"; }); 169 sm.def("overloaded2", [](py::array_t<std::complex<float>>) { return "float complex"; }); 170 sm.def("overloaded2", [](py::array_t<float>) { return "float"; }); 171 172 // Only accept the exact types: 173 sm.def("overloaded3", [](py::array_t<int>) { return "int"; }, py::arg().noconvert()); 174 sm.def("overloaded3", [](py::array_t<double>) { return "double"; }, py::arg().noconvert()); 175 176 // Make sure we don't do unsafe coercion (e.g. float to int) when not using forcecast, but 177 // rather that float gets converted via the safe (conversion to double) overload: 178 sm.def("overloaded4", [](py::array_t<long long, 0>) { return "long long"; }); 179 sm.def("overloaded4", [](py::array_t<double, 0>) { return "double"; }); 180 181 // But we do allow conversion to int if forcecast is enabled (but only if no overload matches 182 // without conversion) 183 sm.def("overloaded5", [](py::array_t<unsigned int>) { return "unsigned int"; }); 184 sm.def("overloaded5", [](py::array_t<double>) { return "double"; }); 185 186 // test_greedy_string_overload 187 // Issue 685: ndarray shouldn't go to std::string overload 188 sm.def("issue685", [](std::string) { return "string"; }); 189 sm.def("issue685", [](py::array) { return "array"; }); 190 sm.def("issue685", [](py::object) { return "other"; }); 191 192 // test_array_unchecked_fixed_dims 193 sm.def("proxy_add2", [](py::array_t<double> a, double v) { 194 auto r = a.mutable_unchecked<2>(); 195 for (ssize_t i = 0; i < r.shape(0); i++) 196 for (ssize_t j = 0; j < r.shape(1); j++) 197 r(i, j) += v; 198 }, py::arg().noconvert(), py::arg()); 199 200 sm.def("proxy_init3", [](double start) { 201 py::array_t<double, py::array::c_style> a({ 3, 3, 3 }); 202 auto r = a.mutable_unchecked<3>(); 203 for (ssize_t i = 0; i < r.shape(0); i++) 204 for (ssize_t j = 0; j < r.shape(1); j++) 205 for (ssize_t k = 0; k < r.shape(2); k++) 206 r(i, j, k) = start++; 207 return a; 208 }); 209 sm.def("proxy_init3F", [](double start) { 210 py::array_t<double, py::array::f_style> a({ 3, 3, 3 }); 211 auto r = a.mutable_unchecked<3>(); 212 for (ssize_t k = 0; k < r.shape(2); k++) 213 for (ssize_t j = 0; j < r.shape(1); j++) 214 for (ssize_t i = 0; i < r.shape(0); i++) 215 r(i, j, k) = start++; 216 return a; 217 }); 218 sm.def("proxy_squared_L2_norm", [](py::array_t<double> a) { 219 auto r = a.unchecked<1>(); 220 double sumsq = 0; 221 for (ssize_t i = 0; i < r.shape(0); i++) 222 sumsq += r[i] * r(i); // Either notation works for a 1D array 223 return sumsq; 224 }); 225 226 sm.def("proxy_auxiliaries2", [](py::array_t<double> a) { 227 auto r = a.unchecked<2>(); 228 auto r2 = a.mutable_unchecked<2>(); 229 return auxiliaries(r, r2); 230 }); 231 232 // test_array_unchecked_dyn_dims 233 // Same as the above, but without a compile-time dimensions specification: 234 sm.def("proxy_add2_dyn", [](py::array_t<double> a, double v) { 235 auto r = a.mutable_unchecked(); 236 if (r.ndim() != 2) throw std::domain_error("error: ndim != 2"); 237 for (ssize_t i = 0; i < r.shape(0); i++) 238 for (ssize_t j = 0; j < r.shape(1); j++) 239 r(i, j) += v; 240 }, py::arg().noconvert(), py::arg()); 241 sm.def("proxy_init3_dyn", [](double start) { 242 py::array_t<double, py::array::c_style> a({ 3, 3, 3 }); 243 auto r = a.mutable_unchecked(); 244 if (r.ndim() != 3) throw std::domain_error("error: ndim != 3"); 245 for (ssize_t i = 0; i < r.shape(0); i++) 246 for (ssize_t j = 0; j < r.shape(1); j++) 247 for (ssize_t k = 0; k < r.shape(2); k++) 248 r(i, j, k) = start++; 249 return a; 250 }); 251 sm.def("proxy_auxiliaries2_dyn", [](py::array_t<double> a) { 252 return auxiliaries(a.unchecked(), a.mutable_unchecked()); 253 }); 254 255 sm.def("array_auxiliaries2", [](py::array_t<double> a) { 256 return auxiliaries(a, a); 257 }); 258 259 // test_array_failures 260 // Issue #785: Uninformative "Unknown internal error" exception when constructing array from empty object: 261 sm.def("array_fail_test", []() { return py::array(py::object()); }); 262 sm.def("array_t_fail_test", []() { return py::array_t<double>(py::object()); }); 263 // Make sure the error from numpy is being passed through: 264 sm.def("array_fail_test_negative_size", []() { int c = 0; return py::array(-1, &c); }); 265 266 // test_initializer_list 267 // Issue (unnumbered; reported in #788): regression: initializer lists can be ambiguous 268 sm.def("array_initializer_list1", []() { return py::array_t<float>(1); }); // { 1 } also works, but clang warns about it 269 sm.def("array_initializer_list2", []() { return py::array_t<float>({ 1, 2 }); }); 270 sm.def("array_initializer_list3", []() { return py::array_t<float>({ 1, 2, 3 }); }); 271 sm.def("array_initializer_list4", []() { return py::array_t<float>({ 1, 2, 3, 4 }); }); 272 273 // test_array_resize 274 // reshape array to 2D without changing size 275 sm.def("array_reshape2", [](py::array_t<double> a) { 276 const ssize_t dim_sz = (ssize_t)std::sqrt(a.size()); 277 if (dim_sz * dim_sz != a.size()) 278 throw std::domain_error("array_reshape2: input array total size is not a squared integer"); 279 a.resize({dim_sz, dim_sz}); 280 }); 281 282 // resize to 3D array with each dimension = N 283 sm.def("array_resize3", [](py::array_t<double> a, size_t N, bool refcheck) { 284 a.resize({N, N, N}, refcheck); 285 }); 286 287 // test_array_create_and_resize 288 // return 2D array with Nrows = Ncols = N 289 sm.def("create_and_resize", [](size_t N) { 290 py::array_t<double> a; 291 a.resize({N, N}); 292 std::fill(a.mutable_data(), a.mutable_data() + a.size(), 42.); 293 return a; 294 }); 295} 296