test_numpy_array.cpp 19.4 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
/*
    tests/test_numpy_array.cpp -- test core array functionality

    Copyright (c) 2016 Ivan Smirnov <i.s.smirnov@gmail.com>

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE file.
*/

#include <pybind11/numpy.h>
#include <pybind11/stl.h>

13
14
#include "pybind11_tests.h"

15
#include <cstdint>
16
#include <utility>
17

18
19
20
21
22
23
24
// Size / dtype checks.
struct DtypeCheck {
    py::dtype numpy{};
    py::dtype pybind11{};
};

template <typename T>
25
DtypeCheck get_dtype_check(const char *name) {
26
    py::module_ np = py::module_::import("numpy");
27
28
29
30
31
32
33
    DtypeCheck check{};
    check.numpy = np.attr("dtype")(np.attr(name));
    check.pybind11 = py::dtype::of<T>();
    return check;
}

std::vector<DtypeCheck> get_concrete_dtype_checks() {
34
35
36
37
38
39
40
41
42
    return {// Normalization
            get_dtype_check<std::int8_t>("int8"),
            get_dtype_check<std::uint8_t>("uint8"),
            get_dtype_check<std::int16_t>("int16"),
            get_dtype_check<std::uint16_t>("uint16"),
            get_dtype_check<std::int32_t>("int32"),
            get_dtype_check<std::uint32_t>("uint32"),
            get_dtype_check<std::int64_t>("int64"),
            get_dtype_check<std::uint64_t>("uint64")};
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
}

struct DtypeSizeCheck {
    std::string name{};
    int size_cpp{};
    int size_numpy{};
    // For debugging.
    py::dtype dtype{};
};

template <typename T>
DtypeSizeCheck get_dtype_size_check() {
    DtypeSizeCheck check{};
    check.name = py::type_id<T>();
    check.size_cpp = sizeof(T);
    check.dtype = py::dtype::of<T>();
    check.size_numpy = check.dtype.attr("itemsize").template cast<int>();
    return check;
}

std::vector<DtypeSizeCheck> get_platform_dtype_size_checks() {
    return {
        get_dtype_size_check<short>(),
        get_dtype_size_check<unsigned short>(),
        get_dtype_size_check<int>(),
        get_dtype_size_check<unsigned int>(),
        get_dtype_size_check<long>(),
        get_dtype_size_check<unsigned long>(),
        get_dtype_size_check<long long>(),
        get_dtype_size_check<unsigned long long>(),
    };
}

// Arrays.
77
78
using arr = py::array;
using arr_t = py::array_t<uint16_t, 0>;
79
static_assert(std::is_same<arr_t::value_type, uint16_t>::value, "");
80

81
82
template <typename... Ix>
arr data(const arr &a, Ix... index) {
83
    return arr(a.nbytes() - a.offset_at(index...), (const uint8_t *) a.data(index...));
84
85
}

86
87
template <typename... Ix>
arr data_t(const arr_t &a, Ix... index) {
88
    return arr(a.size() - a.index_at(index...), a.data(index...));
89
90
}

91
92
template <typename... Ix>
arr &mutate_data(arr &a, Ix... index) {
93
    auto *ptr = (uint8_t *) a.mutable_data(index...);
94
    for (py::ssize_t i = 0; i < a.nbytes() - a.offset_at(index...); i++) {
95
        ptr[i] = (uint8_t) (ptr[i] * 2);
96
    }
97
98
99
    return a;
}

100
101
template <typename... Ix>
arr_t &mutate_data_t(arr_t &a, Ix... index) {
102
    auto ptr = a.mutable_data(index...);
103
    for (py::ssize_t i = 0; i < a.size() - a.index_at(index...); i++) {
104
        ptr[i]++;
105
    }
106
107
108
    return a;
}

