Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : #pragma once 5 : 6 : #include <algorithm> 7 : #include <array> 8 : #include <cstddef> 9 : 10 : #include "Utilities/Algorithm.hpp" 11 : #include "Utilities/Array.hpp" 12 : #include "Utilities/ConstantExpressions.hpp" 13 : #include "Utilities/Numeric.hpp" 14 : 15 : /*! 16 : * \ingroup DataStructuresGroup 17 : * 18 : * \brief Iterate over all nonzero index permutations for a Levi-Civita 19 : * symbol. 20 : * 21 : * \details This class provides an iterator that allows you to loop over 22 : * only the nonzero index permutations of a Levi-Civita symbol of dimension 23 : * `dimension`. Inside the loop, the operator `()` 24 : * returns an `std::array` containing an ordered list of the indices of this 25 : * permutation, the operator `[]` returns a specific index from the same 26 : * `std::array`, and the function `sign()` returns the sign of the 27 : * Levi-Civita symbol for this permutation. 28 : * 29 : * Note that the Levi-Civita symbol in odd dimensions (like 3) is invariant 30 : * under cyclic permutations of its indices, but in even dimensions (like 4) 31 : * it changes sign under cyclic permutations. 32 : * 33 : * \example 34 : * \snippet Test_LeviCivitaIterator.cpp levi_civita_iterator_example 35 : */ 36 : template <size_t Dim> 37 1 : class LeviCivitaIterator { 38 : public: 39 0 : explicit LeviCivitaIterator() = default; 40 : 41 : /// \cond HIDDEN_SYMBOLS 42 : ~LeviCivitaIterator() = default; 43 : /// @{ 44 : /// No copy or move semantics 45 : LeviCivitaIterator(const LeviCivitaIterator<Dim>&) = delete; 46 : LeviCivitaIterator(LeviCivitaIterator<Dim>&&) = delete; 47 : LeviCivitaIterator<Dim>& operator=(const LeviCivitaIterator<Dim>&) = delete; 48 : LeviCivitaIterator<Dim>& operator=(LeviCivitaIterator<Dim>&&) = delete; 49 : /// @} 50 : /// \endcond 51 : 52 : // Return false if the end of the loop is reached 53 0 : explicit operator bool() const { return valid_; } 54 : 55 : // Increment the current permutation 56 0 : LeviCivitaIterator& operator++() { 57 : ++permutation_; 58 : if (permutation_ >= signs_.size()) { 59 : valid_ = false; 60 : } 61 : return *this; 62 : } 63 : 64 : /// Return a `std::array` containing the current multi-index, an ordered list 65 : /// of indices for the current permutation. 66 1 : const std::array<size_t, Dim> operator()() const { 67 : return static_cast<std::array<size_t, Dim>>(indexes_[permutation_]); 68 : } 69 : 70 : /// Return a specific index from the multi-index of the current permutation. 71 1 : const size_t& operator[](const size_t i) const { 72 : return indexes_[permutation_][i]; 73 : } 74 : 75 : /// Return the sign of the Levi-Civita symbol for the current permutation. 76 1 : int sign() const { return signs_[permutation_]; }; 77 : 78 : private: 79 : static constexpr cpp20::array<cpp20::array<size_t, Dim>, factorial(Dim)> 80 0 : indexes() { 81 : cpp20::array<cpp20::array<size_t, Dim>, factorial(Dim)> indexes{}; 82 : cpp20::array<size_t, Dim> index{}; 83 : cpp2b::iota(index.begin(), index.end(), size_t(0)); 84 : 85 : indexes[0] = index; 86 : 87 : // cpp20::next_permutation generates the different permutations of index 88 : // loop over them to fill in the rest of the permutations in indexes 89 : size_t permutation = 1; 90 : while (cpp20::next_permutation(index.begin(), index.end())) { 91 : indexes[permutation] = index; 92 : ++permutation; 93 : } 94 : return indexes; 95 : }; 96 : 97 0 : static constexpr cpp20::array<int, factorial(Dim)> signs() { 98 : cpp20::array<int, factorial(Dim)> signs{}; 99 : // By construction, the sign of the first permutation is +1 100 : signs[0] = 1; 101 : 102 : // How do you know whether the corresponding Levi Civita symbol is 103 : // +1 or -1? To find out, compute the number, in the factoradic number 104 : // system, corresponding to each permutation, and sum the digits. If the sum 105 : // is even (odd), the corresponding Levi-Civita symbol will be +1 (-1). This 106 : // works because there are Dim! unique permutations of the Levi Civita 107 : // symbol indices, so each one can be represented by a 108 : // (`Dim`)-digit factorial-number-system number. For more on the 109 : // factoradic number system, see 110 : // https://en.wikipedia.org/wiki/Factorial_number_system 111 : auto factoradic_counter = cpp20::array<size_t, Dim>(); 112 : for (size_t permutation = 1; permutation < factorial(Dim); ++permutation) { 113 : for (size_t i = 0; i < Dim; ++i) { 114 : factoradic_counter[i]++; 115 : if (factoradic_counter[i] < i + 1) { 116 : break; 117 : } else { 118 : factoradic_counter[i] = 0; 119 : } 120 : } 121 : signs[permutation] = 122 : (cpp2b::accumulate(factoradic_counter.begin(), 123 : factoradic_counter.end(), size_t(0)) % 124 : 2) == 0 125 : ? 1 126 : : -1; 127 : } 128 : return signs; 129 : }; 130 : 131 : // Note: here and throughout, use cpp20::array, which is constexpr 132 : // (unlike std::array), to enable constexpr signs_ and indexes_. 133 : static constexpr cpp20::array<cpp20::array<size_t, Dim>, factorial(Dim)> 134 0 : indexes_ = indexes(); 135 0 : static constexpr cpp20::array<int, factorial(Dim)> signs_{signs()}; 136 0 : size_t permutation_{0}; 137 0 : bool valid_{true}; 138 : };