/*------------------------------------------------------------------------------
* preceph.c : precise ephemeris and clock functions
*
*          Copyright (C) 2007-2013 by T.TAKASU, All rights reserved.
*
* references :
*     [1] S.Hilla, The Extended Standard Product 3 Orbit Format (SP3-c),
*         12 February, 2007
*     [2] J.Ray, W.Gurtner, RINEX Extensions to Handle Clock Information,
*         27 August, 1998
*     [3] D.D.McCarthy, IERS Technical Note 21, IERS Conventions 1996, July 1996
*     [4] D.A.Vallado, Fundamentals of Astrodynamics and Applications 2nd ed,
*         Space Technology Library, 2004
*-----------------------------------------------------------------------------*/

#include <unordered_map>
#include <iostream>
#include <string>
#include <array>
#include <map>
#include <ctype.h>

using std::string;
using std::array;
using std::map;

#include <boost/log/trivial.hpp>

#include "eigenIncluder.hpp"
#include "streamTrace.hpp"
#include "navigation.hpp"
#include "constants.h"
#include "station.hpp"
#include "algebra.hpp"
#include "gTime.hpp"
#include "common.hpp"
#include "tides.hpp"
#include "enums.h"

map<string, map<E_Sys, array<double, 3>>> stationRBiasMap;

//     double rbias[2][3]		= {}; /* receiver dcb (0:p1-p2, 1:p1-c1, 2:p2-c2) (m) */

#define NMAX        10              /* order of polynomial interpolation */
#define MAXDTE      900.0           /* max time difference to ephem time (s) */
#define EXTERR_CLK  1E-3            /* extrapolation error for clock (m/s) */
#define EXTERR_EPH  5E-7            /* extrapolation error for ephem (m/s^2) */

/* read satellite antenna parameters -------------------------------------------
* read satellite antenna parameters
* args   : char   *file       I   antenna parameter file
*          gtime_t time       I   time
*          nav_t  *nav        IO  navigation data
* return : status (1:ok,0:error)
* notes  : only support antex format for the antenna parameter file
*-----------------------------------------------------------------------------*/
/*extern int readsap(char *file, gtime_t time, nav_t *nav)
{
	pcvs_t pcvs={0};
	pcv_t pcv0={0},*pcv;
	int i;

	trace(3,"readsap : file=%s time=%s\n",file,time.to_string(0).c_str());

	if (!readpcv(file,&pcvs)) return 0;

	for (i=0;i<MAXSAT;i++) {
		pcv=searchpcv(i+1,"",time,&pcvs);
		nav->pcvs[i]=pcv?*pcv:pcv0;
	}
	free(pcvs.pcv);
	return 1;
}*/
/* read dcb parameters file --------------------------------------------------*/
int readdcb(string file, nav_t *nav)
{
	//todo aaron, use maps for these rather than arrays,
	FILE *fp;
	double cbias;
	char buff[256],str1[32],str2[32]="";
	int type=0;
	SatSys Sat;
//     trace(3,"readdcbf: file=%s\n",file);

	if (!(fp=fopen(file.c_str(), "r")))
	{
//         trace(2,"dcb parameters file open error: %s\n",file);
		return 0;
	}
	while (fgets(buff,sizeof(buff),fp))
	{
		if      (strstr(buff,"DIFFERENTIAL (P1-P2) CODE BIASES"))	type=1;
		else if (strstr(buff,"DIFFERENTIAL (P1-C1) CODE BIASES"))	type=2;
		else if (strstr(buff,"DIFFERENTIAL (P2-C2) CODE BIASES"))	type=3;

		if	(!type
			||sscanf(buff,"%s %s",str1,str2) < 1)
			continue;

		if ((cbias = str2num(buff,26,9)) == 0)
			continue;

		if  ( !strcmp(str1,"G")
			||!strcmp(str1,"R"))
		{
			E_Sys sys;
			if (str1[0] == 'G')		sys = E_Sys::GPS;
			if (str1[0] == 'R')		sys = E_Sys::GLO;

			auto& rbias = stationRBiasMap[str2][sys];

			/* receiver dcb */
			rbias[type-1] = cbias * 1E-9 * CLIGHT; /* ns -> m */ //todo aaron, this looks like it had issues to begin with

		}
		else if (Sat = SatSys(str1), Sat)
		{
			/* satellite dcb */
			if (type == 1)		nav->satNavMap[Sat].cBias_P1_P2		= cbias * 1E-9 * CLIGHT; /* ns -> m */
			if (type == 2)		nav->satNavMap[Sat].cBiasMap[F1]	= cbias * 1E-9 * CLIGHT; /* ns -> m */
			if (type == 3)		nav->satNavMap[Sat].cBiasMap[F2]	= cbias * 1E-9 * CLIGHT; /* ns -> m */
		}
	}
	fclose(fp);

	return 1;
}

/* add satellite fcb ---------------------------------------------------------*/
// int addfcb(nav_t *nav, gtime_t ts, gtime_t te, int sat,
// 				const double *bias, const double *std)	//todo aaron, is this broken?
// {
//     fcbd_t *nav_fcb;
//     int i,j;
//
//     if (nav->nf>0&&fabs(timediff(ts,nav->fcb[nav->nf-1].ts))<=1e-3) {
//         for (i=0;i<3;i++) {
//             nav->fcb[nav->nf-1].bias[sat-1][i]=bias[i];
//             nav->fcb[nav->nf-1].std [sat-1][i]=std [i];
//         }
//         return 1;
//     }
//     if (nav->nf>=nav->nfmax) {
//         nav->nfmax=nav->nfmax<=0?2048:nav->nfmax*2;
//         if (!(nav_fcb=(fcbd_t *)realloc(nav->fcb,sizeof(fcbd_t)*nav->nfmax))) {
//             free(nav->fcb); nav->nf=nav->nfmax=0;
//             return 0;
//         }
//         nav->fcb=nav_fcb;
//     }
//     for (i=0;i<MAXSAT;i++) for (j=0;j<3;j++) {
//         nav->fcb[nav->nf].bias[i][j]=nav->fcb[nav->nf].std[i][j]=0.0;
//     }
//     for (i=0;i<3;i++) {
//         nav->fcb[nav->nf].bias[sat-1][i]=bias[i];
//         nav->fcb[nav->nf].std [sat-1][i]=std [i];
//     }
//     nav->fcb[nav->nf  ].ts=ts;
//     nav->fcb[nav->nf++].te=te;
// 	return 1;
// }
/* read satellite fcb file ---------------------------------------------------*/
// int readfcbf(const char *file, nav_t *nav)
// {
// 	FILE *fp;
// 	gtime_t ts,te;
// 	double ep1[6],ep2[6],bias[3]={0},std[3]={0};
// 	char buff[1024],str[32],*p;
// 	int sat;
//
// //     trace(3,"readfcbf: file=%s\n",file);
//
// 	if (!(fp=fopen(file,"r"))) {
// //         trace(2,"fcb parameters file open error: %s\n",file);
// 		return 0;
// 	}
// 	while (fgets(buff,sizeof(buff),fp)) {
// 		if ((p=strchr(buff,'#'))) *p='\0';
// 		if (sscanf(buff,"%lf/%lf/%lf %lf:%lf:%lf %lf/%lf/%lf %lf:%lf:%lf %s"
// 				"%lf %lf %lf %lf %lf %lf",ep1,ep1+1,ep1+2,ep1+3,ep1+4,ep1+5,
// 				ep2,ep2+1,ep2+2,ep2+3,ep2+4,ep2+5,str,bias,std,bias+1,std+1,
// 				bias+2,std+2)<17) continue;
// 		if (!(sat=SatSys(str))) continue;
// 		ts=epoch2time(ep1);
// 		te=epoch2time(ep2);
// 		if (!addfcb(nav,ts,te,sat,bias,std)) return 0;
// 	}
// 	fclose(fp);
// 	return 1;
// }
/* compare satellite fcb -----------------------------------------------------*/
// bool cmpfcb(fcbd_t&p1, fcbd_t&p2)
// {
// 	fcbd_t *q1=&p1,*q2=&p2;
// 	double tt=timediff(q1->ts,q2->ts);
// 	return tt<-1E-3?-1:(tt>1E-3?1:0);
// }
/* read satellite fcb data -----------------------------------------------------
* read satellite fractional cycle bias (dcb) parameters
* args   : char   *file       I   fcb parameters file (wild-card * expanded)
*          nav_t  *nav        IO  navigation data
* return : status (1:ok,0:error)
* notes  : fcb data appended to navigation data
*-----------------------------------------------------------------------------*/
// int readfcb(string& file, nav_t *nav)
// {
// 	readfcbf(file.c_str(), nav);
// // 	nav->fcbList.sort(cmpfcb);
// 	return 1;
// }

/* polynomial interpolation by Neville's algorithm ---------------------------*/
double interppol(const double *x, double *y, int n)
{
	for (int j=1; j < n;		j++)
	for (int i=0; i < n - j;	i++)
	{
		y[i] = (x[i+j] * y[i] - x[i] * y[i+1]) / (x[i+j] - x[i]);
	}

	return y[0];
}

/* satellite position by precise ephemeris -----------------------------------*/
int pephpos(
	GTime time,
	SatSys Sat,
	nav_t& nav,
	double *rs,
	double *dts,
	double *vare,
	double *varc)
{
	double t[NMAX+1],p[3][NMAX+1],c[2],s[3];

	char id[4];
	Sat.getId(id);
//     trace(4,"pephpos : time=%s sat=%s\n",time.to_string(3).c_str(),id);

	rs[0]	= 0;
	rs[1]	= 0;
	rs[2]	= 0;
	*dts	= 0;

	PephList pephList = nav.pephMap[Sat];

	if	( (pephList.size()							< NMAX + 1)
		||(timediff(time, pephList.begin()->first)	< -MAXDTE)
		||(timediff(time, pephList.rbegin()->first)	> +MAXDTE))
	{
//         trace(3,"no prec ephem %s sat=%s\n",time.to_string(0).c_str(),id);
		return 0;
	}

// 	//search for the ephemeris in the list

	auto peph_it = pephList.lower_bound(time);
	if (peph_it == pephList.end())
	{
		peph_it--;
	}

	auto middle0 = peph_it;

	//go forward a few steps to make sure we're far from the end of the list.
	for (int i = 0; i < NMAX/2; i++)
	{
		peph_it++;
		if (peph_it == pephList.end())
		{
			break;
		}
	}

	//go backward a few steps to make sure we're far from the beginning of the list
	for (int i = 0; i <= NMAX; i++)	//todo aaron, needs +1 to go back an extra step due to end()?
	{
		peph_it--;
		if (peph_it == pephList.begin())
		{
			break;
		}
	}

	auto begin = peph_it;

	//get interpolation parameters and check all ephemerides have values.
	peph_it = begin;
	for (int i = 0; i <= NMAX; i++, peph_it++)
	{
		Peph& peph = peph_it->second;
		if (peph.Pos.norm() <= 0)
		{
//             trace(3,"prec ephem outage %s sat=%s\n",time.to_string(0).c_str(), id);
			return 0;
		}

		t[i] = timediff(peph.time, time);
		auto& pos = peph.Pos;
#if 0
		p[0][i]=pos[0];
		p[1][i]=pos[1];
#else
		/* correciton for earh rotation ver.2.4.0 */
		double sinl = sin(OMGE * t[i]);
		double cosl = cos(OMGE * t[i]);
		p[0][i] = cosl * pos[0] - sinl * pos[1];
		p[1][i] = sinl * pos[0] + cosl * pos[1];
#endif
		p[2][i] = pos[2];
	}

	for (int i = 0; i < 3; i++)
	{
		rs[i] = interppol(t, p[i], NMAX + 1);
	}
	double std = 0;
	if (vare)
	{
		for (int i = 0; i < 3; i++)
			s[i] = middle0->second.PosStd[i];
		std = norm(s, 3);

		/* extrapolation error for orbit */
		if      (t[0   ] > 0) std += EXTERR_EPH * SQR(t[0   ]) / 2;		//todo aaron, needs straigtening as below?
		else if (t[NMAX] < 0) std += EXTERR_EPH * SQR(t[NMAX]) / 2;

		*vare = SQR(std);
	}

	/* linear interpolation for clock */
	auto middle1 = middle0;
	if (middle0 != pephList.begin())
	{
		middle0--;
	}
	t[0] = timediff(time, middle0->second.time);
	t[1] = timediff(time, middle1->second.time);
	c[0] = middle0->second.Clk;
	c[1] = middle1->second.Clk;

	if 		(t[0] <= 0)
	{
		*dts = c[0];

		if (*dts != 0)
			std = middle0->second.ClkStd * CLIGHT	+ EXTERR_CLK * fabs(t[0]);
	}
	else if (t[1] >= 0)
	{
		*dts = c[1];

		if (*dts != 0)
			std = middle1->second.ClkStd * CLIGHT	+ EXTERR_CLK * fabs(t[1]);
	}
	else if ( c[0] != 0
			&&c[1] != 0)
	{
		*dts = (c[1] * t[0] - c[0] * t[1]) / (t[0] - t[1]);

		double inv0 = 1 / middle0->second.ClkStd * CLIGHT + EXTERR_CLK * fabs(t[0]);
		double inv1 = 1 / middle1->second.ClkStd * CLIGHT + EXTERR_CLK * fabs(t[1]);
		std			= 1 / (inv0 + inv1);
	}
	else
	{
		*dts = 0;
	}
	if (varc)
		*varc=SQR(std);
	return 1;
}

