///////////////////////////////////////////////////////////////
//  Copyright 2020 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

#ifndef BOOST_MULTIPRECISION_RATIONAL_ADAPTOR_HPP
#define BOOST_MULTIPRECISION_RATIONAL_ADAPTOR_HPP

#include <boost/multiprecision/number.hpp>
#include <boost/multiprecision/detail/hash.hpp>
#include <boost/multiprecision/detail/float128_functions.hpp>
#include <boost/multiprecision/detail/no_exceptions_support.hpp>

namespace boost {
namespace multiprecision {
namespace backends {

template <class Backend>
struct rational_adaptor
{
   //
   // Each backend need to declare 3 type lists which declare the types
   // with which this can interoperate.  These lists must at least contain
   // the widest type in each category - so "long long" must be the final
   // type in the signed_types list for example.  Any narrower types if not
   // present in the list will get promoted to the next wider type that is
   // in the list whenever mixed arithmetic involving that type is encountered.
   //
   typedef typename Backend::signed_types    signed_types;
   typedef typename Backend::unsigned_types  unsigned_types;
   typedef typename Backend::float_types     float_types;

   typedef typename std::tuple_element<0, unsigned_types>::type ui_type;

   static Backend get_one()
   {
      Backend t;
      t = static_cast<ui_type>(1);
      return t;
   }
   static Backend get_zero()
   {
      Backend t;
      t = static_cast<ui_type>(0);
      return t;
   }

   static const Backend& one()
   {
      static const Backend result(get_one());
      return result;
   }
   static const Backend& zero()
   {
      static const Backend result(get_zero());
      return result;
   }

   void normalize()
   {
      using default_ops::eval_gcd;
      using default_ops::eval_eq;
      using default_ops::eval_divide;

      Backend g, t;
      eval_gcd(g, m_num, m_denom);
      if (!eval_eq(g, one()))
      {
         eval_divide(t, m_num, g);
         m_num.swap(t);
         eval_divide(t, m_denom, g);
         m_denom = std::move(t);
      }
   }

   // We must have a default constructor:
   rational_adaptor()
      : m_num(zero()), m_denom(one()) {}

   rational_adaptor(const rational_adaptor& o) : m_num(o.m_num), m_denom(o.m_denom) {}
   rational_adaptor(rational_adaptor&& o) = default;

   // Optional constructors, we can make this type slightly more efficient
   // by providing constructors from any type we can handle natively.
   // These will also cause number<> to be implicitly constructible
   // from these types unless we make such constructors explicit.
   //
   template <class Arithmetic>
   rational_adaptor(const Arithmetic& val, typename std::enable_if<std::is_constructible<Backend, Arithmetic>::value && !std::is_floating_point<Arithmetic>::value>::type const* = nullptr)
      : m_num(val), m_denom(one()) {}

   //
   // Pass-through 2-arg construction of components:
   //
   template <class T, class U>
   rational_adaptor(const T& a, const U& b, typename std::enable_if<std::is_constructible<Backend, T const&>::value && std::is_constructible<Backend, U const&>::value>::type const* = nullptr)
      : m_num(a), m_denom(b)
   {
      normalize();
   }
   template <class T, class U>
   rational_adaptor(T&& a, const U& b, typename std::enable_if<std::is_constructible<Backend, T>::value && std::is_constructible<Backend, U>::value>::type const* = nullptr)
      : m_num(static_cast<T&&>(a)), m_denom(b)
   {
      normalize();
   }
   template <class T, class U>
   rational_adaptor(T&& a, U&& b, typename std::enable_if<std::is_constructible<Backend, T>::value && std::is_constructible<Backend, U>::value>::type const* = nullptr)
      : m_num(static_cast<T&&>(a)), m_denom(static_cast<U&&>(b))
   {
      normalize();
   }
   template <class T, class U>
   rational_adaptor(const T& a, U&& b, typename std::enable_if<std::is_constructible<Backend, T>::value && std::is_constructible<Backend, U>::value>::type const* = nullptr)
      : m_num(a), m_denom(static_cast<U&&>(b))
   {
      normalize();
   }
   //
   // In the absense of converting constructors, operator= takes the strain.
   // In addition to the usual suspects, there must be one operator= for each type
   // listed in signed_types, unsigned_types, and float_types plus a string constructor.
   //
   rational_adaptor& operator=(const rational_adaptor& o) = default;
   rational_adaptor& operator=(rational_adaptor&& o) = default;
   template <class Arithmetic>
   inline typename std::enable_if<!std::is_floating_point<Arithmetic>::value, rational_adaptor&>::type operator=(const Arithmetic& i)
   {
      m_num = i;
      m_denom = one();
      return *this;
   }
   rational_adaptor& operator=(const char* s)
   {
      using default_ops::eval_eq;

      std::string                        s1;
      multiprecision::number<Backend>    v1, v2;
      char                               c;
      bool                               have_hex = false;
      const char* p = s; // saved for later

      while ((0 != (c = *s)) && (c == 'x' || c == 'X' || c == '-' || c == '+' || (c >= '0' && c <= '9') || (have_hex && (c >= 'a' && c <= 'f')) || (have_hex && (c >= 'A' && c <= 'F'))))
      {
         if (c == 'x' || c == 'X')
            have_hex = true;
         s1.append(1, c);
         ++s;
      }
      v1.assign(s1);
      s1.erase();
      if (c == '/')
      {
         ++s;
         while ((0 != (c = *s)) && (c == 'x' || c == 'X' || c == '-' || c == '+' || (c >= '0' && c <= '9') || (have_hex && (c >= 'a' && c <= 'f')) || (have_hex && (c >= 'A' && c <= 'F'))))
         {
            if (c == 'x' || c == 'X')
               have_hex = true;
            s1.append(1, c);
            ++s;
         }
         v2.assign(s1);
      }
      else
         v2 = 1;
      if (*s)
      {
         BOOST_MP_THROW_EXCEPTION(std::runtime_error(std::string("Could not parse the string \"") + p + std::string("\" as a valid rational number.")));
      }
      multiprecision::number<Backend> gcd;
      eval_gcd(gcd.backend(), v1.backend(), v2.backend());
      if (!eval_eq(gcd.backend(), one()))
      {
         v1 /= gcd;
         v2 /= gcd;
      }
      num() = std::move(std::move(v1).backend());
      denom() = std::move(std::move(v2).backend());
      return *this;
   }
   template <class Float>
   typename std::enable_if<std::is_floating_point<Float>::value, rational_adaptor&>::type operator=(Float i)
   {
      using default_ops::eval_eq;
      BOOST_MP_FLOAT128_USING using std::floor; using std::frexp; using std::ldexp;

      int   e;
      Float f = frexp(i, &e);
#ifdef BOOST_HAS_FLOAT128
      f = ldexp(f, std::is_same<float128_type, Float>::value ? 113 : std::numeric_limits<Float>::digits);
      e -= std::is_same<float128_type, Float>::value ? 113 : std::numeric_limits<Float>::digits;
#else
      f = ldexp(f, std::numeric_limits<Float>::digits);
      e -= std::numeric_limits<Float>::digits;
#endif
      number<Backend> num(f);
      number<Backend> denom(1u);
      if (e > 0)
      {
         num <<= e;
      }
      else if (e < 0)
      {
         denom <<= -e;
      }
      number<Backend> gcd;
      eval_gcd(gcd.backend(), num.backend(), denom.backend());
      if (!eval_eq(gcd.backend(), one()))
      {
         num /= gcd;
         denom /= gcd;
      }
      this->num() = std::move(std::move(num).backend());
      this->denom() = std::move(std::move(denom).backend());
      return *this;
   }

   void swap(rational_adaptor& o)
   {
      m_num.swap(o.m_num);
      m_denom.swap(o.m_denom);
   }
   std::string str(std::streamsize digits, std::ios_base::fmtflags f) const
   {
      using default_ops::eval_eq;
      //
      // We format the string ourselves so we can match what GMP's mpq type does:
      //
      std::string result = num().str(digits, f);
      if (!eval_eq(denom(), one()))
      {
         result.append(1, '/');
         result.append(denom().str(digits, f));
      }
      return result;
   }
   void negate()
   {
      m_num.negate();
   }
   int compare(const rational_adaptor& o) const
   {
      std::ptrdiff_t s1 = eval_get_sign(*this);
      std::ptrdiff_t s2 = eval_get_sign(o);
      if (s1 != s2)
      {
         return s1 < s2 ? -1 : 1;
      }
      else if (s1 == 0)
         return 0; // both zero.

      bool neg = false;
      if (s1 >= 0)
      {
         s1 = eval_msb(num()) + eval_msb(o.denom());
         s2 = eval_msb(o.num()) + eval_msb(denom());
      }
      else
      {
         Backend t(num());
         t.negate();
         s1 = eval_msb(t) + eval_msb(o.denom());
         t = o.num();
         t.negate();
         s2 = eval_msb(t) + eval_msb(denom());
         neg = true;
      }
      s1 -= s2;
      if (s1 < -1)
         return neg ? 1 : -1;
      else if (s1 > 1)
         return neg ? -1 : 1;

      Backend t1, t2;
      eval_multiply(t1, num(), o.denom());
      eval_multiply(t2, o.num(), denom());
      return t1.compare(t2);
   }
   //
   // Comparison with arithmetic types, default just constructs a temporary:
   //
   template <class A>
   typename std::enable_if<boost::multiprecision::detail::is_arithmetic<A>::value, int>::type compare(A i) const
   {
      rational_adaptor t;
      t = i;  //  Note: construct directly from i if supported.
      return compare(t);
   }

   Backend& num() { return m_num; }
   const Backend& num()const { return m_num; }
   Backend& denom() { return m_denom; }
   const Backend& denom()const { return m_denom; }

   #ifndef BOOST_MP_STANDALONE
   template <class Archive>
   void serialize(Archive& ar, const std::integral_constant<bool, true>&)
   {
      // Saving
      number<Backend> n(num()), d(denom());
      ar& boost::make_nvp("numerator", n);
      ar& boost::make_nvp("denominator", d);
   }
   template <class Archive>
   void serialize(Archive& ar, const std::integral_constant<bool, false>&)
   {
      // Loading
      number<Backend> n, d;
      ar& boost::make_nvp("numerator", n);
      ar& boost::make_nvp("denominator", d);
      num() = n.backend();
      denom() = d.backend();
   }
   template <class Archive>
   void serialize(Archive& ar, const unsigned int /*version*/)
   {
      using tag = typename Archive::is_saving;
      using saving_tag = std::integral_constant<bool, tag::value>;
      serialize(ar, saving_tag());
   }
   #endif // BOOST_MP_STANDALONE

