#include <stan/math/prim.hpp>
#include <test/unit/math/prim/prob/vector_rng_test_helper.hpp>
#include <test/unit/math/prim/prob/NegativeBinomial2LogTestRig.hpp>
#include <test/unit/math/expect_near_rel.hpp>
#include <gtest/gtest.h>
#include <boost/random/mersenne_twister.hpp>
#include <boost/math/distributions.hpp>
#include <algorithm>
#include <limits>
#include <vector>
#include <string>

TEST(ProbDistributionsNegativeBinomial2Log, errorCheck) {
  check_dist_throws_all_types(NegativeBinomial2LogTestRig());
}

TEST(ProbDistributionsNegBinomial2Log, error_check) {
  using std::log;

  boost::random::mt19937 rng;
  EXPECT_NO_THROW(stan::math::neg_binomial_2_log_rng(6, 2, rng));
  EXPECT_NO_THROW(stan::math::neg_binomial_2_log_rng(-0.5, 1, rng));
  EXPECT_NO_THROW(stan::math::neg_binomial_2_log_rng(log(1e8), 1, rng));

  EXPECT_THROW(stan::math::neg_binomial_2_log_rng(0, -2, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::neg_binomial_2_log_rng(6, -2, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::neg_binomial_2_log_rng(-6, -0.1, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::neg_binomial_2_log_rng(
                   stan::math::positive_infinity(), 2, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::neg_binomial_2_log_rng(
                   stan::math::positive_infinity(), 6, rng),
               std::domain_error);
  EXPECT_THROW(stan::math::neg_binomial_2_log_rng(
                   2, stan::math::positive_infinity(), rng),
               std::domain_error);

  std::string error_msg;

  error_msg
      = "neg_binomial_2_log_rng: Exponential "
        "of the log-location parameter divided by the precision "
        "parameter is inf";
  try {
    stan::math::neg_binomial_2_log_rng(log(1e300), 1e-300, rng);
    FAIL() << "neg_binomial_2_log_rng should have thrown" << std::endl;
  } catch (const std::exception& e) {
    if (std::string(e.what()).find(error_msg) == std::string::npos)
      FAIL() << "Error message is different than expected" << std::endl
             << "EXPECTED: " << error_msg << std::endl
             << "FOUND: " << e.what() << std::endl;
    SUCCEED();
  }

  error_msg
      = "neg_binomial_2_log_rng: Random number that "
        "came from gamma distribution is";
  try {
    stan::math::neg_binomial_2_log_rng(log(1e10), 1e20, rng);
    FAIL() << "neg_binomial_2_log_rng should have thrown" << std::endl;
  } catch (const std::exception& e) {
    if (std::string(e.what()).find(error_msg) == std::string::npos)
      FAIL() << "Error message is different than expected" << std::endl
             << "EXPECTED: " << error_msg << std::endl
             << "FOUND: " << e.what() << std::endl;
    SUCCEED();
  }
}

TEST(ProbDistributionsNegBinomial2Log, chiSquareGoodnessFitTest) {
  boost::random::mt19937 rng;
  int N = 1000;
  int K = stan::math::round(2 * std::pow(N, 0.4));
  boost::math::negative_binomial_distribution<> dist(1.1,
                                                     1.1 / (1.1 + exp(2.4)));
  boost::math::chi_squared mydist(K - 1);

  int loc[K - 1];
  for (int i = 1; i < K; i++)
    loc[i - 1] = i - 1;

  int count = 0;
  double bin[K];
  double expect[K];
  for (int i = 0; i < K; i++) {
    bin[i] = 0;
    expect[i] = N * pdf(dist, i);
  }
  expect[K - 1] = N * (1 - cdf(dist, K - 1));

  while (count < N) {
    int a = stan::math::neg_binomial_2_log_rng(2.4, 1.1, rng);
    int i = 0;
    while (i < K - 1 && a > loc[i])
      ++i;
    ++bin[i];
    count++;
  }

  double chi = 0;

  for (int j = 0; j < K; j++)
    chi += ((bin[j] - expect[j]) * (bin[j] - expect[j]) / expect[j]);

  EXPECT_TRUE(chi < quantile(complement(mydist, 1e-6)));
}

TEST(ProbDistributionsNegBinomial2Log, chiSquareGoodnessFitTest2) {
  boost::random::mt19937 rng;
  int N = 1000;
  int K = stan::math::round(2 * std::pow(N, 0.4));
  boost::math::negative_binomial_distribution<> dist(0.6,
                                                     0.6 / (0.6 + exp(2.4)));
  boost::math::chi_squared mydist(K - 1);

  int loc[K - 1];
  for (int i = 1; i < K; i++)
    loc[i - 1] = i - 1;

  int count = 0;
  double bin[K];
  double expect[K];
  for (int i = 0; i < K; i++) {
    bin[i] = 0;
    expect[i] = N * pdf(dist, i);
  }
  expect[K - 1] = N * (1 - cdf(dist, K - 1));

  while (count < N) {
    int a = stan::math::neg_binomial_2_log_rng(2.4, 0.6, rng);
    int i = 0;
    while (i < K - 1 && a > loc[i])
      ++i;
    ++bin[i];
    count++;
  }

  double chi = 0;

  for (int j = 0; j < K; j++)
    chi += ((bin[j] - expect[j]) * (bin[j] - expect[j]) / expect[j]);

  EXPECT_TRUE(chi < quantile(complement(mydist, 1e-6)));
}

TEST(ProbDistributionsNegBinomial2Log, chiSquareGoodnessFitTest3) {
  boost::random::mt19937 rng;
  int N = 1000;
  int K = stan::math::round(2 * std::pow(N, 0.4));
  boost::math::negative_binomial_distribution<> dist(121,
                                                     121 / (121 + exp(2.4)));
  boost::math::chi_squared mydist(K - 1);

  int loc[K - 1];
  for (int i = 1; i < K; i++)
    loc[i - 1] = i - 1;

  int count = 0;
  double bin[K];
  double expect[K];
  for (int i = 0; i < K; i++) {
    bin[i] = 0;
    expect[i] = N * pdf(dist, i);
  }
  expect[K - 1] = N * (1 - cdf(dist, K - 1));

  while (count < N) {
    int a = stan::math::neg_binomial_2_log_rng(2.4, 121, rng);
    int i = 0;
    while (i < K - 1 && a > loc[i])
      ++i;
    ++bin[i];
    count++;
  }

  double chi = 0;

  for (int j = 0; j < K; j++)
    chi += ((bin[j] - expect[j]) * (bin[j] - expect[j]) / expect[j]);

  EXPECT_TRUE(chi < quantile(complement(mydist, 1e-6)));
}

TEST(ProbNegBinomial2, log_matches_lpmf) {
  double y = 0.8;
  double mu = 1.1;
  double phi = 2.3;

  EXPECT_FLOAT_EQ((stan::math::neg_binomial_2_lpmf(y, mu, phi)),
                  (stan::math::neg_binomial_2_log(y, mu, phi)));
  EXPECT_FLOAT_EQ((stan::math::neg_binomial_2_lpmf<true>(y, mu, phi)),
                  (stan::math::neg_binomial_2_log<true>(y, mu, phi)));
  EXPECT_FLOAT_EQ((stan::math::neg_binomial_2_lpmf<false>(y, mu, phi)),
                  (stan::math::neg_binomial_2_log<false>(y, mu, phi)));
  EXPECT_FLOAT_EQ(
      (stan::math::neg_binomial_2_lpmf<true, double, double, double>(y, mu,
                                                                     phi)),
      (stan::math::neg_binomial_2_log<true, double, double, double>(y, mu,
                                                                    phi)));
  EXPECT_FLOAT_EQ(
      (stan::math::neg_binomial_2_lpmf<false, double, double, double>(y, mu,
                                                                      phi)),
      (stan::math::neg_binomial_2_log<false, double, double, double>(y, mu,
                                                                     phi)));
  EXPECT_FLOAT_EQ(
      (stan::math::neg_binomial_2_lpmf<double, double, double>(y, mu, phi)),
      (stan::math::neg_binomial_2_log<double, double, double>(y, mu, phi)));
}

TEST(ProbDistributionsNegBinomial2Log, neg_binomial_2_log_grid_test) {
  std::vector<double> mu_log_to_test
      = {-101, -27, -3, -1, -0.132, 0, 4, 10, 87};
  std::vector<double> phi_to_test = {2e-5, 0.36, 1, 10, 2.3e5, 1.8e10, 6e16};
  std::vector<int> n_to_test = {0, 1, 10, 39, 101, 3048, 150054};

  for (double mu_log : mu_log_to_test) {
    for (double phi : phi_to_test) {
      for (int n : n_to_test) {
        double val_log = stan::math::neg_binomial_2_log_lpmf(n, mu_log, phi);
        std::stringstream msg;
        double val_orig
            = stan::math::neg_binomial_2_lpmf(n, std::exp(mu_log), phi);
        msg << std::setprecision(22)
            << "neg_binomial_2_log_lpmf yields different result (" << val_log
            << ") than neg_binomial_2_lpmf (" << val_orig << ") for n = " << n
            << ", mu_log = " << mu_log << ", phi = " << phi << ".";
        stan::test::expect_near_rel(msg.str(), val_log, val_orig);
      }
    }
  }
}


/**************************************************************************\
|
|    Copyright (C) 2009 Marc Stevens
|
|    This program is free software: you can redistribute it and/or modify
|    it under the terms of the GNU General Public License as published by
|    the Free Software Foundation, either version 3 of the License, or
|    (at your option) any later version.
|
|    This program is distributed in the hope that it will be useful,
|    but WITHOUT ANY WARRANTY; without even the implied warranty of
|    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
|    GNU General Public License for more details.
|
|    You should have received a copy of the GNU General Public License
|    along with this program.  If not, see <http://www.gnu.org/licenses/>.
|
\**************************************************************************/

#include <vector>
#include <string>
#include <map>
#include <cmath>
#include <algorithm>

#include <hashclash/saveload_bz2.hpp>

#include "main.hpp"

#include <boost/lexical_cast.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/filesystem/convenience.hpp>
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>

#include <hashclash/sdr.hpp>
#include <hashclash/rng.hpp>
#include <hashclash/timer.hpp>
#include <hashclash/md5detail.hpp>

#include "distribution.hpp" // distribution tables
#include "storage.hpp"

void determine_nrblocks_distribution(birthday_parameters& parameters);

boost::mutex global_mutex;
#define LOCK_GLOBAL_MUTEX	boost::mutex::scoped_lock lock(global_mutex);

using namespace hashclash;
using namespace std;

/* LOCK_GLOBAL_MUTEX not needed */
uint32 ihv1[4], ihv2[4], ihv2mod[4], msg1[16], msg2[16], precomp1[4], precomp2[4];
uint32 hybridmask = 0, distinguishedpointmask = 0, maximumpathlength = 1;
unsigned maxblocks = 64;
bool memhardlimit = false;
unsigned parameterspathtyperange = 0;
unsigned procmodn = 0, procmodi = 0;
vector< pair<uint32,uint32> > singleblockdata;
/**/

/* LOCK_GLOBAL_MUTEX required */
uint32 colla1, collb1, collc1, colla2, collb2, collc2;
uint64 totwork = 0, totworkallproc = 0;
unsigned bestnrblocks = 64;
bool quit = false;
unsigned collrobinhoods = 0, collequalihvs = 0, collusefull = 0;
vector< vector<trail_type> > trail_distribution(0);
//vector< pair<trail_type, trail_type> > collisions_queue(0);
/**/

// LOCK_GLOBAL_MUTEX required
void status_line()
{
	unsigned totcoll = main_storage.get_totcoll();
	unsigned collqueue = main_storage.get_collqueuesize();
	if (procmodn > 1)
		cout << "Work: mine=2^(" << log(double(totwork))/log(double(2)) << ") all=2^(" << log(double(totworkallproc))/log(double(2)) << ")";
	else
		cout << "Work: 2^(" << log(double(totwork))/log(double(2)) << ")";
	cout << ", Coll.: " << main_storage.get_totcoll()
		 << "(uf=" << collusefull << ",nuf=" << collequalihvs
		 << ",?=" << (totcoll-collusefull-collequalihvs-collrobinhoods-collqueue)
		 << ",q=" << collqueue << ",rh=" << collrobinhoods
		 << "), Blocks: " << bestnrblocks << endl; //"     \r" << flush;
}

// LOCK_GLOBAL_MUTEX not needed
unsigned nrblocks(uint32 dihv[4])
{
	uint32 ptrmaskt2 = 0;
	for (unsigned j = 0; j < parameterspathtyperange; ++j)
		ptrmaskt2 |= 2<<j;
	sdr p1naf = naf(dihv[3]);
	uint32 p1nafmask = p1naf.mask;
	uint32 p2nafmask = naf(dihv[1]-dihv[3]).mask;
	p2nafmask = rotate_right(p2nafmask, 21);
	uint32 p1mask = p1nafmask;
	for (unsigned j = 0; j < parameterspathtyperange; ++j)
		p1mask |= p1nafmask << (j+1);
	p2nafmask &= ~p1mask;
	for (unsigned b = 0; b < 32; ++b)
		if (p2nafmask & (1<<b))
			p2nafmask &= ~(ptrmaskt2<<b);
	p1nafmask &= ~(p2nafmask<<1);
	p1nafmask &= ~(p1nafmask>>31);
	return hw(p1nafmask) + 2*hw(p2nafmask);
}

// LOCK_GLOBAL_MUTEX not needed
void precomputestate(uint32 ihv[4], uint32 block[16])
{
        #define HASHCLASH_MD5COMPRESS_STEP(f, a, b, c, d, m, ac, rc) \
                a += f(b, c, d) + m + ac; a = rotate_left(a,rc); a += b;

	uint32 a = ihv[0]; uint32 b = ihv[1]; uint32 c = ihv[2]; uint32 d = ihv[3];

	HASHCLASH_MD5COMPRESS_STEP(md5_ff, a, b, c, d, block[ 0], 0xd76aa478,  7);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, d, a, b, c, block[ 1], 0xe8c7b756, 12);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, c, d, a, b, block[ 2], 0x242070db, 17);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, b, c, d, a, block[ 3], 0xc1bdceee, 22);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, a, b, c, d, block[ 4], 0xf57c0faf,  7);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, d, a, b, c, block[ 5], 0x4787c62a, 12);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, c, d, a, b, block[ 6], 0xa8304613, 17);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, b, c, d, a, block[ 7], 0xfd469501, 22);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, a, b, c, d, block[ 8], 0x698098d8,  7);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, d, a, b, c, block[ 9], 0x8b44f7af, 12);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, c, d, a, b, block[10], 0xffff5bb1, 17);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, b, c, d, a, block[11], 0x895cd7be, 22);
	HASHCLASH_MD5COMPRESS_STEP(md5_ff, a, b, c, d, block[12], 0x6b901122,  7);

	ihv[0] = a; ihv[1] = b; ihv[2] = c; ihv[3] = d;
}