109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
template <typename... Ix>
py::ssize_t index_at(const arr &a, Ix... idx) {
    return a.index_at(idx...);
}
template <typename... Ix>
py::ssize_t index_at_t(const arr_t &a, Ix... idx) {
    return a.index_at(idx...);
}
template <typename... Ix>
py::ssize_t offset_at(const arr &a, Ix... idx) {
    return a.offset_at(idx...);
}
template <typename... Ix>
py::ssize_t offset_at_t(const arr_t &a, Ix... idx) {
    return a.offset_at(idx...);
}
template <typename... Ix>
py::ssize_t at_t(const arr_t &a, Ix... idx) {
    return a.at(idx...);
}
template <typename... Ix>
arr_t &mutate_at_t(arr_t &a, Ix... idx) {
    a.mutable_at(idx...)++;
    return a;
}

#define def_index_fn(name, type)                                                                  \
    sm.def(#name, [](type a) { return name(a); });                                                \
    sm.def(#name, [](type a, int i) { return name(a, i); });                                      \
    sm.def(#name, [](type a, int i, int j) { return name(a, i, j); });                            \
139
140
    sm.def(#name, [](type a, int i, int j, int k) { return name(a, i, j, k); });

141
142
template <typename T, typename T2>
py::handle auxiliaries(T &&r, T2 &&r2) {
143
144
145
    if (r.ndim() != 2) {
        throw std::domain_error("error: ndim != 2");
    }
146
147
148
149
150
151
152
153
154
155
156
157
158
    py::list l;
    l.append(*r.data(0, 0));
    l.append(*r2.mutable_data(0, 0));
    l.append(r.data(0, 1) == r2.mutable_data(0, 1));
    l.append(r.ndim());
    l.append(r.itemsize());
    l.append(r.shape(0));
    l.append(r.shape(1));
    l.append(r.size());
    l.append(r.nbytes());
    return l.release();
}

159
160
161
// note: declaration at local scope would create a dangling reference!
static int data_i = 42;

162
TEST_SUBMODULE(numpy_array, sm) {
163
164
    try {
        py::module_::import("numpy");
165
    } catch (const py::error_already_set &) {
166
167
        return;
    }
168

169
170
171
172
    // test_dtypes
    py::class_<DtypeCheck>(sm, "DtypeCheck")
        .def_readonly("numpy", &DtypeCheck::numpy)
        .def_readonly("pybind11", &DtypeCheck::pybind11)
173
174
        .def("__repr__", [](const DtypeCheck &self) {
            return py::str("<DtypeCheck numpy={} pybind11={}>").format(self.numpy, self.pybind11);
175
176
177
178
179
180
181
        });
    sm.def("get_concrete_dtype_checks", &get_concrete_dtype_checks);

    py::class_<DtypeSizeCheck>(sm, "DtypeSizeCheck")
        .def_readonly("name", &DtypeSizeCheck::name)
        .def_readonly("size_cpp", &DtypeSizeCheck::size_cpp)
        .def_readonly("size_numpy", &DtypeSizeCheck::size_numpy)
182
183
184
        .def("__repr__", [](const DtypeSizeCheck &self) {
            return py::str("<DtypeSizeCheck name='{}' size_cpp={} size_numpy={} dtype={}>")
                .format(self.name, self.size_cpp, self.size_numpy, self.dtype);
185
186
187
        });
    sm.def("get_platform_dtype_size_checks", &get_platform_dtype_size_checks);

188
    // test_array_attributes
189
190
191
192
193
194
195
196
197
198
    sm.def("ndim", [](const arr &a) { return a.ndim(); });
    sm.def("shape", [](const arr &a) { return arr(a.ndim(), a.shape()); });
    sm.def("shape", [](const arr &a, py::ssize_t dim) { return a.shape(dim); });
    sm.def("strides", [](const arr &a) { return arr(a.ndim(), a.strides()); });
    sm.def("strides", [](const arr &a, py::ssize_t dim) { return a.strides(dim); });
    sm.def("writeable", [](const arr &a) { return a.writeable(); });
    sm.def("size", [](const arr &a) { return a.size(); });
    sm.def("itemsize", [](const arr &a) { return a.itemsize(); });
    sm.def("nbytes", [](const arr &a) { return a.nbytes(); });
    sm.def("owndata", [](const arr &a) { return a.owndata(); });
199

200
    // test_index_offset
201
202
203
204
    def_index_fn(index_at, const arr &);
    def_index_fn(index_at_t, const arr_t &);
    def_index_fn(offset_at, const arr &);
    def_index_fn(offset_at_t, const arr_t &);
205
    // test_data
206
207
    def_index_fn(data, const arr &);
    def_index_fn(data_t, const arr_t &);
208
    // test_mutate_data, test_mutate_readonly
209
210
211
212
    def_index_fn(mutate_data, arr &);
    def_index_fn(mutate_data_t, arr_t &);
    def_index_fn(at_t, const arr_t &);
    def_index_fn(mutate_at_t, arr_t &);
213

214
    // test_make_c_f_array
215
216
    sm.def("make_f_array", [] { return py::array_t<float>({2, 2}, {4, 8}); });
    sm.def("make_c_array", [] { return py::array_t<float>({2, 2}, {8, 4}); });
217

218
219
    // test_empty_shaped_array
    sm.def("make_empty_shaped_array", [] { return py::array(py::dtype("f"), {}, {}); });
220
221
    // test numpy scalars (empty shape, ndim==0)
    sm.def("scalar_int", []() { return py::array(py::dtype("i"), {}, {}, &data_i); });
222

223
    // test_wrap
224
    sm.def("wrap", [](const py::array &a) {
225
226
227
228
229
        return py::array(a.dtype(),
                         {a.shape(), a.shape() + a.ndim()},
                         {a.strides(), a.strides() + a.ndim()},
                         a.data(),
                         a);
230
    });
231

232
    // test_numpy_view
233
    struct ArrayClass {
234
        int data[2] = {1, 2};
235
236
237
238
239
240
241
        ArrayClass() { py::print("ArrayClass()"); }
        ~ArrayClass() { py::print("~ArrayClass()"); }
    };
    py::class_<ArrayClass>(sm, "ArrayClass")
        .def(py::init<>())
        .def("numpy_view", [](py::object &obj) {
            py::print("ArrayClass::numpy_view()");
242
            auto &a = obj.cast<ArrayClass &>();
243
            return py::array_t<int>({2}, {4}, a.data, obj);
244
        });
245

246
    // test_cast_numpy_int64_to_uint64
247
    sm.def("function_taking_uint64", [](uint64_t) {});
248

249
    // test_isinstance
250
    sm.def("isinstance_untyped", [](py::object yes, py::object no) {
251
252
        return py::isinstance<py::array>(std::move(yes))
               && !py::isinstance<py::array>(std::move(no));
253
    });
254
    sm.def("isinstance_typed", [](const py::object &o) {
255
256
257
        return py::isinstance<py::array_t<double>>(o) && !py::isinstance<py::array_t<int>>(o);
    });

258
    // test_constructors
259
    sm.def("default_constructors", []() {
260
261
262
        return py::dict("array"_a = py::array(),
                        "array_t<int32>"_a = py::array_t<std::int32_t>(),
                        "array_t<double>"_a = py::array_t<double>());
263
    });
264
    sm.def("converting_constructors", [](const py::object &o) {
265
266
267
        return py::dict("array"_a = py::array(o),
                        "array_t<int32>"_a = py::array_t<std::int32_t>(o),
                        "array_t<double>"_a = py::array_t<double>(o));
268
    });
269

270
    // test_overload_resolution
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
    sm.def("overloaded", [](const py::array_t<double> &) { return "double"; });
    sm.def("overloaded", [](const py::array_t<float> &) { return "float"; });
    sm.def("overloaded", [](const py::array_t<int> &) { return "int"; });
    sm.def("overloaded", [](const py::array_t<unsigned short> &) { return "unsigned short"; });
    sm.def("overloaded", [](const py::array_t<long long> &) { return "long long"; });
    sm.def("overloaded",
           [](const py::array_t<std::complex<double>> &) { return "double complex"; });
    sm.def("overloaded", [](const py::array_t<std::complex<float>> &) { return "float complex"; });

    sm.def("overloaded2",
           [](const py::array_t<std::complex<double>> &) { return "double complex"; });
    sm.def("overloaded2", [](const py::array_t<double> &) { return "double"; });
    sm.def("overloaded2",
           [](const py::array_t<std::complex<float>> &) { return "float complex"; });
    sm.def("overloaded2", [](const py::array_t<float> &) { return "float"; });
286

287
288
    // [workaround(intel)] ICC 20/21 breaks with py::arg().stuff, using py::arg{}.stuff works.

289
    // Only accept the exact types:
290
291
292
293
294
295
    sm.def(
        "overloaded3", [](const py::array_t<int> &) { return "int"; }, py::arg{}.noconvert());
    sm.def(
        "overloaded3",
        [](const py::array_t<double> &) { return "double"; },
        py::arg{}.noconvert());
296
297
298

    // Make sure we don't do unsafe coercion (e.g. float to int) when not using forcecast, but
    // rather that float gets converted via the safe (conversion to double) overload:
299
300
    sm.def("overloaded4", [](const py::array_t<long long, 0> &) { return "long long"; });
    sm.def("overloaded4", [](const py::array_t<double, 0> &) { return "double"; });
301
302
303

    // But we do allow conversion to int if forcecast is enabled (but only if no overload matches
    // without conversion)
304
305
    sm.def("overloaded5", [](const py::array_t<unsigned int> &) { return "unsigned int"; });
    sm.def("overloaded5", [](const py::array_t<double> &) { return "double"; });
306

307
    // test_greedy_string_overload
308
    // Issue 685: ndarray shouldn't go to std::string overload
309
310
311
    sm.def("issue685", [](const std::string &) { return "string"; });
    sm.def("issue685", [](const py::array &) { return "array"; });
    sm.def("issue685", [](const py::object &) { return "other"; });
312

313
    // test_array_unchecked_fixed_dims
314
315
316
317
318
319
320
321
    sm.def(
        "proxy_add2",
        [](py::array_t<double> a, double v) {
            auto r = a.mutable_unchecked<2>();
            for (py::ssize_t i = 0; i < r.shape(0); i++) {
                for (py::ssize_t j = 0; j < r.shape(1); j++) {
                    r(i, j) += v;
                }
322
            }
323
324
325
        },
        py::arg{}.noconvert(),
        py::arg());
326

327
    sm.def("proxy_init3", [](double start) {
328
        py::array_t<double, py::array::c_style> a({3, 3, 3});
329
        auto r = a.mutable_unchecked<3>();
330
331
332
333
334
335
336
        for (py::ssize_t i = 0; i < r.shape(0); i++) {
            for (py::ssize_t j = 0; j < r.shape(1); j++) {
                for (py::ssize_t k = 0; k < r.shape(2); k++) {
                    r(i, j, k) = start++;
                }
            }
        }
337
338
339
        return a;
    });
    sm.def("proxy_init3F", [](double start) {
340
        py::array_t<double, py::array::f_style> a({3, 3, 3});
341
        auto r = a.mutable_unchecked<3>();
342
343
344
345
346
347
348
        for (py::ssize_t k = 0; k < r.shape(2); k++) {
            for (py::ssize_t j = 0; j < r.shape(1); j++) {
                for (py::ssize_t i = 0; i < r.shape(0); i++) {
                    r(i, j, k) = start++;
                }
            }
        }
349
350
        return a;
    });
351
    sm.def("proxy_squared_L2_norm", [](const py::array_t<double> &a) {
352
353
        auto r = a.unchecked<1>();
        double sumsq = 0;
354
        for (py::ssize_t i = 0; i < r.shape(0); i++) {
355
            sumsq += r[i] * r(i); // Either notation works for a 1D array
356
        }
357
358
        return sumsq;
    });
359
360
361
362
363
364
365

    sm.def("proxy_auxiliaries2", [](py::array_t<double> a) {
        auto r = a.unchecked<2>();
        auto r2 = a.mutable_unchecked<2>();
        return auxiliaries(r, r2);
    });

366
367
368
369
370
371
372
373
374
375
376
377
    sm.def("proxy_auxiliaries1_const_ref", [](py::array_t<double> a) {
        const auto &r = a.unchecked<1>();
        const auto &r2 = a.mutable_unchecked<1>();
        return r(0) == r2(0) && r[0] == r2[0];
    });

    sm.def("proxy_auxiliaries2_const_ref", [](py::array_t<double> a) {
        const auto &r = a.unchecked<2>();
        const auto &r2 = a.mutable_unchecked<2>();
        return r(0, 0) == r2(0, 0);
    });

378
    // test_array_unchecked_dyn_dims
379
    // Same as the above, but without a compile-time dimensions specification:
380
381
382
383
384
385
    sm.def(
        "proxy_add2_dyn",
        [](py::array_t<double> a, double v) {
            auto r = a.mutable_unchecked();
            if (r.ndim() != 2) {
                throw std::domain_error("error: ndim != 2");
386
            }
387
388
389
390
391
392
393
394
            for (py::ssize_t i = 0; i < r.shape(0); i++) {
                for (py::ssize_t j = 0; j < r.shape(1); j++) {
                    r(i, j) += v;
                }
            }
        },
        py::arg{}.noconvert(),
        py::arg());
395
    sm.def("proxy_init3_dyn", [](double start) {
396
        py::array_t<double, py::array::c_style> a({3, 3, 3});
397
        auto r = a.mutable_unchecked();
398
399
400
401
402
403
404
405
406
407
        if (r.ndim() != 3) {
            throw std::domain_error("error: ndim != 3");
        }
        for (py::ssize_t i = 0; i < r.shape(0); i++) {
            for (py::ssize_t j = 0; j < r.shape(1); j++) {
                for (py::ssize_t k = 0; k < r.shape(2); k++) {
                    r(i, j, k) = start++;
                }
            }
        }
408
409
410
411
412
413
        return a;
    });
    sm.def("proxy_auxiliaries2_dyn", [](py::array_t<double> a) {
        return auxiliaries(a.unchecked(), a.mutable_unchecked());
    });

414
    sm.def("array_auxiliaries2", [](py::array_t<double> a) { return auxiliaries(a, a); });
415

416
    // test_array_failures
417
418
    // Issue #785: Uninformative "Unknown internal error" exception when constructing array from
    // empty object:
419
420
    sm.def("array_fail_test", []() { return py::array(py::object()); });
    sm.def("array_t_fail_test", []() { return py::array_t<double>(py::object()); });
421
    // Make sure the error from numpy is being passed through:
422
423
424
425
    sm.def("array_fail_test_negative_size", []() {
        int c = 0;
        return py::array(-1, &c);
    });
426

427
    // test_initializer_list
428
    // Issue (unnumbered; reported in #788): regression: initializer lists can be ambiguous
429
430
    sm.def("array_initializer_list1", []() { return py::array_t<float>(1); });
    // { 1 } also works for the above, but clang warns about it
431
432
433
    sm.def("array_initializer_list2", []() { return py::array_t<float>({1, 2}); });
    sm.def("array_initializer_list3", []() { return py::array_t<float>({1, 2, 3}); });
    sm.def("array_initializer_list4", []() { return py::array_t<float>({1, 2, 3, 4}); });
uentity's avatar
uentity committed
434

435
    // test_array_resize
uentity's avatar
uentity committed
436
437
    // reshape array to 2D without changing size
    sm.def("array_reshape2", [](py::array_t<double> a) {
438
        const auto dim_sz = (py::ssize_t) std::sqrt(a.size());
439
440
441
442
        if (dim_sz * dim_sz != a.size()) {
            throw std::domain_error(
                "array_reshape2: input array total size is not a squared integer");
        }
uentity's avatar
uentity committed
443
444
445
446
447
448
449
450
        a.resize({dim_sz, dim_sz});
    });

    // resize to 3D array with each dimension = N
    sm.def("array_resize3", [](py::array_t<double> a, size_t N, bool refcheck) {
        a.resize({N, N, N}, refcheck);
    });

451
    // test_array_create_and_resize
uentity's avatar
uentity committed
452
453
454
455
456
457
458
    // return 2D array with Nrows = Ncols = N
    sm.def("create_and_resize", [](size_t N) {
        py::array_t<double> a;
        a.resize({N, N});
        std::fill(a.mutable_data(), a.mutable_data() + a.size(), 42.);
        return a;
    });
459

Nick Cullen's avatar
Nick Cullen committed
460
461
462
    sm.def("array_view",
           [](py::array_t<uint8_t> a, const std::string &dtype) { return a.view(dtype); });

Nick Cullen's avatar
Nick Cullen committed
463
464
465
466
467
468
469
    sm.def("reshape_initializer_list", [](py::array_t<int> a, size_t N, size_t M, size_t O) {
        return a.reshape({N, M, O});
    });
    sm.def("reshape_tuple", [](py::array_t<int> a, const std::vector<int> &new_shape) {
        return a.reshape(new_shape);
    });

470
471
    sm.def("index_using_ellipsis",
           [](const py::array &a) { return a[py::make_tuple(0, py::ellipsis(), 0)]; });
472
473

    // test_argument_conversions
474
    sm.def(
475
        "accept_double", [](const py::array_t<double, 0> &) {}, py::arg("a"));
476
477
    sm.def(
        "accept_double_forcecast",
478
        [](const py::array_t<double, py::array::forcecast> &) {},
479
480
481
        py::arg("a"));
    sm.def(
        "accept_double_c_style",
482
        [](const py::array_t<double, py::array::c_style> &) {},
483
484
485
        py::arg("a"));
    sm.def(
        "accept_double_c_style_forcecast",
486
        [](const py::array_t<double, py::array::forcecast | py::array::c_style> &) {},
487
488
489
        py::arg("a"));
    sm.def(
        "accept_double_f_style",
490
        [](const py::array_t<double, py::array::f_style> &) {},
491
492
493
        py::arg("a"));
    sm.def(
        "accept_double_f_style_forcecast",
494
        [](const py::array_t<double, py::array::forcecast | py::array::f_style> &) {},
495
496
        py::arg("a"));
    sm.def(
497
        "accept_double_noconvert", [](const py::array_t<double, 0> &) {}, "a"_a.noconvert());
498
499
    sm.def(
        "accept_double_forcecast_noconvert",
500
        [](const py::array_t<double, py::array::forcecast> &) {},
501
502
503
        "a"_a.noconvert());
    sm.def(
        "accept_double_c_style_noconvert",
504
        [](const py::array_t<double, py::array::c_style> &) {},
505
506
507
        "a"_a.noconvert());
    sm.def(
        "accept_double_c_style_forcecast_noconvert",
508
        [](const py::array_t<double, py::array::forcecast | py::array::c_style> &) {},
509
510
511
        "a"_a.noconvert());
    sm.def(
        "accept_double_f_style_noconvert",
512
        [](const py::array_t<double, py::array::f_style> &) {},
513
514
515
        "a"_a.noconvert());
    sm.def(
        "accept_double_f_style_forcecast_noconvert",
516
        [](const py::array_t<double, py::array::forcecast | py::array::f_style> &) {},
517
        "a"_a.noconvert());
518
519

    // Check that types returns correct npy format descriptor
520
521
522
523
    sm.def("test_fmt_desc_float", [](const py::array_t<float> &) {});
    sm.def("test_fmt_desc_double", [](const py::array_t<double> &) {});
    sm.def("test_fmt_desc_const_float", [](const py::array_t<const float> &) {});
    sm.def("test_fmt_desc_const_double", [](const py::array_t<const double> &) {});
524
525

    sm.def("round_trip_float", [](double d) { return d; });
526
}