/* satellite clock by precise clock ------------------------------------------*/
int pephclk(GTime time, string id, nav_t& nav, double *dtSat, double *varc)
{
//     BOOST_LOG_TRIVIAL(debug)
// 	<< "pephclk : time=" << time.to_string(3)
// 	<< " id=" << id;

	PclkList pclkList = nav.pclkMap[id];

	if	( (pclkList.size()							< 2)
		||(timediff(time, pclkList.front().	time)	< -MAXDTE)
		||(timediff(time, pclkList.back().	time)	> +MAXDTE))
	{
		BOOST_LOG_TRIVIAL(debug)
		<< "no prec clock " << time.to_string(0)
		<< " id=" << id;

		return -1;	//non zero for pass, negative for no result
	}

	//search for the ephemeris in the list
	auto pclk_it = pclkList.begin();
	while (pclk_it->time < time)
	{
		pclk_it++;
		if (pclk_it == pclkList.end())
		{
			pclk_it--;
			break;
		}
	}
	auto middle1 = pclk_it;
	auto middle0 = middle1;
	if (middle0 != pclkList.begin())
	{
		middle0--;
	}

	/* linear interpolation for clock */
	double t[2];
	double c[2];
	t[0] = timediff(time, middle0->time);
	t[1] = timediff(time, middle1->time);
	c[0] = middle0->clk;
	c[1] = middle1->clk;

	double std = 0;

	if		(t[0] <= 0)
	{
		*dtSat = c[0];

		if (*dtSat == 0)
			return 0;

		std	= middle0->std * CLIGHT	+ EXTERR_CLK * fabs(t[0]);
	}
	else if (t[1] >= 0)
	{
		*dtSat = c[1];

		if (*dtSat == 0)
			return 0;

		std	= middle1->std * CLIGHT	+ EXTERR_CLK * fabs(t[1]);
	}
	else if	( c[0] != 0
			&&c[1] != 0)
	{
		*dtSat = (c[1] * t[0] - c[0] * t[1]) / (t[0] - t[1]);

		double inv0 = 1 / middle0->std * CLIGHT + EXTERR_CLK * fabs(t[0]);
		double inv1 = 1 / middle1->std * CLIGHT + EXTERR_CLK * fabs(t[1]);
		std			= 1 / (inv0 + inv1);
	}
	else
	{
		BOOST_LOG_TRIVIAL(debug)
		<< "prec clock outage " << time.to_string(0)
		<< " sat=" << id;

		return 0;
	}

	if (varc)
		*varc = SQR(std);

	return 1;
}
/* satellite antenna phase center offset ---------------------------------------
* compute satellite antenna phase center offset in ecef
* args   : gtime_t time       I   time (gpst)
*          double *rs         I   satellite position and velocity (ecef)
*                                 {x,y,z,vx,vy,vz} (m|m/s)
*          int    sat         I   satellite number
*          nav_t  *nav        I   navigation data
*          double *dant       I   satellite antenna phase center offset (ecef)
*                                 {dx,dy,dz} (m) (iono-free LC value)
* return : none
*-----------------------------------------------------------------------------*/
void satantoff(
	Trace&		trace,
	GTime		time,
	Vector3d&	rs,
	SatSys& 	Sat,
	SatNav*		satNav_ptr,
	Vector3d&	dant,
	PcoMapType*	pcoMap_ptr)
{
	auto&	lam = satNav_ptr->lamMap;
	double gmst,erpv[5]={0};
	E_FType j = F1;
	E_FType k = F2;
	tracepde(4,trace, "satantoff: time=%s sat=%2d\n",time.to_string(3).c_str() ,Sat);

	/* sun position in ecef */
	Vector3d rsun;
	sunmoonpos(gpst2utc(time),erpv,rsun.data(),NULL,&gmst);

	/* unit vectors of satellite fixed coordinates */
	Vector3d r = -rs;
	Vector3d ez = r.normalized();
	r = rsun - rs;
	Vector3d es = r.normalized();
	r = ez.cross(es);
	Vector3d ey = r.normalized();
	Vector3d ex = ey.cross(ez);

	int sys = Sat.sys;
	if 	( sys == E_Sys::GAL
		||sys == E_Sys::SBS)
	{
		k = F5;
	}

	if 	( lam[j] == 0
		||lam[k] == 0)
	{
		return;
	}

	double gamma	= SQR(lam[k]) / SQR(lam[j]);		//todo aaron, can use obs2lc?
	double C1		= gamma	/ (gamma - 1);
	double C2		= -1	/ (gamma - 1);

	if (pcoMap_ptr == nullptr)
	{
		return;
	}
	auto& pcoMap = *pcoMap_ptr;

	/* iono-free LC */
	for (int i = 0; i < 3; i++)
	{
		/* ENU to NEU */
		Vector3d pcoJ;
		Vector3d pcoK;
		if (pcoMap.find(j) == pcoMap.end())		pcoJ = Vector3d::Zero();
		else									pcoJ = pcoMap[j];
		if (pcoMap.find(k) == pcoMap.end())		pcoK = Vector3d::Zero();
		else									pcoK = pcoMap[k];
		double dant1	= pcoJ[1] * ex(i)
						+ pcoJ[0] * ey(i)
						+ pcoJ[2] * ez(i);	//todo aaron, matrix
		double dant2	= pcoK[1] * ex(i)
						+ pcoK[0] * ey(i)
						+ pcoK[2] * ez(i);

		dant(i)	= C1 * dant1
				+ C2 * dant2;
	}
}

/* satellite position/clock by precise ephemeris/clock -------------------------
* compute satellite position/clock with precise ephemeris/clock
* args   : gtime_t time       I   time (gpst)
*          int    sat         I   satellite number
*          nav_t  *nav        I   navigation data
*          int    opt         I   sat postion option
*                                 (0: center of mass, 1: antenna phase center)
*          double *rs         O   sat position and velocity (ecef)
*                                 {x,y,z,vx,vy,vz} (m|m/s)
*          double *dts        O   sat clock {bias,drift} (s|s/s)
*          double *var        IO  sat position and clock error variance (m)
*                                 (NULL: no output)
* return : status (1:ok,0:error or data outage)
* notes  : clock includes relativistic correction but does not contain code bias
*          before calling the function, nav->peph, nav->ne, nav->pclk and
*          nav->nc must be set by calling readsp3(), readrnx() or readrnxt()
*          if precise clocks are not set, clocks in sp3 are used instead
*-----------------------------------------------------------------------------*/
int peph2pos(
	Trace&		trace,
	GTime		time,
	SatSys&		Sat,
	nav_t& 		nav,
	int			opt,
	Obs&		obs,
	PcoMapType*	pcoMap_ptr)
{
	Vector3d dAnt	= Vector3d::Zero();
	Vector3d rst	= Vector3d::Zero();

	double dtss1 = 0,dtss2 = 0,dtst,vare=0,varc=0,tt=1E-3;

	tracepde(4,trace, "peph2pos: time=%s sat=%2d opt=%d\n", time.to_string(3).c_str(), Sat, opt);

	/* satellite position and clock bias */
	if 	( !pephpos(time, Sat, nav, obs.rSat.data(),	&dtss1,	&vare,	&varc)
		||!pephclk(time, Sat, nav, 					&dtss2,			&varc))
	{
		return 0;
	}

	if (dtss2 != 0)
	{
		double delta = dtss1 - dtss2;
		dtss1 = dtss2;
	}

	time = timeadd(time,tt);

	if 	( !pephpos(time, Sat, nav, rst.data(),		&dtst,	NULL,	NULL)
		||!pephclk(time, Sat, nav, 					&dtst,			NULL))
		return 0;

	/* satellite antenna offset correction */
	if (opt)
	{
		satantoff(trace, time, obs.rSat, Sat, &nav.satNavMap[Sat], dAnt, pcoMap_ptr);
	}

	obs.satVel = (rst - obs.rSat) / tt;
	obs.rSat += dAnt;

	/* relativistic effect correction */
	if (dtss1 != 0)
	{
		double deltaDt = dtst-dtss1;
		double relativisticAdj = 2 * obs.rSat.dot(obs.satVel) / CLIGHT / CLIGHT;
		obs.dtSat[0] = dtss1 - relativisticAdj;
		obs.dtSat[1] = deltaDt / tt;
	}
	else
	{
		/* no precise clock */
		obs.dtSat[0] = 0;
		obs.dtSat[1] = 0;
	}

	obs.var = vare + varc;

// 	obs.svh = 1;
	return 1;
}


#include <SparseMFModel.h>
#include <Utils.h>
#include <MlUtils.h>
#include <Eigen/Dense>
#include <Checksum.h>
#include <algorithm>
#include <ModelGradient.h>

// #define DEBUG