// LOCK_GLOBAL_MUTEX not needed
inline void birthday_step(uint32& x, uint32& y, uint32& z)
{
	uint32* block;
	uint32* precomp;
	uint32* ihv;
	if (x <= y) {
		precomp = precomp1;
		block = msg1;
		ihv = ihv1;
	} else {
		precomp = precomp2;
		block = msg2;
		ihv = ihv2mod;
	}
	{
		uint32 a = precomp[0], b = precomp[1], c = precomp[2], d = precomp[3];
		HASHCLASH_MD5COMPRESS_STEP(md5_ff, d, a, b, c, z, 0xfd987193, 12);
		HASHCLASH_MD5COMPRESS_STEP(md5_ff, c, d, a, b, x, 0xa679438e, 17);
		HASHCLASH_MD5COMPRESS_STEP(md5_ff, b, c, d, a, y, 0x49b40821, 22);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, a, b, c, d, block[ 1], 0xf61e2562,  5);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, d, a, b, c, block[ 6], 0xc040b340,  9);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, c, d, a, b, block[11], 0x265e5a51, 14);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, b, c, d, a, block[ 0], 0xe9b6c7aa, 20);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, a, b, c, d, block[ 5], 0xd62f105d,  5);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, d, a, b, c, block[10], 0x02441453,  9);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, c, d, a, b, y, 0xd8a1e681, 14);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, b, c, d, a, block[ 4], 0xe7d3fbc8, 20);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, a, b, c, d, block[ 9], 0x21e1cde6,  5);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, d, a, b, c, x, 0xc33707d6,  9);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, c, d, a, b, block[ 3], 0xf4d50d87, 14);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, b, c, d, a, block[ 8], 0x455a14ed, 20);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, a, b, c, d, z, 0xa9e3e905,  5);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, d, a, b, c, block[ 2], 0xfcefa3f8,  9);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, c, d, a, b, block[ 7], 0x676f02d9, 14);
		HASHCLASH_MD5COMPRESS_STEP(md5_gg, b, c, d, a, block[12], 0x8d2a4c8a, 20);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, a, b, c, d, block[ 5], 0xfffa3942,  4);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, d, a, b, c, block[ 8], 0x8771f681, 11);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, c, d, a, b, block[11], 0x6d9d6122, 16);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, b, c, d, a, x, 0xfde5380c, 23);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, a, b, c, d, block[ 1], 0xa4beea44,  4);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, d, a, b, c, block[ 4], 0x4bdecfa9, 11);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, c, d, a, b, block[ 7], 0xf6bb4b60, 16);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, b, c, d, a, block[10], 0xbebfbc70, 23);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, a, b, c, d, z, 0x289b7ec6,  4);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, d, a, b, c, block[ 0], 0xeaa127fa, 11);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, c, d, a, b, block[ 3], 0xd4ef3085, 16);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, b, c, d, a, block[ 6], 0x04881d05, 23);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, a, b, c, d, block[ 9], 0xd9d4d039,  4);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, d, a, b, c, block[12], 0xe6db99e5, 11);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, c, d, a, b, y, 0x1fa27cf8, 16);
		HASHCLASH_MD5COMPRESS_STEP(md5_hh, b, c, d, a, block[ 2], 0xc4ac5665, 23);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, a, b, c, d, block[ 0], 0xf4292244,  6);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, d, a, b, c, block[ 7], 0x432aff97, 10);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, c, d, a, b, x, 0xab9423a7, 15);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, b, c, d, a, block[ 5], 0xfc93a039, 21);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, a, b, c, d, block[12], 0x655b59c3,  6);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, d, a, b, c, block[ 3], 0x8f0ccc92, 10);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, c, d, a, b, block[10], 0xffeff47d, 15);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, b, c, d, a, block[ 1], 0x85845dd1, 21);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, a, b, c, d, block[ 8], 0x6fa87e4f,  6);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, d, a, b, c, y, 0xfe2ce6e0, 10);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, c, d, a, b, block[ 6], 0xa3014314, 15);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, b, c, d, a, z, 0x4e0811a1, 21);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, a, b, c, d, block[ 4], 0xf7537e82,  6);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, d, a, b, c, block[11], 0xbd3af235, 10);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, c, d, a, b, block[ 2], 0x2ad7d2bb, 15);
		HASHCLASH_MD5COMPRESS_STEP(md5_ii, b, c, d, a, block[ 9], 0xeb86d391, 21);
		a += ihv[0];
		b += ihv[1];
		c += ihv[2];
		d += ihv[3];
		if (maxblocks != 1) {
			x = a;
			y = d - c;
			z = (d - b) & hybridmask;
		} else {
			x = a;
			y = d;
			z = c & hybridmask;
		}
	}
}

// LOCK_GLOBAL_MUTEX not needed
void generate_trail(trail_type& trail)
{
	uint32 x = trail.start[0], y = trail.start[1], z = trail.start[2];
	trail.len = 1;
	birthday_step(x,y,z);
	while (trail.len <= maximumpathlength && 0!=(x & distinguishedpointmask)) {
		birthday_step(x,y,z);
		++trail.len;
	}
	trail.end[0] = x;
	trail.end[1] = y;
	trail.end[2] = z;
}

// do not use LOCK_GLOBAL_MUTEX
// function possibly calls LOCK_GLOBAL_MUTEX
void find_collision(const trail_type& trail1, const trail_type& trail2)
{
	uint32 a1 = trail1.start[0];
	uint32 b1 = trail1.start[1];
	uint32 c1 = trail1.start[2];
	uint32 a2 = trail2.start[0];
	uint32 b2 = trail2.start[1];
	uint32 c2 = trail2.start[2];
	uint32 len1 = trail1.len;
	uint32 len2 = trail2.len;
	// sanity check
	if (trail1.len > maximumpathlength || 0!=(trail1.end[0]&distinguishedpointmask)
	    || trail2.len > maximumpathlength || 0!=(trail2.end[0]&distinguishedpointmask))
	{
		LOCK_GLOBAL_MUTEX;
		cout << "X" << flush;
		++collrobinhoods; // have to categorize this collision
		return;
	}

	while (len1 > len2)
	{
		birthday_step(a1, b1, c1);
		--len1;
	}
	while (len2 > len1)
	{
		birthday_step(a2, b2, c2);
		--len2;
	}

	// check for robin hood
	if (a1 == a2 && b1 == b2 && c1 == c2)
	{
		LOCK_GLOBAL_MUTEX;
		++collrobinhoods;
		return;
	}

	uint32 oa1 = a1, oa2 = a2, ob1 = b1, ob2 = b2, oc1 = c1, oc2 = c2;
	while ((a1 != a2 || b1 != b2 || c1 != c2) && len1 > 0)
	{
		oa1 = a1; oa2 = a2; ob1 = b1; ob2 = b2; oc1 = c1; oc2 = c2;
		birthday_step(a1, b1, c1);
		birthday_step(a2, b2, c2);
		--len1;
	}
	if (len1 == 0 && (a1 != a2 || b1 != b2 || c1 != c2))
	{
		LOCK_GLOBAL_MUTEX;
		cerr << "find_collision(): len=0 (len1=" << trail1.len << ",len2=" << trail2.len << ")           " << endl;
		++collrobinhoods;
		return;
	}

	// check for same ihv birthday collision
	if ((oa1 <= ob1) == (oa2 <= ob2))
	{
		LOCK_GLOBAL_MUTEX;
		++collequalihvs;
		return;
	}

	uint32 ihvmsg1[4];
	uint32 ihvmsg2[4];
	if (oa2 <= ob2) {
		swap(oa2, oa1);
		swap(ob2, ob1);
		swap(oc2, oc1);
	}
	uint32 lmsg1[16];
	uint32 lmsg2[16];
	for (unsigned i = 0; i < 13; ++i)
	{
		lmsg1[i] = msg1[i];
		lmsg2[i] = msg2[i];
	}
	lmsg1[13] = oc1; lmsg1[14] = oa1; lmsg1[15] = ob1;
	ihvmsg1[0] = ihv1[0]; ihvmsg1[1] = ihv1[1];
	ihvmsg1[2] = ihv1[2]; ihvmsg1[3] = ihv1[3];
	md5compress(ihvmsg1, lmsg1);
	lmsg2[13] = oc2; lmsg2[14] = oa2; lmsg2[15] = ob2;
	ihvmsg2[0] = ihv2[0]; ihvmsg2[1] = ihv2[1];
	ihvmsg2[2] = ihv2[2]; ihvmsg2[3] = ihv2[3];
	md5compress(ihvmsg2, lmsg2);
	uint32 dihv[4] = { ihvmsg2[0]-ihvmsg1[0], ihvmsg2[1]-ihvmsg1[1],
					   ihvmsg2[2]-ihvmsg1[2], ihvmsg2[3]-ihvmsg1[3] };

	unsigned n = 64;
	if (maxblocks == 1) {
		if (dihv[0] == -(1<<5)
			&& dihv[3] == -(1<<5)+(1<<25)
			&& (dihv[2]&hybridmask) == (uint32(-(1<<5)+(1<<25)+(1<<23))&hybridmask)
			)
		{
			pair<uint32,uint32> bc(dihv[1],dihv[2]);
			if (binary_search(singleblockdata.begin(), singleblockdata.end(), bc))
				n = 1;
		} else if (dihv[0] == (1<<5)
			&& dihv[3] == (1<<5)-(1<<25)
			&& (dihv[2]&hybridmask) == (uint32((1<<5)-(1<<25)-(1<<23))&hybridmask)
			)
		{
			pair<uint32,uint32> bc(-dihv[1],-dihv[2]);
			if (binary_search(singleblockdata.begin(), singleblockdata.end(), bc))
				n = 1;
		} else
			cout << "bad!!" << endl;
	} else {
		if (dihv[0] != 0 && dihv[2] != dihv[3])
		{
			cerr << "dihv fails assumptions" << endl;
			return;
//			throw;
		}
		n = nrblocks(dihv);
	}
	LOCK_GLOBAL_MUTEX;
	++collusefull;
	if (quit == true)
		return;

	if (n < bestnrblocks) {
		bestnrblocks = n;
		cout << endl;
		status_line();
	}

	if (n <= maxblocks)
	{
		if (n >= bestnrblocks) {
			cout << endl;
			status_line();
		}
		colla1 = oa1;
		collb1 = ob1;
		collc1 = oc1;
		colla2 = oa2;
		collb2 = ob2;
		collc2 = oc2;
		cout << endl;

		cout << "IHV1   = {" << ihvmsg1[0] << "," << ihvmsg1[1] << "," << ihvmsg1[2] << "," << ihvmsg1[3] << "}" << endl;
		cout << "IHV2   = {" << ihvmsg2[0] << "," << ihvmsg2[1] << "," << ihvmsg2[2] << "," << ihvmsg2[3] << "}" << endl;
		cout << "dIHV   = {" << dihv[0] << "," << dihv[1] << "," << dihv[2] << "," << dihv[3] << "}" << endl;
		sdr v = naf(dihv[3]);
		sdr w = naf(dihv[1] - dihv[3]);
		cout << "Dv     = " << v << endl;
		cout << "Dw     = " << w << endl;
		cout << "Blocks = " << n << endl << endl;
		cout << "Msg1   = ";
		for (unsigned i = 0; i < 13; ++i)
			cout << msg1[i] << " ";
		cout << oc1 << " " << oa1 << " " << ob1 << endl;
		cout << "Msg2   = ";
		for (unsigned i = 0; i < 13; ++i)
			cout << msg2[i] << " ";
		cout << oc2 << " " << oa2 << " " << ob2 << endl;
#if 1
		quit = true;
#else
		string filename1 = workdir + "/birthdayblock1_" + boost::lexical_cast<string>(v)+"_" + boost::lexical_cast<string>(w)+".bin";
		string filename2 = workdir + "/birthdayblock2_" + boost::lexical_cast<string>(v)+"_" + boost::lexical_cast<string>(w)+".bin";
		ofstream of1(filename1.c_str(), ios::binary | ios::app);
		ofstream of2(filename2.c_str(), ios::binary | ios::app);
		save_block(of1,msg1);
		save_block(of2,msg2);
		cout << "Wrote birthdaycollision block to: " << endl;
		cout << "\t" << filename1 << endl << "\t" << filename2 << endl;
#endif
	}
}