 private:
   Backend m_num, m_denom;
};

//
// Helpers:
//
template <class T>
inline constexpr typename std::enable_if<std::numeric_limits<T>::is_specialized && !std::numeric_limits<T>::is_signed, bool>::type
is_minus_one(const T&)
{
   return false;
}
template <class T>
inline constexpr typename std::enable_if<!std::numeric_limits<T>::is_specialized || std::numeric_limits<T>::is_signed, bool>::type
is_minus_one(const T& val)
{
   return val == -1;
}

//
// Required non-members:
//
template <class Backend>
inline void eval_add(rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   eval_add_subtract_imp(a, a, b, true);
}
template <class Backend>
inline void eval_subtract(rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   eval_add_subtract_imp(a, a, b, false);
}

template <class Backend>
inline void eval_multiply(rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   eval_multiply_imp(a, a, b.num(), b.denom());
}

template <class Backend>
void eval_divide(rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   using default_ops::eval_divide;
   rational_adaptor<Backend> t;
   eval_divide(t, a, b);
   a = std::move(t);
}
//
// Conversions:
//
template <class R, class IntBackend>
inline typename std::enable_if<number_category<R>::value == number_kind_floating_point>::type eval_convert_to(R* result, const rational_adaptor<IntBackend>& backend)
{
   //
   // The generic conversion is as good as anything we can write here:
   //
   ::boost::multiprecision::detail::generic_convert_rational_to_float(*result, backend);
}

template <class R, class IntBackend>
inline typename std::enable_if<(number_category<R>::value != number_kind_integer) && (number_category<R>::value != number_kind_floating_point) && !std::is_enum<R>::value>::type eval_convert_to(R* result, const rational_adaptor<IntBackend>& backend)
{
   using default_ops::eval_convert_to;
   R d;
   eval_convert_to(result, backend.num());
   eval_convert_to(&d, backend.denom());
   *result /= d;
}

template <class R, class Backend>
inline typename std::enable_if<number_category<R>::value == number_kind_integer>::type eval_convert_to(R* result, const rational_adaptor<Backend>& backend)
{
   using default_ops::eval_divide;
   using default_ops::eval_convert_to;
   Backend t;
   eval_divide(t, backend.num(), backend.denom());
   eval_convert_to(result, t);
}

//
// Hashing support, not strictly required, but it is used in our tests:
//
template <class Backend>
inline std::size_t hash_value(const rational_adaptor<Backend>& arg)
{
   std::size_t result = hash_value(arg.num());
   std::size_t result2 = hash_value(arg.denom());
   boost::multiprecision::detail::hash_combine(result, result2);
   return result;
}
//
// assign_components:
//
template <class Backend>
void assign_components(rational_adaptor<Backend>& result, Backend const& a, Backend const& b)
{
   using default_ops::eval_gcd;
   using default_ops::eval_divide;
   using default_ops::eval_eq;

   Backend g;
   eval_gcd(g, a, b);
   if (eval_eq(g, rational_adaptor<Backend>::one()))
   {
      result.num() = a;
      result.denom() = b;
   }
   else
   {
      eval_divide(result.num(), a, g);
      eval_divide(result.denom(), b, g);
   }
}
//
// Again for arithmetic types, overload for whatever arithmetic types are directly supported:
//
template <class Backend, class Arithmetic1, class Arithmetic2>
inline void assign_components(rational_adaptor<Backend>& result, const Arithmetic1& a, const Arithmetic2& b)
{
   using default_ops::eval_gcd;
   using default_ops::eval_divide;
   using default_ops::eval_eq;

   Backend g;
   result.num()   = a;
   eval_gcd(g, result.num(), b);
   if (eval_eq(g, rational_adaptor<Backend>::one()))
   {
      result.denom() = b;
   }
   else
   {
      eval_divide(result.num(), g);
      eval_divide(result.denom(), b, g);
   }
}
//
// Optional comparison operators:
//
template <class Backend>
inline bool eval_is_zero(const rational_adaptor<Backend>& arg)
{
   using default_ops::eval_is_zero;
   return eval_is_zero(arg.num());
}

template <class Backend>
inline int eval_get_sign(const rational_adaptor<Backend>& arg)
{
   using default_ops::eval_get_sign;
   return eval_get_sign(arg.num());
}

template <class Backend>
inline bool eval_eq(const rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   using default_ops::eval_eq;
   return eval_eq(a.num(), b.num()) && eval_eq(a.denom(), b.denom());
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value&& std::is_integral<Arithmetic>::value, bool>::type
   eval_eq(const rational_adaptor<Backend>& a, Arithmetic b)
{
   using default_ops::eval_eq;
   return eval_eq(a.denom(), rational_adaptor<Backend>::one()) && eval_eq(a.num(), b);
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value&& std::is_integral<Arithmetic>::value, bool>::type
   eval_eq(Arithmetic b, const rational_adaptor<Backend>& a)
{
   using default_ops::eval_eq;
   return eval_eq(a.denom(), rational_adaptor<Backend>::one()) && eval_eq(a.num(), b);
}

//
// Arithmetic operations, starting with addition:
//
template <class Backend, class Arithmetic>
void eval_add_subtract_imp(rational_adaptor<Backend>& result, const Arithmetic& arg, bool isaddition)
{
   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_add;
   using default_ops::eval_gcd;
   Backend t;
   eval_multiply(t, result.denom(), arg);
   if (isaddition)
      eval_add(result.num(), t);
   else
      eval_subtract(result.num(), t);
   //
   // There is no need to re-normalize here, we have
   // (a + bm) / b
   // and gcd(a + bm, b) = gcd(a, b) = 1
   //
   /*
   eval_gcd(t, result.num(), result.denom());
   if (!eval_eq(t, rational_adaptor<Backend>::one()) != 0)
   {
      Backend t2;
      eval_divide(t2, result.num(), t);
      t2.swap(result.num());
      eval_divide(t2, result.denom(), t);
      t2.swap(result.denom());
   }
   */
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_add(rational_adaptor<Backend>& result, const Arithmetic& arg)
{
   eval_add_subtract_imp(result, arg, true);
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_subtract(rational_adaptor<Backend>& result, const Arithmetic& arg)
{
   eval_add_subtract_imp(result, arg, false);
}

template <class Backend>
void eval_add_subtract_imp(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b, bool isaddition)
{
   using default_ops::eval_eq;
   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_add;
   using default_ops::eval_subtract;
   //
   // Let  a = an/ad
   //      b = bn/bd
   //      g = gcd(ad, bd)
   // result = rn/rd
   //
   // Then:
   // rn = an * (bd/g) + bn * (ad/g)
   // rd = ad * (bd/g)
   //    = (ad/g) * (bd/g) * g
   //
   // And the whole thing can then be rescaled by
   //      gcd(rn, g)
   //
   Backend gcd, t1, t2, t3, t4;
   //
   // Begin by getting the gcd of the 2 denominators:
   //
   eval_gcd(gcd, a.denom(), b.denom());
   //
   // Do we have gcd > 1:
   //
   if (!eval_eq(gcd, rational_adaptor<Backend>::one()))
   {
      //
      // Scale the denominators by gcd, and put the results in t1 and t2:
      //
      eval_divide(t1, b.denom(), gcd);
      eval_divide(t2, a.denom(), gcd);
      //
      // multiply the numerators by the scale denominators and put the results in t3, t4:
      //
      eval_multiply(t3, a.num(), t1);
      eval_multiply(t4, b.num(), t2);
      //
      // Add them up:
      //
      if (isaddition)
         eval_add(t3, t4);
      else
         eval_subtract(t3, t4);
      //
      // Get the gcd of gcd and our numerator (t3):
      //
      eval_gcd(t4, t3, gcd);
      if (eval_eq(t4, rational_adaptor<Backend>::one()))
      {
         result.num() = t3;
         eval_multiply(result.denom(), t1, a.denom());
      }
      else
      {
         //
         // Uncommon case where gcd is not 1, divide the numerator
         // and the denominator terms by the new gcd.  Note we perform division
         // on the existing gcd value as this is the smallest of the 3 denominator
         // terms we'll be multiplying together, so there's a good chance it's a
         // single limb value already:
         //
         eval_divide(result.num(), t3, t4);
         eval_divide(t3, gcd, t4);
         eval_multiply(t4, t1, t2);
         eval_multiply(result.denom(), t4, t3);
      }
   }
   else
   {
      //
      // Most common case (approx 60%) where gcd is one:
      //
      eval_multiply(t1, a.num(), b.denom());
      eval_multiply(t2, a.denom(), b.num());
      if (isaddition)
         eval_add(result.num(), t1, t2);
      else
         eval_subtract(result.num(), t1, t2);
      eval_multiply(result.denom(), a.denom(), b.denom());
   }
}

template <class Backend>
inline void eval_add(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   eval_add_subtract_imp(result, a, b, true);
}
template <class Backend>
inline void eval_subtract(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   eval_add_subtract_imp(result, a, b, false);
}

template <class Backend, class Arithmetic>
void eval_add_subtract_imp(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const Arithmetic& b, bool isaddition)
{
   using default_ops::eval_add;
   using default_ops::eval_subtract;
   using default_ops::eval_multiply;

   if (&result == &a)
      return eval_add_subtract_imp(result, b, isaddition);

   eval_multiply(result.num(), a.denom(), b);
   if (isaddition)
      eval_add(result.num(), a.num());
   else
      BOOST_IF_CONSTEXPR(std::numeric_limits<Backend>::is_signed == false)
   {
      Backend t;
      eval_subtract(t, a.num(), result.num());
      result.num() = std::move(t);
   }
   else
   {
      eval_subtract(result.num(), a.num());
      result.negate();
   }
   result.denom() = a.denom();
   //
   // There is no need to re-normalize here, we have
   // (a + bm) / b
   // and gcd(a + bm, b) = gcd(a, b) = 1
   //
}
template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_add(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const Arithmetic& b)
{
   eval_add_subtract_imp(result, a, b, true);
}
template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_subtract(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const Arithmetic& b)
{
   eval_add_subtract_imp(result, a, b, false);
}

//
// Multiplication:
//
template <class Backend>
void eval_multiply_imp(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const Backend& b_num, const Backend& b_denom)
{
   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_gcd;
   using default_ops::eval_get_sign;
   using default_ops::eval_eq;

   Backend gcd_left, gcd_right, t1, t2;
   eval_gcd(gcd_left, a.num(), b_denom);
   eval_gcd(gcd_right, b_num, a.denom());
   //
   // Unit gcd's are the most likely case:
   //
   bool b_left = eval_eq(gcd_left, rational_adaptor<Backend>::one());
   bool b_right = eval_eq(gcd_right, rational_adaptor<Backend>::one());

   if (b_left && b_right)
   {
      eval_multiply(result.num(), a.num(), b_num);
      eval_multiply(result.denom(), a.denom(), b_denom);
   }
   else if (b_left)
   {
      eval_divide(t2, b_num, gcd_right);
      eval_multiply(result.num(), a.num(), t2);
      eval_divide(t1, a.denom(), gcd_right);
      eval_multiply(result.denom(), t1, b_denom);
   }
   else if (b_right)
   {
      eval_divide(t1, a.num(), gcd_left);
      eval_multiply(result.num(), t1, b_num);
      eval_divide(t2, b_denom, gcd_left);
      eval_multiply(result.denom(), a.denom(), t2);
   }
   else
   {
      eval_divide(t1, a.num(), gcd_left);
      eval_divide(t2, b_num, gcd_right);
      eval_multiply(result.num(), t1, t2);
      eval_divide(t1, a.denom(), gcd_right);
      eval_divide(t2, b_denom, gcd_left);
      eval_multiply(result.denom(), t1, t2);
   }
   //
   // We may have b_denom negative if this is actually division, if so just correct things now:
   //
   if (eval_get_sign(b_denom) < 0)
   {
      result.num().negate();
      result.denom().negate();
   }
}

template <class Backend>
void eval_multiply(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   using default_ops::eval_multiply;

   if (&a == &b)
   {
      // squaring, gcd's are 1:
      eval_multiply(result.num(), a.num(), b.num());
      eval_multiply(result.denom(), a.denom(), b.denom());
      return;
   }
   eval_multiply_imp(result, a, b.num(), b.denom());
}

template <class Backend, class Arithmetic>
void eval_multiply_imp(Backend& result_num, Backend& result_denom, Arithmetic arg)
{
   if (arg == 0)
   {
      result_num = rational_adaptor<Backend>::zero();
      result_denom = rational_adaptor<Backend>::one();
      return;
   }
   else if (arg == 1)
      return;

   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_gcd;
   using default_ops::eval_convert_to;

   Backend gcd, t;
   Arithmetic integer_gcd;
   eval_gcd(gcd, result_denom, arg);
   eval_convert_to(&integer_gcd, gcd);
   arg /= integer_gcd;
   if (boost::multiprecision::detail::unsigned_abs(arg) > 1)
   {
      eval_multiply(t, result_num, arg);
      result_num = std::move(t);
   }
   else if (is_minus_one(arg))
      result_num.negate();
   if (integer_gcd > 1)
   {
      eval_divide(t, result_denom, integer_gcd);
      result_denom = std::move(t);
   }
}
template <class Backend>
void eval_multiply_imp(Backend& result_num, Backend& result_denom, Backend arg)
{
   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_gcd;
   using default_ops::eval_convert_to;
   using default_ops::eval_is_zero;
   using default_ops::eval_eq;
   using default_ops::eval_get_sign;

   if (eval_is_zero(arg))
   {
      result_num = rational_adaptor<Backend>::zero();
      result_denom = rational_adaptor<Backend>::one();
      return;
   }
   else if (eval_eq(arg, rational_adaptor<Backend>::one()))
      return;

   Backend gcd, t;
   eval_gcd(gcd, result_denom, arg);
   if (!eval_eq(gcd, rational_adaptor<Backend>::one()))
   {
      eval_divide(t, arg, gcd);
      arg = t;
   }
   else
      t = arg;
   if (eval_get_sign(arg) < 0)
      t.negate();

   if (!eval_eq(t, rational_adaptor<Backend>::one()))
   {
      eval_multiply(t, result_num, arg);
      result_num = std::move(t);
   }
   else if (eval_get_sign(arg) < 0)
      result_num.negate();
   if (!eval_eq(gcd, rational_adaptor<Backend>::one()))
   {
      eval_divide(t, result_denom, gcd);
      result_denom = std::move(t);
   }
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_multiply(rational_adaptor<Backend>& result, const Arithmetic& arg)
{
   eval_multiply_imp(result.num(), result.denom(), arg);
}

template <class Backend, class Arithmetic>
typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && std::is_integral<Arithmetic>::value>::type
   eval_multiply_imp(rational_adaptor<Backend>& result, const Backend& a_num, const Backend& a_denom, Arithmetic b)
{
   if (b == 0)
   {
      result.num() = rational_adaptor<Backend>::zero();
      result.denom() = rational_adaptor<Backend>::one();
      return;
   }
   else if (b == 1)
   {
      result.num() = a_num;
      result.denom() = a_denom;
      return;
   }

   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_gcd;
   using default_ops::eval_convert_to;

   Backend gcd;
   Arithmetic integer_gcd;
   eval_gcd(gcd, a_denom, b);
   eval_convert_to(&integer_gcd, gcd);
   b /= integer_gcd;
   if (boost::multiprecision::detail::unsigned_abs(b) > 1)
      eval_multiply(result.num(), a_num, b);
   else if (is_minus_one(b))
   {
      result.num() = a_num;
      result.num().negate();
   }
   else
      result.num() = a_num;
   if (integer_gcd > 1)
      eval_divide(result.denom(), a_denom, integer_gcd);
   else
      result.denom() = a_denom;
}
template <class Backend>
inline void eval_multiply_imp(rational_adaptor<Backend>& result, const Backend& a_num, const Backend& a_denom, const Backend& b)
{
   result.num() = a_num;
   result.denom() = a_denom;
   eval_multiply_imp(result.num(), result.denom(), b);
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_multiply(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const Arithmetic& b)
{
   if (&result == &a)
      return eval_multiply(result, b);

   eval_multiply_imp(result, a.num(), a.denom(), b);
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_multiply(rational_adaptor<Backend>& result, const Arithmetic& b, const rational_adaptor<Backend>& a)
{
   return eval_multiply(result, a, b);
}

//
// Division:
//
template <class Backend>
inline void eval_divide(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, const rational_adaptor<Backend>& b)
{
   using default_ops::eval_multiply;
   using default_ops::eval_get_sign;

   if (eval_get_sign(b.num()) == 0)
   {
      BOOST_MP_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
      return;
   }
   if (&a == &b)
   {
      // Huh? Really?
      result.num() = result.denom() = rational_adaptor<Backend>::one();
      return;
   }
   if (&result == &b)
   {
      rational_adaptor<Backend> t(b);
      return eval_divide(result, a, t);
   }
   eval_multiply_imp(result, a, b.denom(), b.num());
}

template <class Backend, class Arithmetic>
inline typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && (std::is_integral<Arithmetic>::value || std::is_same<Arithmetic, Backend>::value)>::type
   eval_divide(rational_adaptor<Backend>& result, const Arithmetic& b, const rational_adaptor<Backend>& a)
{
   using default_ops::eval_get_sign;

   if (eval_get_sign(a.num()) == 0)
   {
      BOOST_MP_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
      return;
   }
   if (&a == &result)
   {
      eval_multiply_imp(result.denom(), result.num(), b);
      result.num().swap(result.denom());
   }
   else
      eval_multiply_imp(result, a.denom(), a.num(), b);

   if (eval_get_sign(result.denom()) < 0)
   {
      result.num().negate();
      result.denom().negate();
   }
}

template <class Backend, class Arithmetic>
typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && std::is_integral<Arithmetic>::value>::type
eval_divide(rational_adaptor<Backend>& result, Arithmetic arg)
{
   if (arg == 0)
   {
      BOOST_MP_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
      return;
   }
   else if (arg == 1)
      return;
   else if (is_minus_one(arg))
   {
      result.negate();
      return;
   }
   if (eval_get_sign(result) == 0)
   {
      return;
   }

   using default_ops::eval_multiply;
   using default_ops::eval_gcd;
   using default_ops::eval_convert_to;
   using default_ops::eval_divide;

   Backend gcd, t;
   Arithmetic integer_gcd;
   eval_gcd(gcd, result.num(), arg);
   eval_convert_to(&integer_gcd, gcd);
   arg /= integer_gcd;

   eval_multiply(t, result.denom(), boost::multiprecision::detail::unsigned_abs(arg));
   result.denom() = std::move(t);
   if (arg < 0)
   {
      result.num().negate();
   }
   if (integer_gcd > 1)
   {
      eval_divide(t, result.num(), integer_gcd);
      result.num() = std::move(t);
   }
}
template <class Backend>
void eval_divide(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, Backend arg)
{
   using default_ops::eval_multiply;
   using default_ops::eval_gcd;
   using default_ops::eval_convert_to;
   using default_ops::eval_divide;
   using default_ops::eval_is_zero;
   using default_ops::eval_eq;
   using default_ops::eval_get_sign;

   if (eval_is_zero(arg))
   {
      BOOST_MP_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
      return;
   }
   else if (eval_eq(a, rational_adaptor<Backend>::one()) || (eval_get_sign(a) == 0))
   {
      if (&result != &a)
         result = a;
      return;
   }

   Backend gcd, u_arg, t;
   eval_gcd(gcd, a.num(), arg);
   bool has_unit_gcd = eval_eq(gcd, rational_adaptor<Backend>::one());
   if (!has_unit_gcd)
   {
      eval_divide(u_arg, arg, gcd);
      arg = u_arg;
   }
   else
      u_arg = arg;
   if (eval_get_sign(u_arg) < 0)
      u_arg.negate();

   eval_multiply(t, a.denom(), u_arg);
   result.denom() = std::move(t);

   if (!has_unit_gcd)
   {
      eval_divide(t, a.num(), gcd);
      result.num() = std::move(t);
   }
   else if (&result != &a)
      result.num() = a.num();

   if (eval_get_sign(arg) < 0)
   {
      result.num().negate();
   }
}
template <class Backend>
void eval_divide(rational_adaptor<Backend>& result, Backend arg)
{
   eval_divide(result, result, arg);
}

template <class Backend, class Arithmetic>
typename std::enable_if<std::is_convertible<Arithmetic, Backend>::value && std::is_integral<Arithmetic>::value>::type
   eval_divide(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& a, Arithmetic arg)
{
   if (&result == &a)
      return eval_divide(result, arg);
   if (arg == 0)
   {
      BOOST_MP_THROW_EXCEPTION(std::overflow_error("Integer division by zero"));
      return;
   }
   else if (arg == 1)
   {
      result = a;
      return;
   }
   else if (is_minus_one(arg))
   {
      result = a;
      result.num().negate();
      return;
   }

   if (eval_get_sign(a) == 0)
   {
      result = a;
      return;
   }

   using default_ops::eval_multiply;
   using default_ops::eval_divide;
   using default_ops::eval_gcd;
   using default_ops::eval_convert_to;

   Backend gcd;
   Arithmetic integer_gcd;
   eval_gcd(gcd, a.num(), arg);
   eval_convert_to(&integer_gcd, gcd);
   arg /= integer_gcd;
   eval_multiply(result.denom(), a.denom(), boost::multiprecision::detail::unsigned_abs(arg));

   if (integer_gcd > 1)
   {
      eval_divide(result.num(), a.num(), integer_gcd);
   }
   else
      result.num() = a.num();
   if (arg < 0)
   {
      result.num().negate();
   }
}

//
// Increment and decrement:
//
template <class Backend>
inline void eval_increment(rational_adaptor<Backend>& arg)
{
   using default_ops::eval_add;
   eval_add(arg.num(), arg.denom());
}
template <class Backend>
inline void eval_decrement(rational_adaptor<Backend>& arg)
{
   using default_ops::eval_subtract;
   eval_subtract(arg.num(), arg.denom());
}

//
// abs:
//
template <class Backend>
inline void eval_abs(rational_adaptor<Backend>& result, const rational_adaptor<Backend>& arg)
{
   using default_ops::eval_abs;
   eval_abs(result.num(), arg.num());
   result.denom() = arg.denom();
}

} // namespace backends

//
// Import the backend into this namespace:
//
using boost::multiprecision::backends::rational_adaptor;
//
// Define a category for this number type, one of:
//
//    number_kind_integer
//    number_kind_floating_point
//    number_kind_rational
//    number_kind_fixed_point
//    number_kind_complex
//
template<class Backend>
struct number_category<rational_adaptor<Backend> > : public std::integral_constant<int, number_kind_rational>
{};

template <class IntBackend>
struct expression_template_default<backends::rational_adaptor<IntBackend> > : public expression_template_default<IntBackend>
{};

template <class Backend, expression_template_option ExpressionTemplates>
struct component_type<number<rational_adaptor<Backend>, ExpressionTemplates> >
{
   typedef number<Backend, ExpressionTemplates> type;
};

template <class IntBackend, expression_template_option ET>
inline number<IntBackend, ET> numerator(const number<rational_adaptor<IntBackend>, ET>& val)
{
   return val.backend().num();
}
template <class IntBackend, expression_template_option ET>
inline number<IntBackend, ET> denominator(const number<rational_adaptor<IntBackend>, ET>& val)
{
   return val.backend().denom();
}

template <class Backend>
struct is_unsigned_number<rational_adaptor<Backend> > : public is_unsigned_number<Backend>
{};

}} // namespace boost::multiprecision

namespace std {

   template <class IntBackend, boost::multiprecision::expression_template_option ExpressionTemplates>
   class numeric_limits<boost::multiprecision::number<boost::multiprecision::rational_adaptor<IntBackend>, ExpressionTemplates> > : public std::numeric_limits<boost::multiprecision::number<IntBackend, ExpressionTemplates> >
   {
      using base_type = std::numeric_limits<boost::multiprecision::number<IntBackend> >;
      using number_type = boost::multiprecision::number<boost::multiprecision::rational_adaptor<IntBackend> >;

   public:
      static constexpr bool is_integer = false;
      static constexpr bool is_exact = true;
      static constexpr      number_type(min)() { return (base_type::min)(); }
      static constexpr      number_type(max)() { return (base_type::max)(); }
      static constexpr number_type lowest() { return -(max)(); }
      static constexpr number_type epsilon() { return base_type::epsilon(); }
      static constexpr number_type round_error() { return epsilon() / 2; }
      static constexpr number_type infinity() { return base_type::infinity(); }
      static constexpr number_type quiet_NaN() { return base_type::quiet_NaN(); }
      static constexpr number_type signaling_NaN() { return base_type::signaling_NaN(); }
      static constexpr number_type denorm_min() { return base_type::denorm_min(); }
   };

   template <class IntBackend, boost::multiprecision::expression_template_option ExpressionTemplates>
   constexpr bool numeric_limits<boost::multiprecision::number<boost::multiprecision::rational_adaptor<IntBackend>, ExpressionTemplates> >::is_integer;
   template <class IntBackend, boost::multiprecision::expression_template_option ExpressionTemplates>
   constexpr bool numeric_limits<boost::multiprecision::number<boost::multiprecision::rational_adaptor<IntBackend>, ExpressionTemplates> >::is_exact;

} // namespace std

#endif


// Boost.Units - A C++ library for zero-overhead dimensional analysis and
// unit/quantity manipulation and conversion
//
// Copyright (C) 2003-2008 Matthias Christian Schabel
// Copyright (C) 2008 Steven Watanabe
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_UNITS_CODATA_DEUTERON_CONSTANTS_HPP
#define BOOST_UNITS_CODATA_DEUTERON_CONSTANTS_HPP

#include <boost/units/static_constant.hpp>

#include <boost/units/systems/detail/constants.hpp>
#include <boost/units/systems/si/amount.hpp>
#include <boost/units/systems/si/area.hpp>
#include <boost/units/systems/si/electric_charge.hpp>
#include <boost/units/systems/si/energy.hpp>
#include <boost/units/systems/si/frequency.hpp>
#include <boost/units/systems/si/length.hpp>
#include <boost/units/systems/si/mass.hpp>
#include <boost/units/systems/si/magnetic_flux_density.hpp>
#include <boost/units/systems/si/time.hpp>
#include <boost/units/systems/si/wavenumber.hpp>

#include <boost/units/systems/si/codata/typedefs.hpp>

/// \file
/// CODATA recommended values of fundamental atomic and nuclear constants
/// CODATA 2006 values as of 2007/03/30

namespace boost {

namespace units {

namespace si {

namespace constants {

namespace codata {

/// CODATA recommended values of the fundamental physical constants: NIST SP 961

/// deuteron mass
BOOST_UNITS_PHYSICAL_CONSTANT(m_d,quantity<mass>,3.34358320e-27*kilograms,1.7e-34*kilograms);
/// deuteron-electron mass ratio
BOOST_UNITS_PHYSICAL_CONSTANT(m_d_over_m_e,quantity<dimensionless>,3670.4829654*dimensionless(),1.6e-6*dimensionless());
/// deuteron-proton mass ratio
BOOST_UNITS_PHYSICAL_CONSTANT(m_d_over_m_p,quantity<dimensionless>,1.99900750108*dimensionless(),2.2e-10*dimensionless());
/// deuteron molar mass
BOOST_UNITS_PHYSICAL_CONSTANT(M_d,quantity<mass_over_amount>,2.013553212724e-3*kilograms/mole,7.8e-14*kilograms/mole);
/// deuteron rms charge radius
BOOST_UNITS_PHYSICAL_CONSTANT(R_d,quantity<length>,2.1402e-15*meters,2.8e-18*meters);
/// deuteron magnetic moment
BOOST_UNITS_PHYSICAL_CONSTANT(mu_d,quantity<energy_over_magnetic_flux_density>,0.433073465e-26*joules/tesla,1.1e-34*joules/tesla);
/// deuteron-Bohr magneton ratio
BOOST_UNITS_PHYSICAL_CONSTANT(mu_d_over_mu_B,quantity<dimensionless>,0.4669754556e-3*dimensionless(),3.9e-12*dimensionless());
/// deuteron-nuclear magneton ratio
BOOST_UNITS_PHYSICAL_CONSTANT(mu_d_over_mu_N,quantity<dimensionless>,0.8574382308*dimensionless(),7.2e-9*dimensionless());
/// deuteron g-factor
BOOST_UNITS_PHYSICAL_CONSTANT(g_d,quantity<dimensionless>,0.8574382308*dimensionless(),7.2e-9*dimensionless());
/// deuteron-electron magnetic moment ratio
BOOST_UNITS_PHYSICAL_CONSTANT(mu_d_over_mu_e,quantity<dimensionless>,-4.664345537e-4*dimensionless(),3.9e-12*dimensionless());
/// deuteron-proton magnetic moment ratio
BOOST_UNITS_PHYSICAL_CONSTANT(mu_d_over_mu_p,quantity<dimensionless>,0.3070122070*dimensionless(),2.4e-9*dimensionless());
/// deuteron-neutron magnetic moment ratio
BOOST_UNITS_PHYSICAL_CONSTANT(mu_d_over_mu_n,quantity<dimensionless>,-0.44820652*dimensionless(),1.1e-7*dimensionless());

} // namespace codata

} // namespace constants

} // namespace si

} // namespace units

} // namespace boost

#endif // BOOST_UNITS_CODATA_DEUTERON_CONSTANTS_HPP


/*
 *            Copyright 2009-2018 The VOTCA Development Team
 *                       (http://www.votca.org)
 *
 *      Licensed under the Apache License, Version 2.0 (the "License")
 *
 * You may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *              http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <votca/xtp/aomatrix.h>
#include <votca/xtp/bsecoupling.h>
#include <votca/tools/constants.h>
#include <boost/format.hpp>

namespace votca { namespace xtp {

using boost::format;
using namespace tools;

void BSECoupling::Initialize(Property& options){

    #if (GWBSE_DOUBLE)
        CTP_LOG(ctp::logDEBUG, *_pLog) <<  " Compiled with full double support" << flush;
    #else
        CTP_LOG(ctp::logDEBUG, *_pLog) <<  " Compiled with float/double mixture (standard)" << flush;
    #endif

    std::string key = Identify();
    _doSinglets=false;
    _doTriplets=false;
   _output_perturbation=false;

    _openmp_threads = 0;

    if ( options.exists( key + ".openmp") ) {
                 _openmp_threads = options.get(key + ".openmp").as<int> ();
            }

    string spintype   = options.get(key + ".spin").as<string> ();
        if(spintype=="all"){
            _doSinglets=true;
            _doTriplets=true;
        }
        else if(spintype=="triplet"){
            _doTriplets=true;
        }
        else if(spintype=="singlet"){
            _doSinglets=true;
        }
        else{
            throw std::runtime_error((boost::format("Choice % for type not known. Available singlet,triplet,all") % spintype).str());
        }

   if ( options.exists( key + ".algorithm") ) {
                string algorithm = options.get(key + ".algorithm").as<string> ();
                 if(algorithm=="perturbation"){
                    _output_perturbation=true;
                 }
   }

        _levA  = options.get(key + ".moleculeA.states").as<int> ();
        _levB  = options.get(key + ".moleculeB.states").as<int> ();
        _occA  = options.get(key + ".moleculeA.occLevels").as<int> ();
        _occB  = options.get(key + ".moleculeB.occLevels").as<int> ();
        _unoccA  = options.get(key + ".moleculeA.unoccLevels").as<int> ();
        _unoccB  = options.get(key + ".moleculeB.unoccLevels").as<int> ();

}

void BSECoupling::WriteToProperty(const Orbitals& orbitalsA, const Orbitals& orbitalsB,
                        Property& summary, const QMState& stateA, const QMState& stateB){
  Property &coupling_summary = summary.add("coupling","");
  double energyA =0;
   double energyB =0;
  double JAB_pert=0;
  double JAB_diag=0;
  if(stateA.Type()==QMStateType::Singlet){
      energyA=orbitalsA.BSESingletEnergies()(stateA.Index())*conv::hrt2ev;
      energyB=orbitalsB.BSESingletEnergies()(stateB.Index())*conv::hrt2ev;
      JAB_pert=getSingletCouplingElement(stateA.Index(),stateB.Index(),0);
      JAB_diag=getSingletCouplingElement(stateA.Index(),stateB.Index(),1);
  }else if(stateA.Type()==QMStateType::Triplet){
      energyA=orbitalsA.BSETripletEnergies()(stateA.Index())*conv::hrt2ev;
      energyB=orbitalsB.BSETripletEnergies()(stateB.Index())*conv::hrt2ev;
      JAB_pert=getTripletCouplingElement(stateA.Index(),stateB.Index(),0);
      JAB_diag=getTripletCouplingElement(stateA.Index(),stateB.Index(),1);
  }
  coupling_summary.setAttribute("stateA", stateA.ToString());
  coupling_summary.setAttribute("stateB", stateB.ToString());
  coupling_summary.setAttribute("eA", (format("%1$1.6e") % energyA).str());
  coupling_summary.setAttribute("eB", (format("%1$1.6e") % energyB).str());
  coupling_summary.setAttribute("j_pert", (format("%1$1.6e") % JAB_pert).str());
  coupling_summary.setAttribute("j_diag", (format("%1$1.6e") % JAB_diag).str());
}

void BSECoupling::Addoutput(Property &type_summary,const Orbitals& orbitalsA,
                               const Orbitals& orbitalsB){
    tools::Property& bsecoupling= type_summary.add(Identify(),"");
    string algorithm="j_diag";
    if (_output_perturbation){
        algorithm="j_pert";
    }
    if (_doSinglets){
        QMStateType singlet=QMStateType(QMStateType::Singlet);
        Property &singlet_summary = bsecoupling.add(singlet.ToLongString(),"");
        singlet_summary.setAttribute("algorithm",algorithm);
        for (int stateA = 0; stateA < _levA ; ++stateA ) {
             QMState qmstateA=QMState(singlet,stateA,false);
           for (int stateB = 0; stateB <_levB ; ++stateB ) {
               QMState qmstateB=QMState(singlet,stateB,false);
               WriteToProperty(orbitalsA, orbitalsB, singlet_summary, qmstateA, qmstateB);
           }
        }
    }

    if ( _doTriplets){
        QMStateType triplet=QMStateType(QMStateType::Triplet);
        Property &triplet_summary = bsecoupling.add(triplet.ToLongString(),"");
        triplet_summary.setAttribute("algorithm",algorithm);
        for (int stateA = 0; stateA < _levA ; ++stateA ) {
               QMState qmstateA=QMState(triplet,stateA,false);
           for (int stateB = 0; stateB <_levB ; ++stateB ) {
               QMState qmstateB=QMState(triplet,stateB,false);
               WriteToProperty(orbitalsA, orbitalsB, triplet_summary, qmstateA, qmstateB);
           }
        }
    }
}

double BSECoupling::getSingletCouplingElement( int levelA, int levelB, int methodindex) {
    return JAB_singlet[methodindex]( levelA  , levelB +  _levA ) * votca::tools::conv::hrt2ev;
}

double BSECoupling::getTripletCouplingElement( int levelA, int levelB, int methodindex) {
    return JAB_triplet[methodindex]( levelA  , levelB + _levA ) * votca::tools::conv::hrt2ev;
}

/**
 * \brief evaluates electronic couplings
 *
 * @param _orbitalsA molecular orbitals of molecule A
 * @param _orbitalsB molecular orbitals of molecule B
 * @param _orbitalsAB molecular orbitals of the dimer AB
 */
void BSECoupling::CalculateCouplings(const Orbitals& orbitalsA, const Orbitals& orbitalsB, Orbitals& orbitalsAB) {
       CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Calculating exciton couplings" << flush;
    #ifdef _OPENMP

    if ( _openmp_threads > 0 ) omp_set_num_threads(_openmp_threads);
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << " Using "<< omp_get_max_threads()<<" threads" << flush;
    #endif

    CheckAtomCoordinates(orbitalsA, orbitalsB, orbitalsAB);

    // constructing the direct product orbA x orbB
    int basisA = orbitalsA.getBasisSetSize();
    int basisB = orbitalsB.getBasisSetSize();

    if ( ( basisA == 0 ) || ( basisB == 0 ) ) {
        throw std::runtime_error("Basis set size is not stored in monomers");
    }

    // number of levels stored in monomers
    int levelsA = orbitalsA.getNumberOfLevels();
    int levelsB = orbitalsB.getNumberOfLevels();

    // get exciton information of molecule A
    int _bseA_cmax        = orbitalsA.getBSEcmax();
    int _bseA_cmin        = orbitalsA.getBSEcmin();
    int _bseA_vmax        = orbitalsA.getBSEvmax();
    int _bseA_vmin        = orbitalsA.getBSEvmin();
    int _bseA_vtotal      = _bseA_vmax - _bseA_vmin +1 ;
    int _bseA_ctotal      = _bseA_cmax - _bseA_cmin +1 ;
    int _bseA_size        = _bseA_vtotal * _bseA_ctotal;
    int _bseA_singlet_exc = orbitalsA.BSESingletCoefficients().cols();
    int _bseA_triplet_exc = orbitalsA.BSETripletCoefficients().cols();

    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   molecule A has " << _bseA_singlet_exc << " singlet excitons with dimension " << _bseA_size << flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   molecule A has " << _bseA_triplet_exc << " triplet excitons with dimension " << _bseA_size << flush;

    // now, two storage assignment matrices for two-particle functions
    Eigen::MatrixXi combA;
    combA.resize(_bseA_size,2);
    int cnt = 0;
    for ( int _v = 0; _v < _bseA_vtotal; _v++){
        for ( int _c = 0; _c < _bseA_ctotal; _c++){
            combA(cnt,0) = _v;
            combA(cnt,1) = _bseA_vtotal + _c;
            cnt++;
        }
    }

    // get exciton information of molecule B
    int _bseB_cmax        = orbitalsB.getBSEcmax();
    int _bseB_cmin        = orbitalsB.getBSEcmin();
    int _bseB_vmax        = orbitalsB.getBSEvmax();
    int _bseB_vmin        = orbitalsB.getBSEvmin();
    int _bseB_vtotal      = _bseB_vmax - _bseB_vmin +1 ;
    int _bseB_ctotal      = _bseB_cmax - _bseB_cmin +1 ;
    int _bseB_size        = _bseB_vtotal * _bseB_ctotal;
    int _bseB_singlet_exc = orbitalsB.BSESingletCoefficients().cols();
    int _bseB_triplet_exc = orbitalsB.BSETripletCoefficients().cols();

    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   molecule B has " << _bseB_singlet_exc << " singlet excitons with dimension " << _bseB_size << flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   molecule B has " << _bseB_triplet_exc << " triplet excitons with dimension " << _bseB_size << flush;

    // now, two storage assignment matrices for two-particle functions
    Eigen::MatrixXi combB;
    combB.resize(_bseB_size,2);
    cnt = 0;
    for ( int _v = 0; _v < _bseB_vtotal; _v++){
        for ( int _c = 0; _c < _bseB_ctotal; _c++){
            combB(cnt,0) = _bseA_vtotal + _bseA_ctotal + _v;
            combB(cnt,1) = _bseA_vtotal + _bseA_ctotal + _bseB_vtotal + _c;
            cnt++;
        }
    }

    if(_levA>_bseA_singlet_exc){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of excitons you want is greater than stored for molecule A. Setting to max number available" << flush;
        _levA=_bseA_singlet_exc;
    }
    if(_levB>_bseB_singlet_exc){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of excitons you want is greater than stored for molecule B. Setting to max number available" << flush;
        _levB=_bseB_singlet_exc;
    }

    if(_levA>_bseA_singlet_exc){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of Frenkel states you want is greater than stored for molecule A. Setting to max number available" << flush;
        _levA=_bseA_singlet_exc;
    }
    if(_levB>_bseB_singlet_exc){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of Frenkel states you want is greater than stored for molecule B. Setting to max number available" << flush;
        _levB=_bseB_singlet_exc;
    }

    if(_unoccA>_bseA_ctotal){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of occupied orbitals in molecule A for CT creation exceeds number of KS-orbitals in BSE" << flush;
        _unoccA=_bseA_ctotal;
    }
    else if (_unoccA<0){
        _unoccA=_bseA_ctotal;
    }
    if(_unoccB>_bseB_ctotal){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of occupied orbitals in molecule B for CT creation exceeds number of KS-orbitals in BSE" << flush;
        _unoccB=_bseB_ctotal;
    }
    else if (_unoccB<0){
        _unoccB=_bseB_ctotal;
    }

    if(_occA>_bseA_vtotal){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of unoccupied orbitals in molecule A for CT creation exceeds number of KS-orbitals in BSE" << flush;
        _occA=_bseA_vtotal;
    }
    else if (_occA<0){
        _occA=_bseA_vtotal;
    }
    if(_occB>_bseB_vtotal){
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Number of unoccupied orbitals in molecule B for CT creation exceeds number of KS-orbitals in BSE" << flush;
        _occB=_bseB_vtotal;
    }else if (_occB<0){
        _occB=_bseB_vtotal;
    }

    // get exciton information of pair AB
    int _bseAB_cmax = orbitalsAB.getBSEcmax();
    int _bseAB_cmin = orbitalsAB.getBSEcmin();
    int _bseAB_vmax = orbitalsAB.getBSEvmax();
    int _bseAB_vmin = orbitalsAB.getBSEvmin();
    int _bseAB_vtotal = _bseAB_vmax - _bseAB_vmin +1 ;
    int _bseAB_ctotal = _bseAB_cmax - _bseAB_cmin +1 ;
    int _bseAB_size   = _bseAB_vtotal * _bseAB_ctotal;
    // check if electron-hole interaction matrices are stored
    if ( ! orbitalsAB.hasEHinteraction_triplet() && _doTriplets){
       throw std::runtime_error( "BSE EH for triplets not stored " );
    }
    if ( ! orbitalsAB.hasEHinteraction_singlet() && _doSinglets){
       throw std::runtime_error( "BSE EH for singlets not stored " );
    }
    const MatrixXfd&    eh_t = orbitalsAB.eh_t();
    const MatrixXfd&    eh_s = orbitalsAB.eh_s();
    if(_doTriplets){
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   dimer AB has BSE EH interaction triplet with dimension " << eh_t.rows() << " x " <<  eh_t.cols() << flush;
    }
    if(_doSinglets){
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   dimer AB has BSE EH interaction singlet with dimension " << eh_s.rows() << " x " <<  eh_s.cols() << flush;
    }
    // now, two storage assignment matrices for two-particle functions
    Eigen::MatrixXi combAB;
    combAB.resize(_bseAB_size,2);
    cnt = 0;
    for ( int _v = 0; _v < _bseAB_vtotal; _v++){
        for ( int _c = 0; _c < _bseAB_ctotal; _c++){
            combAB(cnt,0) = _bseAB_vmin + _v;
            combAB(cnt,1) = _bseAB_vmin + _bseAB_vtotal + _c;
            cnt++;
        }
    }

    // DFT levels of monomers can be reduced to those used in BSE
    levelsA = _bseA_vtotal + _bseA_ctotal;
    levelsB = _bseB_vtotal + _bseB_ctotal;
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   levels used in BSE of molA: " << _bseA_vmin << " to " << _bseA_cmax << " total: " << _bseA_vtotal + _bseA_ctotal <<  flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   levels used in BSE of molB: " << _bseB_vmin << " to " << _bseB_cmax << " total: " << _bseB_vtotal + _bseB_ctotal <<  flush;

    if ( ( levelsA == 0 ) || (levelsB == 0) ) {
        throw std::runtime_error("No information about number of occupied/unoccupied levels is stored" );
    }

    //       | Orbitals_A          0 |      | Overlap_A |
    //       | 0          Orbitals_B |.T  X   | Overlap_B |  X  ( Orbitals_AB )

    Eigen::MatrixXd psi_AxB =Eigen::MatrixXd::Zero( levelsA + levelsB, basisA + basisB  );
    // constructing merged orbitals
    psi_AxB.block(0,0,levelsA , basisA) = orbitalsA.MOCoefficients().block(_bseA_vmin,0, _bseA_cmax+1-_bseA_vmin, basisA );
    psi_AxB.block(levelsA, basisA,levelsB,basisB) =orbitalsB.MOCoefficients().block(_bseB_vmin,0,_bseB_cmax+1-_bseA_vmin,basisB);

    // psi_AxB * S_AB * psi_AB
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   projecting monomer onto dimer orbitals" << flush;

    Eigen::MatrixXd overlapAB;
    if ( orbitalsAB.hasAOOverlap() ) {
            CTP_LOG(ctp::logDEBUG,*_pLog) << "Reading overlap matrix from orbitals" << flush;
           overlapAB= orbitalsAB.AOOverlap();
    }else{
        CTP_LOG(ctp::logDEBUG,*_pLog) << "Calculating overlap matrix for basisset: "<< orbitalsAB.getDFTbasis()<< flush;
        overlapAB=CalculateOverlapMatrix(orbitalsAB);
    }

    Eigen::MatrixXd psi_AxB_dimer_basis = psi_AxB.transpose()*overlapAB*orbitalsAB.MOCoefficients();
    overlapAB.resize(0,0);
    int LevelsA = levelsA;
    for (int i=0;i<psi_AxB_dimer_basis.rows();i++){
        double mag=psi_AxB_dimer_basis.row(i).squaredNorm();
        if (mag<0.95){
            int monomer = 0;
            int level = 0;
            if ( i < LevelsA ) {
                monomer = 1;
                level   = _bseA_vmin + i;
            } else {
                monomer = 2;
                level   = _bseB_vmin + i -levelsA;
            }
            CTP_LOG(ctp::logERROR,*_pLog) << "\nERROR: " << i << " Projection of orbital "
                    << level << " of monomer " << monomer << " on dimer is insufficient,mag="
                    <<mag<<" maybe the orbital order is screwed up, otherwise increase dimer basis.\n"<<flush;
        }
    }

    //notation AB is CT states with A+B-, BA is the counterpart
    //Setting up CT-states:
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   Setting up CT-states" << flush;
    //Number of A+B- states
    int noAB=_occA*_unoccB;
    //Number of A-B+ states
    int noBA=_unoccA*_occB;

    Eigen::MatrixXi comb_CTAB=Eigen::MatrixXi::Zero(noAB,2);
    cnt = 0;
    // iterate A over occupied, B over unoccupied
    int v_start=_bseA_vtotal-_occA;
    for ( int _v = v_start; _v < _bseA_vtotal; _v++){
        for ( int _c = 0; _c <_unoccB; _c++){
            comb_CTAB(cnt,0) =_v;
            comb_CTAB(cnt,1) = _bseA_vtotal+_bseA_ctotal+_bseB_vtotal + _c;

            cnt++;
        }
    }
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  <<"  "<<noAB <<" CT states A+B- created" << flush;

    Eigen::MatrixXi  comb_CTBA=Eigen::MatrixXi::Zero(noBA,2);
    cnt = 0;
    // iterate A over unoccupied, B over occupied
    v_start=_bseB_vtotal-_occB;
    for ( int _v = v_start; _v < _bseB_vtotal; _v++){
        for ( int _c = 0; _c <_unoccA; _c++){
            comb_CTBA(cnt,0) =_bseA_vtotal+_bseA_ctotal+_v;
            comb_CTBA(cnt,1) = _bseA_vtotal+ _c;

            cnt++;
        }
    }
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  <<"  "<<noBA <<" CT states B+A- created" << flush;

    // these 4 matrixes, matrix(i,j) contains the j-th dimer MO component of the i-th excitation

    ctAB.resize(noAB,_bseAB_size);
    #pragma omp parallel for
    for ( int _i_CT = 0 ; _i_CT < noAB ; _i_CT++){
    for ( int _i_bseAB = 0 ; _i_bseAB < _bseAB_size ; _i_bseAB++){
        ctAB(_i_CT,_i_bseAB)=psi_AxB_dimer_basis( comb_CTAB(_i_CT,0), combAB( _i_bseAB,0) ) * psi_AxB_dimer_basis( comb_CTAB(_i_CT,1), combAB( _i_bseAB,1) );
        }
    }

    ctBA.resize(noBA,_bseAB_size);
    #pragma omp parallel for
    for ( int _i_CT = 0 ; _i_CT < noBA ; _i_CT++){
    for ( int _i_bseAB = 0 ; _i_bseAB < _bseAB_size ; _i_bseAB++){
        ctBA(_i_CT,_i_bseAB)=psi_AxB_dimer_basis( comb_CTBA(_i_CT,0), combAB( _i_bseAB,0) ) * psi_AxB_dimer_basis( comb_CTBA(_i_CT,1), combAB( _i_bseAB,1) );
        }
    }

    _kap.resize(_bseA_size,_bseAB_size);
    #pragma omp parallel for
    for ( int _i_bseA = 0 ; _i_bseA < _bseA_size ; _i_bseA++){
        for ( int _i_bseAB = 0 ; _i_bseAB < _bseAB_size ; _i_bseAB++){
            _kap(_i_bseA,_i_bseAB) = psi_AxB_dimer_basis( combA(_i_bseA,0), combAB( _i_bseAB,0) ) * psi_AxB_dimer_basis( combA(_i_bseA,1), combAB( _i_bseAB,1) );

        }
    }

    _kbp.resize(_bseB_size,_bseAB_size);
    #pragma omp parallel for
    for ( int _i_bseB = 0 ; _i_bseB < _bseB_size ; _i_bseB++){
        for ( int _i_bseAB = 0 ; _i_bseAB < _bseAB_size ; _i_bseAB++){
            _kbp(_i_bseB,_i_bseAB) = psi_AxB_dimer_basis( combB(_i_bseB,0), combAB( _i_bseAB,0) ) * psi_AxB_dimer_basis( combB(_i_bseB,1), combAB( _i_bseAB,1) );
        }
    }

    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()   << "   construct projection of product functions " << flush;

    psi_AxB_dimer_basis.resize(0,0);
    combAB.resize(0,0);
    combA.resize(0,0);
    combB.resize(0,0);
    // now the different spin types
            if (_doSinglets) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Evaluating singlets" << flush;
                Eigen::MatrixXd Hamiltonian_AB = eh_s.cast<double>();
                CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Setup Hamiltonian" << flush;
                const Eigen::MatrixXd bseA_T = orbitalsA.BSESingletCoefficients().block(0,0,orbitalsA.BSESingletCoefficients().rows(),_levA).transpose().cast<double>();
                const Eigen::MatrixXd bseB_T =orbitalsB.BSESingletCoefficients().block(0,0,orbitalsB.BSESingletCoefficients().rows(),_levB).transpose().cast<double>();

                JAB_singlet = ProjectExcitons(bseA_T, bseB_T, Hamiltonian_AB);
                CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   calculated singlet couplings " << flush;
            }

            if (_doTriplets) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Evaluating triplets" << flush;
                Eigen::MatrixXd Hamiltonian_AB = eh_s.cast<double>();
                CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "  Converted Hamiltonian to double" << flush;
                const Eigen::MatrixXd bseA_T = orbitalsA.BSETripletCoefficients().block(0,0,orbitalsA.BSETripletCoefficients().rows(),_levA).transpose().cast<double>();
                const Eigen::MatrixXd bseB_T =orbitalsB.BSETripletCoefficients().block(0,0,orbitalsB.BSETripletCoefficients().rows(),_levB).transpose().cast<double>();
                JAB_triplet = ProjectExcitons(bseA_T, bseB_T, Hamiltonian_AB);
                CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   calculated triplet couplings " << flush;
            }

    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "  Done with exciton couplings" << flush;
    return;
};

std::vector< Eigen::MatrixXd > BSECoupling::ProjectExcitons(const Eigen::MatrixXd& bseA_T, const Eigen::MatrixXd& bseB_T,
                                  Eigen::MatrixXd& H){

     // get projection of monomer excitons on dimer product functions
     Eigen::MatrixXd _proj_excA = bseA_T* _kap;
     Eigen::MatrixXd _proj_excB =  bseB_T* _kbp;

     _bse_exc=_levA+_levB;
     int ctABsize=ctAB.rows();
     int ctBAsize=ctBA.rows();
     _ct=ctABsize+ctBAsize;
     int nobasisfunc=H.rows();

     Eigen::MatrixXd fe_states=Eigen::MatrixXd::Zero(_bse_exc,nobasisfunc);
     fe_states.block(0,0, _levA, nobasisfunc )=_proj_excA;
     fe_states.block( _levA,0,_levB,nobasisfunc )=_proj_excB;

     Eigen::MatrixXd ct_states=Eigen::MatrixXd::Zero(_ct,nobasisfunc);

      if (_ct > 0) {
        //orthogonalize ct-states with respect to the FE states.
        CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << " Orthogonalizing CT-states with respect to FE-states" << flush;

        if (ctABsize > 0) {
          ct_states.block(0, 0, ctABsize, nobasisfunc) = ctAB;
        }
        if (ctBAsize > 0) {
          ct_states.block(ctABsize, 0, ctBAsize, nobasisfunc) = ctBA;
        }

        //orthogonalize ct-states with respect to FE states
        Eigen::MatrixXd correction = ct_states * fe_states.transpose() * fe_states;
        ct_states -= correction;
        correction.resize(0, 0);
        //normalize
        Eigen::VectorXd norm=ct_states.rowwise().norm();
        for (int i = 0; i < _ct; i++) {
          ct_states.row(i) /= norm(i);
        }
        int minstateindex=0;
        double minnorm=norm.minCoeff(&minstateindex);
        if(minnorm<0.95){
          CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << " WARNING: CT-state " << minstateindex << " norm is only " << minnorm << flush;
        }
        }
     Eigen::MatrixXd projection(_bse_exc+_ct,nobasisfunc);
     CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << " merging projections into one vector  " << flush;
  projection.block(0 ,0, _bse_exc,nobasisfunc)=fe_states;

     if(_ct>0){
    projection.block(_bse_exc,0,_ct ,nobasisfunc )=ct_states;
     }
      CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   Setting up coupling matrix size "<< _bse_exc +_ct<<"x"<<_bse_exc +_ct << flush;
     // matrix _J
    //  E_A         J_AB        J_A_ABCT        J_A_BACT
    //  J_BA        E_B         J_B_ABCT        J_B_BACT
    //  J_ABCT_A    J_ABCT_B    E_ABCT          J_ABCT_BACT
    //  J_BACT_A   J_BACT_B    J_BACT_ABCT     E_BACT

     // this only works for hermitian/symmetric H so only in TDA

     Eigen::MatrixXd J_dimer=projection*H*projection.transpose();
     H.resize(0,0);

    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   Setting up overlap matrix size "<< _bse_exc +_ct<<"x"<<_bse_exc +_ct << flush;
    Eigen::MatrixXd S_dimer=projection*projection.transpose();

    projection.resize(0,0);
    if(tools::globals::verbose &&  _bse_exc+_ct<100){
         CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------"<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << "_J_dimer[Ryd]"<<flush;

     CTP_LOG(ctp::logDEBUG, *_pLog) << J_dimer<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << "_S_dimer"<<flush;

     CTP_LOG(ctp::logDEBUG, *_pLog) << S_dimer<<flush;
      CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------"<<flush;
    }

    Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(S_dimer);
    Eigen::MatrixXd Sm1=es.operatorInverseSqrt();
    J_dimer=Sm1*J_dimer*Sm1;

    if(tools::globals::verbose && _bse_exc+_ct<100){
         CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------"<<flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << "_J_ortho[Ryd]"<<flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << J_dimer<<flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << "_S-1/2"<<flush;
    CTP_LOG(ctp::logDEBUG, *_pLog) << Sm1<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------"<<flush;
    }
     CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  <<
             "   Smallest value of dimer overlapmatrix is "<<es.eigenvalues()(0)<< flush;

    std::vector< Eigen::MatrixXd >J;

     CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "   Running Perturbation algorithm"<< flush;
    J.push_back( Perturbation(J_dimer));
    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp()  << "    Running Projection algorithm"<< flush;
    J.push_back( Fulldiag(J_dimer));

       if(tools::globals::verbose){
     CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------"<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << "Jeff_pert[Hrt]"<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << J[0]<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << "Jeff_diag[Hrt]"<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << J[1]<<flush;
     CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------"<<flush;
     }

     return J;
}

Eigen::MatrixXd BSECoupling::Perturbation(const Eigen::MatrixXd& J_dimer){

    Eigen::MatrixXd Jmatrix =Eigen::MatrixXd::Zero(_bse_exc, _bse_exc);
    bool diag_ct = true;
    Eigen::MatrixXd J_result=J_dimer;
    if (_ct > 0 && diag_ct) {
        Eigen::MatrixXd transformation = Eigen::MatrixXd::Identity(_bse_exc + _ct, _bse_exc + _ct);
        Eigen::MatrixXd Ct = J_dimer.block(_bse_exc,_bse_exc,_ct, _ct);
        Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(Ct);
        transformation.block(_bse_exc, _bse_exc ,_ct,_ct) = es.eigenvectors();
        Ct.resize(0, 0);

        if (tools::globals::verbose) {
            CTP_LOG(ctp::logDEBUG, *_pLog) << "FE state hamiltonian" << flush;
            CTP_LOG(ctp::logDEBUG, *_pLog) << J_dimer.block(0,0, _bse_exc,_bse_exc) << flush;
            if (_ct > 0) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << "eigenvalues of CT states" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << es.eigenvalues() << flush;
            }
        }

     J_result = transformation.transpose()*J_dimer*transformation;
        if (tools::globals::verbose && _bse_exc + _ct < 100) {
            CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------" << flush;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "_J_ortho[Hrt] CT-state diag" << flush;
            CTP_LOG(ctp::logDEBUG, *_pLog) << J_result << flush;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------" << flush;
        }
    }
    for (int stateA = 0; stateA < _levA; stateA++) {
        double Ea = J_result(stateA, stateA);
        for (int stateB = 0; stateB < _levB; stateB++) {
            int stateBd = stateB + _levA;
            CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Calculating coupling between exciton A"
                    << stateA + 1 << " and exciton B" << stateB + 1 << flush;
            double J = J_result(stateA, stateBd);

            double Eb = J_result(stateBd, stateBd);
            for (int k = _bse_exc; k < (_bse_exc + _ct); k++) {
                double Eab = J_result(k, k);
                if (std::abs(Eab - Ea) < 0.001) {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "Energydifference between state A "
                            << stateA + 1 << "and CT state " << k + 1 << " is " << Eab - Ea << "[Hrt]" << flush;
                }
                if (std::abs(Eab - Eb) < 0.001) {
                    CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "Energydifference between state B "
                            << stateB + 1 << "and CT state " << k + 1 << " is " << Eab - Eb << "[Hrt]" << flush;

                }
                J += 0.5 * J_result(k, stateA) * J_result(k, stateBd)*(1 / (Ea - Eab) + 1 / (Eb - Eab)); // Have no clue why 0.5
            }
            Jmatrix(stateA, stateBd) = J;
            Jmatrix(stateBd, stateA) = J;

        }
    }
    return Jmatrix;
}

Eigen::MatrixXd BSECoupling::Fulldiag(const Eigen::MatrixXd& J_dimer){
    Eigen::MatrixXd Jmat = Eigen::MatrixXd::Zero(_bse_exc, _bse_exc);

    Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(J_dimer);
    if (tools::globals::verbose && _bse_exc + _ct < 10) {
        CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------" << flush;
        CTP_LOG(ctp::logDEBUG, *_pLog) << "Eigenvectors of J" << flush;
        CTP_LOG(ctp::logDEBUG, *_pLog) << es.eigenvectors() << flush;
        CTP_LOG(ctp::logDEBUG, *_pLog) << "J_eigenvalues[Hrt]" << flush;
        CTP_LOG(ctp::logDEBUG, *_pLog) << es.eigenvalues() << flush;
        CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------" << flush;
    }
    //Calculate projection on subspace for every pair of excitons separately
    for (int stateA = 0; stateA < _levA; stateA++) {
        for (int stateB = 0; stateB < _levB; stateB++) {
            int stateBd = stateB + _levA;
            CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Calculating coupling between exciton A" << stateA + 1 << " and exciton B" << stateB + 1 << flush;
            std::vector<int> index;
            std::vector<int> signvec;
            for (int i = 0; i < _bse_exc + _ct; i++) {
                if (i == int(stateA) || i == int(stateBd)) {
                    double close = 0.0;
                    int ind = 0;
                    int sign = 0;
                    //row
                    for (int j = 0; j < _bse_exc + _ct; j++) {
                        bool check = true;
                        // if index i is already in index
                        // should not happen but if one vector was similar to two others.
                        for (unsigned l = 0; l < index.size(); l++) {
                            if (j == index[l]) {
                                check = false;
                                break;
                            }
                        }
                        if (check && std::abs(es.eigenvalues()(i, j)) > close) {
                            ind = j;
                            close = std::abs(es.eigenvalues()(i, j));
                            if (es.eigenvalues()(i, j) >= 0) {
                                sign = 1;
                            } else {
                                sign = -1;
                            }
                        }
                    }
                    index.push_back(ind);
                    signvec.push_back(sign);
                }
            }

            CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Order is: [Initial state n->nth eigenvalue]" << flush;
            CTP_LOG(ctp::logDEBUG, *_pLog) << "    A" << stateA + 1 << ":" << stateA + 1 << "->" << index[0] + 1 << " ";
            CTP_LOG(ctp::logDEBUG, *_pLog) << "    B" << stateB + 1 << ":" << stateBd + 1 << "->" << index[1] + 1 << " " << flush;

            //setting up transformation matrix Tmat and diagonal matrix Emat for the eigenvalues;
            Eigen::MatrixXd Emat = Eigen::MatrixXd::Zero(2, 2);
            Eigen::MatrixXd Tmat = Eigen::MatrixXd::Zero(2, 2);
            //find the eigenvectors which are most similar to the initial states
            //row
            for (int i = 0; i < 2; i++) {
                int k = index[i];
                double sign = signvec[i];
                double normr = 1 / std::sqrt(es.eigenvectors()(stateA, k) * es.eigenvectors()(stateA, k) + es.eigenvectors()(stateBd, k) * es.eigenvectors()(stateBd, k));
                Tmat(0, i) = sign * es.eigenvectors()(stateA, k) * normr;
                Tmat(1, i) = sign * es.eigenvectors()(stateBd, k) * normr;
                Emat(i, i) = es.eigenvectors()(k);
            }

            if ((Tmat(1, 1) * Tmat(0, 0) - Tmat(1, 0) * Tmat(0, 1)) < 0) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << " Reduced state matrix is not in a right handed basis, multiplying second eigenvector by -1 " << flush;
                Tmat(0, 1) = -Tmat(0, 1);
                Tmat(1, 1) = -Tmat(1, 1);
            }

            if (tools::globals::verbose) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << "_T" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << Tmat << flush;

            }

            Eigen::MatrixXd S_small = Tmat*Tmat.transpose();
            if (tools::globals::verbose) {

                CTP_LOG(ctp::logDEBUG, *_pLog) << "S_small" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << S_small << flush;

            }
            //orthogonalize that matrix

            Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> ss(S_small);
            Eigen::MatrixXd sm1=ss.operatorInverseSqrt();
            Emat=sm1*Emat*sm1;

            CTP_LOG(ctp::logDEBUG, *_pLog) << ctp::TimeStamp() << "   Smallest value of dimer overlapmatrix is " << ss.eigenvalues()(0) << flush;
            if (tools::globals::verbose) {

                CTP_LOG(ctp::logDEBUG, *_pLog) << "S-1/2" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << sm1 << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << "E_ortho" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << Emat << flush;
            }
            Tmat = Tmat*sm1;

            if (tools::globals::verbose) {

                CTP_LOG(ctp::logDEBUG, *_pLog) << "T_ortho" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << Tmat << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << "---------------------------------------" << flush;
            }

            Eigen::MatrixXd J_small = Tmat*Emat*Tmat.transpose();
            if (tools::globals::verbose) {
                CTP_LOG(ctp::logDEBUG, *_pLog) << "T_ortho*E_ortho*T_ortho^T" << flush;
                CTP_LOG(ctp::logDEBUG, *_pLog) << J_small << flush;
            }

            Jmat(stateA, stateBd) = J_small(0, 1);
            Jmat(stateBd, stateA) = J_small(1, 0);

        }
    }

