// Copyright John Maddock 2010
// Use, modification and distribution are subject to 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)

#include <boost/math/concepts/real_concept.hpp> // for real_concept
#include <boost/math/constants/constants.hpp>

#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp> // Boost.Test
#include <boost/test/results_collector.hpp>
#include <boost/test/unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <iostream>
#include <iomanip>

BOOST_AUTO_TEST_CASE( test_main )
{
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS

   typedef boost::math::concepts::real_concept rc_t;

   rc_t r1(2.5), r2(0.125), r3(45.5);
   long double               l1(2.5), l2(0.125), l3(45.5);
   long double tol = std::numeric_limits<long double>::epsilon() * 2;

   {
      rc_t t(r1);
      long double   t2(l1);
      t += r2;
      t2 += l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t = r1;
      t += 23L;
      t2 = 23 + l1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t += static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }

   {
      rc_t t(r1);
      long double   t2(l1);
      t -= r2;
      t2 -= l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t = r1;
      t -= 23L;
      t2 = l1 - 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t -= static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }

   {
      rc_t t(r1);
      long double   t2(l1);
      t *= r2;
      t2 *= l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t = r1;
      t *= 23L;
      t2 = 23 * l1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t *= static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }
   {
      rc_t t(r1);
      long double   t2(l1);
      t /= r2;
      t2 /= l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t = r1;
      t /= 23L;
      t2 = l1 / 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1;
      t /= static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }

   {
      rc_t t;
      long double t2;
      t = r1 + r2;
      t2 = l1 + l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = l2 + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125 + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125f + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t2 = l1 + 23L;
      t = r1 + 23L;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 + static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t = 23L + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23uL + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23 + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23u + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<short>(23) + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned short>(23) + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<char>(23) + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<signed char>(23) + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned char>(23) + r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }
   {
      rc_t t;
      long double t2;
      t = r1 - r2;
      t2 = l1 - l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t2 = l2 - l1;
      t = l2 - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125 - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125f - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t2 = l1 - 23L;
      t = r1 - 23L;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 - static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t2 = 23L - l1;
      t = 23L - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23uL - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23 - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23u - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<short>(23) - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned short>(23) - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<char>(23) - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<signed char>(23) - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned char>(23) - r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }
   {
      rc_t t;
      long double t2;
      t = r1 * r2;
      t2 = l1 * l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = l2 * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125 * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125f * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t2 = l1 * 23L;
      t = r1 * 23L;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 * static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t = 23L * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23uL * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23 * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23u * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<short>(23) * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned short>(23) * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<char>(23) * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<signed char>(23) * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned char>(23) * r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }
   {
      rc_t t;
      long double t2;
      t = r1 / r2;
      t2 = l1 / l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / l2;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / 0.125;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / 0.125f;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t2 = l2 / l1;
      t = l2 / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125 / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 0.125f / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t2 = l1 / 23L;
      t = r1 / 23L;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / 23uL;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / 23;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / 23u;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / static_cast<short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / static_cast<unsigned short>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / static_cast<char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / static_cast<signed char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = r1 / static_cast<unsigned char>(23);
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);

      t2 = 23L / l1;
      t = 23L / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23uL / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23 / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = 23u / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<short>(23) / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned short>(23) / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<char>(23) / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<signed char>(23) / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
      t = static_cast<unsigned char>(23) / r1;
      BOOST_CHECK_CLOSE_FRACTION(t.value(), t2, tol);
   }

   {
      BOOST_CHECK_EQUAL(r1 == r2, l1 == l2);
      BOOST_CHECK_EQUAL(r1 == l2, l1 == l2);
      BOOST_CHECK_EQUAL(r1 == 0.125, l1 == l2);
      BOOST_CHECK_EQUAL(r1 == 0.125f, l1 == l2);
      BOOST_CHECK_EQUAL(l1 == r2, l1 == l2);
      BOOST_CHECK_EQUAL(2.5 == r2, l1 == l2);
      BOOST_CHECK_EQUAL(2.5f == r2, l1 == l2);

      BOOST_CHECK_EQUAL(r1 <= r2, l1 <= l2);
      BOOST_CHECK_EQUAL(r1 <= l2, l1 <= l2);
      BOOST_CHECK_EQUAL(r1 <= 0.125, l1 <= l2);
      BOOST_CHECK_EQUAL(r1 <= 0.125f, l1 <= l2);
      BOOST_CHECK_EQUAL(l1 <= r2, l1 <= l2);
      BOOST_CHECK_EQUAL(2.5 <= r2, l1 <= l2);
      BOOST_CHECK_EQUAL(2.5f <= r2, l1 <= l2);

      BOOST_CHECK_EQUAL(r1 >= r2, l1 >= l2);
      BOOST_CHECK_EQUAL(r1 >= l2, l1 >= l2);
      BOOST_CHECK_EQUAL(r1 >= 0.125, l1 >= l2);
      BOOST_CHECK_EQUAL(r1 >= 0.125f, l1 >= l2);
      BOOST_CHECK_EQUAL(l1 >= r2, l1 >= l2);
      BOOST_CHECK_EQUAL(2.5 >= r2, l1 >= l2);
      BOOST_CHECK_EQUAL(2.5f >= r2, l1 >= l2);

      BOOST_CHECK_EQUAL(r1 < r2, l1 < l2);
      BOOST_CHECK_EQUAL(r1 < l2, l1 < l2);
      BOOST_CHECK_EQUAL(r1 < 0.125, l1 < l2);
      BOOST_CHECK_EQUAL(r1 < 0.125f, l1 < l2);
      BOOST_CHECK_EQUAL(l1 < r2, l1 < l2);
      BOOST_CHECK_EQUAL(2.5 < r2, l1 < l2);
      BOOST_CHECK_EQUAL(2.5f < r2, l1 < l2);

      BOOST_CHECK_EQUAL(r1 > r2, l1 > l2);
      BOOST_CHECK_EQUAL(r1 > l2, l1 > l2);
      BOOST_CHECK_EQUAL(r1 > 0.125, l1 > l2);
      BOOST_CHECK_EQUAL(r1 > 0.125f, l1 > l2);
      BOOST_CHECK_EQUAL(l1 > r2, l1 > l2);
      BOOST_CHECK_EQUAL(2.5 > r2, l1 > l2);
      BOOST_CHECK_EQUAL(2.5f > r2, l1 > l2);
   }

   {
      BOOST_MATH_STD_USING
      BOOST_CHECK_CLOSE_FRACTION(acos(r2), acos(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(cos(r2), cos(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(asin(r2), asin(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(atan(r2), atan(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(atan2(r2, r3), atan2(l2, l3), tol);
      BOOST_CHECK_CLOSE_FRACTION(ceil(r2), ceil(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(fmod(r2, r3), fmod(l2, l3), tol);
      BOOST_CHECK_CLOSE_FRACTION(cosh(r2), cosh(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(exp(r2), exp(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(fabs(r2), fabs(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(abs(r2), abs(l2), tol);
      rc_t rc_result;
      long double ld_result;
#ifdef __MINGW32__
      BOOST_CHECK_CLOSE_FRACTION(modf(r2, &rc_result), boost::math::modf(l2, &ld_result), tol);
#else
      BOOST_CHECK_CLOSE_FRACTION(modf(r2, &rc_result), modf(l2, &ld_result), tol);
#endif
      BOOST_CHECK_CLOSE_FRACTION(rc_result, ld_result, tol);
      int i1, i2;
      BOOST_CHECK_CLOSE_FRACTION(frexp(r3, &i1), frexp(l3, &i2), tol);
      BOOST_CHECK_EQUAL(i1, i2);
      BOOST_CHECK_CLOSE_FRACTION(ldexp(r3, i1), ldexp(l3, i1), tol);
      BOOST_CHECK_CLOSE_FRACTION(log(r2), log(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(log10(r2), log10(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(tan(r2), tan(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(pow(r2, r3), pow(l2, l3), tol);
      BOOST_CHECK_CLOSE_FRACTION(pow(r2, i1), pow(l2, i1), tol);
      BOOST_CHECK_CLOSE_FRACTION(sin(r2), sin(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(sinh(r2), sinh(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(sqrt(r2), sqrt(l2), tol);
      BOOST_CHECK_CLOSE_FRACTION(tanh(r2), tanh(l2), tol);

      BOOST_CHECK_EQUAL(iround(r2), boost::math::iround(l2));
      BOOST_CHECK_EQUAL(lround(r2), boost::math::lround(l2));
#ifdef BOOST_HAS_LONG_LONG
      BOOST_CHECK_EQUAL(llround(r2), boost::math::llround(l2));
#endif
      BOOST_CHECK_EQUAL(itrunc(r2), boost::math::itrunc(l2));
      BOOST_CHECK_EQUAL(ltrunc(r2), boost::math::ltrunc(l2));
#ifdef BOOST_HAS_LONG_LONG
      BOOST_CHECK_EQUAL(lltrunc(r2), boost::math::lltrunc(l2));
#endif
   }

   {
      using namespace boost::math::tools;
      tol = std::numeric_limits<long double>::epsilon();
      BOOST_CHECK_CLOSE_FRACTION(max_value<rc_t>(), max_value<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(min_value<rc_t>(), min_value<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(log_max_value<rc_t>(), log_max_value<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(log_min_value<rc_t>(), log_min_value<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(epsilon<rc_t>(), epsilon<long double>(), tol);
      BOOST_CHECK_EQUAL(digits<rc_t>(), digits<long double>());
   }

   {
      using namespace boost::math::constants;
      BOOST_CHECK_CLOSE_FRACTION(pi<rc_t>(), pi<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(root_pi<rc_t>(), root_pi<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(root_half_pi<rc_t>(), root_half_pi<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(root_two_pi<rc_t>(), root_two_pi<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(root_ln_four<rc_t>(), root_ln_four<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(half<rc_t>(), half<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(euler<rc_t>(), euler<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(root_two<rc_t>(), root_two<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(ln_two<rc_t>(), ln_two<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(ln_ln_two<rc_t>(), ln_ln_two<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(third<rc_t>(), third<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(twothirds<rc_t>(), twothirds<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(pi_minus_three<rc_t>(), pi_minus_three<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(four_minus_pi<rc_t>(), four_minus_pi<long double>(), tol);
 //     BOOST_CHECK_CLOSE_FRACTION(pow23_four_minus_pi<rc_t>(), pow23_four_minus_pi<long double>(), tol);
      BOOST_CHECK_CLOSE_FRACTION(exp_minus_half<rc_t>(), exp_minus_half<long double>(), tol);
   }

#else
   std::cout << "<note>The long double tests have been disabled on this platform "
      "either because the long double overloads of the usual math functions are "
      "not available at all, or because they are too inaccurate for these tests "
      "to pass.</note>" << std::endl;
#endif

} // BOOST_AUTO_TEST_CASE( test_main )


//
//  Copyright Toon Knapen, Karl Meerbergen
//
// 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)
//

#include "random.hpp"

#include <boost/numeric/bindings/trans.hpp>
#include <boost/numeric/bindings/unit_upper.hpp>
#include <boost/numeric/bindings/blas/level3/gemm.hpp>
#include <boost/numeric/bindings/blas/level3/herk.hpp>
#include <boost/numeric/bindings/blas/level3/syrk.hpp>
#include <boost/numeric/bindings/blas/level3/trsm.hpp>
#include <boost/numeric/bindings/ublas/matrix.hpp>
#include <boost/numeric/bindings/ublas/matrix_proxy.hpp>
#include <boost/numeric/bindings/ublas/symmetric.hpp>
#include <boost/numeric/bindings/ublas/hermitian.hpp>
#include <boost/numeric/ublas/triangular.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/type_traits/is_complex.hpp>
#include <boost/mpl/if.hpp>

#include <iostream>
#include <complex>

namespace bindings = boost::numeric::bindings;

// Randomize a matrix
template <typename M>
void randomize(M& m)
{
  typedef typename M::size_type  size_type ;
  typedef typename M::value_type value_type ;

  size_type size1 = m.size1() ;
  size_type size2 = m.size2() ;

  for(size_type i=0; i<size2; ++i)
  {
    for(size_type j=0; j<size1; ++j)
    {
      m(j,i) = random_value< value_type >() ;
    }
  }
} // randomize()

template <typename T>
struct RealDiagonal
{
  template <typename M>
  void operator()(M& m) const {}
};

template <typename T>
struct RealDiagonal< std::complex<T> >
{
  template <typename M>
  void operator()(M& m) const
  {
    for(typename M::size_type i=0; i<m.size1(); ++i)
    {
      m(i,i) = m(i,i).real();
    }
  }
};

template <typename M>
boost::numeric::ublas::triangular_adaptor<const M, boost::numeric::ublas::unit_upper> unit_upper_part(const M& m)
{
  return boost::numeric::ublas::triangular_adaptor<const M, boost::numeric::ublas::unit_upper> (m);
}

template <typename M>
boost::numeric::ublas::triangular_adaptor<const M, boost::numeric::ublas::upper> upper_part(const M& m)
{
  return boost::numeric::ublas::triangular_adaptor<const M, boost::numeric::ublas::upper> (m);
}

template <typename M>
boost::numeric::ublas::triangular_adaptor<const M, boost::numeric::ublas::lower> lower_part(const M& m)
{
  return boost::numeric::ublas::triangular_adaptor<const M, boost::numeric::ublas::lower> (m);
}

// Run over all matrix types and aply a functor to it.
template <typename F>
int do_matrix_types(const F& f)
{
  using namespace boost::numeric::ublas ;

  typedef typename F::value_type value_type ;

  {
    matrix<value_type,column_major> m(f.size1(), f.size2());
    if(f(m)) return 255;
  }
  {
    typedef matrix<value_type,column_major> matrix_type ;
    matrix_type m(2*f.size1(), 2*f.size2());
    matrix_range<matrix_type> m_r(m, range(1,1+f.size1()), range(2,2+f.size2()));
    if(f(m_r)) return 255 ;
  }

  return 0;
} // do_matrix_types()

// Functor to select syrk/herk calls given the two matrix_types
template <typename M1>
struct Syrk2
{
  typedef typename M1::value_type                                                         value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Syrk2(const M1& a,
        const ref_matrix_type& a_ref)
    : a_(a), a_ref_(a_ref)
    , c_ref_(a_ref_.size1(), a_ref_.size1())
  {
    randomize(c_ref_);
    RealDiagonal<value_type>()(c_ref_);  // Must have real diagonal for herk.
  }

  template <typename M>
  int operator()(M& c) const
  {
    using namespace boost::numeric::ublas ;

    real_type alpha = 2.0 ;
    real_type beta = -3.0 ;

    c.assign(c_ref_);
    //boost::numeric::bindings::blas::syrk( 'U', 'N', value_type(alpha), a_, value_type(beta), c ) ;
    symmetric_adaptor<M, upper> sc(c);
    boost::numeric::bindings::blas::syrk(value_type(alpha), a_, value_type(beta), sc) ;
    if(norm_frobenius(upper_part(c - (beta*c_ref_ + alpha * prod(a_ref_, trans(a_ref_)))))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(c))) return 255;

    c.assign(c_ref_);
    symmetric_adaptor<M, upper> c_s(c);
    //boost::numeric::bindings::blas::syrk( 'U', 'N', value_type(alpha), a_, value_type(beta), c_s ) ;
    boost::numeric::bindings::blas::syrk(value_type(alpha), a_, value_type(beta), c_s) ;
    if(norm_frobenius(upper_part(c_s - (beta*c_ref_ + alpha * prod(a_ref_, trans(a_ref_)))))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(c_s))) return 255;

    c.assign(c_ref_);
    //boost::numeric::bindings::blas::herk( 'U', 'N', alpha, a_, beta, c ) ;
    hermitian_adaptor<M, upper> hc(c);
    boost::numeric::bindings::blas::herk(alpha, a_, beta, hc) ;
    if(norm_frobenius(upper_part(c - (beta*c_ref_ + alpha * prod(a_ref_, herm(a_ref_)))))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(c))) return 255;

    c.assign(c_ref_);
    hermitian_adaptor<M, upper> c_h(c);
    //boost::numeric::bindings::blas::herk( 'U', 'N', alpha, a_, beta, c_h ) ;
    boost::numeric::bindings::blas::herk(alpha, a_, beta, c_h) ;
    if(norm_frobenius(upper_part(c_h - (beta*c_ref_ + alpha * prod(a_ref_, herm(a_ref_)))))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(c_h))) return 255;

    return 0;
  }

  size_type size1() const
  {
    return c_ref_.size1() ;
  }
  size_type size2() const
  {
    return c_ref_.size2() ;
  }

  const M1& a_ ;
  const ref_matrix_type& a_ref_;
  ref_matrix_type  c_ref_;
}; // Syrk2

template <typename T>
struct Syrk1
{
  typedef T                                                                               value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Syrk1()
    : a_ref_(4, 7)
  {
    randomize(a_ref_);
  }

  template <typename M>
  int operator()(M& a) const
  {
    typedef Syrk2<M> functor_type ;
    a.assign(a_ref_);
    return do_matrix_types(functor_type(a, a_ref_));
  }

  size_type size1() const
  {
    return a_ref_.size1();
  }
  size_type size2() const
  {
    return a_ref_.size2();
  }

  ref_matrix_type a_ref_;
}; // Syrk1

// Functor to select gemm calls given the three matrix_types
template <typename M1, typename M2>
struct Gemm3
{
  typedef typename M1::value_type                                                         value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Gemm3(const M1& a, const M2& b,
        const ref_matrix_type& a_ref, const ref_matrix_type& b_ref)
    : a_(a), b_(b)
    , a_ref_(a_ref), b_ref_(b_ref), c_ref_(a_ref_.size1(), b_ref_.size2())
  {
    randomize(c_ref_);
  }

  template <typename M>
  int operator()(M& c) const
  {
    c.assign(c_ref_);

    value_type alpha = 2.0 ;
    value_type beta = -3.0 ;

    boost::numeric::bindings::blas::gemm(alpha, a_, b_, beta, c) ;
    double safety_factor(1.5);
    if(norm_frobenius(c - (beta*c_ref_ + alpha * prod(a_ref_, b_ref_)))
        > safety_factor*std::numeric_limits< real_type >::epsilon() * norm_frobenius(c)) return 255;

    return 0;
  }

  size_type size1() const
  {
    return c_ref_.size1() ;
  }
  size_type size2() const
  {
    return c_ref_.size2() ;
  }

  const M1& a_ ;
  const M2& b_ ;
  const ref_matrix_type& a_ref_, b_ref_;
  ref_matrix_type  c_ref_;
}; // Gemm3

template <typename M1>
struct Gemm2
{
  typedef typename M1::value_type                                                         value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Gemm2(const M1& a, const ref_matrix_type& a_ref)
    : a_(a)
    , a_ref_(a_ref)
    , b_ref_(a_ref.size2(), 7)
  {
    randomize(b_ref_);
  }

  template <typename M>
  int operator()(M& b) const
  {
    typedef Gemm3<M1,M> functor_type ;
    b.assign(b_ref_);
    return do_matrix_types(functor_type(a_, b, a_ref_, b_ref_));
  }

  size_type size1() const
  {
    return b_ref_.size1();
  }
  size_type size2() const
  {
    return b_ref_.size2();
  }

  const M1&              a_ ;
  const ref_matrix_type& a_ref_;
  ref_matrix_type        b_ref_;
}; // Gemm2

template <typename T>
struct Gemm1
{
  typedef T                                                                               value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Gemm1()
    : a_ref_(4, 7)
  {
    randomize(a_ref_);
  }

  template <typename M>
  int operator()(M& a) const
  {
    typedef Gemm2<M> functor_type ;
    a.assign(a_ref_);
    return do_matrix_types(functor_type(a, a_ref_));
  }

  size_type size1() const
  {
    return a_ref_.size1();
  }
  size_type size2() const
  {
    return a_ref_.size2();
  }

  ref_matrix_type a_ref_;
}; // Gemm1

// Functor to select syrk/herk calls given the two matrix_types
template <typename M1>
struct Trsm2
{
  typedef typename M1::value_type                                                         value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Trsm2(M1& c,
        const ref_matrix_type& b_ref, const ref_matrix_type& c_ref)
    : c_(c), c_ref_(c_ref), b_ref_(b_ref)
    , a_ref_(b_ref_.size1(), c_ref_.size2())
  {
    randomize(a_ref_) ;
  }

  template <typename M>
  int operator()(M& a) const
  {
    using namespace boost::numeric::ublas ;

    real_type alpha = 2.0 ;

    a.assign(a_ref_);
    //boost::numeric::bindings::blas::trsm( 'L', 'U', 'N', 'N', value_type(alpha), b_ref_, a ) ;
    symmetric_adaptor<const ref_matrix_type, upper> sb_ref(b_ref_);
    boost::numeric::bindings::blas::trsm(bindings::tag::left(), value_type(alpha), sb_ref, a) ;
    if(norm_frobenius(alpha * a_ref_ - prod(upper_part(b_ref_), a))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(b_ref_)) * norm_frobenius(a)) return 255;

    a.assign(a_ref_);
    //boost::numeric::bindings::blas::trsm( 'R', 'U', 'N', 'N', value_type(alpha), c_, a ) ;
    symmetric_adaptor<const M1, upper> suc(c_);
    boost::numeric::bindings::blas::trsm(bindings::tag::right(), value_type(alpha), suc, a) ;
    if(norm_frobenius(alpha * a_ref_ - prod(a, upper_part(c_)))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(c_)) * norm_frobenius(a)) return 255;

    a.assign(a_ref_);
    //boost::numeric::bindings::blas::trsm( 'R', 'L', 'N', 'N', value_type(alpha), c_, a ) ;
    symmetric_adaptor<const M1, lower> slc(c_);
    boost::numeric::bindings::blas::trsm(bindings::tag::right(), value_type(alpha), slc, a) ;
    if(norm_frobenius(alpha * a_ref_ - prod(a, lower_part(c_)))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(lower_part(c_)) * norm_frobenius(a)) return 255;

    a.assign(a_ref_);
    //boost::numeric::bindings::blas::trsm( 'L', 'U', 'T', 'N', value_type(alpha), b_ref_, a ) ;
    boost::numeric::bindings::blas::trsm(bindings::tag::left(), value_type(alpha), bindings::trans(sb_ref), a) ;
    if(norm_frobenius(alpha * a_ref_ - prod(trans(upper_part(b_ref_)), a))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(upper_part(b_ref_)) * norm_frobenius(a)) return 255;

    a.assign(a_ref_);
    //boost::numeric::bindings::blas::trsm( 'L', 'U', 'N', 'U', value_type(alpha), b_ref_, a ) ;
    boost::numeric::bindings::blas::trsm(bindings::tag::left(), value_type(alpha), bindings::unit_upper(sb_ref), a) ;
    if(norm_frobenius(alpha * a_ref_ - prod(unit_upper_part(b_ref_), a))
        > std::numeric_limits< real_type >::epsilon() * norm_frobenius(unit_upper_part(b_ref_)) * norm_frobenius(a)) return 255;

    return 0;
  }

  size_type size1() const
  {
    return a_ref_.size1() ;
  }
  size_type size2() const
  {
    return a_ref_.size2() ;
  }

  const M1& c_ ;
  const ref_matrix_type& c_ref_;
  const ref_matrix_type& b_ref_;
  ref_matrix_type        a_ref_;
} ; // Trsm2

template <typename T>
struct Trsm1
{
  typedef T                                                                               value_type ;
  typedef boost::numeric::ublas::matrix<value_type, boost::numeric::ublas::column_major>  ref_matrix_type ;
  typedef typename bindings::remove_imaginary<value_type>::type real_type ;
  typedef typename ref_matrix_type::size_type                                             size_type ;

  Trsm1()
    : b_(4, 4)
    , c_(7, 7)
  {
    randomize(b_);
    randomize(c_);
    for(std::size_t i=0; i<b_.size1(); ++i) if(b_(i,i)==value_type(0.0)) b_(i,i) = value_type(1.0) ;
    for(std::size_t i=0; i<c_.size1(); ++i) if(c_(i,i)==value_type(0.0)) c_(i,i) = value_type(1.0) ;
  }

  template <typename M>
  int operator()(M& c) const
  {
    typedef Trsm2<M> functor_type ;
    return do_matrix_types(functor_type(c, b_, c_));
  }

  size_type size1() const
  {
    return c_.size1();
  }
  size_type size2() const
  {
    return c_.size2();
  }

  ref_matrix_type b_;
  ref_matrix_type c_;
} ; // Trsm1

template <typename T>
int do_value_type()
{
  // Gemm test
  std::cout << "  gemm\n";
  if(do_matrix_types(Gemm1<T>())) return 255 ;

  // Syrk and herk test
  std::cout << "  herk\n";
  if(do_matrix_types(Syrk1<T>())) return 255 ;

  // Trsm test
  std::cout << "  trsm\n";
  if(do_matrix_types(Trsm1<T>())) return 255 ;

  return 0 ;
} // do_value_type()

int main()
{
  // Test for various value_types
  std::cout << "float\n";
  if(do_value_type<float>()) return 255 ;
  std::cout << "double\n";
  if(do_value_type<double>()) return 255 ;
  std::cout << "complex<float>\n";
  if(do_value_type<std::complex<float> >()) return 255 ;
  std::cout << "complex<double>\n";
  if(do_value_type<std::complex<double> >()) return 255 ;

  std::cout << "Regression test succeeded\n" ;

  return 0 ;
}


// Copyright (C) 2014 The Regents of the University of California (Regents).
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
//
//     * Neither the name of The Regents or University of California nor the
//       names of its contributors may be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//
// Please contact the author of this library if you have any questions.
// Author: Chris Sweeney (cmsweeney@cs.ucsb.edu)

#include <Eigen/Core>

#include "theia/sfm/camera/projection_matrix_utils.h"
#include "theia/test/test_utils.h"
#include "theia/util/random.h"
#include "gtest/gtest.h"

namespace theia {

using Eigen::Matrix3d;
using Eigen::Vector3d;

RandomNumberGenerator rng(46);

TEST(CameraIntrinsics, IntrinsicsToCalibrationMatrix) {
  const double focal_length = 800.0;
  const double skew = 1.0;
  const double aspect_ratio = 1.25;
  const double principal_point_x = 320;
  const double principal_point_y = 240;

  Matrix3d gt_calibration_matrix;
  gt_calibration_matrix << focal_length, skew, principal_point_x, 0,
      focal_length * aspect_ratio, principal_point_y, 0, 0, 1;

  Matrix3d calibration_matrix;
  IntrinsicsToCalibrationMatrix(focal_length,
                                skew,
                                aspect_ratio,
                                principal_point_x,
                                principal_point_y,
                                &calibration_matrix);
  for (int i = 0; i < calibration_matrix.rows(); i++) {
    for (int j = 0; j < calibration_matrix.cols(); j++) {
      EXPECT_DOUBLE_EQ(gt_calibration_matrix(i, j), calibration_matrix(i, j));
    }
  }
}

TEST(CameraIntrinsics, CalibrationMatrixToIntrinsics) {
  Matrix3d calibration_matrix;
  calibration_matrix << 800, 1.0, 320, 0, 1000, 240, 0, 0, 1;
  double focal_length, skew, aspect_ratio, principal_point_x, principal_point_y;
  CalibrationMatrixToIntrinsics(calibration_matrix,
                                &focal_length,
                                &skew,
                                &aspect_ratio,
                                &principal_point_x,
                                &principal_point_y);
  EXPECT_DOUBLE_EQ(focal_length, 800);
  EXPECT_DOUBLE_EQ(skew, 1.0);
  EXPECT_DOUBLE_EQ(aspect_ratio, 1.25);
  EXPECT_DOUBLE_EQ(principal_point_x, 320);
  EXPECT_DOUBLE_EQ(principal_point_y, 240);
}

TEST(DecomposeProjectMatrix, Random) {
  RandomNumberGenerator rng(94);
  Matrix3x4d random_pmatrix;
  for (int i = 0; i < 1000; i++) {
    rng.SetRandom(&random_pmatrix);
    Matrix3d calibration_matrix;
    Vector3d rotation, position;
    EXPECT_TRUE(DecomposeProjectionMatrix(
        random_pmatrix, &calibration_matrix, &rotation, &position));
  }
}

TEST(ComposeProjectionMatrix, Identity) {
  const Matrix3d calibration_matrix = Matrix3d::Identity();
  const Vector3d rotation = Vector3d::Zero();
  const Vector3d position = Vector3d::Zero();
  Matrix3x4d projection_matrix;
  EXPECT_TRUE(ComposeProjectionMatrix(
      calibration_matrix, rotation, position, &projection_matrix));
  EXPECT_TRUE(projection_matrix == Matrix3x4d::Identity());
}

TEST(ComposeProjectionMatrix, Random) {
  for (int i = 0; i < 1000; i++) {
    const Vector3d rotation = rng.RandVector3d();
    const Vector3d position = rng.RandVector3d();

    Matrix3d calibration_matrix;
    const double focal_length = 800;
    const double principal_point[2] = {400, 300};
    IntrinsicsToCalibrationMatrix(focal_length,
                                  0.0,
                                  1.0,
                                  principal_point[0],
                                  principal_point[1],
                                  &calibration_matrix);

    Matrix3x4d projection_matrix;
    EXPECT_TRUE(ComposeProjectionMatrix(
        calibration_matrix, rotation, position, &projection_matrix));
  }
}

TEST(ProjectionMatrix, Consistency) {
  const double kTolerance = 1e-6;

  Matrix3x4d random_pmatrix;
  for (int i = 0; i < 1000; i++) {
    rng.SetRandom(&random_pmatrix);
    Matrix3d calibration_matrix;
    Vector3d rotation, position;
    EXPECT_TRUE(DecomposeProjectionMatrix(
        random_pmatrix, &calibration_matrix, &rotation, &position));

    Matrix3x4d composed_pmatrix;
    EXPECT_TRUE(ComposeProjectionMatrix(
        calibration_matrix, rotation, position, &composed_pmatrix));

    EXPECT_TRUE(test::ArraysEqualUpToScale(
        12, random_pmatrix.data(), composed_pmatrix.data(), kTolerance))
        << "gt pmatrix: \n"
        << random_pmatrix << "\ncomposed pmatrix: \n"
        << composed_pmatrix;
  }
}

}  // namespace theia


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_LDEXP_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_LDEXP_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/hierarchy/simd.hpp>
#include <boost/simd/meta/as_logical.hpp>
#include <boost/simd/config.hpp>
#include <boost/simd/constant/limitexponent.hpp>
#include <boost/simd/constant/maxexponent.hpp>
#include <boost/simd/constant/nbmantissabits.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/function/simd/bitwise_cast.hpp>
#include <boost/simd/function/simd/is_equal.hpp>
#include <boost/simd/function/simd/multiplies.hpp>
#include <boost/simd/function/simd/multiplies.hpp>
#include <boost/simd/function/simd/plus.hpp>
#include <boost/simd/function/simd/rshl.hpp>
#include <boost/simd/function/simd/seladd.hpp>
#include <boost/simd/function/simd/selsub.hpp>
#include <boost/simd/function/simd/shift_left.hpp>
#include <boost/dispatch/meta/as_integer.hpp>

#ifndef BOOST_SIMD_NO_DENORMALS
#include <boost/simd/constant/minexponent.hpp>
#include <boost/simd/constant/smallestposval.hpp>
#include <boost/simd/function/simd/if_else.hpp>
#include <boost/simd/function/simd/is_less.hpp>
#include <boost/simd/function/simd/selsub.hpp>
#endif

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;
   BOOST_DISPATCH_OVERLOAD(ldexp_
                             , (typename A0, typename A1, typename X)
                             , bd::cpu_
                             , bs::pack_<bd::integer_<A1>, X>
                             , bs::pack_<bd::integer_<A0>, X>
                             )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const  A1&  a1) const BOOST_NOEXCEPT
      {
        return rshl(a0, a1);
      }
   };

   BOOST_DISPATCH_OVERLOAD(ldexp_
                             , (typename A0, typename A1, typename X, typename Y)
                             , bd::cpu_
                             , bs::pack_<bd::floating_<A0>, X>
                             , bs::pack_<bd::integer_<A1>, Y>
                             )
   {
      BOOST_FORCEINLINE A0 operator()( const A0& a0, const  A1&  a1) const BOOST_NOEXCEPT
      {
        using iA0 = bd::as_integer_t<A0>;
        using sA0 = bd::scalar_of_t<A0>;
        iA0 e = a1;
        A0 f = One<A0>();
  #ifndef BOOST_SIMD_NO_DENORMALS
        auto denormal =  is_less(e, Minexponent<A0>());
        e = selsub(denormal, e, Minexponent<A0>());
        f = if_else(denormal, Smallestposval<A0>(), One<A0>());
  #endif
        auto test = is_equal(e, Limitexponent<A0>());
        f = seladd(test, f, One<A0>());
        e = selsub(test, e, One<iA0>());
        e = e+Maxexponent<A0>();
        e = shift_left(e, Nbmantissabits<sA0>());
        return a0*bitwise_cast<A0>(e)*f;
      }
   };

} } }

#endif


#ifndef USE_CUDA

#include <Spirit_Defines.h>
#include <engine/Hamiltonian_Heisenberg_Pairs.hpp>
#include <engine/Vectormath.hpp>
#include <engine/Neighbours.hpp>
#include <data/Spin_System.hpp>
#include <utility/Constants.hpp>

#include<iostream>

#include <Eigen/Dense>

using std::vector;
using std::function;

using namespace Data;
using namespace Utility;
using Utility::Constants::mu_B;
using Utility::Constants::mu_0;
using Utility::Constants::Pi;
using Engine::Vectormath::check_atom_type;
using Engine::Vectormath::idx_from_pair;

namespace Engine
{
    Hamiltonian_Heisenberg_Pairs::Hamiltonian_Heisenberg_Pairs(
        scalarfield mu_s,
        scalar external_field_magnitude, Vector3 external_field_normal,
        intfield anisotropy_indices, scalarfield anisotropy_magnitudes, vectorfield anisotropy_normals,
        pairfield exchange_pairs, scalarfield exchange_magnitudes,
        pairfield dmi_pairs, scalarfield dmi_magnitudes, vectorfield dmi_normals,
        scalar ddi_radius,
        tripletfield triplets, scalarfield triplet_magnitudes1, scalarfield triplet_magnitudes2,
        quadrupletfield quadruplets, scalarfield quadruplet_magnitudes,
        std::shared_ptr<Data::Geometry> geometry,
        intfield boundary_conditions
    ) :
        Hamiltonian(boundary_conditions), geometry(geometry),
        mu_s(mu_s),
        external_field_magnitude(external_field_magnitude * mu_B), external_field_normal(external_field_normal),
        anisotropy_indices(anisotropy_indices), anisotropy_magnitudes(anisotropy_magnitudes), anisotropy_normals(anisotropy_normals),
        exchange_pairs(exchange_pairs), exchange_magnitudes(exchange_magnitudes),
        dmi_pairs(dmi_pairs), dmi_magnitudes(dmi_magnitudes), dmi_normals(dmi_normals),
        triplets(triplets), triplet_magnitudes1(triplet_magnitudes1), triplet_magnitudes2(triplet_magnitudes2),
        quadruplets(quadruplets), quadruplet_magnitudes(quadruplet_magnitudes)
    {
        // Generate DDI pairs, magnitudes, normals
        this->ddi_pairs = Engine::Neighbours::Get_Pairs_in_Radius(*this->geometry, ddi_radius);
        scalar magnitude;
        Vector3 normal;
        for (unsigned int i = 0; i<ddi_pairs.size(); ++i)
        {
            Engine::Neighbours::DDI_from_Pair(*this->geometry, { ddi_pairs[i].i, ddi_pairs[i].j, ddi_pairs[i].translations }, magnitude, normal);
            this->ddi_magnitudes.push_back(magnitude);
            this->ddi_normals.push_back(normal);
        }

        this->Update_Energy_Contributions();
    }

    void Hamiltonian_Heisenberg_Pairs::Update_Energy_Contributions()
    {
        this->energy_contributions_per_spin = std::vector<std::pair<std::string, scalarfield>>(0);

        // External field
        if (this->external_field_magnitude > 0)
        {
            this->energy_contributions_per_spin.push_back({"Zeeman", scalarfield(0)});
            this->idx_zeeman = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_zeeman = -1;
        // Anisotropy
        if (this->anisotropy_indices.size() > 0)
        {
            this->energy_contributions_per_spin.push_back({"Anisotropy", scalarfield(0) });
            this->idx_anisotropy = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_anisotropy = -1;
        // Exchange
        if (this->exchange_pairs.size() > 0)
        {
            this->energy_contributions_per_spin.push_back({"Exchange", scalarfield(0) });
            this->idx_exchange = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_exchange = -1;
        // DMI
        if (this->dmi_pairs.size() > 0)
        {
            this->energy_contributions_per_spin.push_back({"DMI", scalarfield(0) });
            this->idx_dmi = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_dmi = -1;
        // Dipole-Dipole
        if (this->ddi_pairs.size() > 0)
        {
            this->energy_contributions_per_spin.push_back({"DD", scalarfield(0) });
            this->idx_ddi = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_ddi = -1;
        // Triplets
        if (this->triplets.size() > 0)
        {
            this->energy_contributions_per_spin.push_back({"triplets", scalarfield(0) });
            this->idx_triplet = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_triplet = -1;
        // Quadruplets
        if (this->quadruplets.size() > 0)
        {
            this->energy_contributions_per_spin.push_back({"Quadruplets", scalarfield(0) });
            this->idx_quadruplet = this->energy_contributions_per_spin.size()-1;
        }
        else this->idx_quadruplet = -1;
    }

    void Hamiltonian_Heisenberg_Pairs::Energy_Contributions_per_Spin(const vectorfield & spins, std::vector<std::pair<std::string, scalarfield>> & contributions)
    {
        if (contributions.size() != this->energy_contributions_per_spin.size())
        {
            contributions = this->energy_contributions_per_spin;
        }

        int nos = spins.size();
        for (auto& contrib : contributions)
        {
            // Allocate if not already allocated
            if (contrib.second.size() != nos) contrib.second = scalarfield(nos, 0);
            // Otherwise set to zero
            else Vectormath::fill(contrib.second, 0);
        }

        // External field
        if (this->idx_zeeman >=0 )     E_Zeeman(spins, contributions[idx_zeeman].second);

        // Anisotropy
        if (this->idx_anisotropy >=0 ) E_Anisotropy(spins, contributions[idx_anisotropy].second);

        // Exchange
        if (this->idx_exchange >=0 )   E_Exchange(spins, contributions[idx_exchange].second);
        // DMI
        if (this->idx_dmi >=0 )        E_DMI(spins,contributions[idx_dmi].second);
        // DD
        if (this->idx_ddi >=0 )        E_DDI(spins, contributions[idx_ddi].second);
        // Triplets
        if (this->idx_triplet >=0 ) E_Triplet(spins, contributions[idx_triplet].second);
        // Quadruplets
        if (this->idx_quadruplet >=0 ) E_Quadruplet(spins, contributions[idx_quadruplet].second);
    }

    void Hamiltonian_Heisenberg_Pairs::E_Zeeman(const vectorfield & spins, scalarfield & Energy)
    {
        const int N = geometry->n_cell_atoms;

        #pragma omp parallel for
        for (int icell = 0; icell < geometry->n_cells_total; ++icell)
        {
            for (int ibasis = 0; ibasis < N; ++ibasis)
            {
                int ispin = icell*N + ibasis;
                if (check_atom_type(this->geometry->atom_types[ispin]))
                    Energy[ispin] -= this->mu_s[ibasis] * this->external_field_magnitude * this->external_field_normal.dot(spins[ispin]);
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::E_Anisotropy(const vectorfield & spins, scalarfield & Energy)
    {
        const int N = geometry->n_cell_atoms;

        #pragma omp parallel for
        for (int icell = 0; icell < geometry->n_cells_total; ++icell)
        {
            for (int iani = 0; iani < anisotropy_indices.size(); ++iani)
            {
                int ispin = icell*N + anisotropy_indices[iani];
                if (check_atom_type(this->geometry->atom_types[ispin]))
                    Energy[ispin] -= this->anisotropy_magnitudes[iani] * std::pow(anisotropy_normals[iani].dot(spins[ispin]), 2.0);
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::E_Exchange(const vectorfield & spins, scalarfield & Energy)
    {
        const int Na = geometry->n_cells[0];
        const int Nb = geometry->n_cells[1];
        const int Nc = geometry->n_cells[2];

        #pragma omp parallel for collapse(3)
        for (int da = 0; da < Na; ++da)
        {
            for (int db = 0; db < Nb; ++db)
            {
                for (int dc = 0; dc < Nc; ++dc)
                {
                    for (unsigned int i_pair = 0; i_pair < exchange_pairs.size(); ++i_pair)
                    {
                        std::array<int, 3 > translations = { da, db, dc };
                        int ispin = exchange_pairs[i_pair].i+ Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, exchange_pairs[i_pair]);
                        if (jspin >= 0)
                        {
                            Energy[ispin] -= 0.5 * exchange_magnitudes[i_pair] * spins[ispin].dot(spins[jspin]);
                            #ifndef _OPENMP
                            Energy[jspin] -= 0.5 * exchange_magnitudes[i_pair] * spins[ispin].dot(spins[jspin]);
                            #endif
                        }

                        // To parallelize with OpenMP we avoid atomics by not adding to two different spins in one thread.
                        //		instead, we need to also add the inverse pair to each spin, which makes it similar to the
                        //		neighbours implementation (in terms of the number of pairs)
                        #ifdef _OPENMP
                        int jspin2 = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, exchange_pairs[i_pair], true);
                        if (jspin2 >= 0)
                        {
                            Energy[ispin] -= 0.5 * exchange_magnitudes[i_pair] * spins[ispin].dot(spins[jspin2]);
                        }
                        #endif
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::E_DMI(const vectorfield & spins, scalarfield & Energy)
    {
        const int Na = geometry->n_cells[0];
        const int Nb = geometry->n_cells[1];
        const int Nc = geometry->n_cells[2];

        #pragma omp parallel for collapse(3)
        for (int da = 0; da < Na; ++da)
        {
            for (int db = 0; db < Nb; ++db)
            {
                for (int dc = 0; dc < Nc; ++dc)
                {
                    for (unsigned int i_pair = 0; i_pair < dmi_pairs.size(); ++i_pair)
                    {
                        std::array<int, 3 > translations = { da, db, dc };
                        int ispin = dmi_pairs[i_pair].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, dmi_pairs[i_pair]);
                        if (jspin >= 0)
                        {
                            Energy[ispin] -= 0.5 * dmi_magnitudes[i_pair] * dmi_normals[i_pair].dot(spins[ispin].cross(spins[jspin]));
                            #ifndef _OPENMP
                            Energy[jspin] -= 0.5 * dmi_magnitudes[i_pair] * dmi_normals[i_pair].dot(spins[ispin].cross(spins[jspin]));
                            #endif
                        }

                        // To parallelize with OpenMP we avoid atomics by not adding to two different spins in one thread.
                        //		instead, we need to also add the inverse pair to each spin, which makes it similar to the
                        //		neighbours implementation (in terms of the number of pairs)
                        #ifdef _OPENMP
                        int jspin2 = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, dmi_pairs[i_pair], true);
                        if (jspin2 >= 0)
                        {
                            Energy[ispin] += 0.5 * dmi_magnitudes[i_pair] * dmi_normals[i_pair].dot(spins[ispin].cross(spins[jspin2]));
                        }
                        #endif
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::E_DDI(const vectorfield & spins, scalarfield & Energy)
    {
        // The translations are in angstr�m, so the |r|[m] becomes |r|[m]*10^-10
        const scalar mult = mu_0 * std::pow(mu_B, 2) / ( 4*Pi * 1e-30 );

        scalar result = 0.0;

        for (unsigned int i_pair = 0; i_pair < ddi_pairs.size(); ++i_pair)
        {
            if (ddi_magnitudes[i_pair] > 0.0)
            {
                for (int da = 0; da < geometry->n_cells[0]; ++da)
                {
                    for (int db = 0; db < geometry->n_cells[1]; ++db)
                    {
                        for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                        {
                            std::array<int, 3 > translations = { da, db, dc };
                            int i = ddi_pairs[i_pair].i;
                            int j = ddi_pairs[i_pair].j;
                            int ispin = i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                            int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, ddi_pairs[i_pair]);
                            if (jspin >= 0)
                            {
                                Energy[ispin] -= 0.5 * this->mu_s[i] * this->mu_s[j] * mult / std::pow(ddi_magnitudes[i_pair], 3.0) *
                                    (3 * spins[ispin].dot(ddi_normals[i_pair]) * spins[ispin].dot(ddi_normals[i_pair]) - spins[ispin].dot(spins[ispin]));
                                Energy[jspin] -= 0.5 * this->mu_s[i] * this->mu_s[j] * mult / std::pow(ddi_magnitudes[i_pair], 3.0) *
                                    (3 * spins[ispin].dot(ddi_normals[i_pair]) * spins[ispin].dot(ddi_normals[i_pair]) - spins[ispin].dot(spins[ispin]));
                            }
                        }
                    }
                }
            }
        }
    }// end DipoleDipole

    void Hamiltonian_Heisenberg_Pairs::E_Triplet(const vectorfield & spins, scalarfield & Energy)
    {
        for (unsigned int itrip = 0; itrip < triplets.size(); ++itrip)
        {
            for (int da = 0; da < geometry->n_cells[0]; ++da)
            {
                for (int db = 0; db < geometry->n_cells[1]; ++db)
                {
                    for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                    {
                        std::array<int, 3 > translations = { da, db, dc };
                        int ispin = triplets[itrip].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = triplets[itrip].j + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, triplets[itrip].d_j);
                        int kspin = triplets[itrip].k + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, triplets[itrip].d_k);
                        Vector3 n = {triplets[itrip].n[0], triplets[itrip].n[1], triplets[itrip].n[2]};

                        if ( check_atom_type(this->geometry->atom_types[ispin]) && check_atom_type(this->geometry->atom_types[jspin]) &&
                                check_atom_type(this->geometry->atom_types[kspin]))
                        {
                            Energy[ispin] -= 1.0/2.0 * triplet_magnitudes1[itrip] * pow(spins[ispin].dot(spins[jspin].cross(spins[kspin])),2);
                            Energy[jspin] -= 1.0/2.0 * triplet_magnitudes1[itrip] * pow(spins[ispin].dot(spins[jspin].cross(spins[kspin])),2);
                            Energy[kspin] -= 1.0/2.0 * triplet_magnitudes1[itrip] * pow(spins[ispin].dot(spins[jspin].cross(spins[kspin])),2);

                            Energy[ispin] -= 1.0/3.0 * triplet_magnitudes2[itrip] * spins[ispin].dot(spins[jspin].cross(spins[kspin]))
                                                * (n.dot(spins[ispin]+spins[jspin]+spins[kspin]));
                            Energy[jspin] -= 1.0/3.0 * triplet_magnitudes2[itrip] * spins[ispin].dot(spins[jspin].cross(spins[kspin]))
                                                * (n.dot(spins[ispin]+spins[jspin]+spins[kspin]));
                            Energy[kspin] -= 1.0/3.0 * triplet_magnitudes2[itrip] * spins[ispin].dot(spins[jspin].cross(spins[kspin]))
                                                * (n.dot(spins[ispin]+spins[jspin]+spins[kspin]));

                        }
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::E_Quadruplet(const vectorfield & spins, scalarfield & Energy)
    {
        for (unsigned int iquad = 0; iquad < quadruplets.size(); ++iquad)
        {
            for (int da = 0; da < geometry->n_cells[0]; ++da)
            {
                for (int db = 0; db < geometry->n_cells[1]; ++db)
                {
                    for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                    {
                        std::array<int, 3 > translations = { da, db, dc };
                        int ispin = quadruplets[iquad].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = quadruplets[iquad].j + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, quadruplets[iquad].d_j);
                        int kspin = quadruplets[iquad].k + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, quadruplets[iquad].d_k);
                        int lspin = quadruplets[iquad].l + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, quadruplets[iquad].d_l);

                        if ( check_atom_type(this->geometry->atom_types[ispin]) && check_atom_type(this->geometry->atom_types[jspin]) &&
                                check_atom_type(this->geometry->atom_types[kspin]) && check_atom_type(this->geometry->atom_types[lspin]) )
                        {
                            Energy[ispin] -= 0.25*quadruplet_magnitudes[iquad] * (spins[ispin].dot(spins[jspin])) * (spins[kspin].dot(spins[lspin]));
                            Energy[jspin] -= 0.25*quadruplet_magnitudes[iquad] * (spins[ispin].dot(spins[jspin])) * (spins[kspin].dot(spins[lspin]));
                            Energy[kspin] -= 0.25*quadruplet_magnitudes[iquad] * (spins[ispin].dot(spins[jspin])) * (spins[kspin].dot(spins[lspin]));
                            Energy[lspin] -= 0.25*quadruplet_magnitudes[iquad] * (spins[ispin].dot(spins[jspin])) * (spins[kspin].dot(spins[lspin]));
                        }
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient(const vectorfield & spins, vectorfield & gradient)
    {
        // Set to zero
        Vectormath::fill(gradient, {0,0,0});

        // External field
        Gradient_Zeeman(gradient);

        // Anisotropy
        Gradient_Anisotropy(spins, gradient);

        // Exchange
        this->Gradient_Exchange(spins, gradient);
        // DMI
        this->Gradient_DMI(spins, gradient);
        // DD
        this->Gradient_DDI(spins, gradient);

        // Triplets
        this->Gradient_Triplet(spins, gradient);

        // Quadruplets
        this->Gradient_Quadruplet(spins, gradient);
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient_Zeeman(vectorfield & gradient)
    {
        const int N = geometry->n_cell_atoms;

        #pragma omp parallel for
        for (int icell = 0; icell < geometry->n_cells_total; ++icell)
        {
            for (int ibasis = 0; ibasis < N; ++ibasis)
            {
                int ispin = icell*N + ibasis;
                if (check_atom_type(this->geometry->atom_types[ispin]))
                    gradient[ispin] -= this->mu_s[ibasis] * this->external_field_magnitude * this->external_field_normal;
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient_Anisotropy(const vectorfield & spins, vectorfield & gradient)
    {
        const int N = geometry->n_cell_atoms;

        #pragma omp parallel for
        for (int icell = 0; icell < geometry->n_cells_total; ++icell)
        {
            for (int iani = 0; iani < anisotropy_indices.size(); ++iani)
            {
                int ispin = icell*N + anisotropy_indices[iani];
                if (check_atom_type(this->geometry->atom_types[ispin]))
                    gradient[ispin] -= 2.0 * this->anisotropy_magnitudes[iani] * this->anisotropy_normals[iani] * anisotropy_normals[iani].dot(spins[ispin]);
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient_Exchange(const vectorfield & spins, vectorfield & gradient)
    {
        const int Na = geometry->n_cells[0];
        const int Nb = geometry->n_cells[1];
        const int Nc = geometry->n_cells[2];

        #pragma omp parallel for collapse(3)
        for (int da = 0; da < Na; ++da)
        {
            for (int db = 0; db < Nb; ++db)
            {
                for (int dc = 0; dc < Nc; ++dc)
                {
                    std::array<int, 3> translations = { da, db, dc };
                    for (unsigned int i_pair = 0; i_pair < exchange_pairs.size(); ++i_pair)
                    {
                        int ispin = exchange_pairs[i_pair].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, exchange_pairs[i_pair]);
                        if (jspin >= 0)
                        {
                            gradient[ispin] -= exchange_magnitudes[i_pair] * spins[jspin];
                            #ifndef _OPENMP
                            gradient[jspin] -= exchange_magnitudes[i_pair] * spins[ispin];
                            #endif
                        }

                        // To parallelize with OpenMP we avoid atomics by not adding to two different spins in one thread.
                        //		instead, we need to also add the inverse pair to each spin, which makes it similar to the
                        //		neighbours implementation (in terms of the number of pairs)
                        #ifdef _OPENMP
                        int jspin2 = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, exchange_pairs[i_pair], true);
                        if (jspin2 >= 0)
                        {
                            gradient[ispin] -= exchange_magnitudes[i_pair] * spins[jspin2];
                        }
                        #endif
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient_DMI(const vectorfield & spins, vectorfield & gradient)
    {
        const int Na = geometry->n_cells[0];
        const int Nb = geometry->n_cells[1];
        const int Nc = geometry->n_cells[2];

        #pragma omp parallel for collapse(3)
        for (int da = 0; da < Na; ++da)
        {
            for (int db = 0; db < Nb; ++db)
            {
                for (int dc = 0; dc < Nc; ++dc)
                {
                    std::array<int, 3 > translations = { da, db, dc };
                    for (unsigned int i_pair = 0; i_pair < dmi_pairs.size(); ++i_pair)
                    {
                        int ispin = dmi_pairs[i_pair].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, dmi_pairs[i_pair]);
                        if (jspin >= 0)
                        {
                            gradient[ispin] -= dmi_magnitudes[i_pair] * spins[jspin].cross(dmi_normals[i_pair]);
                            #ifndef _OPENMP
                            gradient[jspin] += dmi_magnitudes[i_pair] * spins[ispin].cross(dmi_normals[i_pair]);
                            #endif
                        }

                        // To parallelize with OpenMP we avoid atomics by not adding to two different spins in one thread.
                        //		instead, we need to also add the inverse pair to each spin, which makes it similar to the
                        //		neighbours implementation (in terms of the number of pairs)
                        #ifdef _OPENMP
                        int jspin2 = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, dmi_pairs[i_pair], true);
                        if (jspin2 >= 0)
                        {
                            gradient[ispin] += dmi_magnitudes[i_pair] * spins[jspin2].cross(dmi_normals[i_pair]);
                        }
                        #endif
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient_DDI(const vectorfield & spins, vectorfield & gradient)
    {
        // The translations are in angstr�m, so the |r|[m] becomes |r|[m]*10^-10
        const scalar mult = mu_0 * std::pow(mu_B, 2) / ( 4*Pi * 1e-30 );

        for (unsigned int i_pair = 0; i_pair < ddi_pairs.size(); ++i_pair)
        {
            if (ddi_magnitudes[i_pair] > 0.0)
            {
                for (int da = 0; da < geometry->n_cells[0]; ++da)
                {
                    for (int db = 0; db < geometry->n_cells[1]; ++db)
                    {
                        for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                        {
                            scalar skalar_contrib = mult / std::pow(ddi_magnitudes[i_pair], 3.0);
                            std::array<int, 3 > translations = { da, db, dc };

                            int i = ddi_pairs[i_pair].i;
                            int j = ddi_pairs[i_pair].j;
                            int ispin = i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                            int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, ddi_pairs[i_pair]);
                            if (jspin >= 0)
                            {
                                gradient[ispin] -= this->mu_s[j] * skalar_contrib * (3 * ddi_normals[i_pair] * spins[jspin].dot(ddi_normals[i_pair]) - spins[jspin]);
                                gradient[jspin] -= this->mu_s[i] * skalar_contrib * (3 * ddi_normals[i_pair] * spins[ispin].dot(ddi_normals[i_pair]) - spins[ispin]);
                            }
                        }
                    }
                }
            }
        }
    }//end Field_DipoleDipole

    void Hamiltonian_Heisenberg_Pairs::Gradient_Triplet(const vectorfield & spins, vectorfield & gradient)
    {
       for (unsigned int itrip = 0; itrip < triplets.size(); ++itrip)
        {
            int i = triplets[itrip].i;
            int j = triplets[itrip].j;
            int k = triplets[itrip].k;
            Vector3 n = {triplets[itrip].n[0], triplets[itrip].n[1], triplets[itrip].n[2]};
            for (int da = 0; da < geometry->n_cells[0]; ++da)
            {
                for (int db = 0; db < geometry->n_cells[1]; ++db)
                {
                    for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                    {
                        std::array<int, 3 > translations = { da, db, dc };
                        int ispin = i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = j + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, triplets[itrip].d_j);
                        int kspin = k + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, triplets[itrip].d_k);

                        if ( check_atom_type(this->geometry->atom_types[ispin]) && check_atom_type(this->geometry->atom_types[jspin]) &&
                                check_atom_type(this->geometry->atom_types[kspin]))
                        {
                            gradient[ispin] -= 3.0 * triplet_magnitudes1[itrip] * spins[ispin].dot(spins[jspin].cross(spins[kspin]))
                                                * spins[jspin].cross(spins[kspin]);
                            gradient[jspin] -= 3.0 * triplet_magnitudes1[itrip] * spins[ispin].dot(spins[jspin].cross(spins[kspin]))
                                                * spins[kspin].cross(spins[ispin]);
                            gradient[kspin] -= 3.0 * triplet_magnitudes1[itrip] * spins[ispin].dot(spins[jspin].cross(spins[kspin]))
                                                * spins[ispin].cross(spins[jspin]);

                            gradient[ispin] -= triplet_magnitudes2[itrip] * (n.dot(spins[ispin]+spins[jspin]+spins[kspin])
                                                * spins[jspin].cross(spins[kspin])
                                               + spins[ispin].dot(spins[jspin].cross(spins[kspin])) * n);
                            gradient[jspin] -= triplet_magnitudes2[itrip] * (n.dot(spins[ispin]+spins[jspin]+spins[kspin])
                                                * spins[kspin].cross(spins[ispin])
                                               + spins[ispin].dot(spins[jspin].cross(spins[kspin])) * n);
                            gradient[kspin] -= triplet_magnitudes2[itrip] * (n.dot(spins[ispin]+spins[jspin]+spins[kspin])
                                                * spins[ispin].cross(spins[jspin])
                                               + spins[ispin].dot(spins[jspin].cross(spins[kspin])) * n);
                        }
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Gradient_Quadruplet(const vectorfield & spins, vectorfield & gradient)
    {
        for (unsigned int iquad = 0; iquad < quadruplets.size(); ++iquad)
        {
            int i = quadruplets[iquad].i;
            int j = quadruplets[iquad].j;
            int k = quadruplets[iquad].k;
            int l = quadruplets[iquad].l;
            for (int da = 0; da < geometry->n_cells[0]; ++da)
            {
                for (int db = 0; db < geometry->n_cells[1]; ++db)
                {
                    for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                    {
                        std::array<int, 3 > translations = { da, db, dc };
                        int ispin = i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = j + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, quadruplets[iquad].d_j);
                        int kspin = k + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, quadruplets[iquad].d_k);
                        int lspin = l + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations, quadruplets[iquad].d_l);

                        if ( check_atom_type(this->geometry->atom_types[ispin]) && check_atom_type(this->geometry->atom_types[jspin]) &&
                                check_atom_type(this->geometry->atom_types[kspin]) && check_atom_type(this->geometry->atom_types[lspin]) )
                        {
                            gradient[ispin] -= quadruplet_magnitudes[iquad] * spins[jspin] * (spins[kspin].dot(spins[lspin]));
                            gradient[jspin] -= quadruplet_magnitudes[iquad] * spins[ispin] * (spins[kspin].dot(spins[lspin]));
                            gradient[kspin] -= quadruplet_magnitudes[iquad] * (spins[ispin].dot(spins[jspin])) * spins[lspin];
                            gradient[lspin] -= quadruplet_magnitudes[iquad] * (spins[ispin].dot(spins[jspin])) * spins[kspin];
                        }
                    }
                }
            }
        }
    }

    void Hamiltonian_Heisenberg_Pairs::Hessian(const vectorfield & spins, MatrixX & hessian)
    {
        int nos = spins.size();

        // Set to zero
        hessian.setZero();

        // Single Spin elements
        for (int alpha = 0; alpha < 3; ++alpha)
        {
            for ( int beta = 0; beta < 3; ++beta )
            {
                for (unsigned int i = 0; i < anisotropy_indices.size(); ++i)
                {
                    if ( check_atom_type(this->geometry->atom_types[anisotropy_indices[i]]) )
                    {
                        int idx_i = 3 * anisotropy_indices[i] + alpha;
                        int idx_j = 3 * anisotropy_indices[i] + beta;
                        // scalar x = -2.0*this->anisotropy_magnitudes[i] * std::pow(this->anisotropy_normals[i][alpha], 2);
                        hessian( idx_i, idx_j ) += -2.0 * this->anisotropy_magnitudes[i] *
                                                            this->anisotropy_normals[i][alpha] *
                                                            this->anisotropy_normals[i][beta];
                    }
                }
            }
        }

        // Spin Pair elements
        // Exchange
        for (int da = 0; da < geometry->n_cells[0]; ++da)
        {
            for (int db = 0; db < geometry->n_cells[1]; ++db)
            {
                for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                {
                    std::array<int, 3 > translations = { da, db, dc };
                    for (unsigned int i_pair = 0; i_pair < this->exchange_pairs.size(); ++i_pair)
                    {
                        int ispin = exchange_pairs[i_pair].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, exchange_pairs[i_pair]);
                        if (jspin >= 0)
                        {
                            for (int alpha = 0; alpha < 3; ++alpha)
                            {
                                int i = 3 * ispin + alpha;
                                int j = 3 * jspin + alpha;

                                hessian(i, j) += -exchange_magnitudes[i_pair];
                                hessian(j, i) += -exchange_magnitudes[i_pair];
                            }
                        }
                    }
                }
            }
        }

        // DMI
        for (int da = 0; da < geometry->n_cells[0]; ++da)
        {
            for (int db = 0; db < geometry->n_cells[1]; ++db)
            {
                for (int dc = 0; dc < geometry->n_cells[2]; ++dc)
                {
                    std::array<int, 3 > translations = { da, db, dc };
                    for (unsigned int i_pair = 0; i_pair < this->dmi_pairs.size(); ++i_pair)
                    {
                        int ispin = dmi_pairs[i_pair].i + Vectormath::idx_from_translations(geometry->n_cells, geometry->n_cell_atoms, translations);
                        int jspin = idx_from_pair(ispin, boundary_conditions, geometry->n_cells, geometry->n_cell_atoms, geometry->atom_types, dmi_pairs[i_pair]);
                        if (jspin >= 0)
                        {
                            for (int alpha = 0; alpha < 3; ++alpha)
                            {
                                for (int beta = 0; beta < 3; ++beta)
                                {
                                    int i = 3 * ispin + alpha;
                                    int j = 3 * jspin + beta;

                                    if ((alpha == 0 && beta == 1))
                                    {
                                        hessian(i, j) += -dmi_magnitudes[i_pair] * dmi_normals[i_pair][2];
                                        hessian(j, i) += -dmi_magnitudes[i_pair] * dmi_normals[i_pair][2];
                                    }
                                    else if ((alpha == 1 && beta == 0))
                                    {
                                        hessian(i, j) +=  dmi_magnitudes[i_pair] * dmi_normals[i_pair][2];
                                        hessian(j, i) +=  dmi_magnitudes[i_pair] * dmi_normals[i_pair][2];
                                    }
                                    else if ((alpha == 0 && beta == 2))
                                    {
                                        hessian(i, j) +=  dmi_magnitudes[i_pair] * dmi_normals[i_pair][1];
                                        hessian(j, i) +=  dmi_magnitudes[i_pair] * dmi_normals[i_pair][1];
                                    }
                                    else if ((alpha == 2 && beta == 0))
                                    {
                                        hessian(i, j) += -dmi_magnitudes[i_pair] * dmi_normals[i_pair][1];
                                        hessian(j, i) += -dmi_magnitudes[i_pair] * dmi_normals[i_pair][1];
                                    }
                                    else if ((alpha == 1 && beta == 2))
                                    {
                                        hessian(i, j) += -dmi_magnitudes[i_pair] * dmi_normals[i_pair][0];
                                        hessian(j, i) += -dmi_magnitudes[i_pair] * dmi_normals[i_pair][0];
                                    }
                                    else if ((alpha == 2 && beta == 1))
                                    {
                                        hessian(i, j) +=  dmi_magnitudes[i_pair] * dmi_normals[i_pair][0];
                                        hessian(j, i) +=  dmi_magnitudes[i_pair] * dmi_normals[i_pair][0];
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        //// Dipole-Dipole
        //for (unsigned int i_pair = 0; i_pair < this->DD_indices.size(); ++i_pair)
        //{
        //	// indices
        //	int idx_1 = DD_indices[i_pair][0];
        //	int idx_2 = DD_indices[i_pair][1];
        //	// prefactor
        //	scalar prefactor = 0.0536814951168
        //		* this->mu_s[idx_1] * this->mu_s[idx_2]
        //		/ std::pow(DD_magnitude[i_pair], 3);
        //	// components
        //	for (int alpha = 0; alpha < 3; ++alpha)
        //	{
        //		for (int beta = 0; beta < 3; ++beta)
        //		{
        //			int idx_h = idx_1 + alpha*nos + 3 * nos*(idx_2 + beta*nos);
        //			if (alpha == beta)
        //				hessian[idx_h] += prefactor;
        //			hessian[idx_h] += -3.0*prefactor*DD_normal[i_pair][alpha] * DD_normal[i_pair][beta];
        //		}
        //	}
        //}

        // Quadruplets
    }

    // Hamiltonian name as string
    static const std::string name = "Heisenberg (Pairs)";
    const std::string& Hamiltonian_Heisenberg_Pairs::Name() { return name; }
}

#endif


#ifndef __wrapper_MSSMNoFV_onshell_susy_parameters_def_gm2calc_1_3_0_hpp__
#define __wrapper_MSSMNoFV_onshell_susy_parameters_def_gm2calc_1_3_0_hpp__

#include <Eigen/Core>
#include <ostream>

#include "identification.hpp"

namespace CAT_3(BACKENDNAME,_,SAFE_VERSION)
{

   namespace gm2calc
   {

      // Member functions:
      inline void MSSMNoFV_onshell_susy_parameters::print(::std::basic_ostream<char, std::char_traits<char> >& arg_1) const
      {
         get_BEptr()->print(arg_1);
      }

      inline void MSSMNoFV_onshell_susy_parameters::clear()
      {
         get_BEptr()->clear();
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_scale(double s)
      {
         get_BEptr()->set_scale(s);
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_scale() const
      {
         return get_BEptr()->get_scale();
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Yd(const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& Yd_)
      {
         get_BEptr()->set_Yd(Yd_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Yd(int i, int k, double value)
      {
         get_BEptr()->set_Yd(i, k, value);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Ye(const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& Ye_)
      {
         get_BEptr()->set_Ye(Ye_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Ye(int i, int k, double value)
      {
         get_BEptr()->set_Ye(i, k, value);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Yu(const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& Yu_)
      {
         get_BEptr()->set_Yu(Yu_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Yu(int i, int k, double value)
      {
         get_BEptr()->set_Yu(i, k, value);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_Mu(double Mu_)
      {
         get_BEptr()->set_Mu(Mu_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_g1(double g1_)
      {
         get_BEptr()->set_g1(g1_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_g2(double g2_)
      {
         get_BEptr()->set_g2(g2_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_g3(double g3_)
      {
         get_BEptr()->set_g3(g3_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_vd(double vd_)
      {
         get_BEptr()->set_vd(vd_);
      }

      inline void MSSMNoFV_onshell_susy_parameters::set_vu(double vu_)
      {
         get_BEptr()->set_vu(vu_);
      }

      inline const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& MSSMNoFV_onshell_susy_parameters::get_Yd() const
      {
         return get_BEptr()->get_Yd();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_Yd(int i, int k) const
      {
         return get_BEptr()->get_Yd(i, k);
      }

      inline const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& MSSMNoFV_onshell_susy_parameters::get_Ye() const
      {
         return get_BEptr()->get_Ye();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_Ye(int i, int k) const
      {
         return get_BEptr()->get_Ye(i, k);
      }

      inline const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& MSSMNoFV_onshell_susy_parameters::get_Yu() const
      {
         return get_BEptr()->get_Yu();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_Yu(int i, int k) const
      {
         return get_BEptr()->get_Yu(i, k);
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_Mu() const
      {
         return get_BEptr()->get_Mu();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_g1() const
      {
         return get_BEptr()->get_g1();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_g2() const
      {
         return get_BEptr()->get_g2();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_g3() const
      {
         return get_BEptr()->get_g3();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_vd() const
      {
         return get_BEptr()->get_vd();
      }

      inline double MSSMNoFV_onshell_susy_parameters::get_vu() const
      {
         return get_BEptr()->get_vu();
      }

      // Wrappers for original constructors:
      inline MSSMNoFV_onshell_susy_parameters::MSSMNoFV_onshell_susy_parameters() :
         WrapperBase(__factory0())
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      inline MSSMNoFV_onshell_susy_parameters::MSSMNoFV_onshell_susy_parameters(double scale_, const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& Yd_, const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& Ye_, const ::Eigen::Matrix<double, 3, 3, 0, 3, 3>& Yu_, double Mu_, double g1_, double g2_, double g3_, double vd_, double vu_) :
         WrapperBase(__factory1(scale_, Yd_, Ye_, Yu_, Mu_, g1_, g2_, g3_, vd_, vu_))
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      // Special pointer-based constructor:
      inline MSSMNoFV_onshell_susy_parameters::MSSMNoFV_onshell_susy_parameters(Abstract_MSSMNoFV_onshell_susy_parameters* in) :
         WrapperBase(in)
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      // Copy constructor:
      inline MSSMNoFV_onshell_susy_parameters::MSSMNoFV_onshell_susy_parameters(const MSSMNoFV_onshell_susy_parameters& in) :
         WrapperBase(in.get_BEptr()->pointer_copy__BOSS())
      {
         get_BEptr()->set_wptr(this);
         get_BEptr()->set_delete_wrapper(false);
      }

      // Assignment operator:
      inline MSSMNoFV_onshell_susy_parameters& MSSMNoFV_onshell_susy_parameters::operator=(const MSSMNoFV_onshell_susy_parameters& in)
      {
         if (this != &in)
         {
            get_BEptr()->pointer_assign__BOSS(in.get_BEptr());
         }
         return *this;
      }

      // Destructor:
      inline MSSMNoFV_onshell_susy_parameters::~MSSMNoFV_onshell_susy_parameters()
      {
         if (get_BEptr() != 0)
         {
            get_BEptr()->set_delete_wrapper(false);
            if (can_delete_BEptr())
            {
               delete BEptr;
               BEptr = 0;
            }
         }
         set_delete_BEptr(false);
      }

      // Returns correctly casted pointer to Abstract class:
      inline Abstract_MSSMNoFV_onshell_susy_parameters* gm2calc::MSSMNoFV_onshell_susy_parameters::get_BEptr() const
      {
         return dynamic_cast<Abstract_MSSMNoFV_onshell_susy_parameters*>(BEptr);
      }
   }

}

#include "gambit/Backends/backend_undefs.hpp"

#endif /* __wrapper_MSSMNoFV_onshell_susy_parameters_def_gm2calc_1_3_0_hpp__ */


/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */

/*-
 * $Id$
 */

#include <boost/python.hpp>

#include <scitbx/array_family/shared.h>

//#define DEBUG 1

/*
 * Integrate Acqiris trace from @p i_sig to @p i_bkg.  This
 * implementation trades accuracy for speed, by only adding corrected
 * numbers which leads to cache misses.  Note that because the signal
 * is negative, signal is subtracted from background.  Should be
 * faster than apd_hitfind.  For reading out the Silicon Avalanche
 * Photodiode SAR 3000 E1 (http://XXX).
 *
 * @param w     Waveform
 * @param i_sig Index of first signal point
 * @param i_bkg Index of first background point
 * @param nmemb Number of points in integral
 * @return      Integral
 */
double
acqiris_integrate(
  const scitbx::af::const_ref<double>& w,
  std::size_t i_sig,
  std::size_t i_bkg,
  std::size_t nmemb)
{
  double integral = w[i_bkg] - w[i_sig];
  for (std::size_t i = 1; i < nmemb; i++)
    integral += w[i_bkg + i] - w[i_sig + i];
  return (integral);
}

/*
 * Optimal window width depends on the decay time of the diode.  At
 * LCLS the pulse length (tenths of fs) is much shorter than the
 * interval between digitised samples (hundreds of ps).  Should be
 * longer than w.size() to make any sense.  This is really more suited
 * for the Optodiode SXUV100 (http://www.optodiode.com/pro_09.html)

 * @param w      Waveform
 * @param wwidth Window width
 * @return       Hit
 */
#ifdef DEBUG
scitbx::af::shared<double>
#else
bool
#endif
apd_hitfind(const scitbx::af::const_ref<double>& w, std::size_t wwidth)
{
#ifdef DEBUG
  scitbx::af::shared<double> window3(
    std::max(std::size_t(0), w.size() - wwidth));
#endif
  double rsum[3] = { 0, 0, 0 }; // running sums for expectations (E)
  double skew_amax, skew_mean, skew_ssq, skew_stddev, skew_sum;

  /*
   * Compute non-central moments up to order three for all windows.
   * This implementation will accumulate error, but requires only one
   * flat O(n) pass over the data.
   *
   * Compute skewness, the third central moment, in each window and
   * store in window3.  Establish baseline (mean and standard
   * deviation) over the first samples.  This is really a threshold.
   * Maybe it makes more sense to use the median and make use of
   * trailing samples as well?
   *
   * Remember absolute maximum skewness above the mean outside the
   * baseline region.
   */
  skew_amax = skew_ssq = skew_sum = 0;
  for (std::size_t i = 0; i < std::min(wwidth, w.size()); i++) {
    rsum[0] += w[i];
    rsum[1] += w[i] * w[i];
    rsum[2] += w[i] * w[i] * w[i];
  }
  for (std::size_t i = wwidth; i < w.size(); i++) {
    const std::size_t j  = i - wwidth;
    const double      c1 = rsum[0] / wwidth;
    const double      c2 = std::max(0.0, rsum[1] / wwidth - c1 * c1);
    const double      c3 = c2 > 0
      ? (rsum[2] / wwidth - 3 * c1 * c2 - c1 * c1 * c1) / std::pow(c2, 1.5)
      : 0;

    if (j < wwidth) {
      skew_sum += c3;
      skew_ssq += c3 * c3;
    } else {
      skew_amax = std::max(skew_amax, std::abs(c3 - skew_sum / wwidth));
    }

#ifdef DEBUG
    window3[j] = c3;
#endif

    rsum[0] += w[i]               - w[j];
    rsum[1] += w[i] * w[i]        - w[j] * w[j];
    rsum[2] += w[i] * w[i] * w[i] - w[j] * w[j] * w[j];
  }

  skew_mean   = skew_sum / wwidth;
  skew_stddev = std::max(0.0, skew_ssq - skew_sum * skew_mean);
  skew_stddev = std::sqrt(
    wwidth > 1 ? skew_stddev / (wwidth - 1) : skew_stddev);

  /*
   * For actual APD, it may make a lot more sense to look at one-step
   * transitions.  XXX Read specs for the two diodes.
   */
  if (skew_amax > 15 * skew_stddev) { // XXX magic
#ifdef DEBUG
    printf("It's a HIT (amax %f, 15stddev %f)\n", skew_amax, 15 * skew_stddev);
#else
    return (true);
#endif
  }

#ifdef DEBUG
  return window3;
#else
  return (false);
#endif
}

BOOST_PYTHON_MODULE(acqiris_ext)
{
  // XXX named arguments!
  using namespace boost::python;
  def("acqiris_integrate", acqiris_integrate);
  def("apd_hitfind", apd_hitfind);
}