// LOCK_GLOBAL_MUTEX required
void distribute_trail(const trail_type& newtrail)
{
	totwork += newtrail.len;
	totworkallproc += newtrail.len;
	uint32 procindex = newtrail.end[1] % procmodn;
	if (procindex == procmodi) {
		main_storage.insert_trail(newtrail);
	} else
		trail_distribution[procindex].push_back(newtrail);
}

// LOCK_GLOBAL_MUTEX required
struct trail_distribute_type {
	uint32 proci;
	uint64 totwork;
	vector< trail_type > trails;
	uint32 check;

	template<class Archive>
	void serialize(Archive& ar, const unsigned int file_version) {
		ar & boost::serialization::make_nvp("proci", proci);
		ar & boost::serialization::make_nvp("totwork", totwork);
		ar & boost::serialization::make_nvp("trails", trails);
		ar & boost::serialization::make_nvp("check", check);
	}
};

// do not use LOCK_GLOBAL_MUTEX
// function possibly calls LOCK_GLOBAL_MUTEX
void load_save_trails(bool dosave = true)
{
	static vector<uint64> workothers(procmodn, 0);
	static vector<unsigned> fileserialothers(procmodn, 0);
	static unsigned savepart = 0;
	try {
		boost::filesystem::directory_iterator dit(workdir + "/" + boost::lexical_cast<string>(procmodi)), ditend;
		for (; dit != ditend; ++dit)
		{
			boost::filesystem::path filepath = *dit;
			if (!exists(*dit)
				|| symbolic_link_exists(*dit)
				|| is_directory(*dit))
				continue;
#if BOOST_VERSION == 104300
			string filename = dit->leaf();
#else
			string filename = dit->path().filename().string();
#endif
			if (filename.size() < 16)
				continue;
			if (filename.substr(0, 12) != "birthdaydata")
				continue;
			if (filename.substr(filename.size()-4) != ".bin")
				continue;

			trail_distribute_type traildata;
			traildata.check = 0;
			try {
				load(traildata, binary_archive, *dit);
			} catch (exception& ) {} catch (...) {}
			if (traildata.check != 0x56139078) continue; // incomplete file
			try {
				boost::filesystem::remove(*dit);
			} catch (exception& ) {} catch (...) {}
			if (traildata.proci >= procmodn) continue;
			if (workothers[traildata.proci] < traildata.totwork)
				workothers[traildata.proci] = traildata.totwork;
			LOCK_GLOBAL_MUTEX;
			for (unsigned i = 0; i < traildata.trails.size(); ++i)
				if (traildata.trails[i].end[1] % procmodn != procmodi)
					cerr << "False trail loaded!!" << endl;
			main_storage.insert_trails(traildata.trails);
		}
	}
	catch (exception& ) {}
	catch (...) {}
	if (dosave) {
		try {
			trail_distribute_type traildata;
			traildata.proci = procmodi;
			traildata.totwork = totwork;
			traildata.check = 0x56139078;
			savepart = (savepart+1) % 64;
			for (unsigned i = 0; i < procmodn; ++i) {
				if ( (i%64) != savepart) continue;
				traildata.trails.clear();
				{
					LOCK_GLOBAL_MUTEX;
					swap(traildata.trails, trail_distribution[i]);
				}
				if (traildata.trails.size()) {
					try {
						++fileserialothers[i];
						string basefilename = workdir + "/" + boost::lexical_cast<string>(i)
							+ "/birthdaydata_" + boost::lexical_cast<string>(procmodi) + "_";
						string tmpfilename = "/tmp/birthdaydata_" + boost::lexical_cast<string>(i)
							+ "_" + boost::lexical_cast<string>(procmodi) + "_";
						save(traildata, binary_archive, tmpfilename + boost::lexical_cast<string>(fileserialothers[i]) + ".tmp");
						boost::filesystem::copy_file( tmpfilename + boost::lexical_cast<string>(fileserialothers[i]) + ".tmp",
							basefilename + boost::lexical_cast<string>(fileserialothers[i]) + ".bin");
						boost::filesystem::remove( tmpfilename + boost::lexical_cast<string>(fileserialothers[i]) + ".tmp" );
					}
					catch (exception& e) { cerr << e.what() << endl; }
					catch (...) {}
				}
			}
		}
		catch (exception& ) {}
		catch (...) {}
	}
	LOCK_GLOBAL_MUTEX;
	workothers[procmodi] = totwork;
	totworkallproc = 0;
	for (unsigned i = 0; i < workothers.size(); ++i)
		totworkallproc += workothers[i];
}

struct coll_less : public std::binary_function<pair<trail_type,trail_type>, pair<trail_type,trail_type>, bool>
{
	bool operator()(const pair<trail_type,trail_type>& _Left, const pair<trail_type,trail_type>& _Right) const {
		return _Left.first.len < _Right.first.len;
	}
};

struct birthday_thread {
	static uint32 id_counter; // = 1 (below class definition)
	uint32 id;
	int _cuda_device_nr;
	cuda_device _cuda_device;

	birthday_thread(int cuda_device_nr = -1)
		: _cuda_device_nr(cuda_device_nr), id(id_counter++)
	{}

	void loop_cuda(bool single = false)
	{
		vector<trail_type> work;
		vector< pair<trail_type, trail_type> > collisions;
		while (true)
		{
			uint64 seed;
			{
				LOCK_GLOBAL_MUTEX;
				seed = uint64(xrng128()) + (uint64(xrng128())<<32)+1111*procmodi;
				xrng128();
				xrng128();

				if (quit) return;
			}
#ifdef HAVE_CUDA
			if (_cuda_device_nr >= 0)
				_cuda_device.cuda_fill_trail_buffer(id, seed, work, collisions, bool(maxblocks == 1));
			else
				return;
#endif

			// insert the trails into the trail hash
			if (collisions.size() > 0)
				collisions.clear();
			else {
				LOCK_GLOBAL_MUTEX;
				for (unsigned i = 0; i < work.size(); ++i)
					distribute_trail(work[i]);
			}
			if (single)
				break;
		}
	}

	void loop(bool single = false)
	{
#ifdef HAVE_CUDA
		if (_cuda_device_nr >= 0) {
			loop_cuda(single);
			return;
		}
#endif // CUDA
		vector<trail_type> work(256);
		vector< pair<trail_type,trail_type> > collisions;
		while (true)
		{
			// generate a batch of new trail starting points
			{
				LOCK_GLOBAL_MUTEX;
				if (quit) return;
				main_storage.get_birthdaycollisions(collisions);
				work.resize(256);
				for (unsigned i = 0; i < work.size(); ++i)
				{
					work[i].start[0] = xrng128();
					work[i].start[1] = xrng128();
					work[i].start[2] = xrng128() & hybridmask;
					work[i].len = 0;
					xrng128();
					xrng128();
				}
			}
			if (collisions.size() > 0)
			{
				for (unsigned i = 0; i < collisions.size(); ++i)
					find_collision(collisions[i].first, collisions[i].second);
				collisions.clear();
				continue;
			}

			// generate the trails
			for (unsigned i = 0; i < work.size(); ++i)
				generate_trail(work[i]);

			// insert the trails into the trail hash
			{
				LOCK_GLOBAL_MUTEX;
				for (unsigned i = 0; i < work.size(); ++i)
					distribute_trail(work[i]);
			}

			if (single)
				break;
		}
	}

	void thread_run()
	{
		try
		{
			{
				LOCK_GLOBAL_MUTEX;
#ifdef HAVE_CUDA
				if (_cuda_device_nr >= 0) {
					cout << "Thread " << id << " created (CUDA)." << endl;
					if (!_cuda_device.init(_cuda_device_nr, ihv1, ihv2, ihv2mod, msg1, msg2, hybridmask, distinguishedpointmask, maximumpathlength))
						return;
//					_cuda_device.benchmark();
				} else
#endif // CUDA
				cout << "Thread " << id << " created." << endl;
			}
			loop();
			cout << "Thread " << id << " exited.          " << endl;
		}
		catch (...)
		{
			cerr << "Exception in thread " << id << "!         " << endl;
		}
		LOCK_GLOBAL_MUTEX;
	}

	void single_run()
	{
		loop(true);
	}

	void operator()()
	{
		thread_run();
	}
};
uint32 birthday_thread::id_counter = 1;
struct birthday_thread_shell {
	birthday_thread* bt;
	birthday_thread_shell(birthday_thread* _bt): bt(_bt) {}
	void operator()() { bt->operator()(); }
};

void birthday(birthday_parameters& parameters)
{
	procmodn = parameters.modn;
	procmodi = parameters.modi;
	trail_distribution.resize(procmodn);

	// add the modi parameter to the seed
	addseed(parameters.modi);

	hybridmask = (parameters.hybridbits==0) ? uint32(0) : (uint32(~0)>>(32-parameters.hybridbits));
	parameterspathtyperange = parameters.pathtyperange;
	memhardlimit = parameters.memhardlimit;
	uint64 maxtrails = 0;
	if (parameters.maxblocks > 16)
		parameters.maxblocks = 16;
	double logprob = log(dist[parameters.hybridbits][parameters.pathtyperange][parameters.maxblocks])/log(double(2));
	if (parameters.maxblocks == 1) {
		bool loaded = false;
		try {
			cout << "Loading 'singleblockdata.bin'..." << flush;
			load_bz2(singleblockdata, "singleblockdata", binary_archive);
			loaded = true;
		} catch (exception& e ) {} catch (...) {}
		if (!loaded) {
			cout << "failed!" << endl;
			cout << "Loading 'singleblockdata.txt.bz2'..." << flush;
			try {
				load_bz2(singleblockdata, "singleblockdata", text_archive);
				loaded = true;
				save_bz2(singleblockdata, "singleblockdata", binary_archive);
			} catch (exception& e) {} catch (...) {}
			if (!loaded) {
				cout << "failed!" << endl;
				return;
			}
		}
		cout << "done: " << singleblockdata.size() << " dIHVs loaded." << endl;
		logprob = (log(double(singleblockdata.size()))/log(2.0)) - (64 - parameters.hybridbits);
	}
	double estcomplexity = 32.825748 - 0.5*logprob + 0.5*double(parameters.hybridbits);
	double estcollisions = 1-logprob;

	if (parameters.maxmemory != 0)
	{
		maxtrails = uint64(parameters.maxmemory) << 19;
		maxtrails /= sizeof(trail_type);
	}
	if (parameters.logpathlength < 0)
	{
		double comppertrail = pow(double(2),estcomplexity)/double(maxtrails);
		double logcpt = log(comppertrail)/log(double(2));
		parameters.logpathlength = unsigned(logcpt+0.9);
	}
	if (parameters.maxmemory == 0)
	{
		maxtrails = pow(double(2),estcomplexity-double(parameters.logpathlength));
		uint64 tmp = (maxtrails*sizeof(trail_type))>>19;
		parameters.maxmemory = unsigned(tmp);
	}
	cout << "Maximum amount of memory in MB for trails: " << parameters.maxmemory << " (local: " << parameters.maxmemory/parameters.modn << ")" << endl;
	cout << "Estimated number of trails that will be stored:    " << maxtrails << " (local: " << maxtrails/parameters.modn << ")" << endl;
	cout << "Estimated number of trails that will be generated: " << uint64(pow(double(2),estcomplexity-parameters.logpathlength)) << endl;
	cout << "Estimated complexity per trail: 2^(" << parameters.logpathlength << ")" << endl;
	cout << "Estimated complexity on trails: 2^(" << estcomplexity << ")" << endl;
	cout << "Estimated complexity on collisions: 2^(" << parameters.logpathlength + estcollisions + 1.321928 << ")" << endl;
	cout << endl;

	distinguishedpointmask = 0;
	unsigned pathlen = parameters.logpathlength;
	for (unsigned k = 0; k < unsigned(parameters.logpathlength) && k < 32; ++k)
		distinguishedpointmask |= 1<<k;

	uint64 mpl = (uint64(1) << parameters.logpathlength)*20;
	maximumpathlength = (mpl>>31) ? 0x7FFFFFFF : mpl;

	maxblocks = parameters.maxblocks;

	for (unsigned k = 0; k < 4; ++k)
	{
		precomp1[k] = ihv1[k] = parameters.ihv1[k];
		precomp2[k] = ihv2[k] = parameters.ihv2[k];
		ihv2mod[k] = ihv2[k];
	}
	if (maxblocks == 1) {
		ihv2mod[0] -= -(1<<5);
		ihv2mod[3] -= -(1<<5) + (1<<25);
		ihv2mod[2] -= -(1<<5) + (1<<25) + (1<<23);
	}
	for (unsigned k = 0; k < 16; ++k)
	{
		msg1[k] = parameters.msg1[k];
		msg2[k] = parameters.msg2[k];
	}
	precomputestate(precomp1, msg1);
	precomputestate(precomp2, msg2);

	main_storage.set_parameters(parameters);
	main_storage.reserve_memory(maxtrails/parameters.modn);

	if (parameters.threads == 0 || parameters.threads > boost::thread::hardware_concurrency())
		parameters.threads = boost::thread::hardware_concurrency();

	int cuda_dev_cnt = 0;
#ifdef HAVE_CUDA
	if (parameters.cuda_enabled)
	{
		try
		{
			cuda_dev_cnt = get_num_cuda_devices();
		}
		catch (std::exception& e)
		{
			std::cerr << "CUDA ERROR: " << e.what() << std::endl;
			cuda_dev_cnt = 0;
		}
		std::cout << "Found " << cuda_dev_cnt << " CUDA devices." << std::endl;
	}
	// if possible save 1 cpucore for better cuda latency
	if (parameters.threads > 1 && cuda_dev_cnt > 0 && parameters.threads == boost::thread::hardware_concurrency())
		--parameters.threads;
#endif

	boost::thread_group threads;
	vector<birthday_thread*> threads_data(parameters.threads, 0);
	for (unsigned i = 0; i < threads_data.size(); ++i)
	{
		threads_data[i] = new birthday_thread();
		threads.create_thread( birthday_thread_shell(threads_data[i]) );
	}
#ifdef HAVE_CUDA
	if (parameters.cuda_enabled) {
		for (int i = 0; i < cuda_dev_cnt; ++i) {
			threads_data.push_back(new birthday_thread(i));
			threads.create_thread( birthday_thread_shell(threads_data[threads_data.size()-1]) );
		}
	}
#endif
	if (threads_data.size() == 0)
		quit = true;

	timer save_timer(true);
	while (!quit)
	{
		boost::this_thread::sleep(boost::posix_time::seconds(10));
		if (procmodn > 1) {
			if (save_timer.time() > 60) {
				load_save_trails();
				save_timer.start();
			} //else load_save_trails(false);
		}
		LOCK_GLOBAL_MUTEX;
		status_line();
	}
	cout << endl << "Waiting for threads to finish..." << flush;
	threads.join_all();
	cout << "done." << endl;

	for (unsigned i = 0; i < threads_data.size(); ++i)
		delete threads_data[i];

}