namespace cirrus {

// FORMAT
// Number of users (32bits)
// Number of factors (32bits)
// Sample1: factor 1 (FEATURE_TYPE) | factor 2 | factor 3
// Sample2 : ...
// ....

void SparseMFModel::initialize_weights(uint64_t users, uint64_t items, uint64_t nfactors) {
  item_fact_reg_ = 0.05;
  user_fact_reg_ = 0.05;

  user_bias_reg_ = 0.05;
  item_bias_reg_ = 0.05;
  global_bias_ = 3.604;

  nusers_ = users;
  nitems_ = items;
  nfactors_ = nfactors;

}

SparseMFModel::SparseMFModel(uint64_t users, uint64_t items, uint64_t nfactors) {
    initialize_weights(users , items, nfactors);
}

SparseMFModel::SparseMFModel(const void* data, uint64_t minibatch_size, uint64_t num_items) {
  initialize_weights(0, 0, 0);
  loadSerialized(data, minibatch_size, num_items);
}

std::unique_ptr<CirrusModel> SparseMFModel::deserialize(void* data, uint64_t /*size*/) const {
  throw std::runtime_error("Not implemented");
  uint32_t* data_p = reinterpret_cast<uint32_t*>(data);
  return std::make_unique<SparseMFModel>(
      reinterpret_cast<void*>(data_p), 10, 10);
}

std::pair<std::unique_ptr<char[]>, uint64_t>
SparseMFModel::serialize() const {
  throw std::runtime_error("not implemented");
    std::pair<std::unique_ptr<char[]>, uint64_t> res;
    uint64_t size = getSerializedSize();

    res.first.reset(new char[size]);
    res.second = size;

    serializeTo(res.first.get());
    return res;
}

void SparseMFModel::serializeTo(void* /*mem*/) const {
  throw std::runtime_error("Not implemented");
}

/**
  * We probably want to put 0 in the values we don't know
  */
void SparseMFModel::randomize() {
  std::default_random_engine generator;
  std::normal_distribution<FEATURE_TYPE> distribution(0, 1.0 / nfactors_); // mean 0 and stddev=1
  for (uint64_t i = 0; i < nusers_; ++i) {
    for (uint64_t j = 0; j < nfactors_; ++j) {
      get_user_weights(i, j) = distribution(generator);
    }
  }
  for (uint64_t i = 0; i < nitems_; ++i) {
    for (uint64_t j = 0; j < nfactors_; ++j) {
      get_item_weights(i, j) = distribution(generator);
    }
  }
}

std::unique_ptr<CirrusModel> SparseMFModel::copy() const {
    std::unique_ptr<SparseMFModel> new_model =
        std::make_unique<SparseMFModel>(nusers_, nitems_, nfactors_);
    return new_model;
}

uint64_t SparseMFModel::getSerializedSize() const {
  throw std::runtime_error("Not implemented");
  return 0;
}

void SparseMFModel::loadSerialized(const void* data, uint64_t minibatch_size, uint64_t num_item_ids) {
#ifdef DEBUG
  std::cout << "SparseMFModel::loadSerialized nusers: "
    << nusers_
    << " nitems_: " << nitems_
    << " nfactors_: " << nfactors_
    << std::endl;
#endif
  // data has minibatch_size vectors of size NUM_FACTORS (user weights)
  // followed by the same (item weights)
  nfactors_ = NUM_FACTORS;
  for (uint64_t i = 0; i < minibatch_size; ++i) {
    std::tuple<int, FEATURE_TYPE,
      std::vector<FEATURE_TYPE>> user_model;
    uint32_t user_id = load_value<uint32_t>(data);
    FEATURE_TYPE user_bias = load_value<FEATURE_TYPE>(data);
    std::get<0>(user_model) = user_id;
    std::get<1>(user_model) = user_bias;
    for (uint64_t j = 0; j < NUM_FACTORS; ++j) {
      FEATURE_TYPE user_weight = load_value<FEATURE_TYPE>(data);
      std::get<2>(user_model).push_back(user_weight);
    }
    user_models.push_back(user_model);
  }
  global_bias_ = 3.604;
  // now we read the item vectors
  for (uint64_t i = 0; i < num_item_ids; ++i) {
    std::pair<FEATURE_TYPE,
      std::vector<FEATURE_TYPE>> item_model;
    uint32_t item_id = load_value<uint32_t>(data);
    FEATURE_TYPE item_bias = load_value<FEATURE_TYPE>(data);
    std::get<0>(item_model) = item_bias;
    std::get<1>(item_model).resize(NUM_FACTORS);
    for (uint64_t j = 0; j < NUM_FACTORS; ++j) {
      FEATURE_TYPE item_weight = load_value<FEATURE_TYPE>(data);
      std::get<1>(item_model)[j] = item_weight;
    }
    item_models[item_id] = item_model;
    //std::cout << "item_id: " << item_id << " model size: " << item_model.second.size() << std::endl;
  }

#ifdef DEBUG
  check();
#endif
}

/**
  * userId : 0 to minibatch_size
  */
FEATURE_TYPE SparseMFModel::predict(uint32_t userId, uint32_t itemId) {
  FEATURE_TYPE user_bias = std::get<1>(user_models[userId]);
  FEATURE_TYPE item_bias = item_models[itemId].first;

  FEATURE_TYPE res = global_bias_ + user_bias + item_bias;

  for (uint32_t i = 0; i < nfactors_; ++i) {
    res += get_user_weights(userId, i) * get_item_weights(itemId, i);
#ifdef DEBUG
    if (std::isnan(res) || std::isinf(res)) {
      std::cout << "userId: " << userId << " itemId: " << itemId
        << " get_user_weights(userId, i): " << get_user_weights(userId, i)
        << " get_item_weights(itemId, i): " << get_item_weights(itemId, i)
        << std::endl;
      throw std::runtime_error("nan error in predict");
    }
#endif
  }
  return res;
}

std::unique_ptr<ModelGradient> SparseMFModel::minibatch_grad(
            const SparseDataset& dataset,
            const Configuration& config,
            uint64_t base_user) {
  FEATURE_TYPE learning_rate = config.get_learning_rate();
  auto gradient = std::make_unique<MFSparseGradient>();
  std::vector<FEATURE_TYPE> item_weights_grad_map[17770];
  std::vector<int> item_weights_lst;
  double training_rmse = 0;
  uint64_t training_rmse_count = 0;

  // iterate all pairs user rating
  for (uint64_t user_from_0 = 0; user_from_0 < dataset.data_.size(); ++user_from_0) {
    std::vector<FEATURE_TYPE> user_weights_grad(NUM_FACTORS);
    uint64_t real_user_id = base_user + user_from_0;

    // we have to populate this value in case this user doesn't have any ratings
    // XXX we should probably optimize this
    gradient->users_bias_grad[real_user_id] = 0;
    for (uint64_t j = 0; j < dataset.data_[user_from_0].size(); ++j) {
      // first user matches the model in user_models[0]
      uint64_t itemId = dataset.data_[user_from_0][j].first;
      FEATURE_TYPE rating = dataset.data_[user_from_0][j].second;

      FEATURE_TYPE pred = predict(user_from_0, itemId);

      FEATURE_TYPE error = rating - pred;
      training_rmse += error * error;
      training_rmse_count++;

      // compute gradient for user bias
      FEATURE_TYPE& user_bias = std::get<1>(user_models[user_from_0]);
      float delta = learning_rate * (error - user_bias_reg_ * user_bias);
      gradient->users_bias_grad[real_user_id] += delta;
      user_bias += delta;

      // compute gradient for item bias
      FEATURE_TYPE& item_bias = item_models[itemId].first;
      delta = learning_rate * (error - item_bias_reg_ * item_bias);
      gradient->items_bias_grad[itemId] += delta;
      item_bias += delta;

#ifdef DEBUG
      if (std::isnan(user_bias) || std::isnan(item_bias) ||
          std::isinf(user_bias) || std::isinf(item_bias))
        throw std::runtime_error("nan in user_bias or item_bias");
#endif

      // update user latent factors
      for (uint64_t k = 0; k < nfactors_; ++k) {
        FEATURE_TYPE delta_user_w =
          learning_rate *
          (error * get_item_weights(itemId, k)
                   - user_fact_reg_ * get_user_weights(user_from_0, k));
        user_weights_grad[k] += delta_user_w;
        std::get<2>(user_models[user_from_0])[k] += delta_user_w;
#ifdef DEBUG
        if (std::isnan(get_user_weights(user_from_0, k)) ||
            std::isinf(get_user_weights(user_from_0, k))) {
          throw std::runtime_error("nan in user weight");
        }
#endif
      }
      //XXX moving this after this inner loop
      //gradient->users_weights_grad.push_back(std::make_pair(real_user_id, std::move(user_weights_grad)));

      // update item latent factors
      for (uint64_t k = 0; k < nfactors_; ++k) {
        //std::cout << "k: " << k << std::endl;
        FEATURE_TYPE delta_item_w =
          learning_rate *
          (error * get_user_weights(user_from_0, k) -
                 item_fact_reg_ * get_item_weights(itemId, k));
        item_models[itemId].second[k] += delta_item_w;

        if (item_weights_grad_map[itemId].size() == 0) {
          item_weights_grad_map[itemId].resize(NUM_FACTORS);
          item_weights_lst.push_back(itemId);
        }
        //std::cout << "UPDATE HERE " << std::endl;
        item_weights_grad_map[itemId][k] += delta_item_w;
#ifdef DEBUG
        if (std::isnan(get_item_weights(itemId, k)) ||
            std::isinf(get_item_weights(itemId, k))) {
          std::cout << "error: " << error << std::endl;
          std::cout << "rating: " << rating << std::endl;
          std::cout << "pred: " << pred << std::endl;
          std::cout << "delta_item_w: " << delta_item_w << std::endl;
          std::cout << "user weight: " << get_user_weights(user_from_0, k) << std::endl;
          std::cout << "item weight: " << get_item_weights(itemId, k) << std::endl;
          std::cout << "learning_rate: " << learning_rate << std::endl;
          throw std::runtime_error("nan in item weight");
        }
#endif
      }
      //gradient->items_weights_grad.push_back(
      //    std::make_pair(itemId, std::move(item_weights_grad)));
    }
    gradient->users_weights_grad.push_back(
        std::make_pair(real_user_id, std::move(user_weights_grad)));
    //std::cout
    //  << "user weights size: " << gradient->users_weights_grad.size()
    //  << " user bias size: " << gradient->users_bias_grad.size() << std::endl;
  }

  for (const auto& item_id : item_weights_lst) {
    auto& item_weights = item_weights_grad_map[item_id];
    gradient->items_weights_grad.push_back(
        std::make_pair(item_id, std::move(item_weights)));
  }

#ifdef DEBUG
  std::cout << "Training rmse: " << std::sqrt(training_rmse / training_rmse_count) << std::endl;
  gradient->print();
  gradient->check();
#endif

  return gradient;
}

FEATURE_TYPE& SparseMFModel::get_user_weights(uint64_t userId, uint64_t factor) {
  return std::get<2>(user_models[userId])[factor];
}

FEATURE_TYPE& SparseMFModel::get_item_weights(uint64_t itemId, uint64_t factor) {
  return item_models[itemId].second[factor];
}

uint64_t SparseMFModel::getSerializedGradientSize() const {
  throw std::runtime_error("Not implemented");
  return 0;
  //  return size() * sizeof(FEATURE_TYPE);
}

std::unique_ptr<ModelGradient> SparseMFModel::loadGradient(void* mem) const {
    auto grad = std::make_unique<MFGradient>(10, 10);
    grad->loadSerialized(mem);
    return grad;
}

double SparseMFModel::checksum() const {
  return 0;
    //return crc32(user_weights_, nusers_ * nfactors_ * sizeof(FEATURE_TYPE));
}

void SparseMFModel::print() const {
    std::cout << "MODEL user weights: Not implemented";
    std::cout << std::endl;
}

void SparseMFModel::check() const {
  std::cout << "SparseMFModel::check() Not Implemented" << std::endl;
}

void SparseMFModel::serializeFromDense(
    MFModel& mf_model,
    uint32_t base_user_id, uint32_t minibatch_size, uint32_t k_items,
    const char* item_data_ptr, char* holder) const {

  uint32_t to_send_size =
    minibatch_size * (sizeof(uint32_t) + (NUM_FACTORS + 1) * sizeof(FEATURE_TYPE)) +
    k_items * (sizeof(uint32_t) + (NUM_FACTORS + 1) * sizeof(FEATURE_TYPE));

  //std::vector<char> buffer(to_send_size);
  char* data_to_send_ptr = holder;

  // first we store data about users
  for (uint32_t i = base_user_id; i < base_user_id + minibatch_size; ++i) {
    store_value<uint32_t>(data_to_send_ptr, i); // user id
    store_value<FEATURE_TYPE>(
        data_to_send_ptr,
        mf_model.get_user_bias(i));  // bias
    for (uint32_t j = 0; j < NUM_FACTORS; ++j) {
      store_value<FEATURE_TYPE>(
          data_to_send_ptr,
          mf_model.get_user_weights(i, j));
    }
  }

  // now we store data about items
  for (uint32_t i = 0; i < k_items; ++i) {
    uint32_t item_id = load_value<uint32_t>(item_data_ptr);
    //std::cout << "item_id: " << item_id << std::endl;
    store_value<uint32_t>(data_to_send_ptr, item_id);
    store_value<FEATURE_TYPE>(data_to_send_ptr, mf_model.get_item_bias(item_id));
    for (uint32_t j = 0; j < NUM_FACTORS; ++j) {
      store_value<FEATURE_TYPE>(data_to_send_ptr, mf_model.get_item_weights(item_id, j));
    }
  }

}

} // namespace cirrus


/*
@copyright Louis Dionne 2015
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#include <boost/hana/assert.hpp>
#include <boost/hana/config.hpp>

//! [main]
#include <boost/hana/set.hpp>
using namespace boost::hana;

int main() {
    BOOST_HANA_CONSTEXPR_LAMBDA auto xs = set(1, 2.2, 'x');
    BOOST_HANA_CONSTEXPR_CHECK(elem(xs, 'x'));
}
//! [main]


/**
 * @brief Diffuse Reflection Model Test
 */

// ********************************************************************************
// Including files
// ********************************************************************************

#include "SceneDiffuse.h"

#include <boost/assert.hpp>
#include <iostream>

#include <glm/gtc/matrix_transform.hpp>

// ********************************************************************************
// Override functions
// ********************************************************************************

void SceneDiffuse::OnInit() {
  if (const auto msg = CompileAndLinkShader()) {
    std::cerr << msg.value() << std::endl;
    BOOST_ASSERT_MSG(false, "failed to compile or link!");
  }

  glEnable(GL_DEPTH_TEST);

  model_ = glm::mat4(1.0f);
  model_ =
      glm::rotate(model_, glm::radians(-35.0f), glm::vec3(1.0f, 0.0f, 0.0f));
  model_ =
      glm::rotate(model_, glm::radians(35.0f), glm::vec3(0.0f, 1.0f, 0.0f));
  view_ = glm::lookAt(glm::vec3(0.0f, 0.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f),
                      glm::vec3(0.0f, 1.0f, 0.0f));
  proj_ = glm::mat4(1.0f);

  prog_.SetUniform("Kd", 0.9f, 0.5f, 0.3f);
  prog_.SetUniform("Ld", 1.0f, 1.0f, 1.0f);
  prog_.SetUniform("LightPosition", view_ * glm::vec4(5.0f, 5.0f, 2.0f, 1.0f));
}

void SceneDiffuse::OnUpdate(float) {}

void SceneDiffuse::OnRender() {
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  const glm::mat4 mv = view_ * model_;
  prog_.SetUniform("ModelViewMatrix", mv);
  prog_.SetUniform("NormalMatrix", glm::mat3(glm::vec3(mv[0]), glm::vec3(mv[1]),
                                             glm::vec3(mv[2])));
  prog_.SetUniform("MVP", proj_ * mv);
  torus_.Render();
}

void SceneDiffuse::OnResize(int w, int h) {
  SetDimensions(w, h);
  glViewport(0, 0, w, h);
  proj_ = glm::perspective(glm::radians(70.0f),
                           static_cast<float>(w) / static_cast<float>(h), 0.3f,
                           100.0f);
}

// ********************************************************************************
// Member functions
// ********************************************************************************

std::optional<std::string> SceneDiffuse::CompileAndLinkShader() {
  if (prog_.Compile("./Assets/Shaders/Diffuse/Diffuse.vs.glsl",
                    ShaderType::Vertex) &&
      prog_.Compile("./Assets/Shaders/Diffuse/Diffuse.fs.glsl",
                    ShaderType::Fragment) &&
      prog_.Link()) {
    prog_.Use();
    return std::nullopt;
  } else {
    return prog_.GetLog();
  }
}


// Copyright Aleksey Gurtovoy 2004
// Copyright Eric Friedman 2002-2003
//
// 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)
//
// See http://www.boost.org/libs/mpl for documentation.

#include <boost/mpl/sort.hpp>

#include <boost/mpl/list_c.hpp>
#include <boost/mpl/equal.hpp>
#include <boost/mpl/equal_to.hpp>

#include <boost/mpl/aux_/test.hpp>

MPL_TEST_CASE()
{
    typedef list_c<int, 3, 4, 0, -5, 8, -1, 7>::type numbers;
    typedef list_c<int, -5, -1, 0, 3, 4, 7, 8>::type manual_result;

    typedef sort< numbers >::type result;

    MPL_ASSERT(( equal< result,manual_result,equal_to<_1,_2> > ));
}


// Copyright (c) 2019 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE Region0Test

#include <boost/test/unit_test.hpp>
#include <cstdint>
#include <vector>
#include <popart/names.hpp>
#include <popart/region.hpp>

using namespace popart;

BOOST_AUTO_TEST_CASE(Region_transpose) {
  view::Region r0({1, 2, 3}, {10, 20, 30});
  auto tr0 = r0.transpose({1, 0, 2});
  view::Region expected({2, 1, 3}, {20, 10, 30});
  BOOST_CHECK(tr0 == expected);
}

BOOST_AUTO_TEST_CASE(Region_reverse) {
  // Lower indices
  unsigned l0 = 1;
  unsigned l1 = 2;
  unsigned l2 = 3;
  // Upper indices
  unsigned u0 = 10;
  unsigned u1 = 20;
  unsigned u2 = 30;
  // Shapes
  unsigned s0 = 100;
  unsigned s1 = 200;
  unsigned s2 = 300;
  view::Region r0({l0, l1, l2}, {u0, u1, u2});
  auto rr0 = r0.reverse({s0, s1, s2}, {2, 1});
  view::Region expected({l0, s1 - u1, s2 - u2}, {u0, s1 - l1, s2 - l2});
  BOOST_CHECK(rr0 == expected);
}

BOOST_AUTO_TEST_CASE(Region_cut) {
  view::Region r0({{2, 1, 0}, {4, 3, 7}});
  auto cr0 = r0.cut({{3}, {2}}, false);
  view::Regions expected({{{3, 1, 0}, {4, 2, 7}},
                          {{3, 2, 0}, {4, 3, 7}},
                          {{2, 1, 0}, {3, 2, 7}},
                          {{2, 2, 0}, {3, 3, 7}}});
  BOOST_CHECK(cr0 == expected);
}

BOOST_AUTO_TEST_CASE(Region_get_full) {
  Shape shape{1, 2, 3};
  auto r0 = view::Region::getFull(shape);

  view::Region expected({{0, 0, 0}, {1, 2, 3}});
  BOOST_CHECK(r0 == expected);
}

