ampsci
High-precision calculations for one- and two-valence atomic systems
Array.hpp
1#pragma once
2#include "Template.hpp"
3#include <array>
4#include <cassert>
5#include <numeric>
6#include <vector>
7
8/*!
9 @brief General-purpose utility library.
10
11 @details
12 Provides lightweight, header-only utilities used throughout ampsci, including:
13 - array and vector operations (@ref Array.hpp, @ref Vector.hpp),
14 - numerical methods (@ref Maths.hpp, @ref Methods.hpp),
15 - string helpers (@ref String.hpp),
16 - strong typing (@ref StrongType.hpp),
17 - template metaprogramming helpers (@ref Template.hpp),
18 - random number utilities (@ref Random.hpp),
19 - OpenMP helpers (@ref omp.hpp),
20 - include this instead of `<omp.h>` to allow compilation with+without OpenMP
21 - output/display widgets (@ref Widgets.hpp).
22*/
23namespace qip {
24
25//==============================================================================
26/*!
27 @brief Iterator with a configurable stride.
28
29 @details
30 Not fully compliant with the standard forward iterator concept,
31 but works with most standard library algorithms.
32 Reverse iterators can be formed with a negative stride.
33 See also @ref ConstStrideIterator.
34*/
35template <typename T>
36class StrideIterator : public qip::Comparison<StrideIterator<T>> {
37protected:
38 T *m_ptr;
39 long m_stride;
40
41public:
42 StrideIterator(T *ptr, long stride) : m_ptr(ptr), m_stride(stride) {}
43
44 T &operator*() { return *m_ptr; }
45
46 const T &operator*() const { return *m_ptr; }
47
48 bool operator==(const StrideIterator &other) const {
49 return m_ptr == other.m_ptr;
50 }
51 bool operator<(const StrideIterator &other) const {
52 return m_ptr < other.m_ptr;
53 }
54
55 StrideIterator &operator++() {
56 m_ptr += m_stride;
57 return *this;
58 }
59 StrideIterator &operator--() {
60 m_ptr -= m_stride;
61 return *this;
62 }
63 StrideIterator operator++(int) {
64 auto temp = *this;
65 ++*this;
66 return temp;
67 }
68 StrideIterator operator--(int) {
69 auto temp = *this;
70 --*this;
71 return temp;
72 }
73
74 StrideIterator &operator+=(long n) {
75 m_ptr += n * m_stride;
76 return *this;
77 }
78
79 StrideIterator &operator-=(long n) {
80 m_ptr -= n * m_stride;
81 return *this;
82 }
83
84 StrideIterator operator+(long n) const {
85 auto out_iter = *this;
86 return out_iter += n;
87 }
88
89 StrideIterator operator-(long n) const {
90 auto out_iter = *this;
91 return out_iter -= n;
92 }
93};
94
95//! Const version of @ref StrideIterator.
96template <typename T>
97class ConstStrideIterator : public qip::Comparison<ConstStrideIterator<T>> {
98protected:
99 T *m_ptr;
100 long m_stride;
101
102public:
103 ConstStrideIterator(T *ptr, long stride) : m_ptr(ptr), m_stride(stride) {}
104
105 const T &operator*() const { return *m_ptr; }
106
107 bool operator==(const ConstStrideIterator &other) const {
108 return m_ptr == other.m_ptr;
109 }
110 bool operator<(const ConstStrideIterator &other) const {
111 return m_ptr < other.m_ptr;
112 }
113
114 ConstStrideIterator &operator++() {
115 m_ptr += m_stride;
116 return *this;
117 }
118 ConstStrideIterator &operator--() {
119 m_ptr -= m_stride;
120 return *this;
121 }
122 ConstStrideIterator operator++(int) {
123 auto temp = *this;
124 ++*this;
125 return temp;
126 }
127 ConstStrideIterator operator--(int) {
128 auto temp = *this;
129 --*this;
130 return temp;
131 }
132
133 ConstStrideIterator &operator+=(long n) {
134 m_ptr += n * m_stride;
135 return *this;
136 }
137
138 ConstStrideIterator &operator-=(long n) {
139 m_ptr -= n * m_stride;
140 return *this;
141 }
142
143 ConstStrideIterator operator+(long n) const {
144 auto out_iter = *this;
145 return out_iter += n;
146 }
147
148 ConstStrideIterator operator-(long n) const {
149 auto out_iter = *this;
150 return out_iter -= n;
151 }
152};
153
154//==============================================================================
155/*!
156 @brief Non-owning view onto a 1D contiguous or strided array segment.
157
158 @details
159 - Size is the total number of elements.
160 - Can have zero size, but accessing data is undefined in that case.
161 - Interface mostly mirrors std::vector; no surprises.
162*/
163template <typename T = double>
165
166private:
167 std::size_t m_size; // number of elements
168 std::size_t m_stride;
169 T *m_data;
170
171public:
172 ArrayView(T *data, std::size_t size, std::size_t stride = 1)
173 : m_size(size), m_stride(stride), m_data(data) {}
174
175 std::size_t size() const { return m_size; }
176
177 T &operator[](std::size_t i) { return m_data[i * m_stride]; }
178
179 T operator[](std::size_t i) const { return m_data[i * m_stride]; }
180
181 T &at(std::size_t i) {
182 assert(i < m_size);
183 return m_data[i * m_stride];
184 }
185
186 T at(std::size_t i) const {
187 assert(i < m_size);
188 return m_data[i * m_stride];
189 }
190
191 T &operator()(std::size_t i) { return at(i); }
192 T operator()(std::size_t i) const { return at(i); }
193
194 T &front() { return at(0); }
195 T front() const { return at(0); }
196 T &back() { return at(m_size - 1); }
197 T back() const { return at(m_size - 1); }
198
199 T *data() { return m_data; }
200
201 //! Iterator to the beginning
202 auto begin() { return StrideIterator(m_data, long(m_stride)); }
203 auto cbegin() const { return ConstStrideIterator(m_data, long(m_stride)); }
204
205 auto end() {
206 return StrideIterator(m_data + long(m_size * m_stride), long(m_stride));
207 }
208 auto cend() const {
209 return ConstStrideIterator(m_data + long(m_size * m_stride),
210 long(m_stride));
211 }
212
213 auto rbegin() {
214 return StrideIterator(m_data + long(m_size * m_stride) - long(m_stride),
215 -long(m_stride));
216 }
217 auto crbegin() const {
218 return ConstStrideIterator(
219 m_data + long(m_size * m_stride) - long(m_stride), -long(m_stride));
220 }
221
222 auto rend() {
223 return StrideIterator(m_data - long(m_stride), -long(m_stride));
224 }
225 auto crend() const {
226 return ConstStrideIterator(m_data - long(m_stride), -long(m_stride));
227 }
228
229 //! Returns a copy of the view as a std::vector.
230 std::vector<T> vector() {
231 std::vector<T> out;
232 for (std::size_t i = 0; i < m_size; ++i) {
233 out.push_back(m_data[i * m_stride]);
234 }
235 return out;
236 }
237};
238
239//==============================================================================
240
241//! Variadic product - helper function
242template <typename T, typename... Args>
243T product(T first, Args... rest) {
244 if constexpr (sizeof...(rest) == 0) {
245 return first;
246 } else {
247 return first * product(rest...);
248 }
249}
250
251template <std::size_t N>
252void NDrange_impl(std::vector<std::array<std::size_t, N>> &result,
253 std::array<std::size_t, N> &current,
254 const std::array<std::size_t, N> &maxValues,
255 std::size_t index) {
256 if (index == N) {
257 result.push_back(current);
258 return;
259 }
260
261 for (std::size_t i = 0; i < maxValues[index]; ++i) {
262 current[index] = i;
263 NDrange_impl<N>(result, current, maxValues, index + 1);
264 }
265}
266
267/*!
268 @brief Returns all index tuples for an N-dimensional range.
269
270 @details
271 Enables iteration over every index combination, e.g.:
272 @code
273 for (auto [i,j,k] : qip::NDrange(3,4,2)) { array.at(i,j,k); }
274 @endcode
275 @note Not memory efficient; do not use for large arrays.
276*/
277template <typename... Args>
278auto NDrange(std::size_t first, Args... rest) {
279 constexpr std::size_t N = sizeof...(rest) + 1;
280
281 const std::array<std::size_t, N> maxValues = {
282 first, static_cast<std::size_t>(rest)...};
283
284 std::vector<std::array<std::size_t, N>> result;
285 result.reserve(product(first, static_cast<std::size_t>(rest)...));
286
287 std::array<std::size_t, N> current = {0};
288
289 NDrange_impl<N>(result, current, maxValues, 0);
290 return result;
291}
292
293//==============================================================================
294/*!
295 @brief N-dimensional array with arithmetic operators.
296
297 @details
298 Stores data in a flat std::vector; indices are computed from the shape.
299 Element-wise arithmetic (+, -, *, /) is provided between arrays of identical
300 shape, and between an array and its element type (see operator+=, etc.).
301 Row and column views (@ref ArrayView) are available for 2D arrays.
302*/
303template <typename T = double>
304class Array : public Arithmetic<Array<T>>, Arithmetic2<Array<T>, T> {
305
306private:
307 // List of sizes for each array dimension
308 std::vector<std::size_t> m_sizes;
309 // Number of array dimensions
310 std::size_t m_Ndim;
311 // Cumulative sizes (used to index into data)
312 std::vector<std::size_t> m_cumulative_sizes;
313 // Total number of elements
314 std::size_t m_total_size;
315 // Raw data
316 std::vector<T> m_data;
317
318public:
319 /*!
320 @brief Constructs an N-dimensional array with the given dimension sizes.
321
322 @details e.g., Array(3,6,2) creates a 362 array.
323 */
324 template <typename... Args>
325 Array(std::size_t first, Args... rest);
326
327 //! Resizes the array. Note: invalidates all underlying data.
328 template <typename... Args>
329 void resize(std::size_t first, Args... rest);
330
331 //! Returns the total number of elements.
332 std::size_t size() const { return m_total_size; }
333
334 //! Returns the size of a specific dimension.
335 std::size_t size(std::size_t dim) const { return m_sizes.at(dim); }
336
337 //! Returns the number of dimensions.
338 std::size_t dimensions() const { return m_Ndim; }
339
340 //! Returns the shape (sizes of all dimensions) of the array.
341 const std::vector<std::size_t> &shape() const { return m_sizes; }
342
343 //! Access element with bounds checking.
344 template <typename... Args>
345 T &at(std::size_t first, Args... rest);
346
347 //! Const access to element with bounds checking.
348 template <typename... Args>
349 T at(std::size_t first, Args... rest) const;
350
351 //! Access element without bounds checking.
352 template <typename... Args>
353 T &operator()(std::size_t first, Args... rest);
354
355 //! Const access to element without bounds checking.
356 template <typename... Args>
357 T operator()(std::size_t first, Args... rest) const;
358
359 //! Pointer to the first element of the underlying contiguous storage.
360 T *data() { return m_data.data(); }
361 const T *data() const { return m_data.data(); }
362
363 //! Const reference to the underlying flat data vector.
364 const std::vector<T> &vector() const { return m_data; }
365
366 //! Number of rows (equivalent to size(0)).
367 std::size_t rows() const { return size(0); }
368 //! Number of columns (equivalent to size(1)).
369 std::size_t cols() const { return size(1); }
370
371 //! A view onto the ith row. Only defined for 2D arrays.
372 ArrayView<T> row(std::size_t i);
373 ArrayView<const T> row(std::size_t i) const;
374 //! A view onto the jth column. Only defined for 2D arrays.
375 ArrayView<T> col(std::size_t j);
376 ArrayView<const T> col(std::size_t j) const;
377
378 //! Iterator to the beginning
379 auto begin() { return m_data.begin(); }
380 //! Constant iterator to the beginning
381 auto cbegin() const { return m_data.cbegin(); }
382 //! Iterator to the end
383 auto end() { return m_data.end(); }
384 //! Constant iterator to the end
385 auto cend() const { return m_data.cend(); }
386
387 //! Reverse iterator to the beginning of the data
388 auto rbegin() { return m_data.rbegin(); }
389 //! Constant reverse iterator to the beginning of the data
390 auto crbegin() const { return m_data.crbegin(); }
391 //! Reverse iterator to the end of the data
392 auto rend() { return m_data.rend(); }
393 //! Constant reverse iterator to the end of the data
394 auto crend() const { return m_data.crend(); }
395
396 //! Element-wise arithmetic between arrays of identical shape (+, -, *, /).
398 Array<T> &operator-=(const Array<T> &other);
399 Array<T> &operator*=(const Array<T> &other);
400 Array<T> &operator/=(const Array<T> &other);
401
402 /*!
403 @brief Element-wise scalar arithmetic (+, -, *, /).
404
405 @details
406 Array op T is provided for +, -, *, /.
407 T op Array is provided only for * and +.
408 */
409 Array<T> &operator+=(const T &t);
410 Array<T> &operator-=(const T &t);
411 Array<T> &operator*=(const T &t);
412 Array<T> &operator/=(const T &t);
413
414private:
415 // Calculates the cumulative_sizes array
416 std::vector<std::size_t> calc_cumulative_size() const;
417
418 // Unchecked index calculation
419 template <typename... Args>
420 std::size_t unchecked_index(std::size_t first, Args... rest) const;
421
422 // Helper for unchecked index calculation
423 template <typename... Args>
424 std::size_t unchecked_index_impl(std::size_t dim, std::size_t first,
425 Args... rest) const;
426
427 // Checked index calculation
428 template <typename... Args>
429 std::size_t checked_index(std::size_t first, Args... rest) const;
430
431 // Helper for checked index calculation
432 template <typename... Args>
433 std::size_t checked_index_impl(std::size_t dim, std::size_t first,
434 Args... rest) const;
435};
436
437//==============================================================================
438// Implementations
439//==============================================================================
440template <typename T>
441std::vector<std::size_t> Array<T>::calc_cumulative_size() const {
442 std::vector<std::size_t> cumulative_size;
443 cumulative_size.reserve(m_sizes.size());
444 for (std::size_t i = 0; i < m_Ndim; ++i) {
445 cumulative_size.push_back(std::accumulate(m_sizes.cbegin() + long(i) + 1,
446 m_sizes.cend(), 1ul,
447 std::multiplies<std::size_t>()));
448 }
449 return cumulative_size;
450}
451
452template <typename T>
453template <typename... Args>
454Array<T>::Array(std::size_t first, Args... rest)
455 : m_sizes({first, static_cast<std::size_t>(rest)...}),
456 m_Ndim(m_sizes.size()),
457 m_cumulative_sizes(calc_cumulative_size()),
458 m_total_size(std::accumulate(m_sizes.cbegin(), m_sizes.cend(), 1ul,
459 std::multiplies<std::size_t>())),
460 m_data(m_total_size) {}
461
462template <typename T>
463template <typename... Args>
464void Array<T>::resize(std::size_t first, Args... rest) {
465 m_sizes = std::vector{first, static_cast<std::size_t>(rest)...};
466 m_Ndim = m_sizes.size();
467 m_cumulative_sizes = calc_cumulative_size();
468 m_total_size = std::accumulate(m_sizes.cbegin(), m_sizes.cend(), 1ul,
469 std::multiplies<std::size_t>());
470 m_data.resize(m_total_size);
471}
472
473template <typename T>
474template <typename... Args>
475std::size_t Array<T>::unchecked_index(std::size_t first, Args... rest) const {
476 return unchecked_index_impl(0, first, rest...);
477}
478
479template <typename T>
480template <typename... Args>
481std::size_t Array<T>::unchecked_index_impl(std::size_t dim, std::size_t first,
482 Args... rest) const {
483 if constexpr (sizeof...(rest) == 0) {
484 return first * m_cumulative_sizes[dim];
485 } else {
486 return first * m_cumulative_sizes[dim] +
487 unchecked_index_impl(dim + 1, rest...);
488 }
489}
490
491template <typename T>
492template <typename... Args>
493std::size_t Array<T>::checked_index(std::size_t first, Args... rest) const {
494 assert(sizeof...(rest) + 1 == m_Ndim &&
495 "Number of arguments must match number of dimensions");
496 return checked_index_impl(0, first, rest...);
497}
498
499template <typename T>
500template <typename... Args>
501std::size_t Array<T>::checked_index_impl(std::size_t dim, std::size_t first,
502 Args... rest) const {
503 assert(first < m_sizes[dim]);
504 if constexpr (sizeof...(rest) == 0) {
505 return first * m_cumulative_sizes[dim];
506 } else {
507 return first * m_cumulative_sizes[dim] +
508 checked_index_impl(dim + 1, rest...);
509 }
510}
511
512template <typename T>
513template <typename... Args>
514T &Array<T>::at(std::size_t first, Args... rest) {
515 return m_data.at(checked_index(first, rest...));
516}
517
518template <typename T>
519template <typename... Args>
520T Array<T>::at(std::size_t first, Args... rest) const {
521 return m_data.at(checked_index(first, rest...));
522}
523
524template <typename T>
525template <typename... Args>
526T &Array<T>::operator()(std::size_t first, Args... rest) {
527 return m_data[unchecked_index(first, rest...)];
528}
529
530template <typename T>
531template <typename... Args>
532T Array<T>::operator()(std::size_t first, Args... rest) const {
533 return m_data[unchecked_index(first, rest...)];
534}
535
536template <typename T>
538 assert(m_sizes == other.m_sizes &&
539 "Arithmetic only defined for equal-dimension arrays");
540 for (std::size_t i = 0; i < m_data.size(); ++i) {
541 this->m_data[i] += other.m_data[i];
542 }
543 return *this;
544}
545
546template <typename T>
548 assert(m_sizes == other.m_sizes &&
549 "Arithmetic only defined for equal-dimension arrays");
550 for (std::size_t i = 0; i < m_data.size(); ++i) {
551 this->m_data[i] -= other.m_data[i];
552 }
553 return *this;
554}
555
556template <typename T>
557Array<T> &Array<T>::operator*=(const Array<T> &other) {
558 assert(m_sizes == other.m_sizes &&
559 "Arithmetic only defined for equal-dimension arrays");
560 for (std::size_t i = 0; i < m_data.size(); ++i) {
561 this->m_data[i] *= other.m_data[i];
562 }
563 return *this;
564}
565
566template <typename T>
567Array<T> &Array<T>::operator/=(const Array<T> &other) {
568 assert(m_sizes == other.m_sizes &&
569 "Arithmetic only defined for equal-dimension arrays");
570 for (std::size_t i = 0; i < m_data.size(); ++i) {
571 this->m_data[i] /= other.m_data[i];
572 }
573 return *this;
574}
575
576template <typename T>
578 for (std::size_t i = 0; i < m_data.size(); ++i) {
579 this->m_data[i] += t;
580 }
581 return *this;
582}
583
584template <typename T>
585Array<T> &Array<T>::operator-=(const T &t) {
586 for (std::size_t i = 0; i < m_data.size(); ++i) {
587 this->m_data[i] -= t;
588 }
589 return *this;
590}
591
592template <typename T>
593Array<T> &Array<T>::operator*=(const T &t) {
594 for (std::size_t i = 0; i < m_data.size(); ++i) {
595 this->m_data[i] *= t;
596 }
597 return *this;
598}
599
600template <typename T>
601Array<T> &Array<T>::operator/=(const T &t) {
602 for (std::size_t i = 0; i < m_data.size(); ++i) {
603 this->m_data[i] /= t;
604 }
605 return *this;
606}
607
608template <typename T>
610 assert(m_Ndim == 2 && "Row only defined for 2D array");
611 return ArrayView(m_data.data() + i * m_sizes[1], m_sizes[1]);
612}
613template <typename T>
615 assert(m_Ndim == 2 && "Col only defined for 2D array");
616 return ArrayView(m_data.data() + j, m_sizes[0], m_sizes[1]);
617}
618template <typename T>
619ArrayView<const T> Array<T>::row(std::size_t i) const {
620 assert(m_Ndim == 2 && "Row only defined for 2D array");
621 return ArrayView(m_data.data() + i * m_sizes[1], m_sizes[1]);
622}
623template <typename T>
624ArrayView<const T> Array<T>::col(std::size_t j) const {
625 assert(m_Ndim == 2 && "Col only defined for 2D array");
626 return ArrayView<const T>(m_data.data() + j, m_sizes[0], m_sizes[1]);
627}
628
629} // namespace qip
Like Arithmetic, but for two different types (T op U).
Definition Template.hpp:66
Helper template that provides +, -, *, / given +=, -=, *=, /=.
Definition Template.hpp:51
Non-owning view onto a 1D contiguous or strided array segment.
Definition Array.hpp:164
auto begin()
Iterator to the beginning.
Definition Array.hpp:202
std::vector< T > vector()
Returns a copy of the view as a std::vector.
Definition Array.hpp:230
N-dimensional array with arithmetic operators.
Definition Array.hpp:304
void resize(std::size_t first, Args... rest)
Resizes the array. Note: invalidates all underlying data.
Definition Array.hpp:464
auto cbegin() const
Constant iterator to the beginning.
Definition Array.hpp:381
std::size_t cols() const
Number of columns (equivalent to size(1)).
Definition Array.hpp:369
T & operator()(std::size_t first, Args... rest)
Access element without bounds checking.
Definition Array.hpp:526
T & at(std::size_t first, Args... rest)
Access element with bounds checking.
Definition Array.hpp:514
Array(std::size_t first, Args... rest)
Constructs an N-dimensional array with the given dimension sizes.
Definition Array.hpp:454
std::size_t dimensions() const
Returns the number of dimensions.
Definition Array.hpp:338
const std::vector< std::size_t > & shape() const
Returns the shape (sizes of all dimensions) of the array.
Definition Array.hpp:341
ArrayView< T > row(std::size_t i)
A view onto the ith row. Only defined for 2D arrays.
Definition Array.hpp:609
auto begin()
Iterator to the beginning.
Definition Array.hpp:379
std::size_t rows() const
Number of rows (equivalent to size(0)).
Definition Array.hpp:367
auto rend()
Reverse iterator to the end of the data.
Definition Array.hpp:392
const std::vector< T > & vector() const
Const reference to the underlying flat data vector.
Definition Array.hpp:364
auto cend() const
Constant iterator to the end.
Definition Array.hpp:385
ArrayView< T > col(std::size_t j)
A view onto the jth column. Only defined for 2D arrays.
Definition Array.hpp:614
T * data()
Pointer to the first element of the underlying contiguous storage.
Definition Array.hpp:360
Array< T > & operator+=(const Array< T > &other)
Element-wise arithmetic between arrays of identical shape (+, -, *, /).
Definition Array.hpp:537
auto crend() const
Constant reverse iterator to the end of the data.
Definition Array.hpp:394
auto rbegin()
Reverse iterator to the beginning of the data.
Definition Array.hpp:388
std::size_t size(std::size_t dim) const
Returns the size of a specific dimension.
Definition Array.hpp:335
auto end()
Iterator to the end.
Definition Array.hpp:383
std::size_t size() const
Returns the total number of elements.
Definition Array.hpp:332
auto crbegin() const
Constant reverse iterator to the beginning of the data.
Definition Array.hpp:390
Helper template that provides !=, >, <=, >= given == and <.
Definition Template.hpp:36
Const version of StrideIterator.
Definition Array.hpp:97
Iterator with a configurable stride.
Definition Array.hpp:36
General-purpose utility library.
Definition Array.hpp:23
auto NDrange(std::size_t first, Args... rest)
Returns all index tuples for an N-dimensional range.
Definition Array.hpp:278
T product(T first, Args... rest)
Variadic product - helper function.
Definition Array.hpp:243