#include <stdio.h>
#include <stdlib.h>
#include <GL/glu.h>
#include <GLFW/glfw3.h>
#include <armadillo>
#include "Object.hpp"
#include "Transform.hpp"
#include "Plotter.hpp"
#include "CelestialBody.hpp"

int main( void )
{
    arma::frowvec eye = {0.0, 0.0, 10.0};
    arma::frowvec camera = {0.0, 0.0, 0.0};

    GLFWwindow* window;

    float timeDelay = 4.0f;

//creacion del sol fijo.
    CelestialBody sol = CelestialBody( "sol", Color3f(1.0,1.0,0.0) , 0.3 );
//creacion de los planetas:
    CelestialBody mercurio = CelestialBody( "mercurio", sol , 5*timeDelay, Color3f(0.9,0.2,0.0) , 0.020 ,0.2);
    CelestialBody venus = CelestialBody( "venus", sol , 13*timeDelay, Color3f(0.55,0.5,0.509) , 0.040 ,0.25);
    CelestialBody tierra = CelestialBody( "tierra", sol , 21*timeDelay, Color3f(0.0,0.5,1.0) , 0.05 ,0.35);
    CelestialBody marte = CelestialBody( "marte", sol , 40*timeDelay, Color3f(1.0,0.0,0.0) , 0.025 ,0.4);
    CelestialBody jupiter = CelestialBody( "jupiter", sol , 240*timeDelay, Color3f(0.78,0.56,0.223) , 0.5 ,0.5);
    CelestialBody saturno = CelestialBody( "saturno", sol , 600*timeDelay, Color3f(0.9,0.84,0.72) , 0.42 ,0.6);
    CelestialBody urano = CelestialBody( "urano", sol , 1500*timeDelay, Color3f(0.3,0.3,1.0) , 0.175 ,0.8);
    CelestialBody neptuno = CelestialBody( "neptuno", sol , 3000*timeDelay, Color3f(0.01,0.01,1.0) , 0.160 ,1.0);

    CelestialBody luna = CelestialBody( "luna", &tierra , 2*timeDelay, Color3f(1.0,1.0,1.0) , 0.55 ,0.80);

    CelestialBody lunaJ1 = CelestialBody("luna",&jupiter,2.5*timeDelay , Color3f(1.0,1.0,1.0), 0.10, 0.65);
    CelestialBody lunaJ2 = CelestialBody("luna",&jupiter,1.534234*timeDelay , Color3f(1.0,1.0,1.0), 0.15, 0.7);
    CelestialBody lunaJ3 = CelestialBody("luna",&jupiter,2.5453*timeDelay , Color3f(1.0,1.0,1.0), 0.13, 0.8);
    //cout<<"afuera "<<&tierra.transf<<endl;

    if( !glfwInit() )
    {
        fprintf( stderr, "Fallo al inicializar GLFW\n" );
        getchar();
        return -1;
    }

    window = glfwCreateWindow(1024, 768, "Sistema Solar", NULL, NULL);
    if( window == NULL ) {
        fprintf( stderr, "Fallo al abrir la ventana de GLFW.\n" );
        getchar();
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);

    glClearColor(0.8f, 0.8f, 1.0f, 0.5f);

//  Proyecciones
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    int width, height;
    glfwGetFramebufferSize(window, &width, &height);

    float ar = width / height;

//  Proyección en paralelo
    glViewport(0, 0, width, height);
    glOrtho(-ar, ar, -1.0, 1.0, -20.0, 20.0) ;

//  Proyección en perspectiva
//    glFrustum(-ar, ar, -ar, ar, 2.0, 4.0);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    Transform Tr = Transform();
    float t_angle = 0.0f;

    do {
        glClear( GL_COLOR_BUFFER_BIT  | GL_DEPTH_BUFFER_BIT );

        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();
        gluLookAt(eye[0], eye[1], eye[2],
                camera[0], camera[1], camera[2],
                0.0, 1.0, 0.0);

//impresion y movimiento de los cuerpos celestes.
sol.makeStep();
mercurio.makeStep();
venus.makeStep();
tierra.makeStep();
marte.makeStep();
jupiter.makeStep();
saturno.makeStep();
urano.makeStep();
neptuno.makeStep();

//impresion y movimiento de lunas.
luna.makeStep();
lunaJ1.makeStep();
lunaJ2.makeStep();
lunaJ3.makeStep();

        glfwSwapBuffers(window);
        glfwPollEvents();

    } while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
           glfwWindowShouldClose(window) == 0 );

    glfwTerminate();

    return 0;
}


#pragma once
#include "hashmap.hpp"
#include "json11.hpp"
#include "pulse.hpp"
// #include "dipole.hpp"
#include <Eigen/Dense>
#include <atomic>
#include <chrono>
#include <complex>
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <gperftools/profiler.h>
#include <iomanip>
#include <iostream>
#include <openssl/md5.h>
#include <sstream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <thread> // std::thread
#include <unistd.h>

#define NSYS 4

using namespace json11;
using namespace std;
using namespace Eigen;

typedef unsigned long long int ullint;
// max number of bytes used to store a pointer in 64 bit
typedef long long int llint;
typedef Eigen::Matrix<complex<double>, NSYS, NSYS> MatrixNcd;
typedef Eigen::Matrix<ullint, Eigen::Dynamic, Eigen::Dynamic> nullmat;
typedef std::vector<MatrixNcd, Eigen::aligned_allocator<MatrixNcd>> nNNmat;
typedef std::vector<MatrixXcd, Eigen::aligned_allocator<MatrixXcd>> nXXmat;
typedef Eigen::Matrix<int32_t, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> XXimat_r;
typedef Eigen::Matrix<bool, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor> XXbmat_r;
typedef Eigen::Matrix<bool, 1, Eigen::Dynamic, Eigen::RowMajor> XXbvet_r;
typedef Eigen::Matrix<int, 1, Eigen::Dynamic, Eigen::RowMajor> XXivet_r;
// typedef Eigen::Matrix<uint16_t, Eigen::Dynamic, 1, Eigen::RowMajor> vetui16;
// typedef std::vector<vetui16, Eigen::aligned_allocator<MatrixXcd>> XXui16mat_r;


#include <fstream>
#include <iostream>
#include <string>
#include <cmath>
#include <chrono>
#include <future>
#include <boost/iostreams/filtering_streambuf.hpp>
#include <boost/iostreams/filter/gzip.hpp>
#include "csv.hpp"
#include "../assembly/assembly.cpp"
#include "../distance/distance.cpp"
#include "../distribution/distribution.cpp"

using namespace std;
using namespace std::chrono;
using namespace csv;

bool task_compute_distance(
	const int task_nb,
	const string kind,
	const vector<string>& assembly_ids
) {
	auto start = system_clock::now();

	cerr << "Thread " << task_nb << " started." << endl;

	string dataFolder = "../data/";
	string sequencesFolder = dataFolder + "sequences/";

	string assembly_dist_path;
	if (kind == "tri-nucleotide") {
		assembly_dist_path = dataFolder + "tri_nucleotide_dist_genome_wide_with_overlap.csv";
	} else {
		assembly_dist_path = dataFolder + "amino_acid_dist_genome_wide.csv";
	}

	ifstream assembly_dist_f(assembly_dist_path);
	Distributions assembly_distributions(assembly_dist_f);

	int i = 0;
	for (auto& accession : assembly_ids) {
		if (i == 0 || (i+1) % 100 == 0) {
			auto tp = system_clock::now();
			cerr << "Thread " << task_nb << ": Processing assembly " << i + 1 << " / " << assembly_ids.size();
			cerr << " (elapsed: " << duration_cast<seconds>(tp - start).count() << " seconds)" << endl;
		}
		++i;

		string cds_dist_path;
		string outputPath;
		if (kind == "tri-nucleotide") {
			cds_dist_path = (
				sequencesFolder + accession + "/" +
				accession + "_tri_nucleotide_dist_without_rc_with_overlap.csv.gz"
			);
			outputPath = (
				sequencesFolder + accession + "/" +
				accession + "_tri_nucleotide_distance_to_mean.csv"
			);
		} else {
			cds_dist_path = (
				sequencesFolder + accession + "/" +
				accession + "_amino_acid_dist.csv.gz"
			);
			outputPath = (
				sequencesFolder + accession + "/" +
				accession + "_amino_acid_distance_to_mean.csv"
			);
		}

		ifstream cds_dist_f(cds_dist_path, ios_base::in | ios_base::binary);
		boost::iostreams::filtering_streambuf<boost::iostreams::input> inbuf;
		inbuf.push(boost::iostreams::gzip_decompressor());
		inbuf.push(cds_dist_f);
		istream instream(&inbuf);

		Distributions cds_distributions(instream);

		auto mean_dist = duplicate_rows(
			assembly_distributions[accession],
			cds_distributions.size()
		);

		vector<string> protein_ids = cds_distributions.Keys();
		xt::xarray<double>& distributions = cds_distributions.Values();

		xt::xarray<double> distances = jensen_shannon_distance(mean_dist, distributions, 1);

		ofstream of(outputPath);
		auto writer = make_csv_writer(of);

		vector<string> headers{"protein_id", "distance"};
		writer << headers;

		for (int n = 0; n < distances.size(); ++n) {
			string protein_id = protein_ids[n];
			double distance = distances[n];
			if (isnan(distance)) {
				cerr << "Distance: NaN value encountered for accession " << accession << endl;
				continue;
			} else if (isinf(distance)) {
				cerr << "Distance: Inf value encountered for accession " << accession << endl;
				continue;
			}
			vector<string> row{protein_id, to_string(distance)};
			writer << row;
		}
	}

	auto tp = system_clock::now();
	cerr << "Thread " << task_nb << ": DONE";
	cerr << " (elapsed: " << duration_cast<seconds>(tp - start).count() << " seconds)" << endl;
	return true;
}

void compute_distance(const string kind, const int n_threads) {
	string dataFolder = "../data/";
	string assembliesPath = dataFolder + "assemblies.csv";

	Assemblies assemblies(assembliesPath);
	auto assembly_ids = assemblies.GetIds();

	cerr << "Processing " << assemblies.Size() << " assemblies" << endl;
	cerr << "Starting " << n_threads << " threads" << endl;

	auto n_per_thread = ceil((double) assembly_ids.size() / (double) n_threads);

	vector<future<bool>> futures;
	for (int i = 0; i < n_threads; ++i) {
		auto start = assembly_ids.begin() + i * n_per_thread;
		auto end = assembly_ids.end();
		int endInt = i * n_per_thread + n_per_thread;
		if (endInt < assembly_ids.size()) {
			end = assembly_ids.begin() + endInt;
		}
		auto ids = vector<string>(start, end);
		futures.push_back(async(task_compute_distance, i+1, kind, ids));
	}
	for (auto& f : futures) {
		if(!f.get()) {
			throw runtime_error("Unexpected error while processing future's output");
		}
	}
	cerr << "DONE" << endl;
}


/*
 * _build_in.cpp
 *
 *  Created on: 2013-2-14
 *      Author: fasiondog
 */