    return Jmat;
}

}}


#include "mantella_bits/optimisationAlgorithm/hookeJeevesAlgorithm.hpp"

// C++ standard library
#include <functional>
#include <string>
#include <utility>

// Armadillo
#include <armadillo>

// Mantella
#include "mantella_bits/optimisationProblem.hpp"
#include "mantella_bits/probability.hpp"

namespace mant {
  HookeJeevesAlgorithm::HookeJeevesAlgorithm()
      : OptimisationAlgorithm(),
        stepSize_(0) {
    setInitialisingFunctions(
        {{[this](
              const arma::uword numberOfDimensions_,
              const arma::mat& initialParameters_) {
            stepSize_ = getInitialStepSize();

            return initialParameters_;
          },
          "Reset step size to the initial one"}});

    setNextParametersFunctions(
        {{[this](
              const arma::uword numberOfDimensions_,
              const arma::mat& parameters_,
              const arma::rowvec& objectiveValues_,
              const arma::rowvec& differences_) {
            if (arma::all(differences_ >= 0)) {
              stepSize_ *= getStepSizeDecrease();
            }

            return parameters_;
          },
          "Decrease the step size if the last objective values where worse than the best one"},
         {[this](
              const arma::uword numberOfDimensions_,
              const arma::mat& parameters_,
              const arma::rowvec& objectiveValues_,
              const arma::rowvec& differences_) {
            arma::mat nextParameters(numberOfDimensions_, 2 * numberOfDimensions_);
            for (arma::uword n = 0; n < numberOfDimensions_; ++n) {
              arma::vec nextParameterCandidate = getBestFoundParameter();

              nextParameterCandidate(n) += stepSize_;
              nextParameters.col(2 * n) = nextParameterCandidate;

              nextParameterCandidate(n) -= 2 * stepSize_;
              nextParameters.col(2 * n + 1) = nextParameterCandidate;
            }

            return nextParameters;
          },
          "Hooke-Jeeves algorithm"}});

    setInitialStepSize(1.0);
    setStepSizeDecrease(0.5);
  }