BOOST_AUTO_TEST_CASE(Region_Scale0) {
  view::Region r0({0, 0}, {3, 3});
  view::Region r1({3, 3}, {6, 6});
  BOOST_CHECK(r0.intersect(r1).isEmpty());
  BOOST_CHECK(r1.intersect(r0).isEmpty());
}

BOOST_AUTO_TEST_CASE(Region_Sub0) {
  view::Region r0({0, 0}, {4, 4});
  view::Region r1({1, 1}, {3, 3});

  view::Regions subs = r0.sub(r1);

  BOOST_CHECK(subs.size() == 4);
  BOOST_CHECK(subs[0].intersect(r1).isEmpty());
  BOOST_CHECK(subs[1].intersect(r1).isEmpty());
  BOOST_CHECK(subs[2].intersect(r1).isEmpty());
  BOOST_CHECK(subs[3].intersect(r1).isEmpty());
}

BOOST_AUTO_TEST_CASE(Region_Sub1) {
  view::Region r0({0, 0}, {6, 6});
  view::Region r1({0, 0}, {4, 4});
  view::Region r2({3, 3}, {7, 7});
  view::Region r3({0, 4}, {3, 6});
  view::Region r4({4, 0}, {6, 3});

  view::Regions subs = r0.sub({r1, r2});

  BOOST_CHECK(subs.size() == 2);

  BOOST_CHECK(subs.front() == r3 || subs.back() == r3);
  BOOST_CHECK(subs.front() == r4 || subs.back() == r4);
  BOOST_CHECK(subs.front() != subs.back());
}

BOOST_AUTO_TEST_CASE(Region_Sub2) {
  view::Region r0({0, 0}, {6, 6});
  view::Region r1({0, 0}, {3, 6});

  view::Regions subs = r0.sub(r1);

  BOOST_CHECK(subs.size() == 1);

  view::Region expected({3, 0}, {6, 6});
  BOOST_CHECK(subs.front() == expected);
  BOOST_CHECK(subs.front() == subs.back());
}

BOOST_AUTO_TEST_CASE(Region_Merge0) {
  view::Region r0({3, 3}, {8, 8});
  view::Region r1({0, 3}, {3, 8});

  auto merge = r0.merge(r1);

  BOOST_CHECK(!merge.second.isEmpty());
  BOOST_CHECK(merge.first == 0);
  BOOST_CHECK(merge.second == view::Region({0, 3}, {8, 8}));
}

BOOST_AUTO_TEST_CASE(Region_Reshape0) {
  view::Region r0({0, 0}, {6, 6});
  view::Region r1({0, 0, 0, 0}, {2, 3, 2, 3});
  view::Region r2({2, 2}, {4, 5});

  view::Regions r = r2.reshape(r0, r1);

  BOOST_CHECK(std::find(r.begin(),
                        r.end(),
                        view::Region({0, 2, 0, 2}, {1, 3, 1, 3})) != r.end());
  BOOST_CHECK(std::find(r.begin(),
                        r.end(),
                        view::Region({0, 2, 1, 0}, {1, 3, 2, 2})) != r.end());
  BOOST_CHECK(std::find(r.begin(),
                        r.end(),
                        view::Region({1, 0, 0, 2}, {2, 1, 1, 3})) != r.end());
  BOOST_CHECK(std::find(r.begin(),
                        r.end(),
                        view::Region({1, 0, 1, 0}, {2, 1, 2, 2})) != r.end());
}

BOOST_AUTO_TEST_CASE(Region_Reshape1) {
  view::Region r0({0, 0}, {8, 8});
  view::Region r1({0, 0, 0}, {8, 2, 4});
  view::Region r2({2, 1}, {7, 6});

  view::Regions r = r2.reshape(r0, r1);

  BOOST_CHECK(std::find(r.begin(),
                        r.end(),
                        view::Region({2, 1, 0}, {7, 2, 2})) != r.end());
  BOOST_CHECK(std::find(r.begin(),
                        r.end(),
                        view::Region({2, 0, 1}, {7, 1, 4})) != r.end());
}

BOOST_AUTO_TEST_CASE(Region_Reshape2) {
  view::Region r0({0, 0}, {1024, 2});
  view::Region r1({0, 0, 0}, {1, 1024, 2});

  view::Regions r = r0.reshape(r0, r1);

  BOOST_CHECK(std::find(r.begin(), r.end(), r1) != r.end());
}

BOOST_AUTO_TEST_CASE(Region_Reshape3) {
  view::Region r0({0, 0}, {2, 80});
  view::Region r1({0, 0, 0, 0}, {2, 5, 4, 4});

  view::Regions r2 = r0.reshape(r0, r1);
  view::Regions r3 = r1.reshape(r1, r0);

  BOOST_CHECK(r2.size() == 1 && r2.front() == r1);
  BOOST_CHECK(r3.size() == 1 && r3.front() == r0);
}

BOOST_AUTO_TEST_CASE(Region_Reshape4) {
  view::Region r0({0}, {160});
  view::Region r1({0, 0, 0, 0}, {2, 5, 4, 4});

  view::Regions r2 = r0.reshape(r0, r1);
  view::Regions r3 = r1.reshape(r1, r0);

  BOOST_CHECK(r2.size() == 1 && r2.front() == r1);
  BOOST_CHECK(r3.size() == 1 && r3.front() == r0);
}

BOOST_AUTO_TEST_CASE(Region_Reshape5) {
  view::Region r0({0, 16}, {2, 64});
  view::Region r1({0, 0}, {2, 80});
  view::Region r2({0, 0, 0, 0}, {2, 5, 4, 4});
  view::Region r3({0, 1, 0, 0}, {2, 4, 4, 4});

  view::Regions r4 = r0.reshape(r1, r2);
  view::Regions r5 = r3.reshape(r2, r1);

  BOOST_CHECK(r4.size() == 1 && r4.front() == r3);
  BOOST_CHECK(r5.size() == 1 && r5.front() == r0);
}

BOOST_AUTO_TEST_CASE(Region_Reshape6) {
  view::Region r0({0, 2}, {2, 5});
  view::Region r1({0, 0}, {2, 8});
  view::Region r2({0}, {16});
  view::Region r3({2}, {5});
  view::Region r4({10}, {13});

  view::Regions r5 = r0.reshape(r1, r2);
  view::Regions r6 = view::mergeRegions(
      {r5.front().reshape(r2, r1).front(), r5.back().reshape(r2, r1).front()});

  BOOST_CHECK(r5.size() == 2 && r5.front() == r3 && r5.back() == r4);
  BOOST_CHECK(r6.size() == 1 && r6.front() == r0);
}

BOOST_AUTO_TEST_CASE(Region_Reshape7) {
  view::Region r0({0, 0, 0}, {2, 256, 106});
  view::Region r1({0, 0, 0}, {2, 512, 106});
  view::Region r2({0, 0, 0, 0}, {2, 512, 106, 1});
  view::Region r3({0, 0, 0, 0}, {2, 256, 106, 1});

  view::Regions r4 = r0.reshape(r1, r2);

  BOOST_CHECK(r4.front() == r3);
}

BOOST_AUTO_TEST_CASE(Region_MergeRegions0) {

  view::Region r0({0, 0}, {3, 4}, view::AccessType::Read);
  view::Region r1({1, 3}, {5, 5}, view::AccessType::Write);

  view::Regions rs = view::mergeRegions({r0, r1});

  BOOST_CHECK(rs.size() == 3);

  auto r2 = std::find(rs.begin(), rs.end(), view::Region({0, 0}, {3, 4}));
  BOOST_CHECK(r2 != rs.end());
  BOOST_CHECK(r2->getAccessType() == view::AccessType::ReadWrite);

  auto r3 = std::find(rs.begin(), rs.end(), view::Region({3, 3}, {5, 4}));
  BOOST_CHECK(r3 != rs.end());
  BOOST_CHECK(r3->getAccessType() == view::AccessType::ReadWrite);

  auto r4 = std::find(rs.begin(), rs.end(), view::Region({1, 4}, {5, 5}));
  BOOST_CHECK(r4 != rs.end());
  BOOST_CHECK(r4->getAccessType() == view::AccessType::ReadWrite);
}

BOOST_AUTO_TEST_CASE(Region_AccessType0) {

  BOOST_CHECK(view::combine({view::AccessType::None, view::AccessType::Read}) ==
              view::AccessType::Read);
  BOOST_CHECK(
      view::combine({view::AccessType::None, view::AccessType::Write}) ==
      view::AccessType::Write);
  BOOST_CHECK(
      view::combine({view::AccessType::None, view::AccessType::ReadWrite}) ==
      view::AccessType::ReadWrite);
  BOOST_CHECK(
      view::combine({view::AccessType::Read, view::AccessType::Write}) ==
      view::AccessType::ReadWrite);
  BOOST_CHECK(
      view::combine({view::AccessType::Read, view::AccessType::ReadWrite}) ==
      view::AccessType::ReadWrite);
  BOOST_CHECK(
      view::combine({view::AccessType::Write, view::AccessType::ReadWrite}) ==
      view::AccessType::ReadWrite);
}

BOOST_AUTO_TEST_CASE(Region_indices) {
  view::Region r0({{2, 1, 0}, {4, 3, 7}});

  auto d0 = r0.dimIndex(0);
  auto d1 = r0.dimIndex(14);
  auto d2 = r0.dimIndex(27);

  auto d0x = std::vector<int64_t>{2, 1, 0};
  auto d1x = std::vector<int64_t>{3, 1, 0};
  auto d2x = std::vector<int64_t>{3, 2, 6};

  BOOST_CHECK(d0 == d0x);
  BOOST_CHECK(d1 == d1x);
  BOOST_CHECK(d2 == d2x);

  for (int64_t i = 0; i < 28; ++i) {
    auto dimIndex  = r0.dimIndex(i);
    auto flatIndex = r0.flatIndex(dimIndex);
    BOOST_CHECK_EQUAL(flatIndex, i);
  }
}


#include <Eigen/Core>

#include <numpy_eigen/boost_python_headers.hpp>
Eigen::Matrix<boost::int64_t, 5, 2> test_long_5_2(const Eigen::Matrix<boost::int64_t, 5, 2> & M)
{
	return M;
}
void export_long_5_2()
{
	boost::python::def("test_long_5_2",test_long_5_2);
}


#define BOOST_TEST_MODULE dnaq test
#include <boost/test/unit_test.hpp>

#include "compact_de_brujin_graph.hpp"

#include <string>
#include <vector>
#include <iostream>
#include <iomanip>

#include "../kmer/kmer32_service.h"

#include "../common.h"
#include "../dna/simple_immutable_dna.h"
#include "../dna/dna.h"
#include "../util.h"

using namespace boost;
using namespace std;

BOOST_AUTO_TEST_CASE( add_contains_test )
{
    const int k = 30;
    Kmer32Service kmer_service(k+1);
    typedef Kmer32Service::Kmer Kmer;

    string s = "ATGCTGATAGGGATCATCAGTCGGCATCTA";
    SimpleImmutableDna dna1(s + "C");

    Kmer e1 = dna_to_kmer<Kmer>(dna1);
    Kmer e1_rc = kmer_service.reverse_complement(e1);

    CompactDeBrujinGraph<Kmer32Service> graph(k, 16);

    BOOST_CHECK_EQUAL(graph.contains_edge(e1), false);
    BOOST_CHECK_EQUAL(graph.contains_edge(e1_rc), false);

    BOOST_CHECK_EQUAL(graph.add_edge(e1), true);

    BOOST_CHECK_EQUAL(graph.contains_edge(e1), true);
    BOOST_CHECK_EQUAL(graph.contains_edge(e1_rc), true);

    BOOST_CHECK_EQUAL(graph.add_edge(e1_rc), false);

    BOOST_CHECK_EQUAL(graph.contains_edge(e1), true);
    BOOST_CHECK_EQUAL(graph.contains_edge(e1_rc), true);

    SimpleImmutableDna dna2(s + "T");
    SimpleImmutableDna dna3("A" + s);
    SimpleImmutableDna dna4("C" + s);

    Kmer e2 = dna_to_kmer<Kmer>(dna2);
    Kmer e3 = dna_to_kmer<Kmer>(dna3);
    Kmer e4 = dna_to_kmer<Kmer>(dna4);

    Kmer v = dna_to_kmer<Kmer>(SimpleImmutableDna(s));

    graph.add_edge(e2);
    graph.add_edge(e3);
    graph.add_edge(e4);

    vector<Kmer> outcome = {e1, e2};
    vector<Kmer> income = {e3, e4};

    BOOST_CHECK(outcome == graph.outcome_edges(v));
    BOOST_CHECK(income == graph.income_edges(v));
}


//==============================================================================
//         Copyright 2003 - 2011   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2014   MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <boost/simd/include/functions/bitwise_cast.hpp>
#include <boost/simd/include/functions/splat.hpp>
#include <boost/simd/sdk/simd/native.hpp>
#include <boost/simd/sdk/simd/io.hpp>

#include <boost/simd/include/constants/one.hpp>
#include <boost/simd/include/constants/true.hpp>

#include <nt2/sdk/unit/module.hpp>
#include <nt2/sdk/unit/tests/relation.hpp>
#include <boost/detail/endian.hpp>

template<class T> struct bitmask_;

template<> struct bitmask_<float>
{
  static boost::simd::uint32_t value() { return 0x3f800000u; }
};

template<> struct bitmask_<double>
{
  static boost::simd::uint64_t value() { return 0x3ff0000000000000ull; }
};