#include <boost/python.hpp>
#include <hikyuu/indicator/build_in.h>

using namespace hku;
using namespace boost::python;

Indicator (*PRICELIST2)(const PriceList&, int) = PRICELIST;
Indicator (*PRICELIST3)(const Indicator&, int) = PRICELIST;
Indicator (*PRICELIST4)(int) = PRICELIST;

Indicator (*KDATA1)(const KData&) = KDATA;
Indicator (*KDATA3)() = KDATA;

Indicator (*OPEN1)(const KData&) = OPEN;
Indicator (*OPEN3)() = OPEN;

Indicator (*HIGH1)(const KData&) = HIGH;
Indicator (*HIGH3)() = HIGH;

Indicator (*LOW1)(const KData&) = LOW;
Indicator (*LOW3)() = LOW;

Indicator (*CLOSE1)(const KData&) = CLOSE;
Indicator (*CLOSE3)() = CLOSE;

Indicator (*AMO1)(const KData&) = AMO;
Indicator (*AMO3)() = AMO;

Indicator (*VOL1)(const KData&) = VOL;
Indicator (*VOL3)() = VOL;

Indicator (*KDATA_PART1)(const KData& kdata, const string& part) = KDATA_PART;
Indicator (*KDATA_PART3)(const string& part) = KDATA_PART;

Indicator (*AMA_1)(int, int, int) = AMA;
Indicator (*AMA_2)(const Indicator&, int, int, int) = AMA;

Indicator (*ATR_1)(int) = ATR;
Indicator (*ATR_2)(const Indicator&, int) = ATR;

Indicator (*DIFF_1)() = DIFF;
Indicator (*DIFF_2)(const Indicator&) = DIFF;

Indicator (*MA_1)(int, const string&) = MA;
Indicator (*MA_2)(const Indicator&, int, const string&) = MA;
//BOOST_PYTHON_FUNCTION_OVERLOADS(MA_1_overload, MA, 1, 2);
//BOOST_PYTHON_FUNCTION_OVERLOADS(MA_2_overload, MA, 2, 3);

Indicator (*SMA_1)(int) = SMA;
Indicator (*SMA_2)(const Indicator&, int) = SMA;

Indicator (*EMA_1)(int) = EMA;
Indicator (*EMA_2)(const Indicator&, int) = EMA;

Indicator (*MACD_1)(int, int, int) = MACD;
Indicator (*MACD_2)(const Indicator&, int, int, int) = MACD;
//BOOST_PYTHON_FUNCTION_OVERLOADS(MACD_1_overload, MACD, 0, 3);
//BOOST_PYTHON_FUNCTION_OVERLOADS(MACD_2_overload, MACD, 1, 4);

Indicator (*REF_1)(int) = REF;
Indicator (*REF_2)(const Indicator&, int) = REF;

Indicator (*SAFTYLOSS_1)(int n1, int n2, double p) = SAFTYLOSS;
Indicator (*SAFTYLOSS_2)(const Indicator&, int n1, int n2, double p) = SAFTYLOSS;
//BOOST_PYTHON_FUNCTION_OVERLOADS(SAFTYLOSS_1_overload, SAFTYLOSS, 0, 3);
//BOOST_PYTHON_FUNCTION_OVERLOADS(SAFTYLOSS_2_overload, SAFTYLOSS, 1, 4);

Indicator (*STDEV_1)(int) = STDEV;
Indicator (*STDEV_2)(const Indicator&, int) = STDEV;

Indicator (*STDP_1)(int) = STDP;
Indicator (*STDP_2)(const Indicator&, int) = STDP;

Indicator (*HHV_1)(int) = HHV;
Indicator (*HHV_2)(const Indicator&, int) = HHV;

Indicator (*LLV_1)(int) = LLV;
Indicator (*LLV_2)(const Indicator&, int) = LLV;

Indicator (*VIGOR_1)(const KData&, int) = VIGOR;
Indicator (*VIGOR_2)(int) = VIGOR;

Indicator (*CVAL_1)(double, size_t) = CVAL;
Indicator (*CVAL_2)(const Indicator&, double, int) = CVAL;

Indicator (*LIUTONGPAN_1)() = LIUTONGPAN;
Indicator (*LIUTONGPAN_2)(const KData&) = LIUTONGPAN;

Indicator (*HSL_1)() = HSL;
Indicator (*HSL_2)(const KData&) = HSL;

Indicator (*IF_1)(const Indicator&, const Indicator&, const Indicator&) = IF;
Indicator (*IF_2)(const Indicator&, price_t, const Indicator&) = IF;
Indicator (*IF_3)(const Indicator&, const Indicator&, price_t) = IF;
Indicator (*IF_4)(const Indicator&, price_t, price_t) = IF;

Indicator (*COUNT_1)(int) = COUNT;
Indicator (*COUNT_2)(const Indicator&, int) = COUNT;

Indicator (*SUM_1)(int) = SUM;
Indicator (*SUM_2)(const Indicator&, int) = SUM;

Indicator (*ABS_1)() = ABS;
Indicator (*ABS_2)(price_t) = ABS;
Indicator (*ABS_3)(const Indicator&) = ABS;

Indicator (*NOT_1)() = NOT;
Indicator (*NOT_2)(const Indicator&) = NOT;

Indicator (*SGN_1)() = SGN;
Indicator (*SGN_2)(price_t) = SGN;
Indicator (*SGN_3)(const Indicator&) = SGN;

Indicator (*EXP_1)() = EXP;
Indicator (*EXP_2)(price_t) = EXP;
Indicator (*EXP_3)(const Indicator&) = EXP;

Indicator (*MAX_1)(const Indicator&, const Indicator&) = MAX;
Indicator (*MAX_2)(const Indicator&, price_t) = MAX;
Indicator (*MAX_3)(price_t, const Indicator&) = MAX;

Indicator (*MIN_1)(const Indicator&, const Indicator&) = MIN;
Indicator (*MIN_2)(const Indicator&, price_t) = MIN;
Indicator (*MIN_3)(price_t, const Indicator&) = MIN;

Indicator (*BETWEEN_1)(const Indicator&, const Indicator&, const Indicator&) = BETWEEN;
Indicator (*BETWEEN_2)(const Indicator&, const Indicator&, price_t) = BETWEEN;
Indicator (*BETWEEN_3)(const Indicator&, price_t, const Indicator&) = BETWEEN;
Indicator (*BETWEEN_4)(const Indicator&, price_t, price_t) = BETWEEN;
Indicator (*BETWEEN_5)(price_t, const Indicator&, const Indicator&) = BETWEEN;
Indicator (*BETWEEN_6)(price_t, const Indicator&, price_t) = BETWEEN;
Indicator (*BETWEEN_7)(price_t, price_t, const Indicator&) = BETWEEN;
Indicator (*BETWEEN_8)(price_t, price_t, price_t) = BETWEEN;

Indicator (*LN_1)() = LN;
Indicator (*LN_2)(price_t) = LN;
Indicator (*LN_3)(const Indicator&) = LN;

Indicator (*LOG_1)() = LOG;
Indicator (*LOG_2)(price_t) = LOG;
Indicator (*LOG_3)(const Indicator&) = LOG;

Indicator (*HHVBARS_1)(int) = HHVBARS;
Indicator (*HHVBARS_2)(const Indicator&, int) = HHVBARS;

Indicator (*LLVBARS_1)(int) = LLVBARS;
Indicator (*LLVBARS_2)(const Indicator&, int) = LLVBARS;

Indicator (*POW_1)(int) = POW;
Indicator (*POW_2)(const Indicator&, int) = POW;
Indicator (*POW_3)(price_t, int) = POW;

Indicator (*SQRT_1)() = SQRT;
Indicator (*SQRT_2)(const Indicator&) = SQRT;
Indicator (*SQRT_3)(price_t) = SQRT;

Indicator (*ROUND_1)(int) = ROUND;
Indicator (*ROUND_2)(const Indicator&, int) = ROUND;
Indicator (*ROUND_3)(price_t, int) = ROUND;

Indicator (*ROUNDUP_1)(int) = ROUNDUP;
Indicator (*ROUNDUP_2)(const Indicator&, int) = ROUNDUP;
Indicator (*ROUNDUP_3)(price_t, int) = ROUNDUP;

Indicator (*ROUNDDOWN_1)(int) = ROUNDDOWN;
Indicator (*ROUNDDOWN_2)(const Indicator&, int) = ROUNDDOWN;
Indicator (*ROUNDDOWN_3)(price_t, int) = ROUNDDOWN;

Indicator (*FLOOR_1)() = FLOOR;
Indicator (*FLOOR_2)(const Indicator&) = FLOOR;
Indicator (*FLOOR_3)(price_t) = FLOOR;

Indicator (*CEILING_1)() = CEILING;
Indicator (*CEILING_2)(const Indicator&) = CEILING;
Indicator (*CEILING_3)(price_t) = CEILING;

Indicator (*INTPART_1)() = INTPART;
Indicator (*INTPART_2)(const Indicator&) = INTPART;
Indicator (*INTPART_3)(price_t) = INTPART;

void export_Indicator_build_in() {
    def("KDATA", KDATA1);
    def("KDATA", KDATA3);

    def("CLOSE", CLOSE1);
    def("CLOSE", CLOSE3);

    def("OPEN", OPEN1);
    def("OPEN", OPEN3);

    def("HIGH", HIGH1);
    def("HIGH", HIGH3);

    def("LOW", LOW1);
    def("LOW", LOW3);

    def("AMO", AMO1);
    def("AMO", AMO3);

    def("VOL", VOL1);
    def("VOL", VOL3);

    def("KDATA_PART", KDATA_PART1, (arg("data"), arg("kpart")));
    def("KDATA_PART", KDATA_PART3, (arg("kpart")));

    def("PRICELIST", PRICELIST2, (arg("data"), arg("discard")=0));
    def("PRICELIST", PRICELIST3, (arg("data"), arg("result_index")=0));
    def("PRICELIST", PRICELIST4, (arg("result_index")=0));

    def("SMA", SMA_1, (arg("n")=22));
    def("SMA", SMA_2, (arg("data"), arg("n")=22));
    def("EMA", EMA_1, (arg("n")=22));
    def("EMA", EMA_2, (arg("data"), arg("n")=22));

    def("MA", MA_1, (arg("n")=22, arg("type")="SMA"),"type='SMA|EMA|AMA'");
    def("MA", MA_2, (arg("data"), arg("n")=22, arg("type")="SMA"), "type='SMA|EMA|AMA'");

    def("AMA", AMA_1, (arg("n")=10, arg("fast_n")=2, arg("slow_n")=30));
    def("AMA", AMA_2, (arg("data"), arg("n")=10, arg("fast_n")=2, arg("slow_n")=30));

    def("ATR", ATR_1, (arg("n")=14));
    def("ATR", ATR_2, (arg("data"), arg("n")=14));

    def("MACD", MACD_1, (arg("n1")=12, arg("n2")=26, arg("n3")=9));
    def("MACD", MACD_2, (arg("data"), arg("n1")=12, arg("n2")=26, arg("n3")=9));

    def("VIGOR", VIGOR_1, (arg("kdata"), arg("n")=2));
    def("VIGOR", VIGOR_2, (arg("n")=2));

    def("SAFTYLOSS", SAFTYLOSS_1, (arg("n1")=10, arg("n2")=3, arg("p")=2.0));
    def("SAFTYLOSS", SAFTYLOSS_2, (arg("data"), arg("n1")=10, arg("n2")=3, arg("p")=2.0));
    def("DIFF", DIFF_1);
    def("DIFF", DIFF_2);

    def("REF", REF_1, (arg("n")));
    def("REF", REF_2, (arg("data"), arg("n")));

    def("STDEV", STDEV_1, (arg("n")=10));
    def("STDEV", STDEV_2, (arg("data"), arg("n")=10));

    def("STDP", STDP_1, (arg("n")=10));
    def("STDP", STDP_2, (arg("data"), arg("n")=10));

    def("POS", POS, (arg("block"), arg("query"), arg("sg")));

    def("HHV", HHV_1, (arg("n")=20));
    def("HHV", HHV_2, (arg("data"), arg("n")=20));

    def("LLV", LLV_1, (arg("n")=20));
    def("LLV", LLV_2, (arg("data"), arg("n")=20));

    def("CVAL", CVAL_1, (arg("value")=0.0, arg("discard")=0));
    def("CVAL", CVAL_2, (arg("data"), arg("value")=0.0, arg("discard")=0));

    def("LIUTONGPAN", LIUTONGPAN_1);
    def("LIUTONGPAN", LIUTONGPAN_2);
    def("HSL", HSL_1);
    def("HSL", HSL_2);

    def("WEAVE", WEAVE);
    def("IF", IF_1);
    def("IF", IF_2);
    def("IF", IF_3);
    def("IF", IF_4);

    def("COUNT", COUNT_1, (arg("n")=20));
    def("COUNT", COUNT_2, (arg("data"), arg("n")=20));

    def("SUM", SUM_1, (arg("n")=20));
    def("SUM", SUM_2, (arg("data"), arg("n")=20));

    def("ABS", ABS_1);
    def("ABS", ABS_2);
    def("ABS", ABS_3);

    def("NOT", NOT_1);
    def("NOT", NOT_2);

    def("SGN", SGN_1);
    def("SGN", SGN_2);
    def("SGN", SGN_3);

    def("EXP", EXP_1);
    def("EXP", EXP_2);
    def("EXP", EXP_3);

    def("MAX", MAX_1);
    def("MAX", MAX_2);
    def("MAX", MAX_3);

    def("MIN", MIN_1);
    def("MIN", MIN_2);
    def("MIN", MIN_3);

    def("BETWEEN", BETWEEN_1);
    def("BETWEEN", BETWEEN_2);
    def("BETWEEN", BETWEEN_3);
    def("BETWEEN", BETWEEN_4);
    def("BETWEEN", BETWEEN_5);
    def("BETWEEN", BETWEEN_6);
    def("BETWEEN", BETWEEN_7);
    def("BETWEEN", BETWEEN_8);

    def("LN", LN_1);
    def("LN", LN_2);
    def("LN", LN_3);

    def("LOG", LOG_1);
    def("LOG", LOG_2);
    def("LOG", LOG_3);

    def("HHVBARS", HHVBARS_1, (arg("n")=20));
    def("HHVBARS", HHVBARS_2, (arg("data"), arg("n")=20));

    def("LLVBARS", LLVBARS_1, (arg("n")=20));
    def("LLVBARS", LLVBARS_2, (arg("data"), arg("n")=20));

    def("POW", POW_1, (arg("n")));
    def("POW", POW_2, (arg("data"), arg("n")));
    def("POW", POW_3), (arg("data"), arg("n"));

    def("SQRT", SQRT_1);
    def("SQRT", SQRT_2);
    def("SQRT", SQRT_3);

    def("ROUND", ROUND_1, (arg("ndigits") = 2));
    def("ROUND", ROUND_2, (arg("data"), arg("ndigits")=2));
    def("ROUND", ROUND_3, (arg("data"), arg("ndigits")=2));

    def("ROUNDUP", ROUNDUP_1, (arg("ndigits") = 2));
    def("ROUNDUP", ROUNDUP_2, (arg("data"), arg("ndigits")=2));
    def("ROUNDUP", ROUNDUP_3, (arg("data"), arg("ndigits")=2));

    def("ROUNDDOWN", ROUNDDOWN_1, (arg("ndigits") = 2));
    def("ROUNDDOWN", ROUNDDOWN_2, (arg("data"), arg("ndigits")=2));
    def("ROUNDDOWN", ROUNDDOWN_3, (arg("data"), arg("ndigits")=2));

    def("FLOOR", FLOOR_1);
    def("FLOOR", FLOOR_2);
    def("FLOOR", FLOOR_3);

    def("CEILING", CEILING_1);
    def("CEILING", CEILING_2);
    def("CEILING", CEILING_3);

    def("INTPART", INTPART_1);
    def("INTPART", INTPART_2);
    def("INTPART", INTPART_3);
}