  void HookeJeevesAlgorithm::optimise(
      OptimisationProblem& optimisationProblem) {
    optimise(optimisationProblem, uniformRandomNumbers(optimisationProblem.numberOfDimensions_));
  }

  void HookeJeevesAlgorithm::setInitialStepSize(
      const double initialStepSize) {
    initialStepSize_ = initialStepSize;
  }

  double HookeJeevesAlgorithm::getInitialStepSize() const {
    return initialStepSize_;
  }

  void HookeJeevesAlgorithm::setStepSizeDecrease(
      const double stepSizeDecrease) {
    stepSizeDecrease_ = stepSizeDecrease;
  }

  double HookeJeevesAlgorithm::getStepSizeDecrease() const {
    return stepSizeDecrease_;
  }
}


#include <hpx/hpx.hpp>
#include <hpx/hpx_init.hpp>
#include <hpx/runtime/serialization/serialize.hpp>
#include <hpx/util/unused.hpp>

#include <boost/shared_array.hpp>

#include <cstddef>
#include <cstdint>
#include <iostream>
#include <stdlib.h>
#include <utility>
#include <vector>

///////////////////////////////////////////////////////////////////////////////
inline std::size_t idx(std::size_t i, std::size_t j, std::size_t s)
{
    return i * s + j;
}

