TRIQS/nda 1.3.0
Multi-dimensional array library for C++
Loading...
Searching...
No Matches
concepts.hpp
Go to the documentation of this file.
1// Copyright (c) 2020-2023 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: Olivier Parcollet, Nils Wentzell
16
17/**
18 * @file
19 * @brief Provides concepts for the nda library.
20 */
21
22#pragma once
23
24#include "./stdutil/concepts.hpp"
25#include "./traits.hpp"
26
27#include <array>
28#include <concepts>
29#include <type_traits>
30#include <utility>
31
32namespace nda {
33
34 /**
35 * @addtogroup utils_concepts
36 * @{
37 */
38
39 // clang has full support for "lambdas in unevaluated context" only for versions >= 17.
40#ifndef __clang__
41
42 // clang-format off
43 /**
44 * @brief Check if a given type can be called with a certain number of long arguments.
45 *
46 * @details An example of a type satisfying this concept is e.g. nda::basic_array or nda::basic_array_view. More
47 * generally, every type modelling the nda::Array concept has to be nda::CallableWithLongs as well.
48 *
49 * @tparam A Type to check.
50 * @tparam R Number of long arguments.
51 */
52 template <typename A, int R>
53 concept CallableWithLongs = requires(A const &a) {
54 // if decltype is not present, the concept will fail to compile for an A for which the a(Is...) is not well formed
55 []<auto... Is>(std::index_sequence<Is...>, auto const &aa) -> decltype(aa(long(Is)...)) {return aa(long(Is)...);}
56 (std::make_index_sequence<R>{}, a);
57 };
58 // clang-format on
59
60#else
61
62 namespace detail {
63
64 // Helper function declaration to check if A can be called with R long arguments.
65 template <auto... Is, typename A>
66 auto call_on_R_longs(std::index_sequence<Is...>, A const &a) -> decltype(a((long{Is})...)); // no impl needed
67
68 } // namespace detail
69
70 /**
71 * @brief Check if a given type can be called with a certain number of long arguments.
72 *
73 * @details An example of a type satisfying this concept is e.g. nda::basic_array or nda::basic_array_view. More
74 * generally, every type modelling the nda::Array concept has to be nda::CallableWithLongs as well.
75 *
76 * @tparam A Type to check.
77 * @tparam R Number of long arguments.
78 */
79 template <typename A, int R>
80 concept CallableWithLongs = requires(A const &a) {
81 { detail::call_on_R_longs(std::make_index_sequence<R>{}, a) };
82 };
83
84#endif // __clang__
85
86 namespace detail {
87
88 // Check if T is of type std::array<long, R> for arbitrary array sizes R.
89 template <typename T>
90 constexpr bool is_std_array_of_long_v = false;
91
92 // Specialization of nda::detail::is_std_array_of_long_v for cvref types.
93 template <typename T>
94 requires(!std::is_same_v<T, std::remove_cvref_t<T>>)
95 constexpr bool is_std_array_of_long_v<T> = is_std_array_of_long_v<std::remove_cvref_t<T>>;
96
97 // Specialization of nda::detail::is_std_array_of_long_v for std::array<long, R>.
98 template <auto R>
99 constexpr bool is_std_array_of_long_v<std::array<long, R>> = true;
100
101 } // namespace detail
102
103 /**
104 * @brief Check if a given type is of type `std::array<long, R>` for some arbitrary `R`.
105 *
106 * @details The shape and the strides of every nda::MemoryArray type is represented as a `std::array<long, R>`, where
107 * `R` is the rank of the array.
108 *
109 * @tparam T Type to check.
110 */
111 template <class T>
112 concept StdArrayOfLong = detail::is_std_array_of_long_v<T>;
113
114 /**
115 * @brief Check if a given type is either an arithmetic or complex type.
116 * @tparam S Type to check.
117 */
118 template <typename S>
119 concept Scalar = nda::is_scalar_v<S>;
120
121 /**
122 * @brief Check if a given type is either a double or complex type.
123 * @tparam S Type to check.
124 */
125 template <typename S>
126 concept DoubleOrComplex = nda::is_double_or_complex_v<S>;
127
128 /**
129 * @brief Check if a given type is an instantiation of some other template type.
130 *
131 * @details See nda::is_instantiation_of for more information.
132 *
133 * @tparam T Type to check.
134 * @tparam TMPLT Template template type to check against.
135 */
136 template <typename T, template <typename...> class TMPLT>
137 concept InstantiationOf = nda::is_instantiation_of_v<TMPLT, T>;
138
139 /** @} */
140
141 namespace mem {
142
143 /**
144 * @addtogroup mem_utils
145 * @{
146 */
147
148 /// @cond
149 // Forward declarations.
150 struct blk_t;
151 enum class AddressSpace;
152 /// @endcond
153
154 /**
155 * @brief Check if a given type satisfies the allocator concept.
156 *
157 * @details Allocators are used to reserve and free memory. Depending on their address space, the memory can either
158 * be allocated on the Host (CPU), on the Device (GPU) or on unified memory (see nda::mem::AddressSpace).
159 *
160 * @note The named <a href="https://en.cppreference.com/w/cpp/named_req/Allocator">C++ Allocator
161 * requirements</a> of the standard library are different.
162 *
163 * @tparam A Type to check.
164 */
165 template <typename A>
166 concept Allocator = requires(A &a) {
167 { a.allocate(size_t{}) } noexcept -> std::same_as<blk_t>;
168 { a.allocate_zero(size_t{}) } noexcept -> std::same_as<blk_t>;
169 { a.deallocate(std::declval<blk_t>()) } noexcept;
170 { A::address_space } -> std::same_as<AddressSpace const &>;
171 };
172
173 /**
174 * @brief Check if a given type satisfies the memory handle concept.
175 *
176 * @details Memory handles are used to manage memory. They are responsible for providing access to the data which
177 * can be located on the stack (CPU), the heap (CPU), the device (GPU) or on unified memory (see
178 * nda::mem::AddressSpace).
179 *
180 * @tparam H Type to check.
181 * @tparam T Value type of the handle.
182 */
183 template <typename H, typename T = typename std::remove_cvref_t<H>::value_type>
184 concept Handle = requires(H const &h) {
185 requires std::is_same_v<typename std::remove_cvref_t<H>::value_type, T>;
186 { h.is_null() } noexcept -> std::same_as<bool>;
187 { h.data() } noexcept -> std::same_as<T *>;
188 { H::address_space } -> std::same_as<AddressSpace const &>;
189 };
190
191 /**
192 * @brief Check if a given type satisfies the owning memory handle concept.
193 *
194 * @details In addition to the requirements of the nda::mem::Handle concept, owning memory handles are aware of the
195 * size of the memory they manage and can be used to release the memory.
196 *
197 * @tparam H Type to check.
198 * @tparam T Value type of the handle.
199 */
200 template <typename H, typename T = typename std::remove_cvref_t<H>::value_type>
201 concept OwningHandle = Handle<H, T> and requires(H const &h) {
202 requires not std::is_const_v<typename std::remove_cvref_t<H>::value_type>;
203 { h.size() } noexcept -> std::same_as<long>;
204 };
205
206 /** @} */
207
208 } // namespace mem
209
210 /**
211 * @addtogroup av_utils
212 * @{
213 */
214
215 /**
216 * @brief Check if a given type satisfies the array concept.
217 *
218 * @details An array is a multi-dimensional container of elements. It is characterized by a certain size (number of
219 * elements), rank (number of dimensions) and shape (extent of each dimension). Furthermore, it provides access to its
220 * elements by overloading the function call operator.
221 *
222 * Examples of types satisfying this concept are e.g. nda::basic_array or nda::basic_array_view.
223 *
224 * @note std::array does not satisfy this concept, as it does not have a shape or provides access via the function
225 * call operator.
226 *
227 * @tparam A Type to check.
228 */
229 template <typename A>
230 concept Array = requires(A const &a) {
231 { a.shape() } -> StdArrayOfLong;
232 { a.size() } -> std::same_as<long>;
233 requires CallableWithLongs<A, get_rank<A>>;
234 };
235
236 /**
237 * @brief Check if a given type satisfies the memory array concept.
238 *
239 * @details In addition to the requirements of the nda::Array concept, memory arrays
240 * provide access to the underlying memory storage and use an nda::idx_map to specify the
241 * layout of the data in memory.
242 *
243 * An example of a type satisfying the nda::Array but not the nda::MemoryArray concept is nda::expr.
244 *
245 * @tparam A Type to check.
246 */
247 template <typename A, typename A_t = std::remove_cvref_t<A>>
248 concept MemoryArray = Array<A> && requires(A &a) {
249 typename A_t::storage_t;
250 requires mem::Handle<typename A_t::storage_t>;
251 typename A_t::value_type;
252 {
253 a.data()
254 } -> std::same_as<std::conditional_t<std::is_const_v<std::remove_reference_t<A>> || std::is_const_v<typename A_t::value_type>,
255 const get_value_t<A>, get_value_t<A>> *>;
256 { a.indexmap().strides() } -> StdArrayOfLong;
257 };
258
259 /**
260 * @brief Check if a given type is an nda::Array of a certain rank.
261 *
262 * @tparam A Type to check.
263 * @tparam R Rank of the nda::Array type.
264 */
265 template <typename A, int R>
266 concept ArrayOfRank = Array<A> and (get_rank<A> == R);
267
268 /**
269 * @brief Check if a given type is an nda::MemoryArray of a certain rank.
270 *
271 * @tparam A Type to check.
272 * @tparam R Rank of the nda::MemoryArray type.
273 */
274 template <typename A, int R>
275 concept MemoryArrayOfRank = MemoryArray<A> and (get_rank<A> == R);
276
277 /**
278 * @brief Check if if a given type is either an nda::Array or an nda::Scalar.
279 * @tparam AS Type to check.
280 */
281 template <typename AS>
282 concept ArrayOrScalar = Array<AS> or Scalar<AS>;
283
284 /**
285 * @brief Check if a given type is a matrix, i.e. an nda::ArrayOfRank<2>.
286 * @note The algebra of the type is not checked here (see nda::get_algebra).
287 * @tparam M Type to check.
288 */
289 template <typename M>
290 concept Matrix = ArrayOfRank<M, 2>;
291
292 /**
293 * @brief Check if a given type is a vector, i.e. an nda::ArrayOfRank<1>.
294 * @note The algebra of the type is not checked here (see nda::get_algebra).
295 * @tparam V Type to check.
296 */
297 template <typename V>
298 concept Vector = ArrayOfRank<V, 1>;
299
300 /**
301 * @brief Check if a given type is a memory matrix, i.e. an nda::MemoryArrayOfRank<2>.
302 * @note The algebra of the type is not checked here (see nda::get_algebra).
303 * @tparam M Type to check.
304 */
305 template <typename M>
306 concept MemoryMatrix = MemoryArrayOfRank<M, 2>;
307
308 /**
309 * @brief Check if a given type is a memory vector, i.e. an nda::MemoryArrayOfRank<1>.
310 * @note The algebra of the type is not checked here (see nda::get_algebra).
311 * @tparam V Type to check.
312 */
313 template <typename V>
314 concept MemoryVector = MemoryArrayOfRank<V, 1>;
315
316 /**
317 * @brief Check if a given type satisfies the array initializer concept for a given nda::MemoryArray type.
318 *
319 * @details They are mostly used in lazy mpi calls (see e.g. nda::mpi_reduce).
320 *
321 * @tparam A Type to check.
322 * @tparam B nda::MemoryArray type.
323 */
324 template <typename A, typename B>
325 concept ArrayInitializer = requires(A const &a) {
326 { a.shape() } -> StdArrayOfLong;
327 typename std::remove_cvref_t<A>::value_type;
328 requires MemoryArray<B> && requires(B &b) { a.invoke(b); }; // FIXME not perfect: it should accept any layout ??
329 };
330
331 // FIXME : We should not need this ... Only used once...
332 /**
333 * @brief Check if a given type is constructible from the value type of a given nda::Array type.
334 *
335 * @tparam A nda::Array type.
336 * @tparam U Type to check.
337 */
338 template <typename A, typename U>
339 concept HasValueTypeConstructibleFrom = Array<A> and (std::is_constructible_v<U, get_value_t<A>>);
340
341 /** @} */
342
343} // namespace nda
#define CUBLAS_CHECK(X,...)
#define NDA_RUNTIME_ERROR
int get_ld(A const &a)
Get the leading dimension in LAPACK jargon of an nda::MemoryMatrix.
Definition tools.hpp:109
static constexpr bool has_C_layout
Constexpr variable that is true if the given nda::Array type has a C memory layout.
Definition tools.hpp:76
static constexpr bool is_conj_array_expr
Constexpr variable that is true if the given type is a conjugate lazy expression.
Definition tools.hpp:52
int get_ncols(A const &a)
Get the number of columns in LAPACK jargon of an nda::MemoryMatrix.
Definition tools.hpp:121
static constexpr bool is_conj_array_expr< expr_call< conj_f, A > >
Specialization of nda::blas::is_conj_array_expr for the conjugate lazy expressions.
Definition tools.hpp:56
static constexpr bool has_F_layout
Constexpr variable that is true if the given nda::Array type has a Fortran memory layout.
Definition tools.hpp:66
const char get_op
Variable template that determines the BLAS matrix operation tag ('N','T','C') based on the given bool...
Definition tools.hpp:91
AddressSpace
Enum providing identifiers for the different memory address spaces.
#define AS_STRING(...)
Definition macros.hpp:31
Memory block consisting of a pointer and its size.