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
17/**
18 * @brief Get the memory layout of the view/array.
19 * @return nda::idx_map specifying the layout of the view/array.
20 */
21[[nodiscard]] constexpr auto const &indexmap() const noexcept { return lay; }
22
23/**
24 * @brief Get the data storage of the view/array.
25 * @return A const reference to the memory handle of the view/array.
26 */
27[[nodiscard]] storage_t const &storage() const & noexcept { return sto; }
28
29/**
30 * @brief Get the data storage of the view/array.
31 * @return A reference to the memory handle of the view/array.
32 */
33[[nodiscard]] storage_t &storage() & noexcept { return sto; }
34
35/**
36 * @brief Get the data storage of the view/array.
37 * @return A copy of the memory handle of the view/array.
38 */
39[[nodiscard]] storage_t storage() && noexcept { return std::move(sto); }
40
41/**
42 * @brief Get the stride order of the memory layout of the view/array (see nda::idx_map for more details on how we
43 * define stride orders).
44 *
45 * @return `std::array<int, Rank>` object specifying the stride order.
46 */
47[[nodiscard]] constexpr auto stride_order() const noexcept { return lay.stride_order; }
48
49/**
50 * @brief Get a pointer to the actual data (in general this is not the beginning of the memory block for a view).
51 * @return Const pointer to the first element of the view/array.
52 */
53[[nodiscard]] ValueType const *data() const noexcept { return sto.data(); }
54
55/**
56 * @brief Get a pointer to the actual data (in general this is not the beginning of thr memory block for a view).
57 * @return Pointer to the first element of the view/array.
58 */
59[[nodiscard]] ValueType *data() noexcept { return sto.data(); }
60
61/**
62 * @brief Get the shape of the view/array.
63 * @return `std::array<long, Rank>` object specifying the shape of the view/array.
64 */
65[[nodiscard]] auto const &shape() const noexcept { return lay.lengths(); }
66
67/**
68 * @brief Get the strides of the view/array (see nda::idx_map for more details on how we define strides).
69 * @return `std::array<long, Rank>` object specifying the strides of the view/array.
70 */
71[[nodiscard]] auto const &strides() const noexcept { return lay.strides(); }
72
73/**
74 * @brief Get the total size of the view/array.
75 * @return Number of elements contained in the view/array.
76 */
77[[nodiscard]] long size() const noexcept { return lay.size(); }
78
79/**
80 * @brief Is the memory layout of the view/array contiguous?
81 * @return True if the nda::idx_map is contiguous, false otherwise.
82 */
83[[nodiscard]] long is_contiguous() const noexcept { return lay.is_contiguous(); }
84
85/**
86 * @brief Is the view/array empty?
87 * @return True if the view/array does not contain any elements.
88 */
89[[nodiscard]] bool empty() const { return sto.is_null(); }
90
91/// @deprecated Use empty() instead.
92[[nodiscard]] bool is_empty() const noexcept { return sto.is_null(); }
93
94/**
95 * @brief Get the extent of the i<sup>th</sup> dimension.
96 * @return Number of elements along the i<sup>th</sup> dimension.
97 */
98[[nodiscard]] long extent(int i) const noexcept {
99#ifdef NDA_ENFORCE_BOUNDCHECK
100 if (i < 0 || i >= rank) {
101 std::cerr << "Error in extent: Dimension " << i << " is incompatible with array of rank " << rank << std::endl;
102 std::terminate();
103 }
104#endif
105 return lay.lengths()[i];
106}
107
108/// @deprecated Use `extent(i)` or `shape()[i]` instead.
109[[nodiscard]] long shape(int i) const noexcept { return extent(i); }
110
111/**
112 * @brief Get a range that generates all valid index tuples.
113 * @return An `itertools::multiplied` range that can be used to iterate over all valid index tuples.
114 */
115[[nodiscard]] auto indices() const noexcept { return itertools::product_range(shape()); }
116
117/**
118 * @brief Is the stride order of the view/array in C-order?
119 * @return True if the stride order of the nda::idx_map is C-order, false otherwise.
120 */
121static constexpr bool is_stride_order_C() noexcept { return layout_t::is_stride_order_C(); }
122
123/**
124 * @brief Is the stride order of the view/array in Fortran-order?
125 * @return True if the stride order of the nda::idx_map is Fortran-order, false otherwise.
126 */
127static constexpr bool is_stride_order_Fortran() noexcept { return layout_t::is_stride_order_Fortran(); }
128
129/**
130 * @brief Access the element of the view/array at the given nda::_linear_index_t.
131 *
132 * @details The linear index specifies the position of the element in the view/array and not the position of the
133 * element w.r.t. to the data pointer (i.e. any possible strides should not be taken into account).
134 *
135 * @param idx nda::_linear_index_t object.
136 * @return Const reference to the element at the given linear index.
137 */
138decltype(auto) operator()(_linear_index_t idx) const noexcept {
139 if constexpr (layout_t::layout_prop == layout_prop_e::strided_1d)
140 return sto[idx.value * lay.min_stride()];
141 else if constexpr (layout_t::layout_prop == layout_prop_e::contiguous)
142 return sto[idx.value];
143 else
144 static_assert(always_false<layout_t>, "Internal error in array/view: Calling this type with a _linear_index_t is not allowed");
145}
146
147/// Non-const overload of @ref nda::basic_array_view::operator()(_linear_index_t) const.
148decltype(auto) operator()(_linear_index_t idx) noexcept {
149 if constexpr (layout_t::layout_prop == layout_prop_e::strided_1d)
150 return sto[idx.value * lay.min_stride()];
151 else if constexpr (layout_t::layout_prop == layout_prop_e::contiguous)
152 return sto[idx.value];
153 else
154 static_assert(always_false<layout_t>, "Internal error in array/view: Calling this type with a _linear_index_t is not allowed");
155}
156
157private:
158// Constexpr variable that is true if bounds checking is disabled.
159#ifdef NDA_ENFORCE_BOUNDCHECK
160static constexpr bool has_no_boundcheck = false;
161#else
162static constexpr bool has_no_boundcheck = true;
163#endif
164
165public:
166/**
167 * @brief Implementation of the function call operator.
168 *
169 * @details This function is an implementation detail an should be private. Since the Green's function library in
170 * TRIQS uses this function, it is kept public (for now).
171 *
172 * @tparam ResultAlgebra Algebra of the resulting view/array.
173 * @tparam SelfIsRvalue True if the view/array is an rvalue.
174 * @tparam Self Type of the calling view/array.
175 * @tparam T Types of the arguments.
176 *
177 * @param self Calling view.
178 * @param idxs Multi-dimensional index consisting of `long`, `nda::range`, `nda::range::all_t`, nda::ellipsis or lazy
179 * arguments.
180 * @return Result of the function call depending on the given arguments and type of the view/array.
181 */
182template <char ResultAlgebra, bool SelfIsRvalue, typename Self, typename... Ts>
183FORCEINLINE static decltype(auto) call(Self &&self, Ts const &...idxs) noexcept(has_no_boundcheck) {
184 // resulting value type
185 using r_v_t = std::conditional_t<std::is_const_v<std::remove_reference_t<Self>>, ValueType const, ValueType>;
186
187 // behavior depends on the given arguments
188 if constexpr (clef::is_any_lazy<Ts...>) {
189 // if there are lazy arguments, e.g. as in A(i_) << i_, a lazy expression is returned
190 return clef::make_expr_call(std::forward<Self>(self), idxs...);
191 } else if constexpr (sizeof...(Ts) == 0) {
192 // if no arguments are given, a full view is returned
193 return basic_array_view<r_v_t, Rank, LayoutPolicy, Algebra, AccessorPolicy, OwningPolicy>{self.lay, self.sto};
194 } else {
195 // otherwise we check the arguments and either access a single element or make a slice
196 static_assert(((layout_t::template argument_is_allowed_for_call_or_slice<Ts> + ...) > 0),
197 "Error in array/view: Slice arguments must be convertible to range, ellipsis, or long (or string if the layout permits it)");
198
199 // number of arguments convertible to long
200 static constexpr int n_args_long = (layout_t::template argument_is_allowed_for_call<Ts> + ...);
201
202 if constexpr (n_args_long == rank) {
203 // access a single element
204 long offset = self.lay(idxs...);
205 if constexpr (is_view or not SelfIsRvalue) {
206 // if the calling object is a view or an lvalue, we return a reference
207 return AccessorPolicy::template accessor<ValueType>::access(self.sto.data(), offset);
208 } else {
209 // otherwise, we return a copy of the value
210 return ValueType{self.sto[offset]};
211 }
212 } else {
213 // access a slice of the view/array
214 auto const [offset, idxm] = self.lay.slice(idxs...);
215 static constexpr auto res_rank = decltype(idxm)::rank();
216 // resulting algebra
217 static constexpr char newAlgebra = (ResultAlgebra == 'M' and (res_rank == 1) ? 'V' : ResultAlgebra);
218 // resulting layout policy
219 using r_layout_p = typename detail::layout_to_policy<std::decay_t<decltype(idxm)>>::type;
220 return basic_array_view<ValueType, res_rank, r_layout_p, newAlgebra, AccessorPolicy, OwningPolicy>{std::move(idxm), {self.sto, offset}};
221 }
222 }
223}
224
225public:
226/**
227 * @brief Function call operator to access the view/array.
228 *
229 * @details Depending on the type of the calling object and the given arguments, this function call does the following:
230 * - If any of the arguments is lazy, an nda::clef::expr with the nda::clef::tags::function tag is returned.
231 * - If no arguments are given, a full view of the calling object is returned:
232 * - If the calling object itself or its value type is const, a view with a const value type is returned.
233 * - Otherwise, a view with a non-const value type is returned.
234 * - If the number of arguments is equal to the rank of the calling object and all arguments are convertible to `long`,
235 * a single element is accessed:
236 * - If the calling object is a view or an lvalue, a (const) reference to the element is returned.
237 * - Otherwise, a copy of the element is returned.
238 * - Otherwise a slice of the calling object is returned with the same value type and accessor and owning policies as
239 * the calling object. The algebra of the slice is the same as well, except if a 1-dimensional slice of a matrix is
240 * taken. In this case, the algebra is changed to 'V'.
241 *
242 * @tparam Ts Types of the function arguments.
243 * @param idxs Multi-dimensional index consisting of `long`, `nda::range`, `nda::range::all_t`, nda::ellipsis or lazy
244 * arguments.
245 * @return Result of the function call depending on the given arguments and type of the view/array.
246 */
247template <typename... Ts>
248FORCEINLINE decltype(auto) operator()(Ts const &...idxs) const & noexcept(has_no_boundcheck) {
249 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
250 "Error in array/view: Incorrect number of parameters in call operator");
251 return call<Algebra, false>(*this, idxs...);
252}
253
254/// Non-const overload of `nda::basic_array_view::operator()(Ts const &...) const &`.
255template <typename... Ts>
256FORCEINLINE decltype(auto) operator()(Ts const &...idxs) & noexcept(has_no_boundcheck) {
257 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
258 "Error in array/view: Incorrect number of parameters in call operator");
259 return call<Algebra, false>(*this, idxs...);
260}
261
262/// Rvalue overload of `nda::basic_array_view::operator()(Ts const &...) const &`.
263template <typename... Ts>
264FORCEINLINE decltype(auto) operator()(Ts const &...idxs) && noexcept(has_no_boundcheck) {
265 static_assert((rank == -1) or (sizeof...(Ts) == rank) or (sizeof...(Ts) == 0) or (ellipsis_is_present<Ts...> and (sizeof...(Ts) <= rank + 1)),
266 "Error in array/view: Incorrect number of parameters in call operator");
267 return call<Algebra, true>(*this, idxs...);
268}
269
270/**
271 * @brief Subscript operator to access the 1-dimensional view/array.
272 *
273 * @details Depending on the type of the calling object and the given argument, this subscript operation does the
274 * following:
275 * - If the argument is lazy, an nda::clef::expr with the nda::clef::tags::function tag is returned.
276 * - If the argument is convertible to `long`, a single element is accessed:
277 * - If the calling object is a view or an lvalue, a (const) reference to the element is returned.
278 * - Otherwise, a copy of the element is returned.
279 * - Otherwise a slice of the calling object is returned with the same value type, algebra and accessor and owning
280 * policies as the calling object.
281 *
282 * @tparam T Type of the argument.
283 * @param idx 1-dimensional index that is either a `long`, `nda::range`, `nda::range::all_t`, nda::ellipsis or a lazy
284 * argument.
285 * @return Result of the subscript operation depending on the given argument and type of the view/array.
286 */
287template <typename T>
288decltype(auto) operator[](T const &idx) const & noexcept(has_no_boundcheck) {
289 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
290 return call<Algebra, false>(*this, idx);
291}
292
293/// Non-const overload of `nda::basic_array_view::operator[](T const &) const &`.
294template <typename T>
295decltype(auto) operator[](T const &x) & noexcept(has_no_boundcheck) {
296 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
297 return call<Algebra, false>(*this, x);
298}
299
300/// Rvalue overload of `nda::basic_array_view::operator[](T const &) const &`.
301template <typename T>
302decltype(auto) operator[](T const &x) && noexcept(has_no_boundcheck) {
303 static_assert((rank == 1), "Error in array/view: Subscript operator is only available for rank 1 views/arrays in C++17/20");
304 return call<Algebra, true>(*this, x);
305}
306
307/// Rank of the nda::array_iterator for the view/array.
308static constexpr int iterator_rank = (has_strided_1d(layout_t::layout_prop) ? 1 : Rank);
309
310/// Const iterator type of the view/array.
311using const_iterator = array_iterator<iterator_rank, ValueType const, typename AccessorPolicy::template accessor<ValueType>::pointer>;
312
313/// Iterator type of the view/array.
314using iterator = array_iterator<iterator_rank, ValueType, typename AccessorPolicy::template accessor<ValueType>::pointer>;
315
316private:
317// Make an iterator for the view/array depending on its type.
318template <typename Iterator>
319[[nodiscard]] auto make_iterator(bool at_end) const noexcept {
320 if constexpr (iterator_rank == Rank) {
321 // multi-dimensional iterator
322 if constexpr (layout_t::is_stride_order_C()) {
323 // C-order case (array_iterator already traverses the data in C-order)
324 return Iterator{indexmap().lengths(), indexmap().strides(), sto.data(), at_end};
325 } else {
326 // general case (we need to permute the shape and the strides according to the stride order of the layout)
327 return Iterator{nda::permutations::apply(layout_t::stride_order, indexmap().lengths()),
328 nda::permutations::apply(layout_t::stride_order, indexmap().strides()), sto.data(), at_end};
329 }
330 } else {
331 // 1-dimensional iterator
332 return Iterator{std::array<long, 1>{size()}, std::array<long, 1>{indexmap().min_stride()}, sto.data(), at_end};
333 }
334}
335
336public:
337/// Get a const iterator to the beginning of the view/array.
338[[nodiscard]] const_iterator begin() const noexcept { return make_iterator<const_iterator>(false); }
339
340/// Get a const iterator to the beginning of the view/array.
341[[nodiscard]] const_iterator cbegin() const noexcept { return make_iterator<const_iterator>(false); }
342
343/// Get an iterator to the beginning of the view/array.
344iterator begin() noexcept { return make_iterator<iterator>(false); }
345
346/// Get a const iterator to the end of the view/array.
347[[nodiscard]] const_iterator end() const noexcept { return make_iterator<const_iterator>(true); }
348
349/// Get a const iterator to the end of the view/array.
350[[nodiscard]] const_iterator cend() const noexcept { return make_iterator<const_iterator>(true); }
351
352/// Get an iterator to the end of the view/array.
353iterator end() noexcept { return make_iterator<iterator>(true); }
354
355/**
356 * @brief Addition assignment operator.
357 *
358 * @details It first performs the (lazy) addition with the right hand side operand and then assigns the result to the
359 * left hand side view/array.
360 *
361 * See nda::operator+(L &&, R &&) and nda::operator+(A &&, S &&) for more details.
362 *
363 * @tparam RHS nda::Scalar or nda::Array type.
364 * @param rhs Right hand side operand of the addition assignment operation.
365 * @return Reference to this object.
366 */
367template <typename RHS>
368auto &operator+=(RHS const &rhs) noexcept {
369 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
370 return operator=(*this + rhs);
371}
372
373/**
374 * @brief Subtraction assignment operator.
375 *
376 * @details It first performs the (lazy) subtraction with the right hand side operand and then assigns the result to
377 * the left hand side view/array.
378 *
379 * See nda::operator-(L &&, R &&) and nda::operator-(A &&, S &&) for more details.
380 *
381 * @tparam RHS nda::Scalar or nda::Array type.
382 * @param rhs Right hand side operand of the subtraction assignment operation.
383 * @return Reference to this object.
384 */
385template <typename RHS>
386auto &operator-=(RHS const &rhs) noexcept {
387 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
388 return operator=(*this - rhs);
389}
390
391/**
392 * @brief Multiplication assignment operator.
393 *
394 * @details It first performs the (lazy) multiplication with the right hand side operand and then assigns the result
395 * to the left hand side view/array.
396 *
397 * See nda::operator*(L &&, R &&) and nda::operator*(A &&, S &&) for more details.
398 *
399 * @tparam RHS nda::Scalar or nda::Array type.
400 * @param rhs Right hand side operand of the multiplication assignment operation.
401 * @return Reference to this object.
402 */
403template <typename RHS>
404auto &operator*=(RHS const &rhs) noexcept {
405 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
406 return operator=((*this) * rhs);
407}
408
409/**
410 * @brief Division assignment operator.
411 *
412 * @details It first performs the (lazy) division with the right hand side operand and then assigns the result to the
413 * left hand side view/array.
414 *
415 * See nda::operator/(L &&, R &&) and nda::operator/(A &&, S &&) for more details.
416 *
417 * @tparam RHS nda::Scalar or nda::Array type.
418 * @param rhs Right hand side operand of the division assignment operation.
419 * @return Reference to this object.
420 */
421template <typename RHS>
422auto &operator/=(RHS const &rhs) noexcept {
423 static_assert(not is_const, "Error in array/view: Can not assign to a const view");
424 return operator=(*this / rhs);
425}
426
427/**
428 * @brief Assignment operator makes a deep copy of a general contiguous range and assigns it to the 1-dimensional
429 * view/array.
430 *
431 * @tparam R Range type.
432 * @param rhs Right hand side range object.
433 * @return Reference to this object.
434 */
435template <std::ranges::contiguous_range R>
436auto &operator=(R const &rhs) noexcept
437 requires(Rank == 1 and not MemoryArray<R>)
438{
439 *this = basic_array_view{rhs};
440 return *this;
441}
442
443private:
444// Implementation of the assignment from an n-dimensional array type.
445template <typename RHS>
446void assign_from_ndarray(RHS const &rhs) { // FIXME noexcept {
447#ifdef NDA_ENFORCE_BOUNDCHECK
448 if (this->shape() != rhs.shape())
449 NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Size mismatch:"
450 << "\n LHS.shape() = " << this->shape() << "\n RHS.shape() = " << rhs.shape();
451#endif
452 // compile-time check if assignment is possible
453 static_assert(std::is_assignable_v<value_type &, get_value_t<RHS>>, "Error in assign_from_ndarray: Incompatible value types");
454
455 // are both operands nda::MemoryArray types?
456 static constexpr bool both_in_memory = MemoryArray<self_t> and MemoryArray<RHS>;
457
458 // do both operands have the same stride order?
459 static constexpr bool same_stride_order = get_layout_info<self_t>.stride_order == get_layout_info<RHS>.stride_order;
460
461 // prefer optimized options if possible
462 if constexpr (both_in_memory and same_stride_order) {
463 if (rhs.empty()) return;
464 // are both operands strided in 1d?
465 static constexpr bool both_1d_strided = has_layout_strided_1d<self_t> and has_layout_strided_1d<RHS>;
466 if constexpr (mem::on_host<self_t, RHS> and both_1d_strided) {
467 // vectorizable copy on host
468 for (long i = 0; i < size(); ++i) (*this)(_linear_index_t{i}) = rhs(_linear_index_t{i});
469 return;
470 } else if constexpr (!mem::on_host<self_t, RHS> and have_same_value_type_v<self_t, RHS>) {
471 // check for block-layout and use mem::memcpy2D if possible
472 auto bl_layout_dst = get_block_layout(*this);
473 auto bl_layout_src = get_block_layout(rhs);
474 if (bl_layout_dst && bl_layout_src) {
475 auto [n_bl_dst, bl_size_dst, bl_str_dst] = *bl_layout_dst;
476 auto [n_bl_src, bl_size_src, bl_str_src] = *bl_layout_src;
477 // check that the total memory size is the same
478 if (n_bl_dst * bl_size_dst != n_bl_src * bl_size_src) NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Incompatible block sizes";
479 // if either destination or source consists of a single block, we can chunk it up to make the layouts compatible
480 if (n_bl_dst == 1 && n_bl_src > 1) {
481 n_bl_dst = n_bl_src;
482 bl_size_dst /= n_bl_src;
483 bl_str_dst = bl_size_dst;
484 }
485 if (n_bl_src == 1 && n_bl_dst > 1) {
486 n_bl_src = n_bl_dst;
487 bl_size_src /= n_bl_dst;
488 bl_str_src = bl_size_src;
489 }
490 // copy only if block-layouts are compatible, otherwise continue to fallback
491 if (n_bl_dst == n_bl_src && bl_size_dst == bl_size_src) {
492 mem::memcpy2D<mem::get_addr_space<self_t>, mem::get_addr_space<RHS>>((void *)data(), bl_str_dst * sizeof(value_type), (void *)rhs.data(),
493 bl_str_src * sizeof(value_type), bl_size_src * sizeof(value_type),
494 n_bl_src);
495 return;
496 }
497 }
498 }
499 }
500 // otherwise fallback to elementwise assignment
501 if constexpr (mem::on_device<self_t> || mem::on_device<RHS>) {
502 NDA_RUNTIME_ERROR << "Error in assign_from_ndarray: Fallback to elementwise assignment not implemented for arrays/views on the GPU";
503 }
504 nda::for_each(shape(), [this, &rhs](auto const &...args) { (*this)(args...) = rhs(args...); });
505}
506
507// Implementation to fill a view/array with a constant scalar value.
508template <typename Scalar>
509void fill_with_scalar(Scalar const &scalar) noexcept {
510 // we make a special implementation if the array is strided in 1d or contiguous
511 if constexpr (has_layout_strided_1d<self_t>) {
512 const long L = size();
513 auto *__restrict const p = data(); // no alias possible here!
514 if constexpr (has_contiguous_layout<self_t>) {
515 for (long i = 0; i < L; ++i) p[i] = scalar;
516 } else {
517 const long stri = indexmap().min_stride();
518 const long Lstri = L * stri;
519 for (long i = 0; i < Lstri; i += stri) p[i] = scalar;
520 }
521 } else {
522 // no compile-time memory layout guarantees
523 for (auto &x : *this) x = scalar;
524 }
525}
526
527// Implementation of the assignment from a scalar value.
528template <typename Scalar>
529void assign_from_scalar(Scalar const &scalar) noexcept {
530 static_assert(!is_const, "Error in assign_from_ndarray: Cannot assign to a const view");
531 if constexpr (Algebra != 'M') {
532 // element-wise assignment for non-matrix algebras
533 fill_with_scalar(scalar);
534 } else {
535 // a scalar has to be interpreted as a unit matrix for matrix algebras (the scalar in the shortest diagonal)
536 // FIXME : A priori faster to put 0 everywhere and then change the diag to avoid the if.
537 // FIXME : Benchmark and confirm.
538 if constexpr (is_scalar_or_convertible_v<Scalar>)
539 fill_with_scalar(0);
540 else
541 fill_with_scalar(Scalar{0 * scalar}); // FIXME : improve this
542 const long imax = std::min(extent(0), extent(1));
543 for (long i = 0; i < imax; ++i) operator()(i, i) = scalar;
544 }
545}