inline std::size_t locidx(
    std::size_t i, std::size_t j, std::size_t T, std::size_t nl)
{
    std::size_t tnl = T / nl;
    if (T % nl > 0)
        ++tnl;
    return i / tnl;
}

///////////////////////////////////////////////////////////////////////////////
struct partition_data
{
private:
    typedef hpx::serialization::serialize_buffer<double> buffer_type;

public:
    partition_data()
      : n_(0)
      , t_(0)
      , i_(0)
      , j_(0)
    {
    }

    // Create a new (uninitialized) partition of the given size.
    partition_data(std::size_t n)
      : data_(
            std::allocator<double>().allocate(n * n), n * n, buffer_type::take)
      , n_(n)
      , t_(0)
      , i_(0)
      , j_(0)
    {
    }

    // Create a new (initialized) partition of the given size.
    partition_data(std::size_t n, std::size_t t, std::size_t i, std::size_t j)
      : data_(
            std::allocator<double>().allocate(n * n), n * n, buffer_type::take)
      , n_(n)
      , t_(t)
      , i_(i)
      , j_(j)
    {
        srand(idx(i, j, n));
        for (std::size_t k = 0; k != n * n; ++k)
            data_[k] = (double) ((rand() % 2000) - 1000) / 100;
    }

    partition_data(partition_data const& base)
      : data_(base.data_.data(), base.size(), buffer_type::copy)
      , n_(base.n_)
      , t_(base.t_)
      , i_(base.i_)
      , j_(base.j_)
    {
    }

