Home | History | Annotate | Download | only in TensorSymmetry
      1 // This file is part of Eigen, a lightweight C++ template library
      2 // for linear algebra.
      3 //
      4 // Copyright (C) 2013 Christian Seiler <christian (at) iwakd.de>
      5 //
      6 // This Source Code Form is subject to the terms of the Mozilla
      7 // Public License v. 2.0. If a copy of the MPL was not distributed
      8 // with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
      9 
     10 #ifndef EIGEN_CXX11_TENSORSYMMETRY_SYMMETRY_H
     11 #define EIGEN_CXX11_TENSORSYMMETRY_SYMMETRY_H
     12 
     13 namespace Eigen {
     14 
     15 enum {
     16   NegationFlag           = 0x01,
     17   ConjugationFlag        = 0x02
     18 };
     19 
     20 enum {
     21   GlobalRealFlag         = 0x01,
     22   GlobalImagFlag         = 0x02,
     23   GlobalZeroFlag         = 0x03
     24 };
     25 
     26 namespace internal {
     27 
     28 template<std::size_t NumIndices, typename... Sym>                   struct tensor_symmetry_pre_analysis;
     29 template<std::size_t NumIndices, typename... Sym>                   struct tensor_static_symgroup;
     30 template<bool instantiate, std::size_t NumIndices, typename... Sym> struct tensor_static_symgroup_if;
     31 template<typename Tensor_> struct tensor_symmetry_calculate_flags;
     32 template<typename Tensor_> struct tensor_symmetry_assign_value;
     33 template<typename... Sym> struct tensor_symmetry_num_indices;
     34 
     35 } // end namespace internal
     36 
     37 template<int One_, int Two_>
     38 struct Symmetry
     39 {
     40   static_assert(One_ != Two_, "Symmetries must cover distinct indices.");
     41   constexpr static int One = One_;
     42   constexpr static int Two = Two_;
     43   constexpr static int Flags = 0;
     44 };
     45 
     46 template<int One_, int Two_>
     47 struct AntiSymmetry
     48 {
     49   static_assert(One_ != Two_, "Symmetries must cover distinct indices.");
     50   constexpr static int One = One_;
     51   constexpr static int Two = Two_;
     52   constexpr static int Flags = NegationFlag;
     53 };
     54 
     55 template<int One_, int Two_>
     56 struct Hermiticity
     57 {
     58   static_assert(One_ != Two_, "Symmetries must cover distinct indices.");
     59   constexpr static int One = One_;
     60   constexpr static int Two = Two_;
     61   constexpr static int Flags = ConjugationFlag;
     62 };
     63 
     64 template<int One_, int Two_>
     65 struct AntiHermiticity
     66 {
     67   static_assert(One_ != Two_, "Symmetries must cover distinct indices.");
     68   constexpr static int One = One_;
     69   constexpr static int Two = Two_;
     70   constexpr static int Flags = ConjugationFlag | NegationFlag;
     71 };
     72 
     73 /** \class DynamicSGroup
     74   * \ingroup TensorSymmetry_Module
     75   *
     76   * \brief Dynamic symmetry group
     77   *
     78   * The %DynamicSGroup class represents a symmetry group that need not be known at
     79   * compile time. It is useful if one wants to support arbitrary run-time defineable
     80   * symmetries for tensors, but it is also instantiated if a symmetry group is defined
     81   * at compile time that would be either too large for the compiler to reasonably
     82   * generate (using templates to calculate this at compile time is very inefficient)
     83   * or that the compiler could generate the group but that it wouldn't make sense to
     84   * unroll the loop for setting coefficients anymore.
     85   */
     86 class DynamicSGroup;
     87 
     88 /** \internal
     89   *
     90   * \class DynamicSGroupFromTemplateArgs
     91   * \ingroup TensorSymmetry_Module
     92   *
     93   * \brief Dynamic symmetry group, initialized from template arguments
     94   *
     95   * This class is a child class of DynamicSGroup. It uses the template arguments
     96   * specified to initialize itself.
     97   */
     98 template<typename... Gen>
     99 class DynamicSGroupFromTemplateArgs;
    100 
    101 /** \class StaticSGroup
    102   * \ingroup TensorSymmetry_Module
    103   *
    104   * \brief Static symmetry group
    105   *
    106   * This class represents a symmetry group that is known and resolved completely
    107   * at compile time. Ideally, no run-time penalty is incurred compared to the
    108   * manual unrolling of the symmetry.
    109   *
    110   * <b><i>CAUTION:</i></b>
    111   *
    112   * Do not use this class directly for large symmetry groups. The compiler
    113   * may run into a limit, or segfault or in the very least will take a very,
    114   * very, very long time to compile the code. Use the SGroup class instead
    115   * if you want a static group. That class contains logic that will
    116   * automatically select the DynamicSGroup class instead if the symmetry
    117   * group becomes too large. (In that case, unrolling may not even be
    118   * beneficial.)
    119   */
    120 template<typename... Gen>
    121 class StaticSGroup;
    122 
    123 /** \class SGroup
    124   * \ingroup TensorSymmetry_Module
    125   *
    126   * \brief Symmetry group, initialized from template arguments
    127   *
    128   * This class represents a symmetry group whose generators are already
    129   * known at compile time. It may or may not be resolved at compile time,
    130   * depending on the estimated size of the group.
    131   *
    132   * \sa StaticSGroup
    133   * \sa DynamicSGroup
    134   */
    135 template<typename... Gen>
    136 class SGroup : public internal::tensor_symmetry_pre_analysis<internal::tensor_symmetry_num_indices<Gen...>::value, Gen...>::root_type
    137 {
    138   public:
    139     constexpr static std::size_t NumIndices = internal::tensor_symmetry_num_indices<Gen...>::value;
    140     typedef typename internal::tensor_symmetry_pre_analysis<NumIndices, Gen...>::root_type Base;
    141 
    142     // make standard constructors + assignment operators public
    143     inline SGroup() : Base() { }
    144     inline SGroup(const SGroup<Gen...>& other) : Base(other) { }
    145     inline SGroup(SGroup<Gen...>&& other) : Base(other) { }
    146     inline SGroup<Gen...>& operator=(const SGroup<Gen...>& other) { Base::operator=(other); return *this; }
    147     inline SGroup<Gen...>& operator=(SGroup<Gen...>&& other) { Base::operator=(other); return *this; }
    148 
    149     // all else is defined in the base class
    150 };
    151 
    152 namespace internal {
    153 
    154 template<typename... Sym> struct tensor_symmetry_num_indices
    155 {
    156   constexpr static std::size_t value = 1;
    157 };
    158 
    159 template<int One_, int Two_, typename... Sym> struct tensor_symmetry_num_indices<Symmetry<One_, Two_>, Sym...>
    160 {
    161 private:
    162   constexpr static std::size_t One = static_cast<std::size_t>(One_);
    163   constexpr static std::size_t Two = static_cast<std::size_t>(Two_);
    164   constexpr static std::size_t Three = tensor_symmetry_num_indices<Sym...>::value;
    165 
    166   // don't use std::max, since it's not constexpr until C++14...
    167   constexpr static std::size_t maxOneTwoPlusOne = ((One > Two) ? One : Two) + 1;
    168 public:
    169   constexpr static std::size_t value = (maxOneTwoPlusOne > Three) ? maxOneTwoPlusOne : Three;
    170 };
    171 
    172 template<int One_, int Two_, typename... Sym> struct tensor_symmetry_num_indices<AntiSymmetry<One_, Two_>, Sym...>
    173   : public tensor_symmetry_num_indices<Symmetry<One_, Two_>, Sym...> {};
    174 template<int One_, int Two_, typename... Sym> struct tensor_symmetry_num_indices<Hermiticity<One_, Two_>, Sym...>
    175   : public tensor_symmetry_num_indices<Symmetry<One_, Two_>, Sym...> {};
    176 template<int One_, int Two_, typename... Sym> struct tensor_symmetry_num_indices<AntiHermiticity<One_, Two_>, Sym...>
    177   : public tensor_symmetry_num_indices<Symmetry<One_, Two_>, Sym...> {};
    178 
    179 /** \internal
    180   *
    181   * \class tensor_symmetry_pre_analysis
    182   * \ingroup TensorSymmetry_Module
    183   *
    184   * \brief Pre-select whether to use a static or dynamic symmetry group
    185   *
    186   * When a symmetry group could in principle be determined at compile time,
    187   * this template implements the logic whether to actually do that or whether
    188   * to rather defer that to runtime.
    189   *
    190   * The logic is as follows:
    191   * <dl>
    192   * <dt><b>No generators (trivial symmetry):</b></dt>
    193   * <dd>Use a trivial static group. Ideally, this has no performance impact
    194   *     compared to not using symmetry at all. In practice, this might not
    195   *     be the case.</dd>
    196   * <dt><b>More than 4 generators:</b></dt>
    197   * <dd>Calculate the group at run time, it is likely far too large for the
    198   *     compiler to be able to properly generate it in a realistic time.</dd>
    199   * <dt><b>Up to and including 4 generators:</b></dt>
    200   * <dd>Actually enumerate all group elements, but then check how many there
    201   *     are. If there are more than 16, it is unlikely that unrolling the
    202   *     loop (as is done in the static compile-time case) is sensible, so
    203   *     use a dynamic group instead. If there are at most 16 elements, actually
    204   *     use that static group. Note that the largest group with 4 generators
    205   *     still compiles with reasonable resources.</dd>
    206   * </dl>
    207   *
    208   * Note: Example compile time performance with g++-4.6 on an Intenl Core i5-3470
    209   *       with 16 GiB RAM (all generators non-redundant and the subgroups don't
    210   *       factorize):
    211   *
    212   *          # Generators          -O0 -ggdb               -O2
    213   *          -------------------------------------------------------------------
    214   *          1                 0.5 s  /   250 MiB     0.45s /   230 MiB
    215   *          2                 0.5 s  /   260 MiB     0.5 s /   250 MiB
    216   *          3                 0.65s  /   310 MiB     0.62s /   310 MiB
    217   *          4                 2.2 s  /   860 MiB     1.7 s /   770 MiB
    218   *          5               130   s  / 13000 MiB   120   s / 11000 MiB
    219   *
    220   * It is clear that everything is still very efficient up to 4 generators, then
    221   * the memory and CPU requirements become unreasonable. Thus we only instantiate
    222   * the template group theory logic if the number of generators supplied is 4 or
    223   * lower, otherwise this will be forced to be done during runtime, where the
    224   * algorithm is reasonably fast.
    225   */
    226 template<std::size_t NumIndices>
    227 struct tensor_symmetry_pre_analysis<NumIndices>
    228 {
    229   typedef StaticSGroup<> root_type;
    230 };
    231 
    232 template<std::size_t NumIndices, typename Gen_, typename... Gens_>
    233 struct tensor_symmetry_pre_analysis<NumIndices, Gen_, Gens_...>
    234 {
    235   constexpr static std::size_t max_static_generators = 4;
    236   constexpr static std::size_t max_static_elements = 16;
    237   typedef tensor_static_symgroup_if<(sizeof...(Gens_) + 1 <= max_static_generators), NumIndices, Gen_, Gens_...> helper;
    238   constexpr static std::size_t possible_size = helper::size;
    239 
    240   typedef typename conditional<
    241     possible_size == 0 || possible_size >= max_static_elements,
    242     DynamicSGroupFromTemplateArgs<Gen_, Gens_...>,
    243     typename helper::type
    244   >::type root_type;
    245 };
    246 
    247 template<bool instantiate, std::size_t NumIndices, typename... Gens>
    248 struct tensor_static_symgroup_if
    249 {
    250   constexpr static std::size_t size = 0;
    251   typedef void type;
    252 };
    253 
    254 template<std::size_t NumIndices, typename... Gens>
    255 struct tensor_static_symgroup_if<true, NumIndices, Gens...> : tensor_static_symgroup<NumIndices, Gens...> {};
    256 
    257 template<typename Tensor_>
    258 struct tensor_symmetry_assign_value
    259 {
    260   typedef typename Tensor_::Index Index;
    261   typedef typename Tensor_::Scalar Scalar;
    262   constexpr static std::size_t NumIndices = Tensor_::NumIndices;
    263 
    264   static inline int run(const std::array<Index, NumIndices>& transformed_indices, int transformation_flags, int dummy, Tensor_& tensor, const Scalar& value_)
    265   {
    266     Scalar value(value_);
    267     if (transformation_flags & ConjugationFlag)
    268       value = numext::conj(value);
    269     if (transformation_flags & NegationFlag)
    270       value = -value;
    271     tensor.coeffRef(transformed_indices) = value;
    272     return dummy;
    273   }
    274 };
    275 
    276 template<typename Tensor_>
    277 struct tensor_symmetry_calculate_flags
    278 {
    279   typedef typename Tensor_::Index Index;
    280   constexpr static std::size_t NumIndices = Tensor_::NumIndices;
    281 
    282   static inline int run(const std::array<Index, NumIndices>& transformed_indices, int transform_flags, int current_flags, const std::array<Index, NumIndices>& orig_indices)
    283   {
    284     if (transformed_indices == orig_indices) {
    285       if (transform_flags & (ConjugationFlag | NegationFlag))
    286         return current_flags | GlobalImagFlag; // anti-hermitian diagonal
    287       else if (transform_flags & ConjugationFlag)
    288         return current_flags | GlobalRealFlag; // hermitian diagonal
    289       else if (transform_flags & NegationFlag)
    290         return current_flags | GlobalZeroFlag; // anti-symmetric diagonal
    291     }
    292     return current_flags;
    293   }
    294 };
    295 
    296 template<typename Tensor_, typename Symmetry_, int Flags = 0>
    297 class tensor_symmetry_value_setter
    298 {
    299   public:
    300     typedef typename Tensor_::Index Index;
    301     typedef typename Tensor_::Scalar Scalar;
    302     constexpr static std::size_t NumIndices = Tensor_::NumIndices;
    303 
    304     inline tensor_symmetry_value_setter(Tensor_& tensor, Symmetry_ const& symmetry, std::array<Index, NumIndices> const& indices)
    305       : m_tensor(tensor), m_symmetry(symmetry), m_indices(indices) { }
    306 
    307     inline tensor_symmetry_value_setter<Tensor_, Symmetry_, Flags>& operator=(Scalar const& value)
    308     {
    309       doAssign(value);
    310       return *this;
    311     }
    312   private:
    313     Tensor_& m_tensor;
    314     Symmetry_ m_symmetry;
    315     std::array<Index, NumIndices> m_indices;
    316 
    317     inline void doAssign(Scalar const& value)
    318     {
    319       #ifdef EIGEN_TENSOR_SYMMETRY_CHECK_VALUES
    320         int value_flags = m_symmetry.template apply<internal::tensor_symmetry_calculate_flags<Tensor_>, int>(m_indices, m_symmetry.globalFlags(), m_indices);
    321         if (value_flags & GlobalRealFlag)
    322           eigen_assert(numext::imag(value) == 0);
    323         if (value_flags & GlobalImagFlag)
    324           eigen_assert(numext::real(value) == 0);
    325       #endif
    326       m_symmetry.template apply<internal::tensor_symmetry_assign_value<Tensor_>, int>(m_indices, 0, m_tensor, value);
    327     }
    328 };
    329 
    330 } // end namespace internal
    331 
    332 } // end namespace Eigen
    333 
    334 #endif // EIGEN_CXX11_TENSORSYMMETRY_SYMMETRY_H
    335 
    336 /*
    337  * kate: space-indent on; indent-width 2; mixedindent off; indent-mode cstyle;
    338  */
    339