1 #ifndef PYTHONIC_INCLUDE_TYPES_NUMPY_IEXPR_HPP
2 #define PYTHONIC_INCLUDE_TYPES_NUMPY_IEXPR_HPP
3 
4 #include "pythonic/include/types/nditerator.hpp"
5 #include "pythonic/include/types/tuple.hpp"
6 #include "pythonic/utils/array_helper.hpp"
7 
8 #include <numeric>
9 
10 PYTHONIC_NS_BEGIN
11 
12 namespace types
13 {
14   template <size_t L>
15   struct noffset {
16     template <class S, class Ty, size_t M>
17     long operator()(S const &strides, array<Ty, M> const &indices) const;
18     template <class S, class Ty, size_t M, class pS>
19     long operator()(S const &strides, array<Ty, M> const &indices,
20                     pS const &shape) const;
21   };
22 
23   template <class Arg, class... S>
24   struct numpy_gexpr;
25 
26   /* Expression template for numpy expressions - indexing
27    */
28   template <size_t N>
29   struct numpy_iexpr_helper;
30 
31   template <class Arg> // Arg often is a reference, e.g. for something as
32   // simple as a[i]
33   struct numpy_iexpr {
34     // wrapper around another numpy expression to skip first dimension using a
35     // given value.
36     static constexpr size_t value = std::remove_reference<Arg>::type::value - 1;
37     static const bool is_vectorizable =
38         std::remove_reference<Arg>::type::is_vectorizable;
39     using dtype = typename std::remove_reference<Arg>::type::dtype;
40     using value_type = typename std::remove_reference<decltype(
41         numpy_iexpr_helper<value>::get(std::declval<numpy_iexpr>(), 0L))>::type;
42 
43     static constexpr bool is_strided =
44         std::remove_reference<Arg>::type::is_strided;
45 
46     using iterator =
47         typename std::conditional<is_strided || value != 1,
48                                   nditerator<numpy_iexpr>, dtype *>::type;
49     using const_iterator =
50         typename std::conditional<is_strided || value != 1,
51                                   const_nditerator<numpy_iexpr>,
52                                   dtype const *>::type;
53 
54     Arg arg;
55     dtype *buffer;
56     using shape_t =
57         sutils::pop_head_t<typename std::remove_reference<Arg>::type::shape_t>;
58 
59     numpy_iexpr();
60     numpy_iexpr(numpy_iexpr const &) = default;
61     numpy_iexpr(numpy_iexpr &&) = default;
62 
63     template <class Argp>
64     numpy_iexpr(numpy_iexpr<Argp &> const &other);
65     template <class Argp>
66     numpy_iexpr(numpy_iexpr<Argp> const &other);
67 
68     numpy_iexpr(Arg const &arg, long index);
69     numpy_iexpr(Arg const &arg, long index, dtype *b);
70 
71     long size() const;
72 
73     template <class E>
74     struct is_almost_same : std::false_type {
75     };
76     template <class Argp>
77     struct is_almost_same<numpy_iexpr<Argp>>
78         : std::integral_constant<
79               bool, !std::is_same<Arg, Argp>::value &&
80                         std::is_same<typename std::decay<Arg>::type,
81                                      typename std::decay<Argp>::type>::value> {
82     };
83 
84     template <class E, class Requires = typename std::enable_if<
85                            !is_almost_same<E>::value, void>::type>
86     numpy_iexpr &operator=(E const &expr);
87     template <class Argp,
88               class Requires = typename std::enable_if<
89                   is_almost_same<numpy_iexpr<Argp>>::value, void>::type>
90     numpy_iexpr &operator=(numpy_iexpr<Argp> const &expr);
91     numpy_iexpr &operator=(numpy_iexpr const &expr);
92 
93     template <class Op, class E>
94     numpy_iexpr &update_(E const &expr);
95 
96     template <class E>
97     numpy_iexpr &operator+=(E const &expr);
98     numpy_iexpr &operator+=(numpy_iexpr const &expr);
99 
100     template <class E>
101     numpy_iexpr &operator-=(E const &expr);
102     numpy_iexpr &operator-=(numpy_iexpr const &expr);
103 
104     template <class E>
105     numpy_iexpr &operator*=(E const &expr);
106     numpy_iexpr &operator*=(numpy_iexpr const &expr);
107 
108     template <class E>
109     numpy_iexpr &operator/=(E const &expr);
110     numpy_iexpr &operator/=(numpy_iexpr const &expr);
111 
112     template <class E>
113     numpy_iexpr &operator&=(E const &expr);
114     numpy_iexpr &operator&=(numpy_iexpr const &expr);
115 
116     template <class E>
117     numpy_iexpr &operator|=(E const &expr);
118     numpy_iexpr &operator|=(numpy_iexpr const &expr);
119 
120     template <class E>
121     numpy_iexpr &operator^=(E const &expr);
122     numpy_iexpr &operator^=(numpy_iexpr const &expr);
123 
124     const_iterator begin() const;
125     const_iterator end() const;
126 
127     iterator begin();
128     iterator end();
129 
130     dtype const *fbegin() const;
131     dtype const *fend() const;
132 
133     dtype *fbegin();
134     dtype const *fend();
135 
136     /* There are three kind of indexing operator: fast(long), [long] &&
137      *(long):
138      * - fast does ! perform automatic bound wrapping
139      * - [] performs automatic bound wrapping, hen forwards to fast
140      * - () is an alias to [] && directly forwards to []
141      *
142      * For each indexing operator, we have three variant: &, const& && &&:
143      * - & means the numpy_iexpr has been bound to a non-const value, as in
144      *``b=a[i] ; print b[j]``
145      *   in that case the return type if the dim of a is 2 is a reference, to
146      *allow ``b[j] = 1``
147      * - const & means the numpy_iexpr has been bound to a const value, as in
148      *``np.copy(a[i])``
149      *   in that case the return type if the dim of a is 2 is a value (||
150      *const ref)
151      * - && means the numpy_iexpr is a r-value, which happens a lot, as in
152      *``a[i][j]``
153      *   in that case the return type if the dim of a is 2 is a reference.
154      *   It is a bit weird because we return a refrence from a rvalue, but the
155      *reference is bound to
156      *   the buffer of ``a`` that is ! temp.
157      */
fasttypes::numpy_iexpr158     auto fast(long i) const
159         & -> decltype(numpy_iexpr_helper<value>::get(*this, i))
160     {
161       return numpy_iexpr_helper<value>::get(*this, i);
162     }
163 
fasttypes::numpy_iexpr164     auto fast(long i) & -> decltype(numpy_iexpr_helper<value>::get(*this, i))
165     {
166       return numpy_iexpr_helper<value>::get(*this, i);
167     }
168 
fasttypes::numpy_iexpr169     auto fast(long i) &&
170         -> decltype(numpy_iexpr_helper<value>::get(std::move(*this), i))
171     {
172       return numpy_iexpr_helper<value>::get(std::move(*this), i);
173     }
174 
175     dtype const &fast(array<long, value> const &indices) const;
176     dtype &fast(array<long, value> const &indices);
177 
178     template <size_t M>
fasttypes::numpy_iexpr179     auto fast(array<long, M> const &indices) const
180         -> decltype(nget<M - 1>()(*this, indices))
181     {
182       return nget<M - 1>()(*this, indices);
183     }
184 
185     template <class F>
186     typename std::enable_if<
187         is_numexpr_arg<F>::value &&
188             std::is_same<bool, typename F::dtype>::value,
189         numpy_vexpr<numpy_iexpr, ndarray<long, pshape<long>>>>::type
190     fast(F const &filter) const;
191 
192     template <class E, class... Indices>
storetypes::numpy_iexpr193     void store(E elt, Indices... indices)
194     {
195       static_assert(is_dtype<E>::value, "valid store");
196       assert(buffer);
197       *(buffer + noffset<value>{}(*this, array<long, value>{{indices...}})) =
198           static_cast<E>(elt);
199     }
200     template <class... Indices>
loadtypes::numpy_iexpr201     dtype load(Indices... indices) const
202     {
203       assert(buffer);
204       return *(buffer +
205                noffset<value>{}(*this, array<long, value>{{indices...}}));
206     }
207     template <class Op, class E, class... Indices>
updatetypes::numpy_iexpr208     void update(E elt, Indices... indices) const
209     {
210       static_assert(is_dtype<E>::value, "valid store");
211       assert(buffer);
212       Op{}(
213           *(buffer + noffset<value>{}(*this, array<long, value>{{indices...}})),
214           static_cast<E>(elt));
215     }
216 
217 #ifdef USE_XSIMD
218     using simd_iterator = const_simd_nditerator<numpy_iexpr>;
219     using simd_iterator_nobroadcast = simd_iterator;
220     template <class vectorizer>
221     simd_iterator vbegin(vectorizer) const;
222     template <class vectorizer>
223     simd_iterator vend(vectorizer) const;
224 #endif
225 
226     template <class Sp, class... S>
227     typename std::enable_if<
228         is_slice<Sp>::value,
229         numpy_gexpr<numpy_iexpr, normalize_t<Sp>, normalize_t<S>...>>::type
230     operator()(Sp const &s0, S const &... s) const;
231 
232     template <class... S>
operator ()types::numpy_iexpr233     auto operator()(long s0, S const &... s) const
234         -> decltype(std::declval<numpy_iexpr<numpy_iexpr>>()(s...))
235     {
236       return (*this)[s0](s...);
237     }
238 
239     template <class F>
240     typename std::enable_if<
241         is_numexpr_arg<F>::value &&
242             std::is_same<bool, typename F::dtype>::value,
243         numpy_vexpr<numpy_iexpr, ndarray<long, pshape<long>>>>::type
244     operator[](F const &filter) const;
245     auto operator[](long i) const & -> decltype(this->fast(i));
246     auto operator[](long i) & -> decltype(this->fast(i));
247     auto operator[](long i) && -> decltype(std::move(*this).fast(i));
248     template <class Sp>
249     typename std::enable_if<is_slice<Sp>::value,
250                             numpy_gexpr<numpy_iexpr, normalize_t<Sp>>>::type
251     operator[](Sp const &s0) const;
252 
253     dtype const &operator[](array<long, value> const &indices) const;
254     dtype &operator[](array<long, value> const &indices);
255     template <size_t M>
operator []types::numpy_iexpr256     auto operator[](array<long, M> const &indices) const
257         & -> decltype(nget<M - 1>()(*this, indices))
258     {
259       return nget<M - 1>()(*this, indices);
260     }
261 
262     explicit operator bool() const;
263     long flat_size() const;
264     template <size_t I>
shapetypes::numpy_iexpr265     auto shape() const -> decltype(arg.template shape<I + 1>())
266     {
267       return arg.template shape<I + 1>();
268     }
269     template <size_t I>
stridestypes::numpy_iexpr270     auto strides() const -> decltype(arg.template strides<I + 1>())
271     {
272       return arg.template strides<I + 1>();
273     }
274 
275     template <class pS>
reshapetypes::numpy_iexpr276     auto reshape(pS const &new_shape) const -> numpy_iexpr<
277         decltype(std::declval<Arg>().reshape(std::declval<sutils::push_front_t<
278             pS, typename std::tuple_element<
279                     0, typename std::decay<Arg>::type::shape_t>::type>>()))>
280     {
281       assert(buffer);
282       sutils::push_front_t<
283           pS, typename std::tuple_element<
284                   0, typename std::decay<Arg>::type::shape_t>::type>
285           fixed_new_shape;
286       sutils::scopy_shape<1, -1>(
287           fixed_new_shape, new_shape,
288           utils::make_index_sequence<std::tuple_size<pS>::value>{});
289       sutils::assign(std::get<0>(fixed_new_shape), arg.template shape<0>());
290       return numpy_iexpr<decltype(arg.reshape(fixed_new_shape))>(
291           arg.reshape(fixed_new_shape),
292           (buffer - arg.buffer) / arg.template strides<0>());
293     }
294 
copytypes::numpy_iexpr295     ndarray<dtype, shape_t> copy() const
296     {
297       return {*this};
298     }
299 
300     template <class F> // indexing through an array of indices -- a view
301     typename std::enable_if<is_numexpr_arg<F>::value &&
302                                 !is_array_index<F>::value &&
303                                 !std::is_same<bool, typename F::dtype>::value &&
304                                 !is_pod_array<F>::value,
305                             numpy_vexpr<numpy_iexpr, F>>::type
operator []types::numpy_iexpr306     operator[](F const &filter) const
307     {
308       return {*this, filter};
309     }
310 
311     template <class F> // indexing through an array of indices -- a view
312     typename std::enable_if<is_numexpr_arg<F>::value &&
313                                 !is_array_index<F>::value &&
314                                 !std::is_same<bool, typename F::dtype>::value &&
315                                 !is_pod_array<F>::value,
316                             numpy_vexpr<numpy_iexpr, F>>::type
operator []types::numpy_iexpr317     operator[](F const &filter)
318     {
319       return {*this, filter};
320     }
321 
322     template <class Ty>
operator []types::numpy_iexpr323     auto operator[](std::tuple<Ty> const &index) const
324         -> decltype((*this)[std::get<0>(index)])
325     {
326       return (*this)[std::get<0>(index)];
327     }
328 
329   private:
330     /* compute the buffer offset, returning the offset between the
331      * first element of the iexpr and the start of the buffer.
332      * This used to be a plain loop, but g++ fails to unroll it, while it
333      * unrolls it with the template version...
334      */
335     long buffer_offset(Arg const &shape, long index, utils::int_<0>);
336 
337     template <class T, class pS, size_t N>
338     long buffer_offset(ndarray<T, pS> const &arg, long index, utils::int_<N>);
339 
340     template <class E, size_t N>
341     long buffer_offset(E const &arg, long index, utils::int_<N>);
342   };
343 
344   // Indexing an numpy_iexpr that has a dimension greater than one yields a
345   // new numpy_iexpr
346   template <size_t N>
347   struct numpy_iexpr_helper {
348     template <class T>
349     static numpy_iexpr<T> get(T &&e, long i);
350   };
351 
352   // Indexing an iexpr that has a dimension of one yields a qualified scalar.
353   // The qualifier is either:
354   // - a reference if the numpy_iexpr is a ref itself, as in ``b = a[i] ; b[i]
355   // = 1``
356   // - a reference if the numpy_iexpr is a r-value, as in ``a[i][j] = 1``
357   // - a value if the numpy_iexpr is a const ref, as in ``b = a[i] ; c =
358   // b[i]``
359   template <>
360   struct numpy_iexpr_helper<1> {
361     template <class T>
362     static typename T::dtype &get(T const &e, long i);
363     template <class T>
364     static typename T::dtype &get(T &&e, long i);
365     template <class T>
366     static typename T::dtype &get(T &e, long i);
367   };
368 }
369 
370 template <class Arg>
371 struct assignable_noescape<types::numpy_iexpr<Arg>> {
372   using type = types::numpy_iexpr<Arg>;
373 };
374 
375 template <class Arg>
376 struct assignable<types::numpy_iexpr<Arg>> {
377   using type = types::numpy_iexpr<typename assignable<Arg>::type>;
378 };
379 
380 template <class T, class pS>
381 struct assignable<types::numpy_iexpr<types::ndarray<T, pS> &>> {
382   using type = types::numpy_iexpr<types::ndarray<T, pS>>;
383 };
384 
385 template <class T, class pS>
386 struct assignable<types::numpy_iexpr<types::ndarray<T, pS>>> {
387   using type = types::numpy_iexpr<types::ndarray<T, pS>>;
388 };
389 
390 template <class Arg>
391 struct returnable<types::numpy_iexpr<Arg>> {
392   using type = types::numpy_iexpr<typename returnable<Arg>::type>;
393 };
394 
395 template <class Arg>
396 struct lazy<types::numpy_iexpr<Arg>> : assignable<types::numpy_iexpr<Arg>> {
397 };
398 
399 PYTHONIC_NS_END
400 
401 /* type inference stuff  {*/
402 #include "pythonic/include/types/combined.hpp"
403 template <class E, class K>
404 struct __combined<pythonic::types::numpy_iexpr<E>, indexable<K>> {
405   using type = pythonic::types::numpy_iexpr<E>;
406 };
407 
408 template <class E, class K>
409 struct __combined<indexable<K>, pythonic::types::numpy_iexpr<E>> {
410   using type = pythonic::types::numpy_iexpr<E>;
411 };
412 
413 template <class E, class K, class V>
414 struct __combined<pythonic::types::numpy_iexpr<E>, indexable_container<K, V>> {
415   using type = pythonic::types::numpy_iexpr<E>;
416 };
417 
418 template <class E, class K, class V>
419 struct __combined<indexable_container<K, V>, pythonic::types::numpy_iexpr<E>> {
420   using type = pythonic::types::numpy_iexpr<E>;
421 };
422 
423 template <class E, class K>
424 struct __combined<container<K>, pythonic::types::numpy_iexpr<E>> {
425   using type = pythonic::types::numpy_iexpr<E>;
426 };
427 
428 template <class E, class K>
429 struct __combined<pythonic::types::numpy_iexpr<E>, container<K>> {
430   using type = pythonic::types::numpy_iexpr<E>;
431 };
432 template <class E0, class E1>
433 struct __combined<pythonic::types::numpy_iexpr<E0>,
434                   pythonic::types::numpy_iexpr<E1>> {
435   using type = pythonic::types::numpy_iexpr<typename __combined<E0, E1>::type>;
436 };
437 template <class E, class T, class pS>
438 struct __combined<pythonic::types::numpy_iexpr<E>,
439                   pythonic::types::ndarray<T, pS>> {
440   using type = pythonic::types::ndarray<T, pS>;
441 };
442 /*}*/
443 #endif
444