////////////////////////////////////////////////////////////////////////////////
// Test bitwise_cast
////////////////////////////////////////////////////////////////////////////////
#ifndef BOOST_SIMD_HAS_MIC_SUPPORT
NT2_TEST_CASE_TPL(floating, BOOST_SIMD_SIMD_REAL_TYPES )
{
  using boost::simd::native;
  using boost::simd::splat;
  using boost::simd::bitwise_cast;
  typedef native<T,BOOST_SIMD_DEFAULT_EXTENSION>   vT;
  typedef typename boost::simd::meta::as_logical<T>::type  lT;
  typedef typename boost::simd::meta::as_logical<vT>::type  lvT;
  typedef typename boost::dispatch::meta::as_integer<T, unsigned>::type T1;
  typedef native<T1,BOOST_SIMD_DEFAULT_EXTENSION>   vT1;
  typedef typename boost::simd::meta::as_logical<T1>::type  lT1;
  typedef typename boost::simd::meta::as_logical<vT1>::type  lvT1;
  {
    vT  x = boost::simd::One<vT>();
    vT1 y = bitwise_cast<vT1>(x);
    vT1 ref = splat<vT1>(bitmask_<T>::value());
    NT2_TEST_EQUAL(y,ref);
  }
  {
    T  x = boost::simd::One<T>();
    T1 y = bitwise_cast<T1>(x);
      NT2_TEST_EQUAL(y, bitmask_<T>::value());
  }
  {
    lvT x = boost::simd::True<lvT>();
    lvT1 y = bitwise_cast<lvT1>(x);
    lvT1 ref = splat<lvT1>(true);
    NT2_TEST_EQUAL(y, ref);
  }
  {
    lT x = boost::simd::True<lT>();
    lT1 y = bitwise_cast<lT1>(x);
    NT2_TEST_EQUAL(y, boost::simd::logical<T1>(true));
  }
}
#endif

NT2_TEST_CASE_TPL(integer, BOOST_SIMD_SIMD_INTEGRAL_TYPES )
{
  using boost::simd::native;
  using boost::simd::splat;
  using boost::simd::bitwise_cast;

#ifndef BOOST_SIMD_HAS_MIC_SUPPORT
  typedef uint8_t U;
#else
  typedef uint32_t U;
#endif

typedef native<T,BOOST_SIMD_DEFAULT_EXTENSION> vT;
typedef native<U,BOOST_SIMD_DEFAULT_EXTENSION> vU;

  vT  x = boost::simd::One<vT>();
  vU y = bitwise_cast<vU>(x);

  vU ref;

  for(std::size_t i=0; i!=vU::static_size; ++i)
  {
#ifdef BOOST_LITTLE_ENDIAN
    std::size_t j = i;
#else
    std::size_t j = vU::static_size-i-1;
#endif
      ref[j] = (i % (sizeof(T)/sizeof(U))) ? 0u : 1u;
  }

  NT2_TEST_EQUAL(y, ref);
}


#include <CGAL/Simple_cartesian.h>
#include <CGAL/Surface_mesh.h>
#include <boost/unordered_map.hpp>

typedef CGAL::Simple_cartesian<double>  Kernel;
typedef Kernel::Point_3                 Point_3;
typedef CGAL::Surface_mesh<Point_3>     Mesh;
typedef boost::graph_traits<Mesh>::vertex_descriptor vertex_descriptor;

int main()
{
  boost::unordered_map<vertex_descriptor, int> bum;
  Mesh mesh;
  vertex_descriptor vd  = mesh.add_vertex();
  bum[vd] = 7812;
  return 0;
}


//==============================================================================
//         Copyright 2003 - 2012   LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012   LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <boost/simd/ieee/include/functions/exponentbits.hpp>
#include <boost/simd/sdk/simd/native.hpp>
#include <boost/simd/include/functions/splat.hpp>

#include <nt2/sdk/unit/tests/relation.hpp>
#include <nt2/sdk/unit/module.hpp>

NT2_TEST_CASE_TPL ( exponentbits_real__1_0,  BOOST_SIMD_SIMD_REAL_TYPES)
{
  using boost::simd::exponentbits;
  using boost::simd::tag::exponentbits_;
  using boost::simd::native;
  typedef BOOST_SIMD_DEFAULT_EXTENSION  ext_t;
  typedef native<T,ext_t>                 vT;

  for(int i=1; i < 10; i*= 2)
  {
    NT2_TEST_EQUAL(exponentbits(boost::simd::splat<vT>(i))[0],
                   exponentbits(T(i)));
  }
}


#include <UnitTest++/UnitTest++.h>
#include <stdexcept>
#include <iostream>
#include <cmath>
#include <limits>
#include <boost/filesystem.hpp>
#include <fstream>

#include "coela_utility/src/string_utils.h"
#include "coela_core/src/ccd_image.h"
#include "../regions.h"

using namespace coela;
using namespace std;

SUITE(regions)
{
    string test_suite_output_dir= string(UnitTestSuite::GetSuiteName()) + "_tests/";
    TEST(Run_Standard_Suite_Setup) {
        cout << "*** \""<< UnitTestSuite::GetSuiteName() <<"\" unit tests running ***" <<endl;
        boost::filesystem::create_directories(test_suite_output_dir);
    }

    TEST(basic_functionality) {
        PixelPosition centre(5.25,5.25);
        double radius=2.5;
        regions::CircularAperture<coordinate_types::pixels> ap1(centre, radius);

        CcdImage<double> eight_pix;
        eight_pix.pix = PixelArray2d<double>(8,8,0.0);
        eight_pix.initialize_CCD_grid_for_raw_data();
        CcdImage<double> eight_pix_mask =
            ap1.generate_mask_for_covered_portion_of_image(eight_pix);
        string filename = test_suite_output_dir + "eight_pix_img.fits";
        eight_pix.write_to_file(filename);
        filename = test_suite_output_dir + "eight_pix_mask.fits";
        eight_pix_mask.write_to_file(filename);

        CcdImage<double> five_pix;
        five_pix.pix = PixelArray2d<double>(5,5,0.0);
        five_pix.initialize_CCD_grid_for_raw_data();
        CcdImage<double> five_pix_mask = ap1.generate_mask_for_covered_portion_of_image(five_pix);
        filename = test_suite_output_dir + "five_pix_img.fits";
        five_pix.write_to_file(filename);
        filename = test_suite_output_dir + "five_pix_overlapping_mask.fits";
        five_pix_mask.write_to_file(filename);
    }

}


#include "Assembler.hpp"

// std c++ headers
#include <algorithm>
#include <vector>

// MTL4 includes
#include <boost/numeric/mtl/mtl.hpp>

// AMDiS includes
#include "DOFVector.hpp"
#include "Element.hpp"
#include "ElInfo.hpp"
#include "Operator.hpp"
#include "QPsiPhi.hpp"

namespace AMDiS
{
  Assembler::Assembler(not_null<Operator*> op,
                       not_null<const FiniteElemSpace*> row,
				const FiniteElemSpace*  col)
    : operat(op),
      rowFeSpace(row),
      colFeSpace(col ? col : row.get()),
      nRow(rowFeSpace->getBasisFcts()->getNumber()),
      nCol(colFeSpace->getBasisFcts()->getNumber()),
      remember(true),
      rememberElMat(false),
      rememberElVec(false),
      elementMatrix(nRow, nCol),
      elementVector(nRow),
      tmpMat(nRow, nCol),
      lastMatEl(NULL),
      lastVecEl(NULL),
      lastTraverseId(-1)
  {}

  void Assembler::calculateElementMatrix(const ElInfo* elInfo,
                                         ElementMatrix& userMat,
                                         double factor)
  {
    if (remember && (factor != 1.0 || operat->getUhOld()))
      rememberElMat = true;

    Element* el = elInfo->getElement();

    if (el != lastMatEl || !operat->isOptimized())
    {
      initElement(elInfo);

      if (rememberElMat)
        set_to_zero(elementMatrix);

      lastMatEl = el;
    }
    else
    {
      // Only possible in single mesh case when one operator
      // is used more than twice?
      if (rememberElMat)
      {
        if (&userMat != &elementMatrix)
          userMat += factor * elementMatrix;
        return;
      }
    }

    ElementMatrix& mat = rememberElMat ? elementMatrix : userMat;

    if (secondOrderAssembler)
      secondOrderAssembler->calculateElementMatrix(elInfo, mat);
    if (firstOrderAssemblerGrdPsi)
      firstOrderAssemblerGrdPsi->calculateElementMatrix(elInfo, mat);
    if (firstOrderAssemblerGrdPhi)
      firstOrderAssemblerGrdPhi->calculateElementMatrix(elInfo, mat);
    if (zeroOrderAssembler)
      zeroOrderAssembler->calculateElementMatrix(elInfo, mat);

    if (rememberElMat && &userMat != &elementMatrix)
      userMat += factor * elementMatrix;
  }

  void Assembler::calculateElementVector(const ElInfo* elInfo,
                                         DenseVector<double>& userVec,
                                         double factor)
  {
    if (remember && factor != 1.0)
      rememberElVec = true;

    Element* el = elInfo->getElement();

    if ((el != lastMatEl && el != lastVecEl) || !operat->isOptimized())
      initElement(elInfo);

    if (el != lastVecEl || !operat->isOptimized())
    {
      if (rememberElVec)
        set_to_zero(elementVector);

      lastVecEl = el;
    }
    else
    {
      // Only possible in single mesh case when one operator
      // is used more than twice at dof vector?
      if (rememberElVec)
      {
        userVec += factor * elementVector;
        return;
      }
    }

    DenseVector<double>& vec = rememberElVec ? elementVector : userVec;

    if (operat->getUhOld() && remember)
    {
      matVecAssemble(elInfo, vec);
      if (rememberElVec)
        userVec += factor * elementVector;

      return;
    }

    if (firstOrderAssemblerGrdPsi)
      firstOrderAssemblerGrdPsi->calculateElementVector(elInfo, vec);
    if (zeroOrderAssembler)
      zeroOrderAssembler->calculateElementVector(elInfo, vec);

    if (rememberElVec)
      userVec += factor * elementVector;
  }

  void Assembler::matVecAssemble(const ElInfo* elInfo, DenseVector<double>& vec)
  {

    Element* el = elInfo->getElement();
    DenseVector<double> uhOldLoc(operat->getUhOld()->getFeSpace() == rowFeSpace ?
                                 nRow : nCol);
    operat->getUhOld()->getLocalVector(el, uhOldLoc);

    if (el != lastMatEl)
    {
      set_to_zero(elementMatrix);
      calculateElementMatrix(elInfo, elementMatrix);
    }

    vec += elementMatrix * uhOldLoc;
  }

  void Assembler::initElement(const ElInfo* smallElInfo,
                              const ElInfo* largeElInfo,
                              Quadrature* quad)
  {
    if (secondOrderAssembler)
      secondOrderAssembler->initElement(smallElInfo, largeElInfo, quad);
    if (firstOrderAssemblerGrdPsi)
      firstOrderAssemblerGrdPsi->initElement(smallElInfo, largeElInfo, quad);
    if (firstOrderAssemblerGrdPhi)
      firstOrderAssemblerGrdPhi->initElement(smallElInfo, largeElInfo, quad);
    if (zeroOrderAssembler)
      zeroOrderAssembler->initElement(smallElInfo, largeElInfo, quad);
  }

  void Assembler::checkQuadratures()
  {
    if (secondOrderAssembler)
    {
      // create quadrature
      if (!secondOrderAssembler->getQuadrature())
      {
        int dim = rowFeSpace->getMesh()->getDim();
        int degree = operat->getQuadratureDegree(2);
        Quadrature* quadrature = Quadrature::provideQuadrature(dim, degree);
        secondOrderAssembler->setQuadrature(quadrature);
      }
    }
    if (firstOrderAssemblerGrdPsi)
    {
      // create quadrature
      if (!firstOrderAssemblerGrdPsi->getQuadrature())
      {
        int dim = rowFeSpace->getMesh()->getDim();
        int degree = operat->getQuadratureDegree(1, GRD_PSI);
        Quadrature* quadrature = Quadrature::provideQuadrature(dim, degree);
        firstOrderAssemblerGrdPsi->setQuadrature(quadrature);
      }
    }
    if (firstOrderAssemblerGrdPhi)
    {
      // create quadrature
      if (!firstOrderAssemblerGrdPhi->getQuadrature())
      {
        int dim = rowFeSpace->getMesh()->getDim();
        int degree = operat->getQuadratureDegree(1, GRD_PHI);
        Quadrature* quadrature = Quadrature::provideQuadrature(dim, degree);
        firstOrderAssemblerGrdPhi->setQuadrature(quadrature);
      }
    }
    if (zeroOrderAssembler)
    {
      // create quadrature
      if (!zeroOrderAssembler->getQuadrature())
      {
        int dim = rowFeSpace->getMesh()->getDim();
        int degree = operat->getQuadratureDegree(0);
        Quadrature* quadrature = Quadrature::provideQuadrature(dim, degree);
        zeroOrderAssembler->setQuadrature(quadrature);
      }
    }
  }

  void Assembler::finishAssembling()
  {
    lastVecEl = NULL;
    lastMatEl = NULL;
  }

  OptimizedAssembler::OptimizedAssembler(Operator*  op,
                                         Quadrature* quad2,
                                         Quadrature* quad1GrdPsi,
                                         Quadrature* quad1GrdPhi,
                                         Quadrature* quad0,
                                         const FiniteElemSpace* rowFeSpace,
                                         const FiniteElemSpace* colFeSpace)
    : Assembler(op, rowFeSpace, colFeSpace)
  {
    bool opt = (rowFeSpace->getBasisFcts() == colFeSpace->getBasisFcts());

    // create sub assemblers
    secondOrderAssembler =
      SecondOrderAssembler::getSubAssembler(op, this, quad2, opt);
    firstOrderAssemblerGrdPsi =
      FirstOrderAssembler::getSubAssembler(op, this, quad1GrdPsi, GRD_PSI, opt);
    firstOrderAssemblerGrdPhi =
      FirstOrderAssembler::getSubAssembler(op, this, quad1GrdPhi, GRD_PHI, opt);
    zeroOrderAssembler =
      ZeroOrderAssembler::getSubAssembler(op, this, quad0, opt);

    checkQuadratures();
  }

