File: iterating_expression.rst

package info (click to toggle)
xtensor 0.27.1-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,528 kB
  • sloc: cpp: 65,746; makefile: 202; python: 171; javascript: 8
file content (236 lines) | stat: -rw-r--r-- 9,866 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
.. Copyright (c) 2016, Johan Mabille, Sylvain Corlay and Wolf Vollprecht

   Distributed under the terms of the BSD 3-Clause License.

   The full license is in the file LICENSE, distributed with this software.

.. _iterating-expression-label:

Iterating over expressions
==========================

xiterable and inner types
~~~~~~~~~~~~~~~~~~~~~~~~~

*xtensor* provides two base classes for making expressions iterable: ``xconst_iterable`` and ``xiterable``. They define
the API for iterating as described in :ref:`concepts-label`. For an expression to be iterable, it must inherit directly
or indirectly from one of these classes. For instance, the ``xbroadcast`` class is defined as following:

.. code::

    template <class CT, class X>
    class xbroadcast : public xexpression<xbroadcast<CT, X>>,
                       public xconst_iterable<xbroadcast<CT, X>>
    {
        // ...
    };

Some of the methods provided by ``xconst_iterable`` or ``xiterable`` may need to be refined in the inheriting class.
In that case, a common pattern is to make the inheritance private, import the methods we need with using declaration
and redefine the methods whose behavior differ from the one provided in the base class. This is what is done in
``xfunction_base``:

.. code::

    template <class F, class R, class... CT>
    class xfunction_base : private xconst_iterable<xfunction_base<F, R, CT...>>
    {
    public:

        using self_type = xfunction_base<F, R, CT...>;
        using iterable_base = xconst_iterable<self_type>;

        using iterable_base::begin;
        using iterable_base::end;
        using iterable_base::cbegin;
        using iterable_base::cend;
        using iterable_base::rbegin;
        using iterable_base::rend;
        using iterable_base::crbegin;
        using iterable_base::crend;

        template <layout_type L = DL>
        const_storage_iterator storage_begin() const noexcept;
        template <layout_type L = DL>
        const_storage_iterator storage_end() const noexcept;
        template <layout_type L = DL>
        const_storage_iterator storage_cbegin() const noexcept;
        template <layout_type L = DL>
        const_storage_iterator storage_cend() const noexcept;

        template <layout_type L = DL>
        const_reverse_storage_iterator storage_rbegin() const noexcept;
        template <layout_type L = DL>
        const_reverse_storage_iterator storage_rend() const noexcept;
        template <layout_type L = DL>
        const_reverse_storage_iterator storage_crbegin() const noexcept;
        template <layout_type L = DL>
        const_reverse_storage_iterator storage_crend() const noexcept;
    };

The implementation of the iterator methods defined in ``xconst_iterable`` and ``xiterable`` rely on a few
types and methods that must be defined in the inheriting class.

First, as stated in the :ref:`xiterable section <xiterable-inner-label>`, the ``xiterable_inner_types`` structure must be
specialized as illustrated below:

.. code::

    template <class F, class R, class... CT>
    struct xiterable_inner_types<xfunction_base<F, R, CT...>>
    {
        using inner_shape_type = promote_shape_t<typename std::decay_t<CT>::shape_type...>;
        using const_stepper = xfunction_stepper<F, R, CT...>;
        using stepper = const_stepper;
    };

Then the inheriting class must define the following methods:

.. code::

    template <class S>
    const_stepper stepper_begin(const S& shape) const noexcept;
    template <class S>
    const_stepper stepper_end(const S& shape, layout_type l) const noexcept;

If the expression class inherits from ``xiterable`` instead of ``xconst_iterable``, the non-const counterparts
of the previous methods must also be defined. Every method implemented in one of the base class eventually calls
one of these stepper methods, whose mechanics is explained hereafter.

.. _stepper-label:

Steppers
~~~~~~~~

Steppers are the low-level tools for iterating over expressions. They provide a raw API for "stepping" of a given
amount in a given dimension, dereferencing the stepper, and moving it to the beginning or the end of the expression:

.. code::

    reference operator*() const;

    void step(size_type dim, size_type n = 1);
    void step_back(size_type dim, size_type n = 1);
    void reset(size_type dim);
    void reset_back(size_type dim);

    void to_begin();
    void to_end(layout_type l);

The ``reset`` and ``reset_back`` methods are shortcut to ``step_back`` and ``step`` called with ``dim`` and ``shape[dim] - 1``.
The steppers are initialized with a "position" (that may be an index, a pointer to the underlying buffer of an container-based
expression, etc...) in the expression, and can then be used to browse the expression in any direction:

.. image:: stepper_basic.svg