    double& operator[](std::size_t idx)
    {
        return data_[idx];
    }
    double operator[](std::size_t idx) const
    {
        return data_[idx];
    }

    std::size_t size() const
    {
        return n_ * n_;
    }

    std::size_t dim() const
    {
        return n_;
    }

    std::size_t nt() const
    {
        return t_;
    }

    std::size_t pos_i() const
    {
        return i_;
    }

    std::size_t pos_j() const
    {
        return j_;
    }

private:
    // Serialization support: even if all of the code below runs on one
    // locality only, we need to provide an (empty) implementation for the
    // serialization as all arguments passed to actions have to support this.
    friend class hpx::serialization::access;

    template <typename Archive>
    void serialize(Archive& ar, const unsigned int version)
    {
        ar& data_& n_& t_& i_& j_;
    }

private:
    buffer_type data_;
    std::size_t n_;
    std::size_t t_;
    std::size_t i_;
    std::size_t j_;
};

std::ostream& operator<<(std::ostream& os, partition_data const& c)
{
    os << "== block i(" << c.pos_i() << ") j(" << c.pos_j() << ")" <<  std::endl;
    for (std::size_t i = 0; i != c.dim(); ++i)
    {
        for (std::size_t j = 0; j != c.dim(); ++j)
        {
            os << c.pos_i() * c.dim() + i << " " << c.pos_j() * c.dim() + j << " " << c[i * c.dim() + j]
               << std::endl;
        }
    }
    return os;
}

