Skip to content
Snippets Groups Projects
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
algorithm.hh 18.57 KiB
// -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
// vi: set et ts=4 sw=2 sts=2:
#ifndef DUNE_SOLVERS_COMMON_ALGORITHM_HH
#define DUNE_SOLVERS_COMMON_ALGORITHM_HH

#include <dune/common/indices.hh>

#include <dune/common/typeutilities.hh>
#include <dune/common/typetraits.hh>


#include <dune/istl/multitypeblockvector.hh>

namespace Dune {
namespace Solvers {
namespace Hybrid {

namespace Imp {

  // Try if tuple_size is implemented for class
  template<class T, int i>
  constexpr auto size(const Dune::FieldVector<T, i>*, const PriorityTag<5>&)
    -> decltype(std::integral_constant<std::size_t,i>())
  {
    return {};
  }

  // Try if we have an instance of std::integer_sequence
  template<class T, T... t, class Index>
  constexpr auto size(std::integer_sequence<T, t...>, PriorityTag<4>)
  {
    using sizeAsType = std::tuple_size<decltype(std::make_tuple(t...))>;
    return std::integral_constant<std::size_t, sizeAsType::value>();
  }

  // Try if tuple_size is implemented for class
  template<class T>
  constexpr auto size(const T*, const PriorityTag<3>&)
    -> decltype(std::integral_constant<std::size_t,std::tuple_size<T>::value>())
  {
    return {};
  }

  // Try if there's a static constexpr size()
  template<class T>
  constexpr auto size(const T*, const PriorityTag<1>&)
    -> decltype(std::integral_constant<std::size_t,T::size()>())
  {
    return {};
  }

  // As a last resort try if there's a static constexpr size()
  template<class T>
  constexpr auto size(const T* t, const PriorityTag<0>&)
  {
    return t->size();
  }

} // namespace Imp



/**
 * \brief Size query
 *
 * \tparam T Type of container whose size is queried
 *
 * \param t Container whose size is queried
 *
 * \return Size of t
 *
 * If the size of t is known at compile type the size is
 * returned as std::integral_constant<std::size_t, size>.
 * Otherwise the result of t.size() is returned.
 *
 * Supported types for deriving the size at compile time are:
 * * instances of std::integer_sequence
 * * all types std::tuple_size is implemented for
 * * all typed that have a static method ::size()
 * * instances of Dune::FieldVector
 */
template<class T>
constexpr auto size(const T& t)
{
  return Imp::size(&t, PriorityTag<42>());
}



namespace Imp {

  template<class Container, class Index,
    std::enable_if_t<IsTuple<std::decay_t<Container>>::value, int> = 0>
  constexpr decltype(auto) elementAt(Container&& c, Index&&, PriorityTag<2>)
  {
    return std::get<Index::value>(c);
  }

  template<class T, T... t, class Index>
  constexpr decltype(auto) elementAt(std::integer_sequence<T, t...> c, Index&&, PriorityTag<1>)
  {
    return std::get<Index::value>(std::make_tuple(std::integral_constant<T, t>()...));
  }

  template<class Container, class Index>
  constexpr decltype(auto) elementAt(Container&& c, Index&& i, PriorityTag<0>)
  {
    return c[i];
  }

} // namespace Imp



/**
 * \brief Get element at given position from container
 *
 * \tparam Container Type of given container
 * \tparam Index Type of index
 *
 * \param c Given container
 * \param i Index of element to obtain
 *
 * \return The element at position i, i.e. c[i]
 *
 * If this returns the i-th entry of c. It supports the following
 * containers
 * * Containers providing dynamic access via operator[]
 * * Heterogenous containers providing access via operator[](integral_constant<...>)
 * * std::tuple<...>
 * * std::integer_sequence
 */
template<class Container, class Index>
constexpr decltype(auto) elementAt(Container&& c, Index&& i)
{
  return Imp::elementAt(std::forward<Container>(c), std::forward<Index>(i), PriorityTag<42>());
}


namespace Imp {

  template<class Begin, class End>
  class StaticIntegralRange
  {
  public:

    template<std::size_t i>
    constexpr auto operator[](Dune::index_constant<i>) const
    {
      return Dune::index_constant<Begin::value+i>();
    }

    static constexpr auto size()
    {
      return std::integral_constant<typename Begin::value_type, End::value - Begin::value>();
    }
  };

  template<class T>
  class DynamicIntegralRange
  {
  public:
    constexpr DynamicIntegralRange(const T& begin, const T& end):
      begin_(begin),
      end_(end)
    {}

    const T& begin() const
    { return begin_; }

    const T& end() const
    { return end_; }

    constexpr auto size() const
    {
      return end() - begin();
    }

    constexpr T operator[](const T&i) const
    { return begin()+i; }

  private:
    T begin_;
    T end_;
  };

  template<class Begin, class End,
    std::enable_if_t<IsIntegralConstant<Begin>::value and IsIntegralConstant<End>::value, int> = 0>
  constexpr auto integralRange(const Begin& begin, const End& end, const PriorityTag<1>&)
  {
    static_assert(Begin::value <= End::value, "You cannot create an integralRange where end<begin");
    return Imp::StaticIntegralRange<Begin,End>();
  }

  template<class Begin, class End>
  constexpr auto integralRange(const Begin& begin, const End& end, const PriorityTag<0>&)
  {
    assert(begin <= end);
    return Imp::DynamicIntegralRange<End>(begin, end);
  }

} // namespace Imp



/**
 * \brief Create an integral range
 *
 * \tparam Begin Type of begin entry of the range
 * \tparam End Type of end entry of the range
 *
 * \param begin First entry of the range
 * \param end One past the last entry of the range
 *
 * \returns An object encoding the given range
 *
 * If Begin and End are both instances of type
 * std::integral_constant, the returnes range
 * encodes begin and end statically.
 */
template<class Begin, class End>
constexpr auto integralRange(const Begin& begin, const End& end)
{
  return Imp::integralRange(begin, end, PriorityTag<42>());
}

/**
 * \brief Create an integral range starting from 0
 *
 * \tparam End Type of end entry of the range
 *
 * \param end One past the last entry of the range
 *
 * \returns An object encoding the given range
 *
 * This is a short cut for integralRange(_0, end).
 */
template<class End>
constexpr auto integralRange(const End& end)
{
  return Imp::integralRange(Dune::Indices::_0, end, PriorityTag<42>());
}



namespace Imp {

  template<class Range, class F, class Index, Index... i>
  constexpr void forEachIndex(Range&& range, F&& f, std::integer_sequence<Index, i...>)
  {
    std::initializer_list<int>{(f(Hybrid::elementAt(range, std::integral_constant<Index,i>())), 0)...};
  }

  template<class Range, class F,
    std::enable_if_t<IsIntegralConstant<decltype(Hybrid::size(std::declval<Range>()))>::value, int> = 0>
  constexpr void forEach(Range&& range, F&& f, PriorityTag<1>)
  {
    auto size = Hybrid::size(range);
    auto indices = std::make_index_sequence<size>();
    forEachIndex(std::forward<Range>(range), std::forward<F>(f), indices);
  }

  template<class Range, class F>
  constexpr void forEach(Range&& range, F&& f, PriorityTag<0>)
  {
    for(std::size_t i=0; i<range.size(); ++i)
      f(range[i]);
  // \ToDo Why does the following not compile?
  //  for(auto e : range)
  //    f(e);
  }

} // namespace Imp



/**
 * \brief Range based for loop
 *
 * \tparam Range Type of given range
 * \tparam F Type of given predicate
 *
 * \param range The range to loop over
 * \param f A predicate that will be called with each entry of the range
 *
 * This supports looping over the following ranges
 * * ranges obtained from integralRange()
 * * all ranges that provide Hybrid::size() and Hybrid::elementAt()
 *
 * This especially included instances of std::integer_sequence,
 * std::tuple, Dune::TupleVector, and Dune::MultiTypeBlockVector.
 */
template<class Range, class F>
constexpr void forEach(Range&& range, F&& f)
{
  Imp::forEach(std::forward<Range>(range), std::forward<F>(f), PriorityTag<42>());
}



namespace Imp {