  StandardAssembler::StandardAssembler(Operator* op,
                                       Quadrature* quad2,
                                       Quadrature* quad1GrdPsi,
                                       Quadrature* quad1GrdPhi,
                                       Quadrature* quad0,
                                       const FiniteElemSpace* rowFeSpace,
                                       const FiniteElemSpace* colFeSpace)
    : Assembler(op, rowFeSpace, colFeSpace)
  {
    remember = false;

    // create sub assemblers
    secondOrderAssembler =
      SecondOrderAssembler::getSubAssembler(op, this, quad2, false);
    firstOrderAssemblerGrdPsi =
      FirstOrderAssembler::getSubAssembler(op, this, quad1GrdPsi, GRD_PSI, false);
    firstOrderAssemblerGrdPhi =
      FirstOrderAssembler::getSubAssembler(op, this, quad1GrdPhi, GRD_PHI, false);
    zeroOrderAssembler =
      ZeroOrderAssembler::getSubAssembler(op, this, quad0, false);

    checkQuadratures();
  }

} // end namespace AMDiS


#include <boost/parameter.hpp>

namespace boost
{
  int vertex_index = 0;

  template <class T = int>
  struct dfs_visitor
  {};
}

BOOST_PARAMETER_NAME(graph)
BOOST_PARAMETER_NAME(visitor)
BOOST_PARAMETER_NAME(root_vertex)
BOOST_PARAMETER_NAME(index_map)
BOOST_PARAMETER_NAME(color_map)

BOOST_PARAMETER_FUNCTION((void), f, tag,
  (required (graph, *))
(optional     (visitor,           *, boost::dfs_visitor<>())
    (root_vertex,       *, *vertices(graph).first)
    (index_map,         *, get(boost::vertex_index,graph))
    (in_out(color_map), *,
      default_color_map(num_vertices(graph), index_map) )
)
) {}


#pragma once
#include "lue/framework/test/stream.hpp"
#include "lue/framework/core/component/component_array.hpp"
#include <boost/range/irange.hpp>
#include <boost/test/test_tools.hpp>

namespace utf = boost::unit_test;
namespace tt = boost::test_tools;

namespace std {
namespace experimental {

template<
    typename Element,
    lue::Rank rank>
bool compare(
    lue::DynamicSpan<Element, rank> const& lhs,
    lue::DynamicSpan<Element, rank> const& rhs)
{
    // Both spans are equal if the extents in all dimensions are
    // equal. This means that finding a dimension for which this is not
    // the case must not result in a match.

    auto const idxs = boost::irange<lue::Index>(0, rank);

    return std::find_if(idxs.begin(), idxs.end(),

            [&lhs, &rhs](lue::Index const idx)
            {
                return lhs.extent(idx) != rhs.extent(idx);
            }

        ) == idxs.end();
}

template<
    typename Element>
bool operator==(
    lue::DynamicSpan<Element, 1> const& lhs,
    lue::DynamicSpan<Element, 1> const& rhs)
{
    return compare<Element, 1>(lhs, rhs);
}

template<
    typename Element>
bool operator==(
    lue::DynamicSpan<Element, 2> const& lhs,
    lue::DynamicSpan<Element, 2> const& rhs)
{
    return compare<Element, 2>(lhs, rhs);
}

}  // namespace experimental
}  // namespace std

namespace lue {
namespace test {

template<
    typename PartitionData>
void check_array_partition_data_is_equal(
    PartitionData const& partition_data1,
    PartitionData const& partition_data2)
{
    BOOST_REQUIRE_EQUAL(partition_data1.shape(), partition_data2.shape());
    BOOST_REQUIRE_EQUAL(partition_data1.span(), partition_data2.span());

    if constexpr(std::is_floating_point_v<ElementT<PartitionData>>)
    {
        for(int i = 0; i < partition_data1.nr_elements(); ++i)
        {
            BOOST_TEST_CONTEXT("index " << i)
            {
                BOOST_CHECK_MESSAGE(
                    (std::isnan(partition_data1[i]) && std::isnan(partition_data2[i])) ||
                    (partition_data1[i] == partition_data2[i]),
                    fmt::format("{} != {}", partition_data1[i], partition_data2[i]));
            }
        }
    }
    else
    {
        BOOST_CHECK_EQUAL_COLLECTIONS(
            partition_data1.begin(), partition_data1.end(),
            partition_data2.begin(), partition_data2.end());
    }
}

template<
    typename PartitionData>
void check_array_partition_data_is_close(
    PartitionData const& partition_data1,
    PartitionData const& partition_data2,
    ElementT<PartitionData> const tolerance=1e-6)
{
    BOOST_REQUIRE_EQUAL(partition_data1.shape(), partition_data2.shape());
    BOOST_REQUIRE_EQUAL(partition_data1.span(), partition_data2.span());

    for(int i = 0; i < partition_data1.nr_elements(); ++i)
    {
        BOOST_TEST_CONTEXT("index " << i)
        {
            BOOST_TEST(
                partition_data1[i] == partition_data2[i], tt::tolerance(tolerance));
        }
    }
}

template<
    typename Partition>
void check_partition_is_equal(
    Partition const& partition1,
    Partition const& partition2)
{
    bool both_partitions_valid = partition1.valid() && partition2.valid();
    bool both_partitions_invalid =
        (!partition1.valid()) && (!partition2.valid());

    // XOR with bools
    BOOST_REQUIRE(both_partitions_valid != both_partitions_invalid);

    if(both_partitions_valid)
    {
        partition1.wait();
        partition2.wait();

        BOOST_REQUIRE(partition1.get_id());
        BOOST_REQUIRE(partition2.get_id());

        BOOST_REQUIRE_EQUAL(
            partition1.shape().get(), partition2.shape().get());

        check_array_partition_data_is_equal(
            partition1.data().get(),
            partition2.data().get());
    }
}

template<
    typename Partition>
void check_partition_is_close(
    Partition const& partition1,
    Partition const& partition2,
    ElementT<Partition> const tolerance=1e-6)
{
    bool both_partitions_valid = partition1.valid() && partition2.valid();
    bool both_partitions_invalid =
        (!partition1.valid()) && (!partition2.valid());

    // XOR with bools
    BOOST_REQUIRE(both_partitions_valid != both_partitions_invalid);

    if(both_partitions_valid)
    {
        partition1.wait();
        partition2.wait();

        BOOST_REQUIRE(partition1.get_id());
        BOOST_REQUIRE(partition2.get_id());

        BOOST_REQUIRE_EQUAL(
            partition1.shape().get(), partition2.shape().get());

        check_array_partition_data_is_close(
            partition1.data().get(),
            partition2.data().get(),
            tolerance);
    }
}

template<
    typename Partitions>
void check_partitions_are_equal(
    Partitions const& partitions1,
    Partitions const& partitions2)
{
    BOOST_REQUIRE_EQUAL(partitions1.shape(), partitions2.shape());

    for(Index p = 0; p < partitions1.nr_elements(); ++p) {
        BOOST_TEST_CONTEXT(fmt::format("Partition index: {}", p))
        check_partition_is_equal(partitions1[p], partitions2[p]);
    }
}

template<
    typename Partitions>
void check_partitions_are_close(
    Partitions const& partitions1,
    Partitions const& partitions2,
    ElementT<Partitions> const tolerance=1e-6)
{
    BOOST_REQUIRE_EQUAL(partitions1.shape(), partitions2.shape());

    for(Index p = 0; p < partitions1.nr_elements(); ++p) {
        BOOST_TEST_CONTEXT(fmt::format("Partition index: {}", p))
        check_partition_is_close(partitions1[p], partitions2[p], tolerance);
    }
}

template<
    typename Element,
    Rank rank>
void check_arrays_are_equal(
    PartitionedArray<Element, rank> const& array1,
    PartitionedArray<Element, rank> const& array2)
{
    BOOST_REQUIRE_EQUAL(
        shape_in_partitions(array1),
        shape_in_partitions(array2));
    BOOST_REQUIRE_EQUAL(array1.shape(), array2.shape());

    check_partitions_are_equal(array1.partitions(), array2.partitions());
}

template<
    typename Element,
    Rank rank>
void check_arrays_are_close(
    PartitionedArray<Element, rank> const& array1,
    PartitionedArray<Element, rank> const& array2,
    Element const tolerance=1e-6)
{
    BOOST_REQUIRE_EQUAL(
        shape_in_partitions(array1),
        shape_in_partitions(array2));
    BOOST_REQUIRE_EQUAL(array1.shape(), array2.shape());

    check_partitions_are_close(array1.partitions(), array2.partitions(), tolerance);
}

template<
    typename Component>
void check_component_is_equal_(
    Component const& component1,
    Component const& component2)
{
    bool both_components_valid = component1.valid() && component2.valid();
    bool both_components_invalid = (!component1.valid()) && (!component2.valid());

    // XOR with bools
    BOOST_REQUIRE(both_components_valid != both_components_invalid);

    if(both_components_valid)
    {
        component1.wait();
        component2.wait();

        BOOST_REQUIRE(component1.get_id());
        BOOST_REQUIRE(component2.get_id());

        check_component_is_equal(component1, component2);
    }
}

template<
    typename Component,
    Rank rank>
void check_components_are_equal(
    Array<Component, rank> const& components1,
    Array<Component, rank> const& components2)
{
    BOOST_REQUIRE_EQUAL(components1.shape(), components2.shape());

    for(Index c = 0; c < nr_elements(components1); ++c) {
        BOOST_TEST_CONTEXT(fmt::format("Component index: {}", c))
        check_component_is_equal_(components1[c], components2[c]);
    }
}

template<
    typename Component,
    Rank rank>
void check_arrays_are_equal(
    ComponentArray<Component, rank> const& array1,
    ComponentArray<Component, rank> const& array2)
{
    check_components_are_equal(array1.components(), array2.components());
}

}  // namespace test
}  // namespace lue


#include <type_traits>
#include <boost/preprocessor/stringize.hpp>
#include <boost/mpl/vector.hpp>
#include <boost/mpl/print.hpp>
#include <boost/fusion/include/vector.hpp>
#include <desalt/parameter_pack.hpp>
#include <iostream>
#include <typeinfo>
#include <cxxabi.h>

namespace ppack = desalt::parameter_pack;
namespace mpl = boost::mpl;
namespace fu = boost::fusion;
using ppack::type_seq;

using a = type_seq<int, char, double>;
using m = mpl::vector<int, char, double>;
using v = fu::vector<int, char, double>;

#define STATIC_TEST(...) static_assert((__VA_ARGS__), __FILE__ "(" BOOST_PP_STRINGIZE(__LINE__) ")")

STATIC_TEST(std::is_same<ppack::head<a>::type, int>::value);
STATIC_TEST(std::is_same<ppack::tail<a>::type, type_seq<char, double>>::value);
STATIC_TEST(std::is_same<ppack::cons<float, a>::type, type_seq<float, int, char, double>>::value);
STATIC_TEST(ppack::size<a>::value == 3);
STATIC_TEST(std::is_same<ppack::reverse<a>::type, type_seq<double, char, int>>::value);
STATIC_TEST(std::is_same<ppack::append<a, type_seq<float, short>>::type, type_seq<int, char, double, float, short>>::value);
STATIC_TEST(std::is_same<ppack::from_mpl_seq<m>::type, a>::value);
STATIC_TEST(std::is_same<ppack::from_fusion_seq<v>::type, a>::value);
STATIC_TEST(std::is_same<ppack::enumerate_c<int, 0, 5>::type, type_seq<std::integral_constant<int, 0>,
                                                                       std::integral_constant<int, 1>,
                                                                       std::integral_constant<int, 2>,
                                                                       std::integral_constant<int, 3>,
                                                                       std::integral_constant<int, 4>>>::value);
STATIC_TEST(std::is_same<ppack::at_c<a, 0>::type, int>::value);
STATIC_TEST(std::is_same<ppack::at_c<a, 1>::type, char>::value);
STATIC_TEST(std::is_same<ppack::at_c<a, 2>::type, double>::value);

int main() {}


#ifndef BART_SRC_QUADRATURE_ORDINATE_I_HPP_
#define BART_SRC_QUADRATURE_ORDINATE_I_HPP_

#include <deal.II/base/tensor.h>

#include "quadrature/quadrature_types.h"
#include "utility/named_type.h"

namespace bart::quadrature {

/*! \brief Interface for ordinates for quadrature sets.
 *
 * An ordinate is merely a position in cartesian space.
 *
 * @tparam dim spatial dimension.
 */
template <int dim>
class OrdinateI {
 public:
  virtual ~OrdinateI() = default;

  /*! \brief Set the ordinate cartesian position.
   *
   * @return this object.
   */
  virtual auto set_cartesian_position(CartesianPosition<dim>) -> OrdinateI& = 0;

  /*! \brief Get the ordinate cartesian position as an array.
   *
   * @return an array containing the cartesian position.
   */
  virtual auto cartesian_position() const -> std::array<double, dim> = 0;

  /*! \brief Get the ordinate cartesian position as a dealii tensor.
   *
   * @return dealii tensor containing the cartesian position.
   */
  virtual auto cartesian_position_tensor() const -> dealii::Tensor<1, dim> = 0;

  /*! \brief Equality operator between two ordinates, based on position.
   *
   * @return bool indicating if the cartesian positions are the same.
   */
  virtual auto operator==(const OrdinateI&) const -> bool = 0;
  /*! \brief Inequality operator between two ordinates, based on position.
   *
   * @return bool indicating if the cartesian positions are not the same.
   */
  virtual auto operator!=(const OrdinateI&) const -> bool = 0;
  virtual auto operator==(std::array<double, dim>) const -> bool = 0;
  virtual auto operator!=(std::array<double, dim>) const -> bool = 0;
};

/*! \brief Returns a position that is the reflection of a provided ordinate.
 *
 * @tparam dim spatial dimension of the ordiante.
 * @param ordinate ordinate to reflect.
 * @return array containing cartesian position reflected across the origin.
 */
template<int dim>
auto Reflect(const OrdinateI<dim>& ordinate) -> std::array<double, dim> {
  std::array<double, dim> return_array;
  for (unsigned int i = 0; i < dim; ++i)
    return_array.at(i) = -ordinate.cartesian_position().at(i);
  return return_array;
}

} // namespace bart::quadrature