//============================================================================
// Name        : ukf-update.cpp
// Author      : ddigges
// Version     :
// Copyright   : Your copyright notice
// Description : Hello World in C++, Ansi-style
//============================================================================

#include <iostream>
#include <Eigen/Dense>
#include <vector>
#include "ukf.h"

using namespace std;
using Eigen::MatrixXd;
using Eigen::VectorXd;
using std::vector;

int main() {

	//Create a UKF instance
	UKF ukf;

/*******************************************************************************
* Programming assignment calls
*******************************************************************************/

    VectorXd x_out = VectorXd(5);
    MatrixXd P_out = MatrixXd(5, 5);
    ukf.UpdateState(&x_out, &P_out);

	return 0;
}


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

///////////////////////////////////////////////////////////////////////////////////////////////////
#include <gtest/gtest.h>
#include "api/CPP/memory.hpp"
#include <api/CPP/input_layout.hpp>
#include "api/CPP/fully_connected.hpp"
#include <api/CPP/topology.hpp>
#include <api/CPP/tensor.hpp>
#include <api/CPP/network.hpp>
#include <api/CPP/engine.hpp>
#include "test_utils/test_utils.h"
#include <api/CPP/data.hpp>
#include "instrumentation.h"
#include <boost/filesystem.hpp>

#include <cmath>

namespace cldnn
{
    template<> struct type_to_data_type<FLOAT16> { static const data_types value = data_types::f16; };
}

using namespace cldnn;
using namespace tests;

cldnn::format::type layout_4d(cldnn::format f) {
    switch (f.value) {
    case cldnn::format::bfyx:
        return cldnn::format::bfyx;
    case cldnn::format::yxfb:
        return cldnn::format::yxfb;
    default:
        return f.value;
    }
}

template <typename T>
VVVVF<T> fully_connected_reference(VVVVF<T> &input, VVVVF<T> &weights, VF<T> &bias, bool relu = false, T slope = 0.0f) {
    size_t input_f = input[0].size();
    size_t input_y = input[0][0].size();
    size_t input_x = input[0][0][0].size();
    size_t output_b = input.size();        // input is assumed to be bfyx
    size_t output_f = weights.size();    // weights is assumed to be bfyx
    VVVVF<T> output(output_b, VVVF<T>(1, VVF<T>(1, VF<T>(output_f))));
    float res;
    for (size_t b = 0; b < output_b; ++b) {
        for (size_t n = 0; n < output_f; ++n) {
            res = bias[n];
            for (size_t f = 0; f < input_f; ++f) {
                for (size_t y = 0; y < input_y; ++y) {
                    for (size_t x = 0; x < input_x; ++x) {
                        res += (float)input[b][f][y][x] * (float)weights[n][f][y][x];
                    }
                }
            }
            if (relu && res < (float)0)
                res *= (float)slope;
            output[b][0][0][n] = (T)res;
        }
    }
    return output;
}

template <typename T>
void generic_fully_connected_test(cldnn::format test_input_fmt, cldnn::format test_weights_fmt, int input_b, int f, int y, int x, int output_f, bool relu, T slope = 0) {
    int min_random = -2, max_random = 2;
    VVVVF<T> input_rnd = generate_random_4d<T>(input_b, f, y, x, min_random, max_random);
    VVVVF<T> weights_rnd = generate_random_4d<T>(output_f, f, y, x, min_random, max_random);
    VF<T> bias_rnd_vec = generate_random_1d<T>(output_f, min_random, max_random);
    VF<T> input_rnd_vec = flatten_4d<T>(test_input_fmt, input_rnd);
    VF<T> weights_rnd_vec = flatten_4d<T>(test_weights_fmt, weights_rnd);

    const auto& engine = get_test_engine();
    tensor input_tensor(input_b, f, x, y);
    tensor weights_tensor(output_f, f, x, y);
    auto input = memory::allocate(engine, { type_to_data_type<T>::value, test_input_fmt, input_tensor });
    auto weights = memory::allocate(engine, { type_to_data_type<T>::value, test_weights_fmt, weights_tensor });
    auto bias = memory::allocate(engine, { type_to_data_type<T>::value, format::bfyx, { 1,1,output_f,1 } });
    set_values(input, input_rnd_vec);
    set_values(weights, weights_rnd_vec);
    set_values(bias, bias_rnd_vec);

    topology topology(
        input_layout("input", input.get_layout()),
        data("weights", weights),
        data("bias", bias),
        fully_connected("fully_connected", "input", "weights", "bias", relu, slope)
    );

    network network(engine, topology);
    network.set_input_data("input", input);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "fully_connected");

    auto output_memory = outputs.at("fully_connected").get_memory();
    auto output_layout = output_memory.get_layout();
    auto output_ptr = output_memory.pointer<T>();

    //EXPECT_EQ(output_layout.format.value, test_input_fmt);
    tensor output_tensor = output_layout.size;
    int b_size = output_tensor.batch[0];
    int x_size = output_tensor.feature[0];
    EXPECT_EQ(b_size, input_b);
    EXPECT_EQ(x_size, output_f);
    unsigned num_of_operations = f * x * y * 2;
    float ulp = (1.0f / 1024.0f) * num_of_operations;
    bool test_is_correct = true;
    VVVVF<T> output_cpu = fully_connected_reference<T>(input_rnd, weights_rnd, bias_rnd_vec, relu, slope);
    VF<T> output_cpu_vec = flatten_4d<T>(layout_4d(output_layout.format), output_cpu);
    for (size_t i = 0; i < output_cpu_vec.size(); ++i) {
        if (std::abs(float(output_cpu_vec[i]) - float(output_ptr[i])) > ulp) {
            EXPECT_FLOAT_EQ(output_cpu_vec[i], output_ptr[i]); // to print the problematic values
            test_is_correct = false;
            break;
        }
    }

    EXPECT_EQ(test_is_correct, true) << std::endl
        << "failing test parameters:" << std::endl
        << "test_input_fmt = " << format::traits(test_input_fmt).order << std::endl
        << "test_weights_fmt = " << format::traits(test_weights_fmt).order << std::endl
        << "input_b = " << input_b << std::endl
        << "f = " << f << std::endl
        << "y = " << y << std::endl
        << "x = " << x << std::endl
        << "output_f = " << output_f << std::endl
        << "relu = " << relu << std::endl
        << "slope = " << (float)slope << std::endl
        << "type = " << (sizeof(T) == 2 ? "float16" : "float32") << std::endl;
}

TEST(DISABLED_fully_connected_gpu, generic_random_short) {
    VF<cldnn::format> test_input_fmts = { cldnn::format::bfyx, cldnn::format::yxfb };
    VF<cldnn::format> test_weights_fmts = { cldnn::format::yxfb };
    VF<bool> relu = { true, false };
    std::vector<int> batches = { 1, 2, 4, 8, 16 };
    std::vector<int> features = { 1, 2 };
    std::vector<std::pair<int, int>> input_sizes = { {28, 28}, {64, 64}, {100, 100}, {227, 227}, {1000, 1}, {1, 4096} };
    VF<int> outputs_x = { 5, 16 };

    const auto& engine = get_test_engine();
    bool f16_supported = !!engine.get_info().supports_fp16;
    if (!f16_supported) {
        std::cout << "[ SKIPPED  ] float16 combinations are skipped (cl_khr_fp16 is not supported)." << std::endl;
    }

    for (cldnn::format test_input_fmt : test_input_fmts) {
        for (cldnn::format test_weights_fmt : test_weights_fmts) {
            for (const auto& b : batches) {
                for(const auto& f : features) {
                    for (const auto& sizes : input_sizes) {
                        for (int output_f : outputs_x) {
                            for (bool relu_activated : relu) {
                                    generic_fully_connected_test<float>(test_input_fmt, test_weights_fmt, b, f, sizes.second, sizes.first, output_f, relu_activated);
                                    if (!f16_supported) continue;
                                    generic_fully_connected_test<FLOAT16>(test_input_fmt, test_weights_fmt, b, f, sizes.second, sizes.first, output_f, relu_activated);
                            }
                        }
                    }
                }
            }
        }
    }
}

TEST(fully_connected_gpu, no_biases) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //
    //  Biases:
    //   no biases
    //
    //  Output:
    //   2.5    2.75    0.75   7

    const int32_t input_x = 3, input_b = 1,  // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::yxfb,{ input_b, 1, input_x, 1} });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });

    set_values(input_prim, { -0.5f, 2.0f, 0.5f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });

    auto input = input_layout("input", input_prim.get_layout());
    auto w_data = data("weights", weights_prim);
    auto fc = fully_connected("full_con_prim", "input", "weights");
    topology topology;
    topology.add(input);
    topology.add(w_data);
    topology.add(fc);

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(1.5f, output_ptr[0]);
    EXPECT_EQ(0.75f, output_ptr[1]);
    EXPECT_EQ(-2.25f, output_ptr[2]);
    EXPECT_EQ(3.0f, output_ptr[3]);
}

TEST(fully_connected_gpu, no_biases_int8) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //
    //  Input:
    //  8.0f, 2.0f, -4.0f
    //
    //  Weights:
    //   2.0f    1.0f  0.0f
    //  -3.0f   -2.0f  1.0f
    //   0.0f   -2.0f -4.0f
    //  -5.0f   10.0f  8.0f
    //
    //
    //  Biases:
    //   no biases
    //
    //  Output:
    //  18    -32    12   -52

    const int32_t input_x = 3, input_b = 1,  // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ input_b, 1, input_x, 1 } });
    auto weights_prim = memory::allocate(engine, { data_types::i8,format::bfyx,{ weight_b, 1, weight_x, 1 } });

    set_values(input_prim, { 8.4f, 2.3f, -4.99f });
    set_values<char>(weights_prim, { 2, 1, 0, -3, -2, 1, 0, -2, -4, -5, 10, 8 });

    auto input = input_layout("input", input_prim.get_layout());
    auto w_data = data("weights", weights_prim);
    auto ri = reorder("reorder_to_int", "input", { data_types::i8,format::bfyx,{ input_b, 1, input_x, 1 } });
    auto fc = fully_connected("full_con_prim", "reorder_to_int", "weights");
    auto rf = reorder("reorder_to_float", "full_con_prim", { data_types::f32,format::bfyx,{ input_b, 1, 4, 1 } });
    topology topology;
    topology.add(input);
    topology.add(w_data);
    topology.add(fc);
    topology.add(ri);
    topology.add(rf);
    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "reorder_to_float");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(18.0f, output_ptr[0]);
    EXPECT_EQ(-32.0f, output_ptr[1]);
    EXPECT_EQ(12.0f, output_ptr[2]);
    EXPECT_EQ(-52.0f, output_ptr[3]);
}

