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-2024 Simons Foundation
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0.txt
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14//
15// Authors: Thomas Hahn, Miguel Morales, Olivier Parcollet, Nils Wentzell
16
21[[nodiscard]] constexpr auto const &indexmap() const noexcept { return lay; }
22
27[[nodiscard]] storage_t const &storage() const & noexcept { return sto; }
28
33[[nodiscard]] storage_t &storage() & noexcept { return sto; }
34
39[[nodiscard]] storage_t storage() && noexcept { return std::move(sto); }
40
47[[nodiscard]] constexpr auto stride_order() const noexcept { return lay.stride_order; }
48
53[[nodiscard]] ValueType const *data() const noexcept { return sto.data(); }
54
59[[nodiscard]] ValueType *data() noexcept { return sto.data(); }
60
65[[nodiscard]] auto const &shape() const noexcept { return lay.lengths(); }
66
71[[nodiscard]] auto const &strides() const noexcept { return lay.strides(); }
72
77[[nodiscard]] long size() const noexcept { return lay.size(); }
78
83[[nodiscard]] long is_contiguous() const noexcept { return lay.is_contiguous(); }
84
89[[nodiscard]] long has_positive_strides() const noexcept { return lay.has_positive_strides(); }
90
95[[nodiscard]] bool empty() const { return sto.is_null(); }
96
98[[nodiscard]] bool is_empty() const noexcept { return sto.is_null(); }
99
104[[nodiscard]] long extent(int i) const noexcept {
105#ifdef NDA_ENFORCE_BOUNDCHECK
106 if (i < 0 || i >= rank) {
107 std::cerr << "Error in extent: Dimension " << i << " is incompatible with array of rank " << rank << std::endl;
108 std::terminate();
109 }
110#endif
111 return lay.lengths()[i];
112}
113
115[[nodiscard]] long shape(int i) const noexcept { return extent(i); }
116
121[[nodiscard]] auto indices() const noexcept { return itertools::product_range(shape()); }
122
127static constexpr bool is_stride_order_C() noexcept { return layout_t::is_stride_order_C(); }
128
133static constexpr bool is_stride_order_Fortran() noexcept { return layout_t::is_stride_order_Fortran(); }
134
144decltype(auto) operator()(_linear_index_t idx) const noexcept {
145 if constexpr (layout_t::layout_prop == layout_prop_e::strided_1d)
146 return sto[idx.value * lay.min_stride()];
147 else if constexpr (layout_t::layout_prop == layout_prop_e::contiguous)
148 return sto[idx.value];
149 else
150 static_assert(always_false<layout_t>, "Internal error in array/view: Calling this type with a _linear_index_t is not allowed");
151}
152
154decltype(auto) operator()(_linear_index_t idx) noexcept {
155 if constexpr (layout_t::layout_prop == layout_prop_e::strided_1d)
156 return sto[idx.value * lay.min_stride()];
157 else if constexpr (layout_t::layout_prop == layout_prop_e::contiguous)
158 return sto[idx.value];
159 else
160 static_assert(always_false<layout_t>, "Internal error in array/view: Calling this type with a _linear_index_t is not allowed");
161}
162
163private:
164// Constexpr variable that is true if bounds checking is disabled.
165#ifdef NDA_ENFORCE_BOUNDCHECK
166static constexpr bool has_no_boundcheck = false;
167#else
168static constexpr bool has_no_boundcheck = true;
169#endif
170
171public:
188template <char ResultAlgebra, bool SelfIsRvalue, typename Self, typename... Ts>
189FORCEINLINE static decltype(auto) call(Self &&self, Ts const &...idxs) noexcept(has_no_boundcheck) {
190 // resulting value type
191 using r_v_t = std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, ValueType const, ValueType>;
192
193 // behavior depends on the given arguments
194 if constexpr (clef::is_any_lazy<Ts...>) {
195 // if there are lazy arguments, e.g. as in A(i_) << i_, a lazy expression is returned
196 return clef::make_expr_call(std::forward<Self>(self), idxs...);
197 } else if constexpr (sizeof...(Ts) == 0) {
198 // if no arguments are given, a full view is returned
199 return basic_array_view<r_v_t, Rank, LayoutPolicy, Algebra, AccessorPolicy, OwningPolicy>{self.lay, self.sto};
200 } else {
201 // otherwise we check the arguments and either access a single element or make a slice
202 static_assert(((layout_t::template argument_is_allowed_for_call_or_slice<Ts> + ...) > 0),
203 "Error in array/view: Slice arguments must be convertible to range, ellipsis, or long (or string if the layout permits it)");
204
205 // number of arguments convertible to long
206 static constexpr int n_args_long = (layout_t::template argument_is_allowed_for_call<Ts> + ...);
207
208 if constexpr (n_args_long == rank) {
209 // access a single element
210 long offset = self.lay(idxs...);
211 if constexpr (is_view or not SelfIsRvalue) {
212 // if the calling object is a view or an lvalue, we return a reference
213 return AccessorPolicy::template accessor<r_v_t>::access(self.sto.data(), offset);
214 } else {
215 // otherwise, we return a copy of the value
216 return ValueType{self.sto[offset]};
217 }
218 } else {
219 // access a slice of the view/array
220 auto const [offset, idxm] = self.lay.slice(idxs...);
221 static constexpr auto res_rank = decltype(idxm)::rank();
222 // resulting algebra
223 static constexpr char newAlgebra = (ResultAlgebra == 'M' and (res_rank == 1) ? 'V' : ResultAlgebra);
224 // resulting layout policy
225 using r_layout_p = typename detail::layout_to_policy<std::decay_t<decltype(idxm)>>::type;
226 return basic_array_view<r_v_t, res_rank, r_layout_p, newAlgebra, AccessorPolicy, OwningPolicy>{std::move(idxm), {self.sto, offset}};
227 }
228 }
229}
230
231public:
253template <typename... Ts>
254FORCEINLINE decltype(auto) operator()(Ts const &...idxs) const & noexcept(has_no_boundcheck) {
255 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
256 "Error in array/view: Incorrect number of parameters in call operator");
257 return call<Algebra, false>(*this, idxs...);
258}
259
261template <typename... Ts>
262FORCEINLINE decltype(auto) operator()(Ts const &...idxs) & noexcept(has_no_boundcheck) {
263 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
264 "Error in array/view: Incorrect number of parameters in call operator");
265 return call<Algebra, false>(*this, idxs...);
266}
267
269template <typename... Ts>
270FORCEINLINE decltype(auto) operator()(Ts const &...idxs) && noexcept(has_no_boundcheck) {
271 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
272 "Error in array/view: Incorrect number of parameters in call operator");
273 return call<Algebra, true>(*this, idxs...);
274}
275
293template <typename T>
294decltype(auto) operator[](T const &idx) const & noexcept(has_no_boundcheck) {
295 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
296 return call<Algebra, false>(*this, idx);
297}
298
300template <typename T>
301decltype(auto) operator[](T const &x) & noexcept(has_no_boundcheck) {
302 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
303 return call<Algebra, false>(*this, x);
304}
305
307template <typename T>
308decltype(auto) operator[](T const &x) && noexcept(has_no_boundcheck) {
309 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
310 return call<Algebra, true>(*this, x);
311}
312
314static constexpr int iterator_rank = (has_strided_1d(layout_t::layout_prop) ? 1 : Rank);
315
317using const_iterator = array_iterator<iterator_rank, ValueType const, typename AccessorPolicy::template accessor<ValueType>::pointer>;
318
320using iterator = array_iterator<iterator_rank, ValueType, typename AccessorPolicy::template accessor<ValueType>::pointer>;
321
322private:
323// Make an iterator for the view/array depending on its type.
324template <typename Iterator>
325[[nodiscard]] auto make_iterator(bool at_end) const noexcept {
326 if constexpr (iterator_rank == Rank) {
327 // multi-dimensional iterator
328 if constexpr (layout_t::is_stride_order_C()) {
329 // C-order case (array_iterator already traverses the data in C-order)
330 return Iterator{indexmap().lengths(), indexmap().strides(), sto.data(), at_end};
331 } else {
332 // general case (we need to permute the shape and the strides according to the stride order of the layout)
333 return Iterator{nda::permutations::apply(layout_t::stride_order, indexmap().lengths()),
334 nda::permutations::apply(layout_t::stride_order, indexmap().strides()), sto.data(), at_end};
335 }
336 } else {
337 // 1-dimensional iterator
338 return Iterator{std::array<long, 1>{size()}, std::array<long, 1>{indexmap().min_stride()}, sto.data(), at_end};
339 }
340}
341
342public:
344[[nodiscard]] const_iterator begin() const noexcept { return make_iterator<const_iterator>(false); }
345
347[[nodiscard]] const_iterator cbegin() const noexcept { return make_iterator<const_iterator>(false); }
348
350iterator begin() noexcept { return make_iterator<iterator>(false); }
351
353[[nodiscard]] const_iterator end() const noexcept { return make_iterator<const_iterator>(true); }
354
356[[nodiscard]] const_iterator cend() const noexcept { return make_iterator<const_iterator>(true); }
357
359iterator end() noexcept { return make_iterator<iterator>(true); }
360
373template <typename RHS>
374auto &operator+=(RHS const &rhs) noexcept {
375 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
376 return operator=(*this + rhs);
377}
378
391template <typename RHS>
392auto &operator-=(RHS const &rhs) noexcept {
393 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
394 return operator=(*this - rhs);
395}
396
409template <typename RHS>
410auto &operator*=(RHS const &rhs) noexcept {
411 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
412 return operator=((*this) * rhs);
413}
414
427template <typename RHS>
428auto &operator/=(RHS const &rhs) noexcept {
429 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
430 return operator=(*this / rhs);
431}
432
441template <std::ranges::contiguous_range R>
442auto &operator=(R const &rhs) noexcept
443 requires(Rank == 1 and not MemoryArray<R> and not is_scalar_for_v<R, self_t>)
444{
445 *this = array_const_view<std::ranges::range_value_t<R>, 1>{rhs};
446 return *this;
447}
448
449private:
450// Implementation of the assignment from an n-dimensional array type.
451template <typename RHS>
452void assign_from_ndarray(RHS const &rhs) { // FIXME noexcept {
453#ifdef NDA_ENFORCE_BOUNDCHECK
454 if (this->shape() != rhs.shape())
455 NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Size mismatch:"
456 << "\n LHS.shape() = " << this->shape() << "\n RHS.shape() = " << rhs.shape();
457#endif
458 // compile-time check if assignment is possible
459 static_assert(std::is_assignable_v<value_type &, get_value_t<RHS>>, "Error in assign_from_ndarray: Incompatible value types");
460
461 // are both operands nda::MemoryArray types?
462 static constexpr bool both_in_memory = MemoryArray<self_t> and MemoryArray<RHS>;
463
464 // do both operands have the same stride order?
465 static constexpr bool same_stride_order = get_layout_info<self_t>.stride_order == get_layout_info<RHS>.stride_order;
466
467 // prefer optimized options if possible
468 if constexpr (both_in_memory and same_stride_order) {
469 if (rhs.empty()) return;
470 // are both operands strided in 1d?
471 static constexpr bool both_1d_strided = has_layout_strided_1d<self_t> and has_layout_strided_1d<RHS>;
472 if constexpr (mem::on_host<self_t, RHS> and both_1d_strided) {
473 // vectorizable copy on host
474 for (long i = 0; i < size(); ++i) (*this)(_linear_index_t{i}) = rhs(_linear_index_t{i});
475 return;
476 } else if constexpr (!mem::on_host<self_t, RHS> and have_same_value_type_v<self_t, RHS>) {
477 // check for block-layout and use mem::memcpy2D if possible
478 auto bl_layout_dst = get_block_layout(*this);
479 auto bl_layout_src = get_block_layout(rhs);
480 if (bl_layout_dst && bl_layout_src) {
481 auto [n_bl_dst, bl_size_dst, bl_str_dst] = *bl_layout_dst;
482 auto [n_bl_src, bl_size_src, bl_str_src] = *bl_layout_src;
483 // check that the total memory size is the same
484 if (n_bl_dst * bl_size_dst != n_bl_src * bl_size_src) NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Incompatible block sizes";
485 // if either destination or source consists of a single block, we can chunk it up to make the layouts compatible
486 if (n_bl_dst == 1 && n_bl_src > 1) {
487 n_bl_dst = n_bl_src;
488 bl_size_dst /= n_bl_src;
489 bl_str_dst = bl_size_dst;
490 }
491 if (n_bl_src == 1 && n_bl_dst > 1) {
492 n_bl_src = n_bl_dst;
493 bl_size_src /= n_bl_dst;
494 bl_str_src = bl_size_src;
495 }
496 // copy only if block-layouts are compatible, otherwise continue to fallback
497 if (n_bl_dst == n_bl_src && bl_size_dst == bl_size_src) {
498 mem::memcpy2D<mem::get_addr_space<self_t>, mem::get_addr_space<RHS>>((void *)data(), bl_str_dst * sizeof(value_type), (void *)rhs.data(),
499 bl_str_src * sizeof(value_type), bl_size_src * sizeof(value_type),
500 n_bl_src);
501 return;
502 }
503 }
504 }
505 }
506 // otherwise fallback to elementwise assignment
507 if constexpr (mem::on_device<self_t> || mem::on_device<RHS>) {
508 NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Fallback to elementwise assignment not implemented for arrays/views on the GPU";
509 }
510 nda::for_each(shape(), [this, &rhs](auto const &...args) { (*this)(args...) = rhs(args...); });
511}
512
513// Implementation to fill a view/array with a constant scalar value.
514template <typename Scalar>
515void fill_with_scalar(Scalar const &scalar) noexcept {
516 // we make a special implementation if the array is strided in 1d or contiguous
517 if constexpr (has_layout_strided_1d<self_t>) {
518 const long L = size();
519 auto *__restrict const p = data(); // no alias possible here!
520 if constexpr (has_contiguous_layout<self_t>) {
521 for (long i = 0; i < L; ++i) p[i] = scalar;
522 } else {
523 const long stri = indexmap().min_stride();
524 const long Lstri = L * stri;
525 for (long i = 0; i != Lstri; i += stri) p[i] = scalar;
526 }
527 } else {
528 // no compile-time memory layout guarantees
529 for (auto &x : *this) x = scalar;
530 }
531}
532
533// Implementation of the assignment from a scalar value.
534template <typename Scalar>
535void assign_from_scalar(Scalar const &scalar) noexcept {
536 static_assert(!is_const, "Error in assign_from_ndarray: Cannot assign to a const view");
537 if constexpr (Algebra != 'M') {
538 // element-wise assignment for non-matrix algebras
539 fill_with_scalar(scalar);
540 } else {
541 // a scalar has to be interpreted as a unit matrix for matrix algebras (the scalar in the shortest diagonal)
542 // FIXME : A priori faster to put 0 everywhere and then change the diag to avoid the if.
543 // FIXME : Benchmark and confirm.
544 if constexpr (is_scalar_or_convertible_v<Scalar>)
545 fill_with_scalar(0);
546 else
547 fill_with_scalar(Scalar{0 * scalar}); // FIXME : improve this
548 const long imax = std::min(extent(0), extent(1));
549 for (long i = 0; i < imax; ++i) operator()(i, i) = scalar;
550 }
551}
constexpr bool has_strided_1d(layout_prop_e lp)
Checks if a layout property has the strided_1d property.
Definition traits.hpp:266
__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:129
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.