#endif //BART_SRC_QUADRATURE_ORDINATE_I_HPP_


//  (C) Copyright John Maddock 2006.
//  Use, modification and distribution are subject to the
//  Boost Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#include "test_pFq.hpp"

#include <boost/multiprecision/cpp_bin_float.hpp>

#if defined(_MSC_FULL_VER) && (_MSC_FULL_VER < 190023026)
//
// Early msvc versions have <initializer_list> but can't handle
// argument deduction of actual initializer_lists :(
//
#define DISABLE_TESTS
#endif

BOOST_AUTO_TEST_CASE( test_main )
{
#ifndef DISABLE_TESTS
#if !defined(TEST) || (TEST == 2)
   test_spots(0.0F, "float");
#endif
#if !defined(TEST) || (TEST == 3)
   test_spots(0.0, "double");
#endif
#ifndef BOOST_MATH_NO_LONG_DOUBLE_MATH_FUNCTIONS
#if (!defined(TEST) || (TEST == 4)) && (DBL_MAX_EXP != LDBL_MAX_EXP)
   test_spots(0.0L, "long double");
#endif
#ifndef BOOST_MATH_NO_REAL_CONCEPT_TESTS
#if !defined(TEST) || (TEST == 5)
   test_spots(boost::math::concepts::real_concept(0.1), "real_concept");
#endif
#endif
#endif
#if !defined(TEST) || (TEST == 6)
   test_spots(boost::multiprecision::cpp_bin_float_quad(), "cpp_bin_float_quad");
#endif
#if !defined(TEST) || (TEST == 7)
   typedef boost::multiprecision::number<boost::multiprecision::cpp_bin_float<40> > dec_40;
   test_spots(dec_40(), "dec_40");
#endif
#endif
}


//
// Copyright 2016 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
// compliance with the Apache License and the following modification to it:
// Section 6. Trademarks. is deleted and replaced with:
//
// 6. Trademarks. This License does not grant permission to use the trade
//    names, trademarks, service marks, or product names of the Licensor
//    and its affiliates, except as required to comply with Section 4(c) of
//    the License and to reproduce the content of the NOTICE file.
//
// You may obtain a copy of the Apache License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License with the above modification is
// distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the Apache License for the specific
// language governing permissions and limitations under the Apache License.
//

#include "pxr/pxr.h"
#include "pxr/base/gf/ray.h"
#include "pxr/base/gf/line.h"
#include "pxr/base/gf/lineSeg.h"
#include "pxr/base/gf/plane.h"
#include "pxr/base/gf/range3d.h"

#include "pxr/base/tf/pyUtils.h"
#include "pxr/base/tf/wrapTypeHelpers.h"

#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/copy_const_reference.hpp>
#include <boost/python/operators.hpp>
#include <boost/python/return_arg.hpp>
#include <boost/python/tuple.hpp>

#include <string>

using namespace boost::python;

using std::string;

PXR_NAMESPACE_USING_DIRECTIVE

namespace {

static void
SetStartPointHelper( GfRay &self, const GfVec3d &startPoint ) {
    self.SetPointAndDirection( startPoint, self.GetDirection() );
}

static void
SetDirectionHelper( GfRay &self, const GfVec3d &direction ) {
    self.SetPointAndDirection( self.GetStartPoint(), direction );
}

static tuple
FindClosestPointHelper( const GfRay &self, const GfVec3d &point ) {
    double rayDist;
    GfVec3d result = self.FindClosestPoint( point, &rayDist );
    return make_tuple( result, rayDist );
}

static tuple
FindClosestPointsHelper1( const GfRay &l1, const GfLine &l2 )
{
    GfVec3d p1(0), p2(0);
    double t1 = 0.0, t2 = 0.0;
    bool result = GfFindClosestPoints( l1, l2, &p1, &p2, &t1, &t2 );
    return make_tuple( result, p1, p2, t1, t2 );
}

static tuple
FindClosestPointsHelper2( const GfRay &l1, const GfLineSeg &l2 )
{
    GfVec3d p1(0), p2(0);
    double t1 = 0.0, t2 = 0.0;
    bool result = GfFindClosestPoints( l1, l2, &p1, &p2, &t1, &t2 );
    return make_tuple( result, p1, p2, t1, t2 );
}

static tuple
IntersectHelper1( const GfRay &self, const GfVec3d &p0,
                  const GfVec3d &p1, const GfVec3d &p2 )
{
    double dist = 0;
    GfVec3d barycentricCoords(0);
    bool frontFacing = false;
    bool result =
        self.Intersect( p0, p1, p2, &dist, &barycentricCoords, &frontFacing );
    return make_tuple( result, dist, barycentricCoords, frontFacing );
}

static tuple
IntersectHelper2( const GfRay &self, const GfPlane &plane )
{
    double dist = 0;
    bool frontFacing = false;
    bool result = self.Intersect( plane, &dist, &frontFacing );
    return make_tuple( result, dist, frontFacing );
}

static tuple
IntersectHelper3( const GfRay &self, const GfRange3d &box )
{
    double enterDist = 0, exitDist = 0;
    bool result = self.Intersect( box, &enterDist, &exitDist );
    return make_tuple( result, enterDist, exitDist );
}

static tuple
IntersectHelper4( const GfRay &self, const GfVec3d& center, double radius )
{
    double enterDist = 0, exitDist = 0;
    bool result = self.Intersect( center, radius, &enterDist, &exitDist );
    return make_tuple( result, enterDist, exitDist );
}

static tuple
IntersectHelper5(const GfRay &self,
                 const GfVec3d &origin,
                 const GfVec3d &axis,
                 double radius)
{
    double enter = 0, exit = 0;
    bool result = self.Intersect(origin, axis, radius, &enter, &exit);
    return make_tuple(result, enter, exit);
}

static tuple
IntersectHelper6(const GfRay &self,
                 const GfVec3d &origin,
                 const GfVec3d &axis,
                 double radius,
                 double height)
{
    double enter = 0, exit = 0;
    bool result = self.Intersect(origin, axis, radius, height, &enter, &exit);
    return make_tuple(result, enter, exit);
}

static string _Repr(GfRay const &self) {
    return TF_PY_REPR_PREFIX + "Ray(" + TfPyRepr(self.GetStartPoint()) + ", " +
        TfPyRepr(self.GetDirection()) + ")";
}

} // anonymous namespace

void wrapRay()
{
    typedef GfRay This;

    def("FindClosestPoints", FindClosestPointsHelper1,
        "FindClosestPoints( r1, l2 ) -> tuple<intersects=bool, "
        "p1 = GfVec3d, p2 = GfVec3d, t1 = double, t2 = double>\n"
        "\n"
        "r1 : GfRay\n"
        "l2 : GfLine\n"
        "\n"
        "Computes the closest points between a ray and a line,\n"
        "returning a tuple. The first item in the tuple is true if "
        "they intersect. The two points are returned in p1 and p2.\n"
        "The parametric distance of each point on the ray and line is\n"
        "returned in t1 and t2.\n"
        "----------------------------------------------------------------------"
        );
    def("FindClosestPoints", FindClosestPointsHelper2,
        "FindClosestPoints( r1, s2 ) -> tuple<intersects = bool, "
        "p1 = GfVec3d, p2 = GfVec3d, t1 = double, t2 = double>\n"
        "\n"
        "r1 : GfRay\n"
        "s2 : GfLineSeg\n"
        "\n"
        "Computes the closest points between a ray and a line segment,\n"
        "returning a tuple. The first item in the tuple is true if "
        "they intersect. The two points are returned in p1 and p2.\n"
        "The parametric distance of each point on the ray and line\n"
        "segment is returned in t1 and t2.\n"
        "----------------------------------------------------------------------"
        );

    class_<This>("Ray", "", init<>())
        .def(init< const GfVec3d &, const GfVec3d & >())

        .def(TfTypePythonClass())

        .def("SetPointAndDirection",
             &This::SetPointAndDirection, return_self<>())
        .def("SetEnds", &This::SetEnds, return_self<>())

        .add_property( "startPoint", make_function
                       (&This::GetStartPoint,
                        return_value_policy<copy_const_reference>()),
                       SetStartPointHelper )
        .add_property( "direction", make_function
                       (&This::GetDirection,
                        return_value_policy<copy_const_reference>()),
                       SetDirectionHelper )

        .def("GetPoint", &This::GetPoint )

        .def("FindClosestPoint", FindClosestPointHelper)

        .def("Transform", &This::Transform, return_self<>())

        .def("Intersect", IntersectHelper1,
             "Intersect( p0, p1, p2 ) -> tuple<intersects = bool, dist =\n"
             "float, barycentric = GfVec3d, frontFacing = bool>\n"
             "\n"
             "Intersects the ray with the triangle formed by points p0,\n"
             "p1, and p2.  The first item in the tuple is true if the ray\n"
             "intersects the triangle. dist is the the parametric\n"
             "distance to the intersection point, the barycentric\n"
             "coordinates of the intersection point, and the front-facing\n"
             "flag. The barycentric coordinates are defined with respect\n"
             "to the three vertices taken in order.  The front-facing\n"
             "flag is True if the intersection hit the side of the\n"
             "triangle that is formed when the vertices are ordered\n"
             "counter-clockwise (right-hand rule).\n"
             "\n"
             "Barycentric coordinates are defined to sum to 1 and satisfy\n"
             "this relationsip:\n"
             "\n"
             "    intersectionPoint = (barycentricCoords[0] * p0 +\n"
             "                         barycentricCoords[1] * p1 +\n"
             "                         barycentricCoords[2] * p2);\n"
             "----------------------------------------------------------------------"
            )
        .def( "Intersect", IntersectHelper2,
              "Intersect( plane ) -> tuple<intersects = bool, dist = float,\n"
              "frontFacing = bool>\n"
              "\n"
              "Intersects the ray with the Gf.Plane.  The first item in\n"
              "the returned tuple is true if the ray intersects the plane.\n"
              "dist is the parametric distance to the intersection point\n"
              "and frontfacing is true if the intersection is on the side\n"
              "of the plane toward which the plane's normal points.\n"
             "----------------------------------------------------------------------"
            )
        .def( "Intersect", IntersectHelper3,
              "Intersect( range3d ) -> tuple<intersects = bool, enterDist\n"
              "= float, exitDist = float>\n"
              //\n"
              "Intersects the plane with an axis-aligned box in a\n"
              "Gf.Range3d.  intersects is true if the ray intersects it at\n"
              "all within bounds. If there is an intersection then enterDist\n"
              "and exitDist will be the parametric distances to the two\n"
              "intersection points.\n"
              "----------------------------------------------------------------------"
            )
        .def( "Intersect", IntersectHelper4,
              "Intersect( center, radius ) -> tuple<intersects = bool,\n"
              "enterDist = float, exitDist = float>\n"
              "\n"
              "Intersects the plane with an sphere. intersects is true if\n"
              "the ray intersects it at all within the sphere. If there is\n"
              "an intersection then enterDist and exitDist will be the\n"
              "parametric distances to the two intersection points.\n"
              "----------------------------------------------------------------------"
            )
        .def( "Intersect", IntersectHelper5,
              "Intersect( origin, axis, radius ) -> tuple<intersects = bool,\n"
              "enterDist = float, exitDist = float>\n"
              "\n"
              "Intersects the plane with an infinite cylinder. intersects\n"
              "is true if the ray intersects it at all within the\n"
              "sphere. If there is an intersection then enterDist and\n"
              "exitDist will be the parametric distances to the two\n"
              "intersection points.\n"
              "----------------------------------------------------------------------"
            )
        .def( "Intersect", IntersectHelper6,
              "Intersect( origin, axis, radius, height ) -> \n"
              "tuple<intersects = bool, enterDist = float, exitDist = float>\n"
              "\n"
              "Intersects the plane with an cylinder. intersects\n"
              "is true if the ray intersects it at all within the\n"
              "sphere. If there is an intersection then enterDist and\n"
              "exitDist will be the parametric distances to the two\n"
              "intersection points.\n"
              "----------------------------------------------------------------------"
            )

        .def( str(self) )
        .def( self == self )
        .def( self != self )

        .def("__repr__", _Repr)

        ;

}


#include <iostream>
#include <iostream>
#include <tuple>
#include <map>
#include <cassert>
#include <sstream>
#include <random>
#include <algorithm>
#include <vector>
#include <set>
#include <bitset>
#include <boost/preprocessor.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/optional.hpp>

#define PRINT_SEQ_detail(r, d, i, e) do{ std::cout << ( i ? ", " : "" ) << BOOST_PP_STRINGIZE(e) << " = " << (e); }while(0);
#define PRINT_SEQ(SEQ) do{ BOOST_PP_SEQ_FOR_EACH_I( PRINT_SEQ_detail, ~, SEQ) std::cout << std::endl; }while(0)
#define PRINT(X) PRINT_SEQ((X))

/*
        Connect Four

        | | | | | | | |
        | | | | | | | |
        | | | | | | | |
        | |X| | | | | |
        | |O|X|O|X| | |
        | |O|O|X|O| | |

                We create a graph, with nodes of states of the Game, where a
        state is a 2-tuple of (board,turn). We then construct the directed
        graph, which edges which represent placements of tiles.
                The terminal nodes of the graph are those for which their
        is a winner, loser, or draw.

                The strategyies for the respected players is a subset of the
        graph for which.

*/

#include "Board.hpp"
#include "Context.hpp"
#include "Decl.hpp"
#include "GameLogic.hpp"
#include "InputOutput.hpp"

// returns the lowest (*this)(x, Level(x)) which is empty


#pragma once

#include <assert.h>
#include <iostream>
#include <stack>
#include <boost/pending/disjoint_sets.hpp>
#include "../types.hpp"
#include "./utils.hpp"

// #include <boost/serialization/serialization.hpp>
// #include <boost/serialization/map.hpp>