TEST(fully_connected_gpu, xb_f32_batch_1) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //
    //  Biases:
    //   1.0, 2.0, 3.0, 4.0
    //
    //  Output:
    //   2.5    2.75    0.75   7

    const int32_t output_f = 4,  // size of whole output buffer
        input_x = 3, input_b = 1,  // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate( engine, { data_types::f32, format::yxfb, { input_b, 1, input_x, 1 } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx, { 1,1,output_f, 1} });

    set_values(input_prim, { -0.5f, 2.0f, 0.5f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, 2.0f, 3.0f, 4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias")
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.5f, output_ptr[0]);
    EXPECT_EQ(2.75f, output_ptr[1]);
    EXPECT_EQ(0.75f, output_ptr[2]);
    EXPECT_EQ(7.0f, output_ptr[3]);
}

TEST(fully_connected_gpu, xb_f32_batch_2) {
    //  Input  : 3x2
    //  Output : 4x2
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //   1       1.5  0
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //  Biases:
    //   1.0, 2.0, 3.0, 4.0
    //
    //  Output:
    //   2.5    2.75     0.75   7
    //   4      1        2.75   5

    const int32_t output_f = 4,  // size of whole output buffer
        input_x = 3, input_b = 2,  // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::yxfb,{ input_b,1,input_x, 1 } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,output_f,1 } });

    set_values(input_prim, { -0.5f, 1.0f, 2.0f, 1.5f, 0.5f, 0.0f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, 2.0f, 3.0f, 4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias")
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.50f, output_ptr[0]);
    EXPECT_EQ(4.00f, output_ptr[1]);
    EXPECT_EQ(2.75f, output_ptr[2]);
    EXPECT_EQ(1.00f, output_ptr[3]);
    EXPECT_EQ(0.75f, output_ptr[4]);
    EXPECT_EQ(2.75f, output_ptr[5]);
    EXPECT_EQ(7.00f, output_ptr[6]);
    EXPECT_EQ(5.00f, output_ptr[7]);
}

TEST(fully_connected_gpu, x_f32) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //  Biases:
    //   1.0, 2.0, 3.0, 4.0
    //  Output:
    //   2.5    2.75    0.75   7

    const int32_t output_f = 4,                 // size of whole output buffer
        input_x = 3,                 // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::bfyx, { 1,1,input_x,1 } });
    //auto output_prim = memory::allocate({ memory::format::xb_f32,{ output_b,{ { output_f } },{ 1 } } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,output_f,1 } });

    set_values(input_prim, { -0.5f, 2.0f, 0.5f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, 2.0f, 3.0f, 4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias")
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.50f, output_ptr[0]);
    EXPECT_EQ(2.75f, output_ptr[1]);
    EXPECT_EQ(0.75f, output_ptr[2]);
    EXPECT_EQ(7.00f, output_ptr[3]);
}

TEST(fully_connected_gpu, yxfn_f32) {
    //  Input  : 1x2x1x2 - 1 batch 2 feature maps of size 2x1
    //  Output : 2x1 - 2 batches 1 neuron each
    //  Weights: 2x2x1x2 - 2 neurons with weights of 2 feature maps of size 2x1
    //
    //  Input:
    //   1  -2      f0: b0
    //   3  -4      f1: b0

    //  Weights:
    //   1  -1      n0: fm0
    //   2   0      n0: fm1
    //   3   4      n1: fm0
    //   0.5 5      n1: fm1
    //
    //  Biases:
    //   1.0 -5
    //
    //  Output:
    //   10  -28.5

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32, format::yxfb, { 1, 2, 2, 1 } });
    //auto output_prim = memory::allocate({ memory::format::xb_f32,{ 2 ,{ { 1 } }, 1 } });
    auto weights_prim = memory::allocate(engine, { data_types::f32, format::bfyx, { 2, 2, 2, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32, format::bfyx, { 1, 1, 2, 1 } });

    set_values(input_prim, { 1.f, 3.f, -2.f, -4.f });
    set_values(weights_prim, { 1.f, -1.f, 2.0f, 0.f, 3.0f, 4.0f, 0.5f, 5.0f });
    set_values(bias_prim, { 1.0f, -5.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias")
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(10, output_ptr[0]);
    EXPECT_EQ(-28.5, output_ptr[1]);
}

TEST(fully_connected_gpu, xb_f32_batch_1_relu) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //
    //  Biases:
    //   1.0,  -2.0,  3.0,  -4.0
    //
    //  Output:
    //   2.5   0      0.75  0

    const int32_t output_f = 4,  // size of whole output buffer
        input_x = 3, input_b = 1,  // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::yxfb,{ input_b, 1, input_x, 1 } });
    //auto output_prim = memory::allocate({ memory::format::xb_f32,{ output_b,{ { output_f } },{ 1 } } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,output_f, 1 } });

    set_values(input_prim, { -0.5f, 2.0f, 0.5f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, -2.0f, 3.0f, -4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias", true, 0)
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.50f, output_ptr[0]);
    EXPECT_EQ(0.00f, output_ptr[1]);
    EXPECT_EQ(0.75f, output_ptr[2]);
    EXPECT_EQ(0.00f, output_ptr[3]);
}

TEST(fully_connected_gpu, xb_f32_batch_2_relu) {
    //  Input  : 3x2
    //  Output : 4x2
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //   1       1.5  0
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //  Biases:
    //   1.0, -2.0, 3.0, -4.0
    //
    //  Output:
    //   2.5    0   0.75   0
    //   4      0   2.75   0

    const int32_t output_f = 4,  // size of whole output buffer
        input_x = 3, input_b = 2,  // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::yxfb,{ input_b, 1, input_x, 1 } });
    //auto output_prim = memory::allocate({ memory::format::xb_f32,{ output_b,{ { output_f } },{ 1 } } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,output_f,1 } });

    set_values(input_prim, { -0.5f, 1.0f, 2.0f, 1.5f, 0.5f, 0.0f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, -2.0f, 3.0f, -4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias", true, 0)
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.50f, output_ptr[0]);
    EXPECT_EQ(4.00f, output_ptr[1]);
    EXPECT_EQ(0.00f, output_ptr[2]);
    EXPECT_EQ(0.00f, output_ptr[3]);
    EXPECT_EQ(0.75f, output_ptr[4]);
    EXPECT_EQ(2.75f, output_ptr[5]);
    EXPECT_EQ(0.00f, output_ptr[6]);
    EXPECT_EQ(0.00f, output_ptr[7]);
}

TEST(fully_connected_gpu, x_f32_relu) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //
    //  Input:
    //  -0.5     2    0.5
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //  Biases:
    //   1.0, -2.0, 3.0, -4.0
    //  Output:
    //   2.5   0    0.75  0

    const int32_t output_f = 4,                 // size of whole output buffer
        input_x = 3,                 // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,input_x,1 } });
    //auto output_prim = memory::allocate({ memory::format::x_f32,{ 1       ,{ { output_f } }, 1 } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,output_f,1 } });

    set_values(input_prim, { -0.5f, 2.0f, 0.5f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, -2.0f, 3.0f, -4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias", true, 0)
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.50f, output_ptr[0]);
    EXPECT_EQ(0.00f, output_ptr[1]);
    EXPECT_EQ(0.75f, output_ptr[2]);
    EXPECT_EQ(0.00f, output_ptr[3]);
}

TEST(fully_connected_gpu, x_f32_relu_with_negative_slope) {
    //  Input  : 3x1
    //  Output : 4x1
    //  Weights: 4x3
    //  Negative Slope: 0.1
    //
    //  Input:
    //  -0.5     2    0.5
    //
    //  Weights:
    //   1.5     1    0.5
    //  -1       0    0.5
    //   0.5    -0.5 -2
    //  -0.5     1    1.5
    //
    //  Biases:
    //   1.0, -2.0, 3.0, -4.0
    //  Output:
    //   2.5   -0.125    0.75  -0.1

    const int32_t output_f = 4,                 // size of whole output buffer
        input_x = 3,                 // size of whole input buffer
        weight_b = 4, weight_x = 3;  // size of whole weights buffer

    const auto& engine = get_test_engine();

    auto input_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,input_x,1 } });
    //auto output_prim = memory::allocate({ memory::format::x_f32,{ 1       ,{ { output_f } }, 1 } });
    auto weights_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ weight_b, 1, weight_x, 1 } });
    auto bias_prim = memory::allocate(engine, { data_types::f32,format::bfyx,{ 1,1,output_f,1 } });

    set_values(input_prim, { -0.5f, 2.0f, 0.5f });
    set_values(weights_prim, { 1.5f, 1.0f, 0.5f, -1.0f, 0.0f, 0.5f, 0.5f, -0.5f, -2.0f, -0.5f, 1.0f, 1.5f });
    set_values(bias_prim, { 1.0f, -2.0f, 3.0f, -4.0f });

    topology topology(
        input_layout("input", input_prim.get_layout()),
        data("weights", weights_prim),
        data("bias", bias_prim),
        fully_connected("full_con_prim", "input", "weights", "bias", true, 0.1f)
    );

    network network(engine, topology);
    network.set_input_data("input", input_prim);

    auto outputs = network.execute();
    EXPECT_EQ(outputs.size(), size_t(1));
    EXPECT_EQ(outputs.begin()->first, "full_con_prim");

    auto output_prim = outputs.begin()->second.get_memory();

    auto output_ptr = output_prim.pointer<float>();

    EXPECT_EQ(2.50f, output_ptr[0]);
    EXPECT_EQ(-0.125f, output_ptr[1]);
    EXPECT_EQ(0.75f, output_ptr[2]);
    EXPECT_EQ(-0.1f, output_ptr[3]);
}

TEST(fully_connected_gpu, b_fs_yx_fsv4)
{
    const auto& engine = get_test_engine();

    const int in_B = 2;
    const int in_F = 2048;
    const int in_Y = 1;
    const int in_X = 1;

    const int W_B = 1000;
    const int W_F = in_F;
    const int W_Y = in_Y;
    const int W_X = in_X;

    // Input data
    std::vector<char> Data(in_F * in_B); // in_X=in_Y=1
    std::generate(Data.begin(), Data.end(), [i = 0]() mutable { return i++ % 9; });
    auto input = memory::allocate(engine, {data_types::i8, format::bfyx, {in_B, in_F, in_X, in_Y}});
    set_values(input, std::move(Data));

    // Create a topology
    topology topology(input_layout("input", input.get_layout()));

    // Reorder
    topology.add(reorder("reorder_in",
                         "input",
                         layout(data_types::i8, format::b_fs_yx_fsv4, {in_B, in_F, in_X, in_Y})));

    // Weights
    std::vector<char> Weights(W_B * W_F);
    std::generate(Weights.begin(), Weights.end(), [W_F, i = 0]() mutable {
        return i % 2 ? -(i++) / W_F - 1 : (i++) / W_F + 1;
    });
    auto weights_gold =
        memory::allocate(engine, {data_types::i8, format::bfyx, {W_B, W_F, W_X, W_Y}});
    auto weights_imad =
        memory::allocate(engine, {data_types::i8, format::bfyx, {W_B, W_F, W_X, W_Y}});
    set_values(weights_gold, Weights);
    set_values(weights_imad, std::move(Weights));
    topology.add(data("weights_gold", weights_gold), data("weights_imad", weights_imad));

    // Bias, Callibraiton, Quantization
    std::vector<float> vB(in_F), vC(in_F), vQ(in_F);
    std::generate(vB.begin(), vB.end(), [x = 0.1f]() mutable {
        x += 0.01f;
        if (x >= 0.9f)
            x = 0.1f;
        return x;
    });
    std::generate(vC.begin(), vC.end(), [x = 0.2f]() mutable {
        x += 0.01f;
        if (x >= 0.9f)
            x = 0.2f;
        return x;
    });
    std::generate(vQ.begin(), vQ.end(), [x = 0.3f]() mutable {
        x += 0.01f;
        if (x >= 0.9f)
            x = 0.3f;
        return x;
    });
    auto bias_gold = memory::allocate(engine, {data_types::f32, format::bfyx, {1, 1, in_F, 1}});
    auto bias_imad = memory::allocate(engine, {data_types::f32, format::bfyx, {1, 1, in_F, 1}});
    auto callib_gold = memory::allocate(engine, {data_types::f32, format::bfyx, {1, 1, in_F, 1}});
    auto callib_imad = memory::allocate(engine, {data_types::f32, format::bfyx, {1, 1, in_F, 1}});
    auto quant_gold = memory::allocate(engine, {data_types::f32, format::bfyx, {1, 1, in_F, 1}});
    auto quant_imad = memory::allocate(engine, {data_types::f32, format::bfyx, {1, 1, in_F, 1}});
    set_values(bias_gold, vB);
    set_values(bias_imad, std::move(vB));
    set_values(callib_gold, vC);
    set_values(callib_imad, std::move(vC));
    set_values(quant_gold, vQ);
    set_values(quant_imad, std::move(vQ));
    topology.add(data("bias_gold", bias_gold),
                 data("callib_gold", callib_gold),
                 data("quant_gold", quant_gold));
    topology.add(data("bias_imad", bias_imad),
                 data("callib_imad", callib_imad),
                 data("quant_imad", quant_imad));

    // Fully connected
    fully_connected fullc_gold(
        "fullc_gold", "input", "weights_gold", {"bias_gold"}, {"quant_gold"}, {"callib_gold"}, 1.0f);
    fully_connected fullc_imad(
        "fullc_imad", "reorder_in", "weights_imad", {"bias_imad"}, {"quant_imad"}, {"callib_imad"}, 1.0f);
    topology.add(fullc_gold, fullc_imad);

    // Output reorder
    auto reorder_gold =
        reorder("reorder_gold", fullc_gold, layout(data_types::i8, format::bfyx, {in_B, W_B, 1, 1}));
    auto reorder_imad =
        reorder("reorder_imad", fullc_imad, layout(data_types::i8, format::bfyx, {in_B, W_B, 1, 1}));
    topology.add(reorder_gold, reorder_imad);

    // Network build
    build_options build_opt;
    build_opt.set_option(build_option::optimize_data(true));
    network network(engine, topology, build_opt);

    // Network execuiton
    network.set_input_data("input", input);
    auto outputs = network.execute();

    auto out_gold = outputs.find("reorder_gold");
    auto out_test = outputs.find("reorder_imad");

    ASSERT_NE(out_gold, outputs.end());
    ASSERT_NE(out_test, outputs.end());
    auto gold_ptr = out_gold->second.get_memory().pointer<char>();
    auto test_ptr = out_test->second.get_memory().pointer<char>();

    ASSERT_EQ(gold_ptr.size(), test_ptr.size());
    for (size_t i = 0; i < gold_ptr.size(); i++)
    {
        ASSERT_EQ(gold_ptr[i], test_ptr[i]);
    }
}