///////////////////////////////////////////////////////////////////////////////
// This is the server side representation of the data. We expose this as a HPX
// component which allows for it to be created and accessed remotely through
// a global address (hpx::id_type).
struct partition_server : hpx::components::component_base<partition_server>
{
    // construct new instances
    partition_server() {}

    partition_server(partition_data const& data)
      : data_(data)
    {
    }

    partition_server(std::size_t n, std::size_t t, std::size_t i, std::size_t j)
      : data_(n, t, i, j)
    {
    }

    // Access data.
    partition_data get_data() const
    {
        return data_;
    }

    // Every member function which has to be invoked remotely needs to be
    // wrapped into a component action. The macro below defines a new type
    // 'get_data_action' which represents the (possibly remote) member function
    // partition::get_data().
    HPX_DEFINE_COMPONENT_DIRECT_ACTION(
        partition_server, get_data, get_data_action);

private:
    partition_data data_;
};

// The macros below are necessary to generate the code required for exposing
// our partition type remotely.
//
// HPX_REGISTER_COMPONENT() exposes the component creation
// through hpx::new_<>().
typedef hpx::components::component<partition_server> partition_server_type;
HPX_REGISTER_COMPONENT(partition_server_type, partition_server);

// HPX_REGISTER_ACTION() exposes the component member function for remote
// invocation.
typedef partition_server::get_data_action get_data_action;
HPX_REGISTER_ACTION(get_data_action);