namespace reneu{

using SegPairs = std::vector<std::pair<segid_t, segid_t>>;
auto seg_pairs_to_array(SegPairs& pairs){
    // std::cout<<"convert to array."<<std::endl;
    const auto& pairNum = pairs.size();
    xt::xtensor<segid_t, 2>::shape_type sh = {pairNum, 2};
    auto arr = xt::empty<segid_t>(sh);
    for(std::size_t idx=0; idx<pairNum; idx++){
        const auto& [segid0, root] = pairs[idx];
        arr(idx, 0) = segid0;
        arr(idx, 1) = root;
        // std::cout<<"merge "<<segid0<<", "<<root<<std::endl;
    }
    return arr;
}

class DisjointSets{

private:
using Rank_t = std::map<segid_t, size_t>;
using Parent_t = std::map<segid_t, segid_t>;
using PropMapRank_t = boost::associative_property_map<Rank_t>;
using PropMapParent_t = boost::associative_property_map<Parent_t>;
using BoostDisjointSets = boost::disjoint_sets<PropMapRank_t, PropMapParent_t>;

Rank_t _mapRank;
Parent_t _mapParent;
PropMapRank_t _propMapRank;
PropMapParent_t _propMapParent;
BoostDisjointSets _dsets;

public:
DisjointSets():
    _propMapRank(_mapRank), _propMapParent(_mapParent),
    _dsets(_propMapRank, _propMapParent){}

DisjointSets(const Segmentation& seg):
        _propMapRank(_mapRank), _propMapParent(_mapParent),
        _dsets(_propMapRank, _propMapParent){

    auto segids = get_nonzero_segids(seg);
    for(const auto& segid : segids){
        _dsets.make_set(segid);
    }
}

// friend class boost::serialization::access;
// template<class Archive>
// void serialize(Archive ar, const unsigned int version){
//     ar & _mapRank;
//     ar & _mapParent;
//     ar & _propMapRank;
//     ar & _propMapParent;
//     ar & _dsets;
// }

void make_set(const segid_t& segid ){
    // To-Do: use contain function in C++20
    const auto& search = _mapRank.find(segid);
    if(search == _mapRank.end())
        _dsets.make_set(segid);
}

void union_set(const segid_t s0, const segid_t s1){
    _dsets.union_set(s0, s1);
}

void make_and_union_set(const segid_t s0, const segid_t s1){
    make_set(s0);
    make_set(s1);
    union_set(s0, s1);
}

segid_t find_set(segid_t sid){
    const auto& root = _dsets.find_set(sid);
    if(root == 0)
        return sid;
    else
        return root;
}

auto merge_array(const xt::xtensor<segid_t, 2>& arr){
    std::set<std::pair<segid_t, segid_t>> pairs = {};
    assert(arr.shape(1) == 2);

    // in case there exist a lot of duplicates in this array
    // we make a small set first to make it more efficient
    for(std::size_t idx=0; idx<arr.shape(0); idx++){
        const auto& segid0 = arr(idx, 0);
        const auto& segid1 = arr(idx, 1);
        // const auto& pair = std::make_tuple(segid0, segid1);
        pairs.emplace(segid0, segid1);
    }

    for(const auto& [segid0, segid1] : pairs){
        make_and_union_set(segid0, segid1);
    }
}

inline auto py_merge_array(xt::pytensor<segid_t, 2>& pyarr){
    return merge_array(std::move(pyarr));
}

auto to_array(){
    // std::cout<<"find the root for each segment."<<std::endl;
    SegPairs pairs = {};
    for(const auto& [segid0, parent]: _mapParent){
        const auto& root = find_set(segid0);
        if(root != segid0){
            // std::cout<<"add pair to stack: "<<segid0<<", "<< root<<std::endl;
            pairs.emplace_back(segid0, root);
        }
    }

    const auto& arr = seg_pairs_to_array(pairs);
    return arr;
}

auto relabel(Segmentation&& seg){
    auto segids = get_nonzero_segids(seg);
    // Flatten the parents tree so that the parent of every element is its representative.
    _dsets.compress_sets(segids.begin(), segids.end());
    std::cout<< "get "<<
                _dsets.count_sets(segids.begin(), segids.end()) <<
                " final objects."<< std::endl;

    std::cout<< "relabel the fragments to a flat segmentation." << std::endl;
    const auto& [sz, sy, sx] = seg.shape();
    for(std::size_t z=0; z<sz; z++){
        for(std::size_t y=0; y<sy; y++){
            for(std::size_t x=0; x<sx; x++){
                const auto& sid = seg(z,y,x);
                if(sid > 0){
                    const auto& rootID = find_set(sid);
                    if(sid!=rootID){
                        assert(rootID > 0);
                        seg(z,y,x) = rootID;
                    }
                }
            }
        }
    }

    // this implementation will mask out all the objects that is not in the set!
    // We should not do it in the global materialization stage.
    // std::transform(seg.begin(), seg.end(), seg.begin(),
    //    [this](segid_t segid)->segid_t{return this->find_set(segid);}
    // );
    return seg;
}

inline auto py_relabel(PySegmentation& pyseg){
    return relabel(std::move(pyseg));
}

}; // class of DisjointSets

/**
 * @brief given a fragment array and plain segmentation, find the disjoint sets.
 *
 * @param frag The fragment segmentation containing super voxels.
 * @param seg The plain segmentation generated by agglomerating framgents
 * @return The corresponding object pairs that need to be merged
 */
auto agglomerated_segmentation_to_merge_pairs(const PySegmentation& frag, const PySegmentation& seg){
    assert(frag.shape(0) == seg.shape(0));
    assert(frag.shape(1) == seg.shape(1));
    assert(frag.shape(2) == seg.shape(2));

    SegPairs pairs = {};
    // auto dsets = DisjointSets();
    // for(const auto& obj: frag){
    //     if(obj > 0) dsets.make_set(obj);
    // }

    for(std::ptrdiff_t z=0; z<seg.shape(0); z++){
        for(std::ptrdiff_t y=0; y<seg.shape(1); y++){
            for(std::ptrdiff_t x=0; x<seg.shape(2); x++){
                const auto& obj0 = frag(z, y, x);
                if(z>0){
                    const auto& obj1 = frag(z-1, y, x);
                    if(obj0!=obj1 && seg(z,y,x)==seg(z-1,y,x) && obj0>0 && obj1>0){
                        // std::cout<<"merge :"<<obj0<<", "<<obj1<<std::endl;
                        // dsets.make_and_union_set(obj0, obj1);
                        pairs.emplace_back(obj0, obj1);
                    }
                }

                if(y>0){
                    const auto& obj1 = frag(z,y-1,x);
                    if(obj0!=obj1 && seg(z,y,x)==seg(z,y-1,x) && obj0>0 && obj1>0){
                        // std::cout<<"merge :"<<obj0<<", "<<obj1<<std::endl;
                        // dsets.make_and_union_set(obj0, obj1);
                        pairs.emplace_back(obj0, obj1);
                    }
                }

                if(x>0){
                    const auto& obj1 = frag(z, y, x-1);
                    if(obj0!=obj1 && seg(z,y,x)==seg(z,y,x-1) && obj0>0 && obj1>0){
                        // std::cout<<"merge :"<<obj0<<", "<<obj1<<std::endl;
                        // dsets.make_and_union_set(obj0, obj1);
                        pairs.emplace_back(obj0, obj1);
                    }
                }
            }
        }
    }

    std::cout<<"transform to array."<<std::endl;
    auto arr = seg_pairs_to_array(pairs);

    return arr;
}

} // namespace reneu


/* pcmsolver_copyright_start */
/*
 *     PCMSolver, an API for the Polarizable Continuum Model
 *     Copyright (C) 2013-2016 Roberto Di Remigio, Luca Frediani and contributors
 *
 *     This file is part of PCMSolver.
 *
 *     PCMSolver is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     PCMSolver 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 Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
 *
 *     For information on the complete list of contributors to the
 *     PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
 */
/* pcmsolver_copyright_end */

#ifndef IGREENSFUNCTION_HPP
#define IGREENSFUNCTION_HPP

#include <iosfwd>
#include <vector>

#include "Config.hpp"

#include <Eigen/Core>

class Element;

#include "dielectric_profile/ProfileTypes.hpp"

/*! \file IGreensFunction.hpp
 *  \class IGreensFunction
 *  \brief Interface for Green's function classes
 *  \author Luca Frediani and Roberto Di Remigio
 *  \date 2012-2015
 *
 *  The Non-Virtual Interface (NVI) idiom is used. Notice also that some of the return
 *  types are in some cases "auto", meaning that we will let the compiler deduce
 *  them.
 */

/*! \typedef KernelS
 *  \brief functor handle to the kernelS method
 */
typedef pcm::function<double(const Eigen::Vector3d &, const Eigen::Vector3d &)> KernelS;

/*! \typedef KernelD
 *  \brief functor handle to the kernelD method
 */
typedef pcm::function<double(const Eigen::Vector3d &, const Eigen::Vector3d &, const Eigen::Vector3d &)> KernelD;

class IGreensFunction
{
  public:
    virtual ~IGreensFunction() {}
    /*! Returns value of the kernel of the \f$\mathcal{S}\f$ integral operator, i.e. the value of the
     *  Greens's function for the pair of points p1, p2: \f$ G(\mathbf{p}_1, \mathbf{p}_2)\f$
     *  \param[in] p1 first point
     *  \param[in] p2 second point
     */
    double kernelS(const Eigen::Vector3d & p1, const Eigen::Vector3d &p2) const {
      return kernelS_impl(p1, p2);
    }
    /*! Returns value of the kernel of the \f$\mathcal{D}\f$ integral operator for the pair of points p1, p2:
     *  \f$ [\boldsymbol{\varepsilon}\nabla_{\mathbf{p_2}}G(\mathbf{p}_1, \mathbf{p}_2)]\cdot \mathbf{n}_{\mathbf{p}_2}\f$
     *  To obtain the kernel of the \f$\mathcal{D}^\dagger\f$ operator call this methods with \f$\mathbf{p}_1\f$
     *  and \f$\mathbf{p}_2\f$ exchanged and with \f$\mathbf{n}_{\mathbf{p}_2} = \mathbf{n}_{\mathbf{p}_1}\f$
     *  \param[in] direction the direction
     *  \param[in]        p1 first point
     *  \param[in]        p2 second point
     */
    double kernelD(const Eigen::Vector3d & direction, const Eigen::Vector3d & p1, const Eigen::Vector3d & p2) const {
      return kernelD_impl(direction, p1, p2);
    }

    KernelS exportKernelS() const {
      return exportKernelS_impl();
    }
    KernelD exportKernelD() const {
      return exportKernelD_impl();
    }

    /*! Whether the Green's function describes a uniform environment */
    virtual bool uniform() const = 0;
    /*! Returns a dielectric permittivity profile */
    virtual Permittivity permittivity() const = 0;

    /*! Calculates the matrix representation of the S operator
     *  \param[in] e list of finite elements
     */
    virtual Eigen::MatrixXd singleLayer(const std::vector<Element> & e) const = 0;
    /*! Calculates the matrix representation of the D operator
     *  \param[in] e list of finite elements
     */
    virtual Eigen::MatrixXd doubleLayer(const std::vector<Element> & e) const = 0;

    friend std::ostream & operator<<(std::ostream & os, IGreensFunction & gf) {
      return gf.printObject(os);
    }
  protected:
    /*! Returns value of the kernel of the \f$\mathcal{S}\f$ integral operator, i.e. the value of the
     *  Greens's function for the pair of points p1, p2: \f$ G(\mathbf{p}_1, \mathbf{p}_2)\f$
     *  \param[in] p1 first point
     *  \param[in] p2 second point
     */
    virtual double kernelS_impl(const Eigen::Vector3d & p1, const Eigen::Vector3d &p2) const = 0;
    /*! Returns value of the kernel of the \f$\mathcal{D}\f$ integral operator for the pair of points p1, p2:
     *  \f$ [\boldsymbol{\varepsilon}\nabla_{\mathbf{p_2}}G(\mathbf{p}_1, \mathbf{p}_2)]\cdot \mathbf{n}_{\mathbf{p}_2}\f$
     *  To obtain the kernel of the \f$\mathcal{D}^\dagger\f$ operator call this methods with \f$\mathbf{p}_1\f$
     *  and \f$\mathbf{p}_2\f$ exchanged and with \f$\mathbf{n}_{\mathbf{p}_2} = \mathbf{n}_{\mathbf{p}_1}\f$
     *  \param[in] direction the direction
     *  \param[in]        p1 first point
     *  \param[in]        p2 second point
     */
    virtual double kernelD_impl(const Eigen::Vector3d & direction,
        const Eigen::Vector3d & p1, const Eigen::Vector3d & p2) const = 0;
    virtual KernelS exportKernelS_impl() const = 0;
    virtual KernelD exportKernelD_impl() const = 0;
    virtual std::ostream & printObject(std::ostream & os) = 0;
};

#endif // IGREENSFUNCTION_HPP


// Copyright (c) 2014
// Akira Takahashi, Fumiki Fukuda.
// Released under the CC0 1.0 Universal license.

#include <iostream>
#include <utility>
#include <boost/utility/enable_if.hpp>

struct has_add_member_impl {
  template <class T, class U>
  static auto check(T*, U*) -> decltype(
    std::declval<T>().add(std::declval<U>()),
    std::true_type());

  template <class T, class U>
  static auto check(...) -> std::false_type;
};

template <class T, class U>
struct has_add_member
  : decltype(has_add_member_impl::check<T, U>(
			    nullptr, nullptr)) {};

// 関数テンプレートのoperator+。
// 型Tがadd()メンバ関数を持っている場合のみ呼び出せる。
template <class T, class U>
typename boost::enable_if<has_add_member<T, U>, T>::type
  operator+(const T& a, const U& b)
{
  return a.add(b);
}

class X {
  int value;
public:
  X(int value)
    : value(value) {}

  X add(const X& x) const
  {
    return X(value + x.value);
  }

  int getValue() const { return value; }
};

int main()
{
  X a(1);
  X b(2);

  X r = a + b;
  std::cout << r.getValue() << std::endl;
}