TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
_impl_basic_array_view_common.hpp
1// Copyright (c) 2019--present, The Simons Foundation
2// This file is part of TRIQS/nda and is licensed under the Apache License, Version 2.0.
3// SPDX-License-Identifier: Apache-2.0
4// See LICENSE in the root of this distribution for details.
5
10[[nodiscard]] constexpr auto const &indexmap() const noexcept { return lay; }
11
16[[nodiscard]] storage_t const &storage() const & noexcept { return sto; }
17
22[[nodiscard]] storage_t &storage() & noexcept { return sto; }
23
28[[nodiscard]] storage_t storage() && noexcept { return std::move(sto); }
29
36[[nodiscard]] constexpr auto stride_order() const noexcept { return lay.stride_order; }
37
42[[nodiscard]] ValueType const *data() const noexcept { return sto.data(); }
43
48[[nodiscard]] ValueType *data() noexcept { return sto.data(); }
49
54[[nodiscard]] auto const &shape() const noexcept { return lay.lengths(); }
55
60[[nodiscard]] auto const &strides() const noexcept { return lay.strides(); }
61
66[[nodiscard]] long size() const noexcept { return lay.size(); }
67
72[[nodiscard]] long is_contiguous() const noexcept { return lay.is_contiguous(); }
73
78[[nodiscard]] long has_positive_strides() const noexcept { return lay.has_positive_strides(); }
79
84[[nodiscard]] bool empty() const { return sto.is_null(); }
85
87[[nodiscard]] bool is_empty() const noexcept { return sto.is_null(); }
88
93[[nodiscard]] long extent(int i) const noexcept {
94#ifdef NDA_ENFORCE_BOUNDCHECK
95 if (i < 0 || i >= rank) {
96 std::cerr << "Error in extent: Dimension " << i << " is incompatible with array of rank " << rank << std::endl;
97 std::terminate();
98 }
99#endif
100 return lay.lengths()[i];
101}
102
104[[nodiscard]] long shape(int i) const noexcept { return extent(i); }
105
110[[nodiscard]] auto indices() const noexcept { return itertools::product_range(shape()); }
111
116static constexpr bool is_stride_order_C() noexcept { return layout_t::is_stride_order_C(); }
117
122static constexpr bool is_stride_order_Fortran() noexcept { return layout_t::is_stride_order_Fortran(); }
123
133decltype(auto) operator()(_linear_index_t idx) const noexcept {
134 if constexpr (layout_t::layout_prop == layout_prop_e::strided_1d)
135 return sto[idx.value * lay.min_stride()];
136 else if constexpr (layout_t::layout_prop == layout_prop_e::contiguous)
137 return sto[idx.value];
138 else
139 static_assert(always_false<layout_t>, "Internal error in array/view: Calling this type with a _linear_index_t is not allowed");
140}
141
143decltype(auto) operator()(_linear_index_t idx) noexcept {
144 if constexpr (layout_t::layout_prop == layout_prop_e::strided_1d)
145 return sto[idx.value * lay.min_stride()];
146 else if constexpr (layout_t::layout_prop == layout_prop_e::contiguous)
147 return sto[idx.value];
148 else
149 static_assert(always_false<layout_t>, "Internal error in array/view: Calling this type with a _linear_index_t is not allowed");
150}
151
152private:
153// Constexpr variable that is true if bounds checking is disabled.
154#ifdef NDA_ENFORCE_BOUNDCHECK
155static constexpr bool has_no_boundcheck = false;
156#else
157static constexpr bool has_no_boundcheck = true;
158#endif
159
160public:
177template <char ResultAlgebra, bool SelfIsRvalue, typename Self, typename... Ts>
178FORCEINLINE static decltype(auto) call(Self &&self, Ts const &...idxs) noexcept(has_no_boundcheck) {
179 // resulting value type
180 using r_v_t = std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, ValueType const, ValueType>;
181
182 // behavior depends on the given arguments
183 if constexpr (clef::is_any_lazy<Ts...>) {
184 // if there are lazy arguments, e.g. as in A(i_) << i_, a lazy expression is returned
185 return clef::make_expr_call(std::forward<Self>(self), idxs...);
186 } else if constexpr (sizeof...(Ts) == 0) {
187 // if no arguments are given, a full view is returned
188 return basic_array_view<r_v_t, Rank, LayoutPolicy, Algebra, AccessorPolicy, OwningPolicy>{self.lay, self.sto};
189 } else {
190 // otherwise we check the arguments and either access a single element or make a slice
191 static_assert(((layout_t::template argument_is_allowed_for_call_or_slice<Ts> + ...) > 0),
192 "Error in array/view: Slice arguments must be convertible to range, ellipsis, or long (or string if the layout permits it)");
193
194 // number of arguments convertible to long
195 static constexpr int n_args_long = (layout_t::template argument_is_allowed_for_call<Ts> + ...);
196
197 if constexpr (n_args_long == rank) {
198 // access a single element
199 long offset = self.lay(idxs...);
200 if constexpr (is_view or not SelfIsRvalue) {
201 // if the calling object is a view or an lvalue, we return a reference
202 return AccessorPolicy::template accessor<r_v_t>::access(self.sto.data(), offset);
203 } else {
204 // otherwise, we return a copy of the value
205 return ValueType{self.sto[offset]};
206 }
207 } else {
208 // access a slice of the view/array
209 auto const [offset, idxm] = self.lay.slice(idxs...);
210 static constexpr auto res_rank = decltype(idxm)::rank();
211 // resulting algebra
212 static constexpr char newAlgebra = (ResultAlgebra == 'M' and (res_rank == 1) ? 'V' : ResultAlgebra);
213 // resulting layout policy
214 using r_layout_p = typename detail::layout_to_policy<std::decay_t<decltype(idxm)>>::type;
215 return basic_array_view<r_v_t, res_rank, r_layout_p, newAlgebra, AccessorPolicy, OwningPolicy>{std::move(idxm), {self.sto, offset}};
216 }
217 }
218}
219
220public:
242template <typename... Ts>
243FORCEINLINE decltype(auto) operator()(Ts const &...idxs) const & noexcept(has_no_boundcheck) {
244 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
245 "Error in array/view: Incorrect number of parameters in call operator");
246 return call<Algebra, false>(*this, idxs...);
247}
248
250template <typename... Ts>
251FORCEINLINE decltype(auto) operator()(Ts const &...idxs) & noexcept(has_no_boundcheck) {
252 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
253 "Error in array/view: Incorrect number of parameters in call operator");
254 return call<Algebra, false>(*this, idxs...);
255}
256
258template <typename... Ts>
259FORCEINLINE decltype(auto) operator()(Ts const &...idxs) && noexcept(has_no_boundcheck) {
260 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
261 "Error in array/view: Incorrect number of parameters in call operator");
262 return call<Algebra, true>(*this, idxs...);
263}
264
282template <typename T>
283decltype(auto) operator[](T const &idx) const & noexcept(has_no_boundcheck) {
284 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
285 return call<Algebra, false>(*this, idx);
286}
287
289template <typename T>
290decltype(auto) operator[](T const &x) & noexcept(has_no_boundcheck) {
291 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
292 return call<Algebra, false>(*this, x);
293}
294
296template <typename T>
297decltype(auto) operator[](T const &x) && noexcept(has_no_boundcheck) {
298 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
299 return call<Algebra, true>(*this, x);
300}
301
303static constexpr int iterator_rank = (has_strided_1d(layout_t::layout_prop) ? 1 : Rank);
304
306using const_iterator = array_iterator<iterator_rank, ValueType const, typename AccessorPolicy::template accessor<ValueType>::pointer>;
307
309using iterator = array_iterator<iterator_rank, ValueType, typename AccessorPolicy::template accessor<ValueType>::pointer>;
310
311private:
312// Make an iterator for the view/array depending on its type.
313template <typename Iterator>
314[[nodiscard]] auto make_iterator(bool at_end) const noexcept {
315 if constexpr (iterator_rank == Rank) {
316 // multi-dimensional iterator
317 if constexpr (layout_t::is_stride_order_C()) {
318 // C-order case (array_iterator already traverses the data in C-order)
319 return Iterator{indexmap().lengths(), indexmap().strides(), sto.data(), at_end};
320 } else {
321 // general case (we need to permute the shape and the strides according to the stride order of the layout)
322 return Iterator{nda::permutations::apply(layout_t::stride_order, indexmap().lengths()),
323 nda::permutations::apply(layout_t::stride_order, indexmap().strides()), sto.data(), at_end};
324 }
325 } else {
326 // 1-dimensional iterator
327 return Iterator{std::array<long, 1>{size()}, std::array<long, 1>{indexmap().min_stride()}, sto.data(), at_end};
328 }
329}
330
331public:
333[[nodiscard]] const_iterator begin() const noexcept { return make_iterator<const_iterator>(false); }
334
336[[nodiscard]] const_iterator cbegin() const noexcept { return make_iterator<const_iterator>(false); }
337
339iterator begin() noexcept { return make_iterator<iterator>(false); }
340
342[[nodiscard]] const_iterator end() const noexcept { return make_iterator<const_iterator>(true); }
343
345[[nodiscard]] const_iterator cend() const noexcept { return make_iterator<const_iterator>(true); }
346
348iterator end() noexcept { return make_iterator<iterator>(true); }
349
362template <typename RHS>
363auto &operator+=(RHS const &rhs) noexcept {
364 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
365 return operator=(*this + rhs);
366}
367
380template <typename RHS>
381auto &operator-=(RHS const &rhs) noexcept {
382 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
383 return operator=(*this - rhs);
384}
385
398template <typename RHS>
399auto &operator*=(RHS const &rhs) noexcept {
400 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
401 return operator=((*this) * rhs);
402}
403
416template <typename RHS>
417auto &operator/=(RHS const &rhs) noexcept {
418 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
419 return operator=(*this / rhs);
420}
421
430template <std::ranges::contiguous_range R>
431auto &operator=(R const &rhs) noexcept
432 requires(Rank == 1 and not MemoryArray<R> and not is_scalar_for_v<R, self_t>)
433{
434 *this = array_const_view<std::ranges::range_value_t<R>, 1>{rhs};
435 return *this;
436}
437
438private:
439// Implementation of the assignment from an n-dimensional array type.
440template <typename RHS>
441void assign_from_ndarray(RHS const &rhs) { // FIXME noexcept {
442#ifdef NDA_ENFORCE_BOUNDCHECK
443 if (this->shape() != rhs.shape())
444 NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Size mismatch:"
445 << "\n LHS.shape() = " << this->shape() << "\n RHS.shape() = " << rhs.shape();
446#endif
447 // compile-time check if assignment is possible
448 static_assert(std::is_assignable_v<value_type &, get_value_t<RHS>>, "Error in assign_from_ndarray: Incompatible value types");
449
450 // are both operands nda::MemoryArray types?
451 static constexpr bool both_in_memory = MemoryArray<self_t> and MemoryArray<RHS>;
452
453 // do both operands have the same stride order?
454 static constexpr bool same_stride_order = get_layout_info<self_t>.stride_order == get_layout_info<RHS>.stride_order;
455
456 // prefer optimized options if possible
457 if constexpr (both_in_memory and same_stride_order) {
458 if (rhs.empty()) return;
459 // are both operands strided in 1d?
460 static constexpr bool both_1d_strided = has_layout_strided_1d<self_t> and has_layout_strided_1d<RHS>;
461 if constexpr (mem::on_host<self_t, RHS> and both_1d_strided) {
462 // vectorizable copy on host
463 for (long i = 0; i < size(); ++i) (*this)(_linear_index_t{i}) = rhs(_linear_index_t{i});
464 return;
465 } else if constexpr (!mem::on_host<self_t, RHS> and have_same_value_type_v<self_t, RHS>) {
466 // check for block-layout and use mem::memcpy2D if possible
467 auto bl_layout_dst = get_block_layout(*this);
468 auto bl_layout_src = get_block_layout(rhs);
469 if (bl_layout_dst && bl_layout_src) {
470 auto [n_bl_dst, bl_size_dst, bl_str_dst] = *bl_layout_dst;
471 auto [n_bl_src, bl_size_src, bl_str_src] = *bl_layout_src;
472 // check that the total memory size is the same
473 if (n_bl_dst * bl_size_dst != n_bl_src * bl_size_src) NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Incompatible block sizes";
474 // if either destination or source consists of a single block, we can chunk it up to make the layouts compatible
475 if (n_bl_dst == 1 && n_bl_src > 1) {
476 n_bl_dst = n_bl_src;
477 bl_size_dst /= n_bl_src;
478 bl_str_dst = bl_size_dst;
479 }
480 if (n_bl_src == 1 && n_bl_dst > 1) {
481 n_bl_src = n_bl_dst;
482 bl_size_src /= n_bl_dst;
483 bl_str_src = bl_size_src;
484 }
485 // copy only if block-layouts are compatible, otherwise continue to fallback
486 if (n_bl_dst == n_bl_src && bl_size_dst == bl_size_src) {
487 mem::memcpy2D<mem::get_addr_space<self_t>, mem::get_addr_space<RHS>>((void *)data(), bl_str_dst * sizeof(value_type), (void *)rhs.data(),
488 bl_str_src * sizeof(value_type), bl_size_src * sizeof(value_type),
489 n_bl_src);
490 return;
491 }
492 }
493 }
494 }
495 // otherwise fallback to elementwise assignment
496 if constexpr (mem::on_device<self_t> || mem::on_device<RHS>) {
497 NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Fallback to elementwise assignment not implemented for arrays/views on the GPU";
498 }
499 nda::for_each(shape(), [this, &rhs](auto const &...args) { (*this)(args...) = rhs(args...); });
500}
501
502// Implementation to fill a view/array with a constant scalar value.
503template <typename Scalar>
504void fill_with_scalar(Scalar const &scalar) noexcept {
505 // we make a special implementation if the array is strided in 1d or contiguous
506 if constexpr (has_layout_strided_1d<self_t>) {
507 const long L = size();
508 auto *__restrict const p = data(); // no alias possible here!
509 if constexpr (has_contiguous_layout<self_t>) {
510 for (long i = 0; i < L; ++i) p[i] = scalar;
511 } else {
512 const long stri = indexmap().min_stride();
513 const long Lstri = L * stri;
514 for (long i = 0; i != Lstri; i += stri) p[i] = scalar;
515 }
516 } else {
517 // no compile-time memory layout guarantees
518 for (auto &x : *this) x = scalar;
519 }
520}
521
522// Implementation of the assignment from a scalar value.
523template <typename Scalar>
524void assign_from_scalar(Scalar const &scalar) noexcept {
525 static_assert(!is_const, "Error in assign_from_ndarray: Cannot assign to a const view");
526 if constexpr (Algebra != 'M') {
527 // element-wise assignment for non-matrix algebras
528 fill_with_scalar(scalar);
529 } else {
530 // a scalar has to be interpreted as a unit matrix for matrix algebras (the scalar in the shortest diagonal)
531 // FIXME : A priori faster to put 0 everywhere and then change the diag to avoid the if.
532 // FIXME : Benchmark and confirm.
533 if constexpr (is_scalar_or_convertible_v<Scalar>)
534 fill_with_scalar(0);
535 else
536 fill_with_scalar(Scalar{0 * scalar}); // FIXME : improve this
537 const long imax = std::min(extent(0), extent(1));
538 for (long i = 0; i < imax; ++i) operator()(i, i) = scalar;
539 }
540}
constexpr bool has_strided_1d(layout_prop_e lp)
Checks if a layout property has the strided_1d property.
Definition traits.hpp:255
__inline__ void for_each(std::array< Int, R > const &shape, F &&f)
Loop over all possible index values of a given shape and apply a function to them.
Definition for_each.hpp:116
auto get_block_layout(A const &a)
Check if a given nda::MemoryArray has a block-strided layout.
constexpr std::array< T, N > apply(std::array< Int, N > const &p, std::array< T, N > const &a)
Apply a permutation to a std::array.