  template<class IfFunc, class ElseFunc>
  constexpr void ifElse(std::true_type, IfFunc&& ifFunc, ElseFunc&& elseFunc)
  {
    ifFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
  }

  template<class IfFunc, class ElseFunc>
  constexpr void ifElse(std::false_type, IfFunc&& ifFunc, ElseFunc&& elseFunc)
  {
    elseFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
  }

  template<class IfFunc, class ElseFunc>
  constexpr void ifElse(const bool& condition, IfFunc&& ifFunc, ElseFunc&& elseFunc)
  {
    if (condition)
      ifFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
    else
      elseFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
  }

} // namespace Imp



/**
 * \brief A conditional expression
 *
 * This will call either ifFunc or elseFunc depending
 * on the condition. In any case a single argument
 * will be passed to the called function. This will always
 * be the indentity function. Passing an expression through
 * this function will lead to lazy evaluation. This way both
 * 'branches' can contain expressions that are only valid
 * within this branch if the condition is a std::integral_constant<bool,*>.
 *
 * In order to do this, the passed functors must have a single
 * argument of type auto.
 *
 * Due to the lazy evaluation mechanism and support for
 * std::integral_constant<bool,*> this allows to emulate
 * a static if statement.
 */
template<class Condition, class IfFunc, class ElseFunc>
constexpr void ifElse(const Condition& condition, IfFunc&& ifFunc, ElseFunc&& elseFunc)
{
  Imp::ifElse(condition, std::forward<IfFunc>(ifFunc), std::forward<ElseFunc>(elseFunc));
}

/**
 * \brief A conditional expression
 *
 * This provides an ifElse conditional with empty else clause.
 */
template<class Condition, class IfFunc>
constexpr void ifElse(const Condition& condition, IfFunc&& ifFunc)
{
  ifElse(condition, std::forward<IfFunc>(ifFunc), [](auto&& i) {});
}



namespace Imp {

  template<class T1, class T2>
  constexpr auto equals(const T1& t1, const T2& t2, PriorityTag<1>) -> decltype(T1::value, T2::value, std::integral_constant<bool,T1::value == T2::value>())
  { return {}; }