//
// Copyright (c) 2019-2021 CNRS INRIA
//

#include "pinocchio/algorithm/center-of-mass.hpp"
#include "pinocchio/algorithm/joint-configuration.hpp"
#include "pinocchio/algorithm/compute-all-terms.hpp"
#include "pinocchio/algorithm/center-of-mass-derivatives.hpp"
#include "pinocchio/parsers/sample-models.hpp"

#include <boost/test/unit_test.hpp>
#include <boost/utility/binary.hpp>

BOOST_AUTO_TEST_SUITE(BOOST_TEST_MODULE)

BOOST_AUTO_TEST_CASE(test_kinematics_derivatives_vcom)
{
  using namespace Eigen;
  using namespace pinocchio;

  Model model;
  buildModels::humanoid(model);

  Data data_ref(model);

  model.lowerPositionLimit.head<3>().fill(-1.);
  model.upperPositionLimit.head<3>().fill(1.);
  VectorXd q = randomConfiguration(model);
  VectorXd vq(VectorXd::Random(model.nv));
  VectorXd aq(VectorXd::Random(model.nv));

  // Approximate dvcom_dq by finite diff.
  centerOfMass(model,data_ref,q,vq);
  const Eigen::Vector3d vcom0 = data_ref.vcom[0];
  const double alpha = 1e-8;
  Eigen::VectorXd dq = VectorXd::Zero(model.nv);
  Data::Matrix3x dvcom_dqn(3,model.nv);

  for(int k = 0; k < model.nv; ++k)
  {
    dq[k] = alpha;
    centerOfMass(model,data_ref,integrate(model,q,dq),vq);
    dvcom_dqn.col(k) = (data_ref.vcom[0]-vcom0)/alpha;
    dq[k] = 0;
  }

  {
    // Compute dvcom_dq using the algorithm
    Data data(model);
    Data::Matrix3x dvcom_dq = Data::Matrix3x::Zero(3,model.nv);
    centerOfMass(model,data,q,vq);
    getCenterOfMassVelocityDerivatives(model,data,dvcom_dq);

    // Check that algo result and finite-diff approx are similar.
    BOOST_CHECK(dvcom_dq.isApprox(dvcom_dqn,sqrt(alpha)));
  }

  {
    // Compute dvcom_dq using the algorithm
    Data data(model);
    Data::Matrix3x dvcom_dq = Data::Matrix3x::Zero(3,model.nv);
    computeAllTerms(model,data,q,vq);
    getCenterOfMassVelocityDerivatives(model,data,dvcom_dq);

    // Check that algo result and finite-diff approx are similar.
    BOOST_CHECK(dvcom_dq.isApprox(dvcom_dqn,sqrt(alpha)));
  }
}

BOOST_AUTO_TEST_SUITE_END()


///////////////////////////////////////////////////////////////////////////////
// distribution::survival::failure_time::meta::distribution.hpp       		 //
//                                                                           //
//  Copyright 2009 Erwann Rogard. Distributed under the Boost                //
//  Software License, Version 1.0. (See accompanying file                    //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)         //
///////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_FAILURE_TIME_META_DETAIL_TRAITS_HPP_ER_2009
#define BOOST_STATISTICS_DETAIL_DISTRIBUTION_SURVIVAL_FAILURE_TIME_META_DETAIL_TRAITS_HPP_ER_2009
#include <boost/mpl/bool.hpp>
#include <boost/utility/enable_if.hpp>
#include <boost/statistics/detail/traits/remove_cvref.hpp>
#include <boost/statistics/detail/distribution/model/tag/covariate.hpp>
#include <boost/statistics/detail/distribution/model/tag/model.hpp>
#include <boost/statistics/detail/distribution/survival/models/common/meta/is_survival_model.hpp>

namespace boost {
namespace statistics{
namespace detail{
namespace distribution{
namespace survival {
namespace failure_time{
namespace detail{

	template<typename Map>
    struct traits
    {
    	typedef model::tag::covariate tag_x_;
    	typedef model::tag::model tag_m_;

        typedef typename
        	boost::fusion::result_of::at_key<tag_x_,Map>::type cref_x_;
        typedef typename
        	boost::fusion::result_of::at_key<tag_m_,Map>::type cref_m_;

		typedef typename statistics::detail::traits::remove_cvref<
        	cref_x_
        >::type covariate_type;
		typedef typename statistics::detail::traits::remove_cvref<
        	cref_m_
        >::type model_type;
    };

}// detail
}// failure_time
}// survival
}// distribution
}// detail
}// statistics
}// boost

#endif


//[ Vector
///////////////////////////////////////////////////////////////////////////////
//  Copyright 2008 Eric Niebler. 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)
//
// This is an example of using BOOST_PROTO_DEFINE_OPERATORS to Protofy
// expressions using std::vector<>, a non-proto type. It is a port of the
// Vector example from PETE (http://www.codesourcery.com/pooma/download.html).

#include <vector>
#include <iostream>
#include <stdexcept>
#include <boost/mpl/bool.hpp>
#include <boost/proto/core.hpp>
#include <boost/proto/debug.hpp>
#include <boost/proto/context.hpp>
#include <boost/utility/enable_if.hpp>
namespace mpl = boost::mpl;
namespace proto = boost::proto;
using proto::_;

template<typename Expr>
struct VectorExpr;

// Here is an evaluation context that indexes into a std::vector
// expression and combines the result.
struct VectorSubscriptCtx
{
    VectorSubscriptCtx(std::size_t i)
      : i_(i)
    {}

    // Unless this is a vector terminal, use the
    // default evaluation context
    template<typename Expr, typename EnableIf = void>
    struct eval
      : proto::default_eval<Expr, VectorSubscriptCtx const>
    {};

    // Index vector terminals with our subscript.
    template<typename Expr>
    struct eval<
        Expr
      , typename boost::enable_if<
            proto::matches<Expr, proto::terminal<std::vector<_, _> > >
        >::type
    >
    {
        typedef typename proto::result_of::value<Expr>::type::value_type result_type;

        result_type operator ()(Expr &expr, VectorSubscriptCtx const &ctx) const
        {
            return proto::value(expr)[ctx.i_];
        }
    };

    std::size_t i_;
};

// Here is an evaluation context that verifies that all the
// vectors in an expression have the same size.
struct VectorSizeCtx
{
    VectorSizeCtx(std::size_t size)
      : size_(size)
    {}

    // Unless this is a vector terminal, use the
    // null evaluation context
    template<typename Expr, typename EnableIf = void>
    struct eval
      : proto::null_eval<Expr, VectorSizeCtx const>
    {};

    // Index array terminals with our subscript. Everything
    // else will be handled by the default evaluation context.
    template<typename Expr>
    struct eval<
        Expr
      , typename boost::enable_if<
            proto::matches<Expr, proto::terminal<std::vector<_, _> > >
        >::type
    >
    {
        typedef void result_type;

        result_type operator ()(Expr &expr, VectorSizeCtx const &ctx) const
        {
            if(ctx.size_ != proto::value(expr).size())
            {
                throw std::runtime_error("LHS and RHS are not compatible");
            }
        }
    };

    std::size_t size_;
};

// A grammar which matches all the assignment operators,
// so we can easily disable them.
struct AssignOps
  : proto::switch_<struct AssignOpsCases>
{};

// Here are the cases used by the switch_ above.
struct AssignOpsCases
{
    template<typename Tag, int D = 0> struct case_  : proto::not_<_> {};

    template<int D> struct case_< proto::tag::plus_assign, D >         : _ {};
    template<int D> struct case_< proto::tag::minus_assign, D >        : _ {};
    template<int D> struct case_< proto::tag::multiplies_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::divides_assign, D >      : _ {};
    template<int D> struct case_< proto::tag::modulus_assign, D >      : _ {};
    template<int D> struct case_< proto::tag::shift_left_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::shift_right_assign, D >  : _ {};
    template<int D> struct case_< proto::tag::bitwise_and_assign, D >  : _ {};
    template<int D> struct case_< proto::tag::bitwise_or_assign, D >   : _ {};
    template<int D> struct case_< proto::tag::bitwise_xor_assign, D >  : _ {};
};

// A vector grammar is a terminal or some op that is not an
// assignment op. (Assignment will be handled specially.)
struct VectorGrammar
  : proto::or_<
        proto::terminal<_>
      , proto::and_<proto::nary_expr<_, proto::vararg<VectorGrammar> >, proto::not_<AssignOps> >
    >
{};

// Expressions in the vector domain will be wrapped in VectorExpr<>
// and must conform to the VectorGrammar
struct VectorDomain
  : proto::domain<proto::generator<VectorExpr>, VectorGrammar>
{};

// Here is VectorExpr, which extends a proto expr type by
// giving it an operator [] which uses the VectorSubscriptCtx
// to evaluate an expression with a given index.
template<typename Expr>
struct VectorExpr
  : proto::extends<Expr, VectorExpr<Expr>, VectorDomain>
{
    explicit VectorExpr(Expr const &expr)
      : proto::extends<Expr, VectorExpr<Expr>, VectorDomain>(expr)
    {}

    // Use the VectorSubscriptCtx to implement subscripting
    // of a Vector expression tree.
    typename proto::result_of::eval<Expr const, VectorSubscriptCtx const>::type
    operator []( std::size_t i ) const
    {
        VectorSubscriptCtx const ctx(i);
        return proto::eval(*this, ctx);
    }
};

// Define a trait type for detecting vector terminals, to
// be used by the BOOST_PROTO_DEFINE_OPERATORS macro below.
template<typename T>
struct IsVector
  : mpl::false_
{};

template<typename T, typename A>
struct IsVector<std::vector<T, A> >
  : mpl::true_
{};

namespace VectorOps
{
    // This defines all the overloads to make expressions involving
    // std::vector to build expression templates.
    BOOST_PROTO_DEFINE_OPERATORS(IsVector, VectorDomain)

    typedef VectorSubscriptCtx const CVectorSubscriptCtx;

    // Assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &assign(std::vector<T, A> &arr, Expr const &expr)
    {
        VectorSizeCtx const size(arr.size());
        proto::eval(proto::as_expr<VectorDomain>(expr), size); // will throw if the sizes don't match
        for(std::size_t i = 0; i < arr.size(); ++i)
        {
            arr[i] = proto::as_expr<VectorDomain>(expr)[i];
        }
        return arr;
    }

    // Add-assign to a vector from some expression.
    template<typename T, typename A, typename Expr>
    std::vector<T, A> &operator +=(std::vector<T, A> &arr, Expr const &expr)
    {
        VectorSizeCtx const size(arr.size());
        proto::eval(proto::as_expr<VectorDomain>(expr), size); // will throw if the sizes don't match
        for(std::size_t i = 0; i < arr.size(); ++i)
        {
            arr[i] += proto::as_expr<VectorDomain>(expr)[i];
        }
        return arr;
    }
}

int main()
{
    using namespace VectorOps;

    int i;
    const int n = 10;
    std::vector<int> a,b,c,d;
    std::vector<double> e(n);

    for (i = 0; i < n; ++i)
    {
        a.push_back(i);
        b.push_back(2*i);
        c.push_back(3*i);
        d.push_back(i);
    }

    VectorOps::assign(b, 2);
    VectorOps::assign(d, a + b * c);
    a += if_else(d < 30, b, c);

    VectorOps::assign(e, c);
    e += e - 4 / (c + 1);

    for (i = 0; i < n; ++i)
    {
        std::cout
            << " a(" << i << ") = " << a[i]
            << " b(" << i << ") = " << b[i]
            << " c(" << i << ") = " << c[i]
            << " d(" << i << ") = " << d[i]
            << " e(" << i << ") = " << e[i]
            << std::endl;
    }
}
//]