///////////////////////////////////////////////////////////////////////////////
// This is a client side helper class allowing to hide some of the tedious
// boilerplate.
struct partition : hpx::components::client_base<partition, partition_server>
{
    typedef hpx::components::client_base<partition, partition_server> base_type;

    partition() {}

    // Create new component on locality 'where' and initialize the held data
    partition(hpx::id_type where, std::size_t n, std::size_t t, std::size_t i, std::size_t j)
      : base_type(hpx::new_<partition_server>(where, n, t, i, j))
    {
    }

    // Create a new component on the locality co-located to the id 'where'. The
    // new instance will be initialized from the given partition_data.
    partition(hpx::id_type where, partition_data const& data)
      : base_type(hpx::new_<partition_server>(hpx::colocated(where), data))
    {
    }

    // Attach a future representing a (possibly remote) partition.
    partition(hpx::future<hpx::id_type>&& id)
      : base_type(std::move(id))
    {
    }

    // Unwrap a future<partition> (a partition already holds a future to the
    // id of the referenced object, thus unwrapping accesses this inner future).
    partition(hpx::future<partition>&& c)
      : base_type(std::move(c))
    {
    }

    ///////////////////////////////////////////////////////////////////////////
    // Invoke the (remote) member function which gives us access to the data.
    // This is a pure helper function hiding the async.
    hpx::future<partition_data> get_data() const
    {
        partition_server::get_data_action act;
        return hpx::async(act, get_id());
    }
};

///////////////////////////////////////////////////////////////////////////////
struct stepper
{
    // Our data for one time step
    typedef std::vector<partition> space;

    static partition_data inv_core(partition_data const& A_)
    {
        double tmp;
        partition_data A(A_);
        partition_data v(A_.dim());

        for (int i = 0; i < A.dim(); ++i)
        {
            for (int j = 0; j < A.dim(); ++j)
            {
                v[i * A.dim() + j] = 0;
            }
            v[i * A.dim() + i] = 1;
        }
        for (int k = 0; k < A.dim(); ++k)
        {
            tmp = A[k * A.dim() + k];
            for (int j = 0; j < A.dim(); ++j)
            {
                v[k * A.dim() + j] /= tmp;
                A[k * A.dim() + j] /= tmp;
            }

            // can be parallalized
            for (int i = 0; i < k; ++i)
            {
                tmp = A[i * A.dim() + k];
                for (int j = 0; j < A.dim(); ++j)
                {
                    v[i * A.dim() + j] -= tmp * v[k * A.dim() + j];
                    A[i * A.dim() + j] -= tmp * A[k * A.dim() + j];
                }
            }
            for (int i = k + 1; i < A.dim(); ++i)
            {
                tmp = A[i * A.dim() + k];
                for (int j = 0; j < A.dim(); ++j)
                {
                    v[i * A.dim() + j] -= tmp * v[k * A.dim() + j];
                    A[i * A.dim() + j] -= tmp * A[k * A.dim() + j];
                }
            }
        }
        return v;
    }

    //A = A * B
    static partition_data pmm_core(partition_data const& A, partition_data const& B)
    {
        partition_data r(A);

        // i and j can be parallalized
        for (int i = 0; i < A.dim(); ++i)
        {
            for (int j = 0; j < A.dim(); ++j)
            {
                r[i * A.dim() + j] = 0;
                for (int k = 0; k < A.dim(); ++k)
                {
                    r[i * A.dim() + j] += A[i * A.dim() + k] * B[k * A.dim() + j];
                }
            }
        }
        return r;
    }

    //C = C - A * B
    static partition_data pmm_d_core(
        partition_data const& A, partition_data const& B, partition_data const& C)
    {
        partition_data r(C);

        // i and j can be parallalized
        for (int i = 0; i < A.dim(); ++i)
        {
            for (int j = 0; j < A.dim(); ++j)
            {
                for (int k = 0; k < A.dim(); ++k)
                {
                    r[i * A.dim() + j] -= A[i * A.dim() + k] * B[k * A.dim() + j];
                }
            }
        }
        return r;
    }

    static partition inv_part(partition const& A_p, partition const& inv_p)
    {
        using hpx::dataflow;
        using hpx::util::unwrapping;

        hpx::shared_future<partition_data> A_data = A_p.get_data();
        return dataflow(hpx::launch::async,
            unwrapping([inv_p](partition_data const& A) -> partition {
                partition_data m_inv = stepper::inv_core(A);
                return partition(inv_p.get_id(), m_inv);
            }),
            A_data);
    }

    static partition pmm_part(partition const& A_p, partition const& B_p)
    {
        using hpx::dataflow;
        using hpx::util::unwrapping;

        hpx::shared_future<partition_data> A_data = A_p.get_data();
        hpx::shared_future<partition_data> B_data = B_p.get_data();
        return dataflow(hpx::launch::async,
            unwrapping([A_p](partition_data const& A,
                           partition_data const& B) -> partition {
                partition_data r = stepper::pmm_core(A, B);
                return partition(A_p.get_id(), r);
            }),
            A_data, B_data);
    }

    static partition pmm_d_part(
        partition const& A_p, partition const& B_p, partition const& C_p)
    {
        using hpx::dataflow;
        using hpx::util::unwrapping;

        hpx::shared_future<partition_data> A_data = A_p.get_data();
        hpx::shared_future<partition_data> B_data = B_p.get_data();
        hpx::shared_future<partition_data> C_data = C_p.get_data();
        return dataflow(hpx::launch::async,
            unwrapping([C_p](partition_data const& A, partition_data const& B,
                           partition_data const& C) -> partition {
                partition_data r = stepper::pmm_d_core(A, B, C);
                return partition(C_p.get_id(), r);
            }),
            A_data, B_data, C_data);
    }

    // do all the work on 'np' partitions, 'nx' data points each, for 'nt'
    // time steps
    space do_lu(std::size_t T, std::size_t N, bool print_matrices);
};

HPX_PLAIN_ACTION(stepper::inv_part, inv_part_action);
HPX_PLAIN_ACTION(stepper::pmm_part, pmm_part_action);
HPX_PLAIN_ACTION(stepper::pmm_d_part, pmm_d_part_action);

///////////////////////////////////////////////////////////////////////////////
// do all the work on 'np' partitions, 'nx' data points each, for 'nt'
// time steps
stepper::space stepper::do_lu(std::size_t T, std::size_t N, bool print_matrices)
{
    using hpx::dataflow;

    std::vector<hpx::id_type> localities = hpx::find_all_localities();
    std::size_t nl = localities.size();    // Number of localities

    space tiles;
    tiles.resize(T * T);

    space invs;
    invs.resize(T);

    for (std::size_t i = 0; i != T; ++i)
        for (std::size_t j = 0; j != T; ++j)
            tiles[idx(i, j, T)] =
                partition(localities[locidx(i, j, T, nl)], N, T, i, j);

    if (print_matrices) {
        std::cout << "== a == " << std::endl;
        for (std::size_t i = 0; i != T * T; ++i)
        {
            tiles[i].get_data().wait();
            std::cout << tiles[i].get_data().get();
        }
        std::cout << "== a == " << std::endl;
    }

    for (std::size_t i = 0; i != T; ++i)
        invs[i] = partition(localities[locidx(i, 0, T, nl)], N, T, i, 0);

    inv_part_action act_inv;
    pmm_part_action act_pmm;
    pmm_d_part_action act_pmm_d;

    for (std::size_t k = 0; k < T - 1; ++k)
    {
        using hpx::util::placeholders::_1;
        using hpx::util::placeholders::_2;
        auto Op =
            hpx::util::bind(act_inv, localities[locidx(k, 0, T, nl)], _1, _2);
        invs[k] =
            dataflow(hpx::launch::async, Op, tiles[idx(k, k, T)], invs[k]);
        for (std::size_t i = k + 1; i < T; ++i)
        {
            using hpx::util::placeholders::_1;
            using hpx::util::placeholders::_2;
            auto Op = hpx::util::bind(
                act_pmm, localities[locidx(k, 0, T, nl)], _1, _2);
            tiles[idx(i, k, T)] =
                dataflow(hpx::launch::async, Op, tiles[idx(i, k, T)], invs[k]);
            for (std::size_t j = k + 1; j < T; ++j)
            {
                using hpx::util::placeholders::_1;
                using hpx::util::placeholders::_2;
                using hpx::util::placeholders::_3;
                auto Op = hpx::util::bind(
                    act_pmm_d, localities[locidx(i, j, T, nl)], _1, _2, _3);
                tiles[idx(i, j, T)] =
                    dataflow(hpx::launch::async, Op, tiles[idx(i, k, T)],
                        tiles[idx(k, j, T)], tiles[idx(i, j, T)]);
            }
        }
    }

    // Return the LU factorization
    return tiles;
}

///////////////////////////////////////////////////////////////////////////////
int hpx_main(boost::program_options::variables_map& vm)
{
    std::uint64_t N = vm["N"].as<std::uint64_t>();
    std::uint64_t T = vm["T"].as<std::uint64_t>();

    std::vector<hpx::id_type> localities = hpx::find_all_localities();
    std::size_t nl = localities.size();    // Number of localities

    if (N * N < nl)
    {
        std::cout << "The number of tiles should not be smaller than "
                     "the number of localities"
                  << std::endl;
        return hpx::finalize();
    }

    // Create the stepper object
    stepper step;

    // Measure execution time.
    std::uint64_t t = hpx::util::high_resolution_clock::now();

    // Execute nt time steps on nx grid points and print the final solution.
    stepper::space solution = step.do_lu(T, N, vm.count("print-matrices"));
    for (std::size_t i = 0; i != T * T; ++i)
        solution[i].get_data().wait();

    std::uint64_t elapsed = hpx::util::high_resolution_clock::now() - t;
    std::cout << elapsed / 1e9 << std::endl;

    // Print the final solution
    if (vm.count("print-matrices"))
    {
        std::cout << "== lu == " << std::endl;
        for (std::size_t i = 0; i != T * T; ++i)
        {
            std::cout << solution[i].get_data().get();
        }
        std::cout << "== lu == " << std::endl;
    }
    std::uint64_t const num_worker_threads = hpx::get_num_worker_threads();
    hpx::future<std::uint32_t> locs = hpx::get_num_localities();

    return hpx::finalize();
}

int main(int argc, char* argv[])
{
    using namespace boost::program_options;

    options_description desc_commandline;
    desc_commandline.add_options()(
        "print-matrices", "print generated A and computed LU (default: false)")("N",
        value<std::uint64_t>()->default_value(10),
        "Dimension of the submatrices")("T",
        value<std::uint64_t>()->default_value(10),
        "Number of subblocks in each dimension");

    // Initialize and run HPX
    return hpx::init(desc_commandline, argc, argv);
}