  template<class T1, class T2>
  constexpr auto equals(const T1& t1, const T2& t2, PriorityTag<0>)
  {
    return t1==t2;
  }

} // namespace Imp



/**
 * \brief Equality comparison
 *
 * If both types have a static member value, the result of comparing
 * these is returned as std::integral_constant<bool, *>. Otherwise
 * the result of a runtime comparison of t1 and t2 is directly returned.
 */
template<class T1, class T2>
constexpr auto equals(T1&& t1,  T2&& t2)
{
  return Imp::equals(std::forward<T1>(t1), std::forward<T2>(t2), PriorityTag<1>());
}



} // namespace Hybrid







// Implementation of integralRangeFor
namespace Imp {

template<class ST, ST begin, ST end>
struct StaticForLoop
{
  template<class F, class...Args>
  static void apply(F&& f, Args&&... args)
  {
    f(std::integral_constant<ST, begin>(), std::forward<Args>(args)...);
    StaticForLoop<ST, begin+1, end>::apply(std::forward<F>(f), std::forward<Args>(args)...);
  }
};

template<class ST, ST end>
struct StaticForLoop<ST, end, end>
{
  template<class F, class...Args>
  static void apply(F&& f, Args&&...)
  {}
};

// Overload for static ranges
template<class Index, class Begin, class End, class F, class... Args,
    std::enable_if_t<IsIntegralConstant<Begin>::value and IsIntegralConstant<End>::value, int> = 0>
void integralRangeFor(Begin&& begin, End&& end, F&& f, Args&&... args)
{
  static const Index begin_t = begin;
  static const Index end_t = end;
  StaticForLoop<Index, begin_t, end_t>::apply(std::forward<F>(f), std::forward<Args>(args)...);
}

// Overload for dynamic ranges
template<class Index, class Begin, class End, class F, class... Args,
    std::enable_if_t<not(IsIntegralConstant<Begin>::value and IsIntegralConstant<End>::value), int> = 0>
void integralRangeFor(Begin&& begin, End&& end, F&& f, Args&&... args)
{
  for(Index i=begin; i != end; ++i)
    f(i, std::forward<Args>(args)...);
}

}

/**
 * \brief Hybrid for loop over integral range
 *
 * \tparam Index Raw type of used indices
 * \tparam Begin Type of begin index
 * \tparam End Type of end index
 * \tparam F Type of functor containing the loop body
 * \tparam Args Types of further arguments to the loop body
 *
 * \param begin Initial index
 * \param end One past last index
 * \param f Functor to call in each loop instance
 * \param args Additional arguments to be passed to the functor
 *
 * This is a hybrid for loop that can work on statically and dynamically
 * sized containers. The functor is called with index as first argument
 * and all additional arguments. If begin and end are both of type
 * std::integral_constant<*,*> than the loop is static with indices
 * of the form std::integral_constant<Index, *>, otherwise the loop
 * is dynamic with indices type Index.
 */
template<class Index, class Begin, class End, class F, class... Args>
void integralRangeFor(Begin&& begin, End&& end, F&& f, Args&&... args)
{
  Imp::integralRangeFor<Index>(std::forward<Begin>(begin), std::forward<End>(end), std::forward<F>(f), std::forward<Args>(args)...);
}



// Implementation of hybridEquals
namespace Imp {

// Compute t1==t2 either statically or dynamically
template<class T1, class T2>
constexpr auto hybridEquals(const T1& t1, const T2& t2, PriorityTag<1>) -> decltype(T1::value, T2::value, std::integral_constant<bool,T1::value == T2::value>())
{ return {}; }

template<class T1, class T2>
constexpr auto hybridEquals(const T1& t1, const T2& t2, PriorityTag<0>)
{
  return t1==t2;
}
} //end namespace Imp

/**
 * \brief Hybrid equality comparison
 *
 * If both types have a static member value, the result of comparing
 * these is returned as std::integral_constant<bool, *>. Otherwise
 * the result of a runtime comparison of t1 and t2 is directly returned.
 */
template<class T1, class T2>
constexpr auto hybridEquals(const T1& t1, const T2& t2)
{
  return Imp::hybridEquals(t1, t2, PriorityTag<1>());
}



// Implementation of hybridIf
namespace Imp {

template<class IfFunc, class ElseFunc>
constexpr void hybridIf(std::true_type, IfFunc&& ifFunc, ElseFunc&& elseFunc)
{
  ifFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
}

template<class IfFunc, class ElseFunc>
constexpr void hybridIf(std::false_type, IfFunc&& ifFunc, ElseFunc&& elseFunc)
{
  elseFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
}

template<class IfFunc, class ElseFunc>
constexpr void hybridIf(const bool& condition, IfFunc&& ifFunc, ElseFunc&& elseFunc)
{
  if (condition)
    ifFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
  else
    elseFunc([](auto&& x) { return std::forward<decltype(x)>(x);});
}

} //end namespace Imp

/**
 * \brief Hybrid if
 *
 * This will call either ifFunc or elseFunc depending
 * on the condition. In any case a single argument
 * will be passed to the called function. This will always
 * be the indentity function. Passing an expression through
 * this function will lead to lazy evaluation. This way both
 * 'branches' can contain expressions that are only valid
 * within this branch if the condition is a std::integral_constant<bool,*>.
 *
 * In order to do this, the passed functors must have a single
 * argument of type auto.
 */
template<class Condition, class IfFunc, class ElseFunc>
constexpr void hybridIf(const Condition& condition, IfFunc&& ifFunc, ElseFunc&& elseFunc)
{
  Imp::hybridIf(condition, std::forward<IfFunc>(ifFunc), std::forward<ElseFunc>(elseFunc));
}

/**
 * \brief Hybrid if
 *
 * This provides a hybridIf with empty else clause.
 */
template<class Condition, class IfFunc>
constexpr void hybridIf(const Condition& condition, IfFunc&& ifFunc)
{
  hybridIf(condition, std::forward<IfFunc>(ifFunc), [](auto&& i) {});
}



// Everything in the next namespace block is just used to implement StaticSize, HasStaticSize, hybridSize
namespace Imp {

// As a last resort try if there's a static constexpr size()
template<class T>
constexpr auto staticSize(const T*, const PriorityTag<0>&)
  -> decltype(std::integral_constant<std::size_t,T::size()>())
{
  return {};
}

// Try if tuple_size is implemented for class
template<class T>
constexpr auto staticSize(const T*, const PriorityTag<2>&)
  -> decltype(std::integral_constant<std::size_t,std::tuple_size<T>::value>())
{
  return {};
}

// Try if tuple_size is implemented for class
template<class T, int i>
constexpr auto staticSize(const Dune::FieldVector<T, i>*, const PriorityTag<3>&)
  -> decltype(std::integral_constant<std::size_t,i>())
{
  return {};
}

template<class T>
constexpr std::false_type hasStaticSize(const T* t, const PriorityTag<0>& p)
{
  return {};
}

template<class T>
constexpr auto hasStaticSize(const T* t, const PriorityTag<1>& p)
  -> decltype(staticSize(t ,PriorityTag<42>()), std::true_type())
{
  return {};
}

}



/**
 * \brief Check if type is a statically sized container
 *
 * \ingroup Utility
 *
 * Derives from std::true_type or std::false_type
 */
template<class T>
struct HasStaticSize :
  public decltype(Imp::hasStaticSize((typename std::decay<T>::type*)(nullptr), PriorityTag<42>()))
{};



/**
 * \brief Obtain size of statically sized container
 *
 * \ingroup Utility
 *
 * Derives from std::integral_constant<std::size_t, size>
 */
template<class T>
struct StaticSize :
  public decltype(Imp::staticSize((typename std::decay<T>::type*)(nullptr), PriorityTag<42>()))
{};



/**
 * \brief Hybrid size query
 *
 * \tparam T Type of container whose size is queried
 *
 * \param t Container whose size is queried
 *
 * This function is hybrid in the sense that it returns a statically
 * encoded size, i.e., an integral_constant if possible and the
 * dynamic result of the t.size() method otherwise.
 *
 * This is the static-size overload which returns the size i
 * as std::integral_constant<std::size_t, i>.
 */
template<class T,
    std::enable_if_t<HasStaticSize<T>::value, int> = 0>
constexpr auto hybridSize(const T& t)
{
  return Imp::staticSize((T*)(nullptr), PriorityTag<42>());
}

/**
 * \brief Hybrid size query
 *
 * \tparam T Type of container whose size is queried
 *
 * \param t Container whose size is queried
 *
 * This function is hybrid in the sense that it returns a statically
 * encoded size, i.e., an integral_constant if possible and the
 * dynamic result of the *.size() method otherwise.
 *
 * This is the dynamic-size overload which returns the result
 * of t.size().
 */
template<class T,
    std::enable_if_t<not HasStaticSize<T>::value, int> = 0>
constexpr auto hybridSize(const T& t)
{
  return t.size();
}



/**
 * \brief Hybrid for loop over sparse range
 */
template<class... T, class F>
void sparseRangeFor(const Dune::MultiTypeBlockVector<T...>& range, F&& f)
{
  integralRangeFor<std::size_t>(Indices::_0, hybridSize(range), [&](auto&& i) {
      f(range[i], i);
  });
}



/**
 * \brief Hybrid for loop over sparse range
 */
template<class Range, class F>
void sparseRangeFor(Range&& range, F&& f)
{
  auto it = range.begin();
  auto end = range.end();
  for(; it!=end; ++it)
    f(*it, it.index());
}



} // namespace Solvers
} // namespace Dune


#endif// DUNE_SOLVERS_COMMON_FORLOOP_HH