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