numpy.rst revision 12037
111986Sandreas.sandberg@arm.com.. _numpy:
211986Sandreas.sandberg@arm.com
311986Sandreas.sandberg@arm.comNumPy
411986Sandreas.sandberg@arm.com#####
511986Sandreas.sandberg@arm.com
611986Sandreas.sandberg@arm.comBuffer protocol
711986Sandreas.sandberg@arm.com===============
811986Sandreas.sandberg@arm.com
911986Sandreas.sandberg@arm.comPython supports an extremely general and convenient approach for exchanging
1011986Sandreas.sandberg@arm.comdata between plugin libraries. Types can expose a buffer view [#f2]_, which
1111986Sandreas.sandberg@arm.comprovides fast direct access to the raw internal data representation. Suppose we
1211986Sandreas.sandberg@arm.comwant to bind the following simplistic Matrix class:
1311986Sandreas.sandberg@arm.com
1411986Sandreas.sandberg@arm.com.. code-block:: cpp
1511986Sandreas.sandberg@arm.com
1611986Sandreas.sandberg@arm.com    class Matrix {
1711986Sandreas.sandberg@arm.com    public:
1811986Sandreas.sandberg@arm.com        Matrix(size_t rows, size_t cols) : m_rows(rows), m_cols(cols) {
1911986Sandreas.sandberg@arm.com            m_data = new float[rows*cols];
2011986Sandreas.sandberg@arm.com        }
2111986Sandreas.sandberg@arm.com        float *data() { return m_data; }
2211986Sandreas.sandberg@arm.com        size_t rows() const { return m_rows; }
2311986Sandreas.sandberg@arm.com        size_t cols() const { return m_cols; }
2411986Sandreas.sandberg@arm.com    private:
2511986Sandreas.sandberg@arm.com        size_t m_rows, m_cols;
2611986Sandreas.sandberg@arm.com        float *m_data;
2711986Sandreas.sandberg@arm.com    };
2811986Sandreas.sandberg@arm.com
2911986Sandreas.sandberg@arm.comThe following binding code exposes the ``Matrix`` contents as a buffer object,
3011986Sandreas.sandberg@arm.commaking it possible to cast Matrices into NumPy arrays. It is even possible to
3111986Sandreas.sandberg@arm.comcompletely avoid copy operations with Python expressions like
3211986Sandreas.sandberg@arm.com``np.array(matrix_instance, copy = False)``.
3311986Sandreas.sandberg@arm.com
3411986Sandreas.sandberg@arm.com.. code-block:: cpp
3511986Sandreas.sandberg@arm.com
3612037Sandreas.sandberg@arm.com    py::class_<Matrix>(m, "Matrix", py::buffer_protocol())
3711986Sandreas.sandberg@arm.com       .def_buffer([](Matrix &m) -> py::buffer_info {
3811986Sandreas.sandberg@arm.com            return py::buffer_info(
3911986Sandreas.sandberg@arm.com                m.data(),                               /* Pointer to buffer */
4011986Sandreas.sandberg@arm.com                sizeof(float),                          /* Size of one scalar */
4111986Sandreas.sandberg@arm.com                py::format_descriptor<float>::format(), /* Python struct-style format descriptor */
4211986Sandreas.sandberg@arm.com                2,                                      /* Number of dimensions */
4311986Sandreas.sandberg@arm.com                { m.rows(), m.cols() },                 /* Buffer dimensions */
4411986Sandreas.sandberg@arm.com                { sizeof(float) * m.rows(),             /* Strides (in bytes) for each index */
4511986Sandreas.sandberg@arm.com                  sizeof(float) }
4611986Sandreas.sandberg@arm.com            );
4711986Sandreas.sandberg@arm.com        });
4811986Sandreas.sandberg@arm.com
4912037Sandreas.sandberg@arm.comSupporting the buffer protocol in a new type involves specifying the special
5012037Sandreas.sandberg@arm.com``py::buffer_protocol()`` tag in the ``py::class_`` constructor and calling the
5112037Sandreas.sandberg@arm.com``def_buffer()`` method with a lambda function that creates a
5212037Sandreas.sandberg@arm.com``py::buffer_info`` description record on demand describing a given matrix
5312037Sandreas.sandberg@arm.cominstance. The contents of ``py::buffer_info`` mirror the Python buffer protocol
5412037Sandreas.sandberg@arm.comspecification.
5511986Sandreas.sandberg@arm.com
5611986Sandreas.sandberg@arm.com.. code-block:: cpp
5711986Sandreas.sandberg@arm.com
5811986Sandreas.sandberg@arm.com    struct buffer_info {
5911986Sandreas.sandberg@arm.com        void *ptr;
6011986Sandreas.sandberg@arm.com        size_t itemsize;
6111986Sandreas.sandberg@arm.com        std::string format;
6211986Sandreas.sandberg@arm.com        int ndim;
6311986Sandreas.sandberg@arm.com        std::vector<size_t> shape;
6411986Sandreas.sandberg@arm.com        std::vector<size_t> strides;
6511986Sandreas.sandberg@arm.com    };
6611986Sandreas.sandberg@arm.com
6711986Sandreas.sandberg@arm.comTo create a C++ function that can take a Python buffer object as an argument,
6811986Sandreas.sandberg@arm.comsimply use the type ``py::buffer`` as one of its arguments. Buffers can exist
6911986Sandreas.sandberg@arm.comin a great variety of configurations, hence some safety checks are usually
7011986Sandreas.sandberg@arm.comnecessary in the function body. Below, you can see an basic example on how to
7111986Sandreas.sandberg@arm.comdefine a custom constructor for the Eigen double precision matrix
7211986Sandreas.sandberg@arm.com(``Eigen::MatrixXd``) type, which supports initialization from compatible
7311986Sandreas.sandberg@arm.combuffer objects (e.g. a NumPy matrix).
7411986Sandreas.sandberg@arm.com
7511986Sandreas.sandberg@arm.com.. code-block:: cpp
7611986Sandreas.sandberg@arm.com
7711986Sandreas.sandberg@arm.com    /* Bind MatrixXd (or some other Eigen type) to Python */
7811986Sandreas.sandberg@arm.com    typedef Eigen::MatrixXd Matrix;
7911986Sandreas.sandberg@arm.com
8011986Sandreas.sandberg@arm.com    typedef Matrix::Scalar Scalar;
8111986Sandreas.sandberg@arm.com    constexpr bool rowMajor = Matrix::Flags & Eigen::RowMajorBit;
8211986Sandreas.sandberg@arm.com
8312037Sandreas.sandberg@arm.com    py::class_<Matrix>(m, "Matrix", py::buffer_protocol())
8411986Sandreas.sandberg@arm.com        .def("__init__", [](Matrix &m, py::buffer b) {
8511986Sandreas.sandberg@arm.com            typedef Eigen::Stride<Eigen::Dynamic, Eigen::Dynamic> Strides;
8611986Sandreas.sandberg@arm.com
8711986Sandreas.sandberg@arm.com            /* Request a buffer descriptor from Python */
8811986Sandreas.sandberg@arm.com            py::buffer_info info = b.request();
8911986Sandreas.sandberg@arm.com
9011986Sandreas.sandberg@arm.com            /* Some sanity checks ... */
9111986Sandreas.sandberg@arm.com            if (info.format != py::format_descriptor<Scalar>::format())
9211986Sandreas.sandberg@arm.com                throw std::runtime_error("Incompatible format: expected a double array!");
9311986Sandreas.sandberg@arm.com
9411986Sandreas.sandberg@arm.com            if (info.ndim != 2)
9511986Sandreas.sandberg@arm.com                throw std::runtime_error("Incompatible buffer dimension!");
9611986Sandreas.sandberg@arm.com
9711986Sandreas.sandberg@arm.com            auto strides = Strides(
9811986Sandreas.sandberg@arm.com                info.strides[rowMajor ? 0 : 1] / sizeof(Scalar),
9911986Sandreas.sandberg@arm.com                info.strides[rowMajor ? 1 : 0] / sizeof(Scalar));
10011986Sandreas.sandberg@arm.com
10111986Sandreas.sandberg@arm.com            auto map = Eigen::Map<Matrix, 0, Strides>(
10211986Sandreas.sandberg@arm.com                static_cat<Scalar *>(info.ptr), info.shape[0], info.shape[1], strides);
10311986Sandreas.sandberg@arm.com
10411986Sandreas.sandberg@arm.com            new (&m) Matrix(map);
10511986Sandreas.sandberg@arm.com        });
10611986Sandreas.sandberg@arm.com
10711986Sandreas.sandberg@arm.comFor reference, the ``def_buffer()`` call for this Eigen data type should look
10811986Sandreas.sandberg@arm.comas follows:
10911986Sandreas.sandberg@arm.com
11011986Sandreas.sandberg@arm.com.. code-block:: cpp
11111986Sandreas.sandberg@arm.com
11211986Sandreas.sandberg@arm.com    .def_buffer([](Matrix &m) -> py::buffer_info {
11311986Sandreas.sandberg@arm.com        return py::buffer_info(
11411986Sandreas.sandberg@arm.com            m.data(),                /* Pointer to buffer */
11511986Sandreas.sandberg@arm.com            sizeof(Scalar),          /* Size of one scalar */
11611986Sandreas.sandberg@arm.com            /* Python struct-style format descriptor */
11711986Sandreas.sandberg@arm.com            py::format_descriptor<Scalar>::format(),
11811986Sandreas.sandberg@arm.com            /* Number of dimensions */
11911986Sandreas.sandberg@arm.com            2,
12011986Sandreas.sandberg@arm.com            /* Buffer dimensions */
12111986Sandreas.sandberg@arm.com            { (size_t) m.rows(),
12211986Sandreas.sandberg@arm.com              (size_t) m.cols() },
12311986Sandreas.sandberg@arm.com            /* Strides (in bytes) for each index */
12411986Sandreas.sandberg@arm.com            { sizeof(Scalar) * (rowMajor ? m.cols() : 1),
12511986Sandreas.sandberg@arm.com              sizeof(Scalar) * (rowMajor ? 1 : m.rows()) }
12611986Sandreas.sandberg@arm.com        );
12711986Sandreas.sandberg@arm.com     })
12811986Sandreas.sandberg@arm.com
12911986Sandreas.sandberg@arm.comFor a much easier approach of binding Eigen types (although with some
13011986Sandreas.sandberg@arm.comlimitations), refer to the section on :doc:`/advanced/cast/eigen`.
13111986Sandreas.sandberg@arm.com
13211986Sandreas.sandberg@arm.com.. seealso::
13311986Sandreas.sandberg@arm.com
13411986Sandreas.sandberg@arm.com    The file :file:`tests/test_buffers.cpp` contains a complete example
13511986Sandreas.sandberg@arm.com    that demonstrates using the buffer protocol with pybind11 in more detail.
13611986Sandreas.sandberg@arm.com
13711986Sandreas.sandberg@arm.com.. [#f2] http://docs.python.org/3/c-api/buffer.html
13811986Sandreas.sandberg@arm.com
13911986Sandreas.sandberg@arm.comArrays
14011986Sandreas.sandberg@arm.com======
14111986Sandreas.sandberg@arm.com
14211986Sandreas.sandberg@arm.comBy exchanging ``py::buffer`` with ``py::array`` in the above snippet, we can
14311986Sandreas.sandberg@arm.comrestrict the function so that it only accepts NumPy arrays (rather than any
14411986Sandreas.sandberg@arm.comtype of Python object satisfying the buffer protocol).
14511986Sandreas.sandberg@arm.com
14611986Sandreas.sandberg@arm.comIn many situations, we want to define a function which only accepts a NumPy
14711986Sandreas.sandberg@arm.comarray of a certain data type. This is possible via the ``py::array_t<T>``
14811986Sandreas.sandberg@arm.comtemplate. For instance, the following function requires the argument to be a
14911986Sandreas.sandberg@arm.comNumPy array containing double precision values.
15011986Sandreas.sandberg@arm.com
15111986Sandreas.sandberg@arm.com.. code-block:: cpp
15211986Sandreas.sandberg@arm.com
15311986Sandreas.sandberg@arm.com    void f(py::array_t<double> array);
15411986Sandreas.sandberg@arm.com
15511986Sandreas.sandberg@arm.comWhen it is invoked with a different type (e.g. an integer or a list of
15611986Sandreas.sandberg@arm.comintegers), the binding code will attempt to cast the input into a NumPy array
15711986Sandreas.sandberg@arm.comof the requested type. Note that this feature requires the
15812037Sandreas.sandberg@arm.com:file:`pybind11/numpy.h` header to be included.
15911986Sandreas.sandberg@arm.com
16011986Sandreas.sandberg@arm.comData in NumPy arrays is not guaranteed to packed in a dense manner;
16111986Sandreas.sandberg@arm.comfurthermore, entries can be separated by arbitrary column and row strides.
16211986Sandreas.sandberg@arm.comSometimes, it can be useful to require a function to only accept dense arrays
16311986Sandreas.sandberg@arm.comusing either the C (row-major) or Fortran (column-major) ordering. This can be
16411986Sandreas.sandberg@arm.comaccomplished via a second template argument with values ``py::array::c_style``
16511986Sandreas.sandberg@arm.comor ``py::array::f_style``.
16611986Sandreas.sandberg@arm.com
16711986Sandreas.sandberg@arm.com.. code-block:: cpp
16811986Sandreas.sandberg@arm.com
16911986Sandreas.sandberg@arm.com    void f(py::array_t<double, py::array::c_style | py::array::forcecast> array);
17011986Sandreas.sandberg@arm.com
17111986Sandreas.sandberg@arm.comThe ``py::array::forcecast`` argument is the default value of the second
17211986Sandreas.sandberg@arm.comtemplate parameter, and it ensures that non-conforming arguments are converted
17311986Sandreas.sandberg@arm.cominto an array satisfying the specified requirements instead of trying the next
17411986Sandreas.sandberg@arm.comfunction overload.
17511986Sandreas.sandberg@arm.com
17611986Sandreas.sandberg@arm.comStructured types
17711986Sandreas.sandberg@arm.com================
17811986Sandreas.sandberg@arm.com
17912037Sandreas.sandberg@arm.comIn order for ``py::array_t`` to work with structured (record) types, we first
18012037Sandreas.sandberg@arm.comneed to register the memory layout of the type. This can be done via
18112037Sandreas.sandberg@arm.com``PYBIND11_NUMPY_DTYPE`` macro, called in the plugin definition code, which
18212037Sandreas.sandberg@arm.comexpects the type followed by field names:
18311986Sandreas.sandberg@arm.com
18411986Sandreas.sandberg@arm.com.. code-block:: cpp
18511986Sandreas.sandberg@arm.com
18611986Sandreas.sandberg@arm.com    struct A {
18711986Sandreas.sandberg@arm.com        int x;
18811986Sandreas.sandberg@arm.com        double y;
18911986Sandreas.sandberg@arm.com    };
19011986Sandreas.sandberg@arm.com
19111986Sandreas.sandberg@arm.com    struct B {
19211986Sandreas.sandberg@arm.com        int z;
19311986Sandreas.sandberg@arm.com        A a;
19411986Sandreas.sandberg@arm.com    };
19511986Sandreas.sandberg@arm.com
19612037Sandreas.sandberg@arm.com    // ...
19712037Sandreas.sandberg@arm.com    PYBIND11_PLUGIN(test) {
19812037Sandreas.sandberg@arm.com        // ...
19911986Sandreas.sandberg@arm.com
20012037Sandreas.sandberg@arm.com        PYBIND11_NUMPY_DTYPE(A, x, y);
20112037Sandreas.sandberg@arm.com        PYBIND11_NUMPY_DTYPE(B, z, a);
20212037Sandreas.sandberg@arm.com        /* now both A and B can be used as template arguments to py::array_t */
20312037Sandreas.sandberg@arm.com    }
20411986Sandreas.sandberg@arm.com
20511986Sandreas.sandberg@arm.comVectorizing functions
20611986Sandreas.sandberg@arm.com=====================
20711986Sandreas.sandberg@arm.com
20811986Sandreas.sandberg@arm.comSuppose we want to bind a function with the following signature to Python so
20911986Sandreas.sandberg@arm.comthat it can process arbitrary NumPy array arguments (vectors, matrices, general
21011986Sandreas.sandberg@arm.comN-D arrays) in addition to its normal arguments:
21111986Sandreas.sandberg@arm.com
21211986Sandreas.sandberg@arm.com.. code-block:: cpp
21311986Sandreas.sandberg@arm.com
21411986Sandreas.sandberg@arm.com    double my_func(int x, float y, double z);
21511986Sandreas.sandberg@arm.com
21611986Sandreas.sandberg@arm.comAfter including the ``pybind11/numpy.h`` header, this is extremely simple:
21711986Sandreas.sandberg@arm.com
21811986Sandreas.sandberg@arm.com.. code-block:: cpp
21911986Sandreas.sandberg@arm.com
22011986Sandreas.sandberg@arm.com    m.def("vectorized_func", py::vectorize(my_func));
22111986Sandreas.sandberg@arm.com
22211986Sandreas.sandberg@arm.comInvoking the function like below causes 4 calls to be made to ``my_func`` with
22311986Sandreas.sandberg@arm.comeach of the array elements. The significant advantage of this compared to
22411986Sandreas.sandberg@arm.comsolutions like ``numpy.vectorize()`` is that the loop over the elements runs
22511986Sandreas.sandberg@arm.comentirely on the C++ side and can be crunched down into a tight, optimized loop
22611986Sandreas.sandberg@arm.comby the compiler. The result is returned as a NumPy array of type
22711986Sandreas.sandberg@arm.com``numpy.dtype.float64``.
22811986Sandreas.sandberg@arm.com
22911986Sandreas.sandberg@arm.com.. code-block:: pycon
23011986Sandreas.sandberg@arm.com
23111986Sandreas.sandberg@arm.com    >>> x = np.array([[1, 3],[5, 7]])
23211986Sandreas.sandberg@arm.com    >>> y = np.array([[2, 4],[6, 8]])
23311986Sandreas.sandberg@arm.com    >>> z = 3
23411986Sandreas.sandberg@arm.com    >>> result = vectorized_func(x, y, z)
23511986Sandreas.sandberg@arm.com
23611986Sandreas.sandberg@arm.comThe scalar argument ``z`` is transparently replicated 4 times.  The input
23711986Sandreas.sandberg@arm.comarrays ``x`` and ``y`` are automatically converted into the right types (they
23811986Sandreas.sandberg@arm.comare of type  ``numpy.dtype.int64`` but need to be ``numpy.dtype.int32`` and
23911986Sandreas.sandberg@arm.com``numpy.dtype.float32``, respectively)
24011986Sandreas.sandberg@arm.com
24111986Sandreas.sandberg@arm.comSometimes we might want to explicitly exclude an argument from the vectorization
24211986Sandreas.sandberg@arm.combecause it makes little sense to wrap it in a NumPy array. For instance,
24311986Sandreas.sandberg@arm.comsuppose the function signature was
24411986Sandreas.sandberg@arm.com
24511986Sandreas.sandberg@arm.com.. code-block:: cpp
24611986Sandreas.sandberg@arm.com
24711986Sandreas.sandberg@arm.com    double my_func(int x, float y, my_custom_type *z);
24811986Sandreas.sandberg@arm.com
24911986Sandreas.sandberg@arm.comThis can be done with a stateful Lambda closure:
25011986Sandreas.sandberg@arm.com
25111986Sandreas.sandberg@arm.com.. code-block:: cpp
25211986Sandreas.sandberg@arm.com
25311986Sandreas.sandberg@arm.com    // Vectorize a lambda function with a capture object (e.g. to exclude some arguments from the vectorization)
25411986Sandreas.sandberg@arm.com    m.def("vectorized_func",
25511986Sandreas.sandberg@arm.com        [](py::array_t<int> x, py::array_t<float> y, my_custom_type *z) {
25611986Sandreas.sandberg@arm.com            auto stateful_closure = [z](int x, float y) { return my_func(x, y, z); };
25711986Sandreas.sandberg@arm.com            return py::vectorize(stateful_closure)(x, y);
25811986Sandreas.sandberg@arm.com        }
25911986Sandreas.sandberg@arm.com    );
26011986Sandreas.sandberg@arm.com
26111986Sandreas.sandberg@arm.comIn cases where the computation is too complicated to be reduced to
26211986Sandreas.sandberg@arm.com``vectorize``, it will be necessary to create and access the buffer contents
26311986Sandreas.sandberg@arm.commanually. The following snippet contains a complete example that shows how this
26411986Sandreas.sandberg@arm.comworks (the code is somewhat contrived, since it could have been done more
26511986Sandreas.sandberg@arm.comsimply using ``vectorize``).
26611986Sandreas.sandberg@arm.com
26711986Sandreas.sandberg@arm.com.. code-block:: cpp
26811986Sandreas.sandberg@arm.com
26911986Sandreas.sandberg@arm.com    #include <pybind11/pybind11.h>
27011986Sandreas.sandberg@arm.com    #include <pybind11/numpy.h>
27111986Sandreas.sandberg@arm.com
27211986Sandreas.sandberg@arm.com    namespace py = pybind11;
27311986Sandreas.sandberg@arm.com
27411986Sandreas.sandberg@arm.com    py::array_t<double> add_arrays(py::array_t<double> input1, py::array_t<double> input2) {
27511986Sandreas.sandberg@arm.com        auto buf1 = input1.request(), buf2 = input2.request();
27611986Sandreas.sandberg@arm.com
27711986Sandreas.sandberg@arm.com        if (buf1.ndim != 1 || buf2.ndim != 1)
27811986Sandreas.sandberg@arm.com            throw std::runtime_error("Number of dimensions must be one");
27911986Sandreas.sandberg@arm.com
28011986Sandreas.sandberg@arm.com        if (buf1.size != buf2.size)
28111986Sandreas.sandberg@arm.com            throw std::runtime_error("Input shapes must match");
28211986Sandreas.sandberg@arm.com
28311986Sandreas.sandberg@arm.com        /* No pointer is passed, so NumPy will allocate the buffer */
28411986Sandreas.sandberg@arm.com        auto result = py::array_t<double>(buf1.size);
28511986Sandreas.sandberg@arm.com
28611986Sandreas.sandberg@arm.com        auto buf3 = result.request();
28711986Sandreas.sandberg@arm.com
28811986Sandreas.sandberg@arm.com        double *ptr1 = (double *) buf1.ptr,
28911986Sandreas.sandberg@arm.com               *ptr2 = (double *) buf2.ptr,
29011986Sandreas.sandberg@arm.com               *ptr3 = (double *) buf3.ptr;
29111986Sandreas.sandberg@arm.com
29211986Sandreas.sandberg@arm.com        for (size_t idx = 0; idx < buf1.shape[0]; idx++)
29311986Sandreas.sandberg@arm.com            ptr3[idx] = ptr1[idx] + ptr2[idx];
29411986Sandreas.sandberg@arm.com
29511986Sandreas.sandberg@arm.com        return result;
29611986Sandreas.sandberg@arm.com    }
29711986Sandreas.sandberg@arm.com
29811986Sandreas.sandberg@arm.com    PYBIND11_PLUGIN(test) {
29911986Sandreas.sandberg@arm.com        py::module m("test");
30011986Sandreas.sandberg@arm.com        m.def("add_arrays", &add_arrays, "Add two NumPy arrays");
30111986Sandreas.sandberg@arm.com        return m.ptr();
30211986Sandreas.sandberg@arm.com    }
30311986Sandreas.sandberg@arm.com
30411986Sandreas.sandberg@arm.com.. seealso::
30511986Sandreas.sandberg@arm.com
30611986Sandreas.sandberg@arm.com    The file :file:`tests/test_numpy_vectorize.cpp` contains a complete
30711986Sandreas.sandberg@arm.com    example that demonstrates using :func:`vectorize` in more detail.
30812037Sandreas.sandberg@arm.com
30912037Sandreas.sandberg@arm.comDirect access
31012037Sandreas.sandberg@arm.com=============
31112037Sandreas.sandberg@arm.com
31212037Sandreas.sandberg@arm.comFor performance reasons, particularly when dealing with very large arrays, it
31312037Sandreas.sandberg@arm.comis often desirable to directly access array elements without internal checking
31412037Sandreas.sandberg@arm.comof dimensions and bounds on every access when indices are known to be already
31512037Sandreas.sandberg@arm.comvalid.  To avoid such checks, the ``array`` class and ``array_t<T>`` template
31612037Sandreas.sandberg@arm.comclass offer an unchecked proxy object that can be used for this unchecked
31712037Sandreas.sandberg@arm.comaccess through the ``unchecked<N>`` and ``mutable_unchecked<N>`` methods,
31812037Sandreas.sandberg@arm.comwhere ``N`` gives the required dimensionality of the array:
31912037Sandreas.sandberg@arm.com
32012037Sandreas.sandberg@arm.com.. code-block:: cpp
32112037Sandreas.sandberg@arm.com
32212037Sandreas.sandberg@arm.com    m.def("sum_3d", [](py::array_t<double> x) {
32312037Sandreas.sandberg@arm.com        auto r = x.unchecked<3>(); // x must have ndim = 3; can be non-writeable
32412037Sandreas.sandberg@arm.com        double sum = 0;
32512037Sandreas.sandberg@arm.com        for (size_t i = 0; i < r.shape(0); i++)
32612037Sandreas.sandberg@arm.com            for (size_t j = 0; j < r.shape(1); j++)
32712037Sandreas.sandberg@arm.com                for (size_t k = 0; k < r.shape(2); k++)
32812037Sandreas.sandberg@arm.com                    sum += r(i, j, k);
32912037Sandreas.sandberg@arm.com        return sum;
33012037Sandreas.sandberg@arm.com    });
33112037Sandreas.sandberg@arm.com    m.def("increment_3d", [](py::array_t<double> x) {
33212037Sandreas.sandberg@arm.com        auto r = x.mutable_unchecked<3>(); // Will throw if ndim != 3 or flags.writeable is false
33312037Sandreas.sandberg@arm.com        for (size_t i = 0; i < r.shape(0); i++)
33412037Sandreas.sandberg@arm.com            for (size_t j = 0; j < r.shape(1); j++)
33512037Sandreas.sandberg@arm.com                for (size_t k = 0; k < r.shape(2); k++)
33612037Sandreas.sandberg@arm.com                    r(i, j, k) += 1.0;
33712037Sandreas.sandberg@arm.com    }, py::arg().noconvert());
33812037Sandreas.sandberg@arm.com
33912037Sandreas.sandberg@arm.comTo obtain the proxy from an ``array`` object, you must specify both the data
34012037Sandreas.sandberg@arm.comtype and number of dimensions as template arguments, such as ``auto r =
34112037Sandreas.sandberg@arm.commyarray.mutable_unchecked<float, 2>()``.
34212037Sandreas.sandberg@arm.com
34312037Sandreas.sandberg@arm.comIf the number of dimensions is not known at compile time, you can omit the
34412037Sandreas.sandberg@arm.comdimensions template parameter (i.e. calling ``arr_t.unchecked()`` or
34512037Sandreas.sandberg@arm.com``arr.unchecked<T>()``.  This will give you a proxy object that works in the
34612037Sandreas.sandberg@arm.comsame way, but results in less optimizable code and thus a small efficiency
34712037Sandreas.sandberg@arm.comloss in tight loops.
34812037Sandreas.sandberg@arm.com
34912037Sandreas.sandberg@arm.comNote that the returned proxy object directly references the array's data, and
35012037Sandreas.sandberg@arm.comonly reads its shape, strides, and writeable flag when constructed.  You must
35112037Sandreas.sandberg@arm.comtake care to ensure that the referenced array is not destroyed or reshaped for
35212037Sandreas.sandberg@arm.comthe duration of the returned object, typically by limiting the scope of the
35312037Sandreas.sandberg@arm.comreturned instance.
35412037Sandreas.sandberg@arm.com
35512037Sandreas.sandberg@arm.comThe returned proxy object supports some of the same methods as ``py::array`` so
35612037Sandreas.sandberg@arm.comthat it can be used as a drop-in replacement for some existing, index-checked
35712037Sandreas.sandberg@arm.comuses of ``py::array``:
35812037Sandreas.sandberg@arm.com
35912037Sandreas.sandberg@arm.com- ``r.ndim()`` returns the number of dimensions
36012037Sandreas.sandberg@arm.com
36112037Sandreas.sandberg@arm.com- ``r.data(1, 2, ...)`` and ``r.mutable_data(1, 2, ...)``` returns a pointer to
36212037Sandreas.sandberg@arm.com  the ``const T`` or ``T`` data, respectively, at the given indices.  The
36312037Sandreas.sandberg@arm.com  latter is only available to proxies obtained via ``a.mutable_unchecked()``.
36412037Sandreas.sandberg@arm.com
36512037Sandreas.sandberg@arm.com- ``itemsize()`` returns the size of an item in bytes, i.e. ``sizeof(T)``.
36612037Sandreas.sandberg@arm.com
36712037Sandreas.sandberg@arm.com- ``ndim()`` returns the number of dimensions.
36812037Sandreas.sandberg@arm.com
36912037Sandreas.sandberg@arm.com- ``shape(n)`` returns the size of dimension ``n``
37012037Sandreas.sandberg@arm.com
37112037Sandreas.sandberg@arm.com- ``size()`` returns the total number of elements (i.e. the product of the shapes).
37212037Sandreas.sandberg@arm.com
37312037Sandreas.sandberg@arm.com- ``nbytes()`` returns the number of bytes used by the referenced elements
37412037Sandreas.sandberg@arm.com  (i.e. ``itemsize()`` times ``size()``).
37512037Sandreas.sandberg@arm.com
37612037Sandreas.sandberg@arm.com.. seealso::
37712037Sandreas.sandberg@arm.com
37812037Sandreas.sandberg@arm.com    The file :file:`tests/test_numpy_array.cpp` contains additional examples
37912037Sandreas.sandberg@arm.com    demonstrating the use of this feature.
380