In this diagram, the data is stored in row-major order, and we step in the first dimension (dimension index starts at 0).
The positions of the stepper are represented by the red dots.

The ``to_end`` method takes a layout parameter, because the ending positions of a stepper depend on the layout used to iterate.
Indeed, if we call ``step_back`` after a call to ``to_end``, we want the stepper to point to the last element. To ensure this
for both row-major order and column-major order iterations, the ending positions must be set as shown below:

.. image:: stepper_to_end.svg

The red dots are the position of a stepper iterating in column-major while the green ones are the positions of a stepper iterating
in row-major order. Thus, if we assume that ``p`` is a pointer to the last element (the square containing 11), the ending positions
of the stepper are ``p + 1`` in row-major, and ``p + 3`` in column-major order.

A stepper is specific to an expression type, therefore implementing a new kind of expression usually requires to implement a new
kind of stepper. However *xtensor* provides a generic ``xindexed_stepper`` class, that can be used with any kind of expressions.
Even though it is generally not optimal, authors of new expression types can make use of the generic index stepper in a
first implementation.

Broadcasting
~~~~~~~~~~~~

The steppers of container-based expressions rely on strides and backstrides for stepping. A naive implementation of the ``step``
method would be:

.. code::

    template <class C>
    inline void xstepper<C>::step(size_type dim, size_type n)
    {
        m_it += n * p_c->strides()[dim];
    }

where ``m_it`` is an iterator on the underlying buffer, and ``p_c`` a pointer to the container-based expression.

However, this implementation fails when broadcasting is involved. Consider the following expression:

.. code::

    xarray<int> a = {{0, 1,  2,  3},
                     {4, 5,  6,  7},
                     {8, 9, 10, 11}};
    xarray<int> b = {0, 1, 2, 3};
    auto r = a + b;

``r`` is an ``xfunction`` representing the sum of ``a`` and ``b``. The stepper specific to this expression holds the steppers
of the arguments of the function; calling ``step`` or ``step_back`` results in calling ``step`` or ``step_back`` of the
steppers of ``a`` and ``b``.

According to the broadcasting rules, the shape of ``r`` is ``{ 3, 4}``. Thus, calling ``r.stepper_begin().step(1, 1)`` will
eventually call ``b.stepper_begin().step(1, 1)``, leading to undefined behavior since the shape of ``b`` is ``{4}``. To
avoid that, a broadcasting offset is added to the stepper:

.. code::

    template <class C>
    inline void xstepper<C>::step(size_type dim, size_type n)
    {
        if (dim >= m_offset)
        {
            m_it += difference_type(n * p_c->strides()[dim - m_offset]);
        }
    }

This implementation takes into account that the broadcasting is done on the last dimension and dimensions are stored in ascending
order; here dimension 1 of ``a`` corresponds to dimension 0 of ``b``.

This implementation ensures that a step in dimension 0 of the function updates the stepper of ``a`` while the stepper of ``b``
remains unchanged; on the other hand, stepping in dimension 1 will update both steppers, as illustrated below:

.. image:: stepper_broadcasting.svg

The red dots are initial stepper positions, the green dots and blue dots are the positions of the steppers after calling ``step``
with different dimension arguments.

Iterators
~~~~~~~~~

*xtensor* iterator is implemented in the ``xiterator`` class. This latter provides a STL compliant iterator interface, and is built
upon the steppers. Whereas the steppers are tied to the expression they refer to, ``xiterator`` is generic enough to work with any
kind of stepper.

An iterator holds a stepper and a multi-dimensional index. A call to ``operator++`` increases the index and calls the ``step`` method
of the stepper accordingly. The way the index is increased depends on the layout used for iterating. For a row-major order iteration
over a container with shape ``{3, 4}``, the index iterating sequence is:

.. code::

    {0, 0}
    {0, 1}
    {0, 2}
    {0, 3}
    {1, 0}
    {1, 1}
    {1, 2}
    {1, 3}
    {2, 0}
    {2, 1}
    {2, 2}
    {2, 3}

When a member of an index reaches its maximum value, it is reset to 0 and the member in the next dimension is increased. This translates
into the calls of two methods of the stepper, first ``reset`` and then ``step``. This is illustrated by the following picture:

.. image:: stepper_iterating.svg

The green arrows represent the iteration from ``{0, 0}`` to ``{0, 3}``. The blue arrows illustrate what happens when the index is increased
from ``{0, 3}`` to ``{1, 0}``: first the stepper is reset to ``{0, 0}``, then ``step(0, 1)`` is called, setting the stepper to the position
``{1, 0}``.

``xiterator`` implements a random access iterator, providing ``operator--`` and ``operator[]`` methods. The implementation of these methods
is similar to the one of ``operator++``.