// Copyright John Maddock 2008.

// 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)

#ifndef BOOST_MATH_DISTRIBUTIONS_DETAIL_MODE_HPP
#define BOOST_MATH_DISTRIBUTIONS_DETAIL_MODE_HPP

#include <boost/math/tools/minima.hpp> // function minimization for mode
#include <boost/math/policies/error_handling.hpp>
#include <boost/math/distributions/fwd.hpp>

namespace boost{ namespace math{ namespace detail{

template <class Dist>
struct pdf_minimizer
{
   pdf_minimizer(const Dist& d)
      : dist(d) {}

   typename Dist::value_type operator()(const typename Dist::value_type& x)
   {
      return -pdf(dist, x);
   }
private:
   Dist dist;
};

template <class Dist>
typename Dist::value_type generic_find_mode(const Dist& dist, typename Dist::value_type guess, const char* function, typename Dist::value_type step = 0)
{
   BOOST_MATH_STD_USING
   typedef typename Dist::value_type value_type;
   typedef typename Dist::policy_type policy_type;
   //
   // Need to begin by bracketing the maxima of the PDF:
   //
   value_type maxval;
   value_type upper_bound = guess;
   value_type lower_bound;
   value_type v = pdf(dist, guess);
   if(v == 0)
   {
      //
      // Oops we don't know how to handle this, or even in which
      // direction we should move in, treat as an evaluation error:
      //
      policies::raise_evaluation_error(
         function,
         "Could not locate a starting location for the search for the mode, original guess was %1%", guess, policy_type());
   }
   do
   {
      maxval = v;
      if(step != 0)
         upper_bound += step;
      else
         upper_bound *= 2;
      v = pdf(dist, upper_bound);
   }while(maxval < v);

   lower_bound = upper_bound;
   do
   {
      maxval = v;
      if(step != 0)
         lower_bound -= step;
      else
         lower_bound /= 2;
      v = pdf(dist, lower_bound);
   }while(maxval < v);

   boost::uintmax_t max_iter = policies::get_max_root_iterations<policy_type>();

   value_type result = tools::brent_find_minima(
      pdf_minimizer<Dist>(dist),
      lower_bound,
      upper_bound,
      policies::digits<value_type, policy_type>(),
      max_iter).first;
   if(max_iter >= policies::get_max_root_iterations<policy_type>())
   {
      return policies::raise_evaluation_error<value_type>(
         function,
         "Unable to locate solution in a reasonable time:"
         " either there is no answer to the mode of the distribution"
         " or the answer is infinite.  Current best guess is %1%", result, policy_type());
   }
   return result;
}
//
// As above,but confined to the interval [0,1]:
//
template <class Dist>
typename Dist::value_type generic_find_mode_01(const Dist& dist, typename Dist::value_type guess, const char* function)
{
   BOOST_MATH_STD_USING
   typedef typename Dist::value_type value_type;
   typedef typename Dist::policy_type policy_type;
   //
   // Need to begin by bracketing the maxima of the PDF:
   //
   value_type maxval;
   value_type upper_bound = guess;
   value_type lower_bound;
   value_type v = pdf(dist, guess);
   do
   {
      maxval = v;
      upper_bound = 1 - (1 - upper_bound) / 2;
      if(upper_bound == 1)
         return 1;
      v = pdf(dist, upper_bound);
   }while(maxval < v);

   lower_bound = upper_bound;
   do
   {
      maxval = v;
      lower_bound /= 2;
      if(lower_bound < tools::min_value<value_type>())
         return 0;
      v = pdf(dist, lower_bound);
   }while(maxval < v);

   boost::uintmax_t max_iter = policies::get_max_root_iterations<policy_type>();

   value_type result = tools::brent_find_minima(
      pdf_minimizer<Dist>(dist),
      lower_bound,
      upper_bound,
      policies::digits<value_type, policy_type>(),
      max_iter).first;
   if(max_iter >= policies::get_max_root_iterations<policy_type>())
   {
      return policies::raise_evaluation_error<value_type>(
         function,
         "Unable to locate solution in a reasonable time:"
         " either there is no answer to the mode of the distribution"
         " or the answer is infinite.  Current best guess is %1%", result, policy_type());
   }
   return result;
}

}}} // namespaces

#endif // BOOST_MATH_DISTRIBUTIONS_DETAIL_MODE_HPP


/*
   Boneh, Di Crescenzo, Ostrovsky & Persiano
   Public Key Encryption with keyword Search

   See http://eprint.iacr.org/2003/195.pdf
   Section 3.1

   (From reading the protocol to a working implementation - 10 minutes!)

   Compile with modules as specified below

   For MR_PAIRING_CP curve
   cl /O2 /GX peks.cpp cp_pair.cpp zzn2.cpp big.cpp zzn.cpp ecn.cpp miracl.lib

   For MR_PAIRING_MNT curve
   cl /O2 /GX peks.cpp mnt_pair.cpp zzn6a.cpp ecn3.cpp zzn3.cpp zzn2.cpp big.cpp zzn.cpp ecn.cpp miracl.lib

   For MR_PAIRING_BN curve
   cl /O2 /GX peks.cpp bn_pair.cpp zzn12a.cpp ecn2.cpp zzn4.cpp zzn2.cpp big.cpp zzn.cpp ecn.cpp miracl.lib

   For MR_PAIRING_KSS curve
   cl /O2 /GX peks.cpp kss_pair.cpp zzn18.cpp zzn6.cpp ecn3.cpp zzn3.cpp big.cpp zzn.cpp ecn.cpp miracl.lib

   For MR_PAIRING_BLS curve
   cl /O2 /GX peks.cpp bls_pair.cpp zzn24.cpp zzn8.cpp zzn4.cpp zzn2.cpp ecn4.cpp big.cpp zzn.cpp ecn.cpp miracl.lib

   Test program
*/
#include <cstring>
#include "big.h"
#include <ctime>
#include <string>
#include <set>
#include <fstream>
#include <iostream>
#include <stdlib.h>
#include <assert.h>
#include "zzn.h"
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <sstream>
#include "params-peks.h"
#include <zmq.hpp>

using namespace std;

//********* choose just one of these pairs **********
//#define MR_PAIRING_CP      // AES-80 security
//#define AES_SECURITY 80

//#define MR_PAIRING_MNT	// AES-80 security
//#define AES_SECURITY 80

//#define MR_PAIRING_BN    // AES-128 or AES-192 security
//#define AES_SECURITY 128
//#define AES_SECURITY 192

//#define MR_PAIRING_KSS    // AES-192 security
//#define AES_SECURITY 192

//#define MR_PAIRING_BLS    // AES-256 security
//#define AES_SECURITY 256
//*********************************************
#include "pairing_3.h"
#include "ecn.h"
#include "miracl.h"

PFC pfc(AES_SECURITY);  // initialise pairing-friendly curve

void keyGen(string path,G1	&g, Big &alpha, G1& h );
void boneh_Peks(G1 g,G1 h, G1 &PA, Big &PB,string keyword);
void boneh_Trapdoor(Big alpha,G2 &TW, string keyword);
bool boneh_Test(G2 TW, G1 PA, Big PB);
//==========================================================================================================
string to_string(int i)
{
    stringstream ss;
    ss << i;
    return ss.str();
}

const char* const delimiter = "`-=[]\\;\',./~!@#$%^&*()+{}|:\"<>? \n\t\v\b\r\f\a";
bool is_word(std::string& s){
	bool flagd =true, flagc = false;  int count = 0;
	std::string::iterator it = s.begin();
    for (it = s.begin();it != s.end(); ++it)  {
		count++;
		if (std::isalpha(*it)){}
		else{
			flagd = false;
			}
		if (count > 2)  flagc = true;
	}

    return (flagd && flagc);
}
int extractWords_using_find_first_of(TYPE_KEYWORD_DICTIONARY &rKeywordsDictionary,
		TYPE_COUNTER *pKeywordNum,
		ifstream &rFin){

	bool capture = false;
	string line, word;
	while(getline(rFin, line))
    {
		int counter = 0;
		size_t prev = 0, pos;
		boost::trim(line);
		while ((pos = line.find_first_of(delimiter, prev)) != std::string::npos)
		{
			if (pos > prev)
            {
				word = line.substr(prev, pos-prev);
				boost::trim(word);
                //convert the word to lower case
                std::transform(word.begin(),word.end(),word.begin(),::tolower);
				//if (word == "dfossum")
				capture=true;
				if (is_word(word) && capture &&   rKeywordsDictionary.size()<numOfKeywords){
                rKeywordsDictionary.insert(word);
				*pKeywordNum = *pKeywordNum + 1;
				}
			}

			prev = pos+1;
		}
		if (prev < line.length()){
			word = line.substr(prev, std::string::npos);
			boost::trim(word);

            //convert the word to lower case
            std::transform(word.begin(),word.end(),word.begin(),::tolower);
			if (word == "dfossum") capture=true;
			if (is_word(word)&& capture && rKeywordsDictionary.size()<numOfKeywords){
				rKeywordsDictionary.insert(word);
				*pKeywordNum = *pKeywordNum + 1;
			}
		}

	}

	return 0;
}
int extractKeywords(TYPE_KEYWORD_DICTIONARY &rKeywordsDictionary,
		string file_name ,string path)
{
	TYPE_COUNTER keyword_num = 0;
	string fname_with_path;

	// create a file-reading object
	ifstream fin;

	fname_with_path.append(path);
	fname_with_path.append(file_name);
	//fname_with_path.append(".txt");

	// open a file
	fin.open(fname_with_path.c_str(),std::fstream::in);
	if (!fin.good()){
		cout << "No such file exist"<<endl; // exit if file not found
	}
	// Extract keywords from a file
	extractWords_using_find_first_of(rKeywordsDictionary, &keyword_num, fin);

	fin.close();

	return 0;
}
// Function gets the file info and a set of keywords and writes them on the file for the future use.
void writeKeywords(TYPE_KEYWORD_DICTIONARY keywordSet, string file_name, string path){

	string fname_with_path;
	set<string>::iterator it;

	fname_with_path.append(path);
	fname_with_path.append(file_name);
	fname_with_path.append("kw");
	// Opening file to write the keywords on it.
	ofstream myfile;
	myfile.open (fname_with_path.c_str(), ios::out | ios::binary);
	for (it=keywordSet.begin(); it!=keywordSet.end(); ++it){
		myfile << ' ' << *it;
	}
	myfile.close();

}
// Function to read keywords from file
void extractKeywordFile(string *keywordExtracted, int &keywordCounter, string file_name, string path)
{
    ifstream ifile;
	string fname_with_path;
	fname_with_path.append(path);
	fname_with_path.append(file_name);
	fname_with_path.append("kw");
	// Opening file to read the keywords from it.
    ifile.open(fname_with_path.c_str());
    if (!ifile.is_open())
		{cout << "No such file exist"<<endl;}
	string word;
	keywordCounter = 0;
    while (ifile >> word)
    {
		keywordExtracted[keywordCounter] = word;
		keywordCounter++;
     }
	for (int key = 0;key<numOfKeywords;key++)
		cout << file_name << "   "<< keywordExtracted[key]<<endl;
	ifile.close();
}

// Stores the PEKS obtained from the function below in the file.

void writePEKStoFile(string file_name, string path, G1  PA, Big PB){

	char testing[200];
	cout << "PA = " << PA.g << endl;

	Big x = 1;
	Big y = 1;
	PA.g.getxy(x,y);

	string fname_with_path;
	//recreate the file name
	fname_with_path.append(path);
	fname_with_path.append(file_name);
	fname_with_path.append("sc");

	ofstream ofile;
	ofile.open (fname_with_path.c_str(), ios_base::app | ios::binary);
	ofile<<x ;
	testing << x;
	ofile<<endl;
	ofile << y << endl;

	ofile << PB;
	ofile<<endl;
	ofile.close();
}

//Function to encrypt keywords and store them in file using writePEKStoFile function

void KeywordstoPEKS(G1  PA, Big PB,string file_name, string path, G1 g, G1 h, string keywordExtracted[numOfKeywords], int  keywordCounter)
	{
	string keyword;
	for (int i = 0; i < numOfKeywords; i++){
			keyword = keywordExtracted[i];
			boneh_Peks(g, h, PA, PB, keyword);
		//	Finished Boneh Peks, writing PEKS to file";
			writePEKStoFile(file_name,path,PA, PB);

	}

}

//void boneh_Trapdoor(Big alpha,G2 &TW, string keyword)
void myTrapdoorGenerator(Big alpha,G2 &TW, string& keyword){

	string word;
	cout << endl<<"Please keyin the keyword you would like to be searched: ";
	cin >> keyword;
	boneh_Trapdoor(alpha, TW, keyword);

}

void findFileByKeywords(TYPE_KEYWORD_DICTIONARY & listOfFiles, string file_name, string path, G2 TW){
	cout << "We are in Find FILE BY KEYWORD now: "<<endl<<endl;
	int rep;
	bool flagvalid = false;

	int counter = 0;

    string line1 = "";
    string line2 = "";
	string temp1,temp2="",fileNameforList="", fname_with_path;
	fstream ifile;

	for (int filecount = 1; filecount < numOfFiles; filecount++)
	{
		string line1 = "";string line0 = "";
		string line2 = "";
		string line3 = "";

		fstream ifile;
		fname_with_path = "";
		fname_with_path.append(path);
		file_name = "";
		file_name.append("/");
		temp1 = std::to_string(filecount);

		file_name.append(temp1);
		fileNameforList.append(file_name);
		//fileNameforList.append(".");
		file_name.append("sc");
		fname_with_path.append(file_name);

		ifile.open(fname_with_path.c_str());
		if (!ifile.is_open()){cout << "The file "<<fname_with_path<< "is NOT fine \n"<<endl;}
		string word ="";
		string target = "";
		int i = 0;

		while (i < numOfKeywords){

			epoint *temp;

			Big x = 1;
			Big y = 1;

			Big  PB =1; G1 PA;

			ifile>>x;
			ifile>>y;

			ifile>>PB;

			ECn point;
			bool flag = point.set(x,y);
			if(flag == true)
			{
				cout<<"Good!"<<endl;
			}

			PA.g = point;

			i++;

			if (boneh_Test( TW, PA, PB)){
				listOfFiles.insert(file_name);
			}

		}

	}
	ifile.close();

}

void writekeystoFile( string path,const G1 g, const Big alpha, const G1 h){

	cout << "g = " << g.g << endl;
	cout << "h = " << h.g << endl;
	Big x = 1;
	Big y = 1;
	Big xx =1;
	Big yy =1;
	g.g.getxy(x,y);
	h.g.getxy(xx,yy);

	string fname_with_path;
	//recreate the file name
	fname_with_path.append(path);
	fname_with_path.append("/keyfile");
	cout << fname_with_path<<endl;
	ofstream ofile;
	ofile.open (fname_with_path.c_str(),  ios::binary);
	ofile << x ;

	//cout << "/n/n/n/"<< "Hi This is x -----> "<< testing<<endl;
	ofile<<endl;
	ofile << y << endl;
	ofile<< alpha<<endl;
	ofile << xx<<endl;
	ofile << yy<<endl;
	ofile.close();
}
void readKeyFromFile ( string path, G1& g, Big& alpha, G1& h){

		string fname_with_path_key;
		fstream ifile;
		fname_with_path_key = "";
		fname_with_path_key.append(path);

		fname_with_path_key.append("/keyfile");
		ifile.open(fname_with_path_key.c_str());

	epoint *temp;

	Big x = 1;
	Big y = 1;
	Big xx = 1;
	Big yy = 1;

	ifile>>x;
	ifile>>y;

	ifile>>alpha;
	ifile >> xx;
	ifile>>yy;
	ECn point1;
	bool flag = point1.set(x,y);
	if(flag == false)
	{
		cout<<"Error in loading keys!"<<endl;
	}

	g.g = point1;

	ECn point2;
	bool flag1 = point2.set(xx,yy);
	if(flag1 == false)
	{
		cout<<"Error in loading keys1!"<<endl;
	}
	h.g = point2;

	}
//= ===== write trapdoor to file

void writeTrapdoorFile(string fname_with_path,  G2 TW){

	ZZn3 x;
char a[2] = {'0','1'};
ZZn mya(a);
	ZZn3 y;
	ZZn atx;
	ZZn btx;
	ZZn ctx;
	ZZn aty;
	ZZn bty;
	ZZn cty;
	ECn3 mypoint;
	mypoint = TW.g;
	cout << "The value of mypoint is : "<<mypoint<<endl;
	cout << "The value of Trapdoor is : "<<TW.g<<endl;
	mypoint.get(x,y);
	cout << "The value of x is : "<<x<<endl;
	cout << "The value of y is : "<<y<<endl;
	x.get(atx,btx,ctx);
	cout << "The value of a is : "<<atx<<endl;
	cout << "The value of b is : "<<btx<<endl;
	cout << "The value of c is : "<<ctx	<<endl;
	x.get(atx,btx,ctx);
	cout << "The value of aaaa is : "<<a	<<endl;
	y.get(aty,bty,cty);

	//string fname_with_path;
	//recreate the file name

	fname_with_path.append("trapdoor");

	ofstream ofile;
	ofile.open (fname_with_path.c_str(),  ios::binary);
	ofile << atx <<endl<<btx <<endl<<ctx<<endl <<aty<<endl<<bty<<endl<<cty<<endl ;

	ofile.close();
}

void sendTrapdoorToServer(string file_name, string path){
    zmq::context_t context(1);
	zmq::socket_t socket(context,ZMQ_REQ);
	printf("   Connecting to server...");
	//socket.connect ("tcp://localhost:5559");
	socket.connect ("tcp://52.26.80.225:5559");
	printf("OK!\n");
	string line1 = "";
	string fname_with_path;
	size_t sizeLine1 = 0;
	auto start = time_now;
    auto end = time_now;
	float sum = 0.0;

	fstream ifile;
	fname_with_path = "";
	fname_with_path.append(path);
	//file_name = "";
	//file_name.append("/");
	fname_with_path.append(file_name);
	size_t size1;
	ifile.open(fname_with_path.c_str());
	if (!ifile.is_open()){cout << "The file "<<fname_with_path<< "is NOT fine \n"<<endl;}
		while(ifile >> line1){
			cout << "The lines in sendTrapdoor "<<line1<<endl;
			sizeLine1 = (size_t)line1.length();
			zmq::message_t request_line1(sizeLine1);
			memcpy (request_line1.data (), line1.c_str(), sizeLine1);

			start = time_now;
			socket.send (request_line1);
			zmq::message_t reply;
			socket.recv (&reply);
			end = time_now;
			sum += (float)(std::chrono::duration_cast<std::chrono::microseconds>(end-start).count());
		}

	cout<<	"Sending Trapdoor takes   "<<sum<<" microseconds"<<endl;

	ifile.close();
	socket.close();

	}

//=== Send data to server

void sendPeksToServer(string file_name, string serverPath){
    zmq::context_t context(1);
	zmq::socket_t socket(context,ZMQ_REQ);
	printf("   Connecting to server...");
	//socket.connect ("tcp://localhost:5560");
	socket.connect ("tcp://52.26.80.225:5560");
	printf("OK!\n");
	string line1 = "";
    string line2 = ""; string line3 = "";
	string fname_with_path;
	fstream ifile;
	size_t sizeLine1 = 0;
	size_t sizeLine2 = 0;
	size_t sizeLine3 = 0;
	auto start = time_now;
    auto end = time_now;
	float sum = 0.0;
	string line22 = "Hello there how are you0";
	for (int filecount = 1; filecount < numOfFiles; filecount++)
	{

		string temp1 = "";
		fstream ifile;
		fname_with_path = "";
		fname_with_path.append(serverPath);
		file_name = "";
		file_name.append("/");
		temp1 = std::to_string(filecount);
		file_name.append(temp1);
		file_name.append("sc");
		fname_with_path.append(file_name);
		size_t size1, size2,size3;
		ifile.open(fname_with_path.c_str());
		if (!ifile.is_open()){cout << "The file "<<fname_with_path<< "is NOT fine \n"<<endl;}
		int i = 0;
		while (i < numOfKeywords){

			line1 = "";line2 = "";line3 = "";
			ifile>>line1;
			ifile>>line2;
			ifile>>line3;
			sizeLine1 = (size_t)line1.length();
			zmq::message_t request_line1(sizeLine1);
			memcpy (request_line1.data (), line1.c_str(), sizeLine1);

			sizeLine2 = (size_t)line2.length();
			zmq::message_t request_line2(sizeLine2);
			memcpy (request_line2.data (), line2.c_str(), sizeLine2);

			sizeLine3 = (size_t)line3.length();
			zmq::message_t request_line3(sizeLine3);
			memcpy (request_line3.data (), line3.c_str(), sizeLine3);
			start = time_now;
			socket.send (request_line1);
			zmq::message_t reply;
			socket.recv (&reply);
	     	socket.send (request_line2);
			socket.recv (&reply);
			socket.send (request_line3);
			socket.recv (&reply);
			end = time_now;
			sum += (float)(std::chrono::duration_cast<std::chrono::microseconds>(end-start).count());
			i++;
		}

	}

	cout<<	"Sending PEKS, each file takes   "<<sum/(numOfFiles*numOfKeywords)<<" microseconds on average"<<endl;

	cout << "All files have been received and stored in "<< serverPath << "folder "<< endl;
	ifile.close();
	socket.close();

	}

// ================== SERVER SIDE
void writePEKStoFileServer(string fname_with_path, string fname,  string x, string y, string pb){

	//string fname_with_path;
	//recreate the file name

	fname_with_path.append(fname);

	ofstream ofile;
	ofile.open (fname_with_path.c_str(), ios_base::app | ios::binary);
	ofile << x ;

	ofile<<endl;
	ofile << y << endl;

	ofile << pb;
	ofile<<endl;
	ofile.close();
}

void writeTrapdoortoFileServer(string fname_with_path,  string x){

	//string fname_with_path;
	//recreate the file name

	fname_with_path.append("trapdoor");

	ofstream ofile;
	ofile.open (fname_with_path.c_str(), ios_base::app | ios::binary);
	ofile << x ;

	ofile<<endl;

	ofile.close();
}

void receivingPeksServer(string serverPath){

	zmq::context_t context (1);
    zmq::socket_t socket (context, ZMQ_REP);
    socket.bind ("tcp://*:5560");
	int counterKeywords;

	for (int filecount = 1; filecount < numOfFiles; filecount++)
	{

		string temp1 = "";
		ofstream ofile;
		string fname_with_path1 = "";
		fname_with_path1.append(serverPath);
		string file_name = "";
		file_name.append("/");
		temp1 = std::to_string(filecount);
		file_name.append(temp1);
		file_name.append("sc");
		fname_with_path1.append(file_name);
		size_t size1, size2,size3;
		ofile.open (fname_with_path1.c_str(),  ios::binary);

	counterKeywords = 0;
    while (counterKeywords < numOfKeywords) {

        zmq::message_t request1;
        socket.recv (&request1);
        string rpl1 = string(static_cast<char*>(request1.data()), request1.size());
        cout <<  rpl1<< endl;

		zmq::message_t reply1 (8);
        memcpy (reply1.data (), "World", 8);
        socket.send (reply1);

	//sleep(1);
		zmq::message_t request2;
		socket.recv (&request2);
        string rpl2 = string(static_cast<char*>(request2.data()), request2.size());
        cout <<  rpl2<< endl;

        zmq::message_t reply2 (8);
        memcpy (reply2.data (), "World", 8);
        socket.send (reply2);

	//sleep(1);
	//sleep(1);
		zmq::message_t request3;
		socket.recv (&request3);
        string rpl3 = string(static_cast<char*>(request3.data()), request3.size());
        cout <<  rpl3<< endl;

		zmq::message_t reply3 (8);
        memcpy (reply3.data (), "World", 8);
        socket.send (reply3);
		writePEKStoFileServer(serverPath ,file_name, rpl1, rpl2,rpl3);

		counterKeywords++;

        //  Do some 'work'

        //  Send reply back to client

    }
}

	cout << "All files have been received and stored in "<< serverPath << "folder "<< endl;
	socket.close();

}

void receivingTrapdoorServer(string serverPath){

	zmq::context_t context (1);
    zmq::socket_t socket (context, ZMQ_REP);
    socket.bind ("tcp://*:5559");

		ofstream ofile;
		string fname_with_path1 = "";
		fname_with_path1.append(serverPath);
		string file_name = "trapdoor";
		fname_with_path1.append(file_name);
		size_t size1;
		ofile.open (fname_with_path1.c_str(),  ios::binary);

	int numOfLinesTrapdoor = 0;
    while (numOfLinesTrapdoor < 6) {

        zmq::message_t request1;
        socket.recv (&request1);
        string rpl1 = string(static_cast<char*>(request1.data()), request1.size());
        cout <<  rpl1<< endl;

		zmq::message_t reply1 (8);
        memcpy (reply1.data (), "World", 8);
        socket.send (reply1);

		writeTrapdoortoFileServer(serverPath, rpl1);

		numOfLinesTrapdoor++;

    }

	cout << "All files have been received and stored in "<< serverPath << "folder "<< endl;
	socket.close();

}

void findFileByKeywordsServer(TYPE_KEYWORD_DICTIONARY & listOfFiles, string file_name, string path, string serverPath){

	G2 TW;
	string tdfname = "";
	tdfname.append(serverPath);
	tdfname.append("trapdoor");
	ZZn3 x; ZZn3 y;
	ECn3 point;
	Big aux1 = 1;	Big aux2 = 1;
	Big aux3 = 1;	Big aux4 = 1;
	Big aux5 = 1;	Big aux6 = 1;
	ifstream fileTrapdoor;
	fileTrapdoor.open(tdfname.c_str());
	fileTrapdoor >> aux1;fileTrapdoor >> aux2;
	fileTrapdoor >> aux3;fileTrapdoor >> aux4;
	fileTrapdoor >> aux5;fileTrapdoor >> aux6;
	ZZn atx(aux1);
	ZZn btx(aux2);
	ZZn ctx(aux3);
	ZZn aty(aux4);
	ZZn bty(aux5);
	ZZn cty(aux6);
	x.set(atx,btx,ctx);
	y.set(aty,bty,cty);
	point.set(x,y);
	TW.g = point;
	cout << "We are in THE TESTING FUNC and the value is "<<TW.g<<endl;
	sleep(3);
	fileTrapdoor.close();

	int rep;
	bool flagvalid = false;

	int counter = 0;

    string line1 = "";
    string line2 = "";
	string temp1,temp2="",fileNameforList="", fname_with_path;
	fstream ifile;

	for (int filecount = 1; filecount < numOfFiles; filecount++)
	{
		string line1 = "";string line0 = "";
		string line2 = "";
		string line3 = "";

		fstream ifile;
		fname_with_path = "";
		fname_with_path.append(path);
		file_name = "";
		file_name.append("/");
		temp1 = std::to_string(filecount);

		file_name.append(temp1);
		fileNameforList.append(file_name);
		//fileNameforList.append(".");
		file_name.append("sc");
		fname_with_path.append(file_name);

		ifile.open(fname_with_path.c_str());
		if (!ifile.is_open()){cout << "The file "<<fname_with_path<< "is NOT fine \n"<<endl;}
		string word ="";
		string target = "";
		int i = 0;

		while (i < numOfKeywords){

			epoint *temp;

			Big x = 1;
			Big y = 1;

			Big  PB =1; G1 PA;

			ifile>>x;
			ifile>>y;

			ifile>>PB;

			ECn point;
			bool flag = point.set(x,y);
			if(flag == true)
			{
				cout<<"Good!"<<endl;
			}

			PA.g = point;

			i++;

			if (boneh_Test(TW, PA, PB)){
				listOfFiles.insert(file_name);
			}

		}

	}
	ifile.close();

}

//=========================================================================================================

void mainMenu(){
	cout << "\n\n\n\n\n\n\n\n\n\n";
	cout << "Select one of the following: \n";
	cout << "(1) Sender\n";
	cout << "(2) Receiver\n";
	cout << "(3) Server\n";
	cout << "(4) Exit\n";

	cout << "Choice: ";

}
void senderMenu(string path, const bool flagEnc, int numOfFiles){
	//int choiceSender = 0;
	cout << "\n\n\n\n\n\n\n\n\n\n";
	cout << "The default path for the email files is: \n";
	cout << path<<endl;
	cout << "The  number of email files is: ";
	cout << numOfFiles <<endl;
	cout << "Select one of the following: \n";
	cout << "(1) Change the default path\n";
	cout << "(2) Change the default number of files\n";
	cout << "(3) Extract and encrypt keywords for each file "; if (flagEnc){ cout << "(Done)\n"; } else cout << "\n";
	cout << "(4) Send encrypted files to the server\n";
	cout << "(5) Return to the main menu\n";
	cout << "Choice: ";
}

void receiverMenu(const bool flagTrapdoor, string path){
	string filename = "/keyfile";
	string filename_with_path = "";
	filename_with_path.append(path);
	filename_with_path.append(filename);
	cout << filename_with_path<<endl;
	ifstream keyfile (filename_with_path.c_str());

	cout << "\n\n\n\n\n\n\n\n\n\n";
	cout << "Select one of the following: \n";
	cout << "(1) Generate keys ";  if (keyfile) { cout<< "(Done!) \n";} else cout <<"\n";
	cout << "(2) Generate trapdoor "; if (flagTrapdoor){ cout << "(Done!)\n"; } else cout << "\n";
	cout << "(3) Send Trapdoor to server (trapdoor should have been genrated first.)\n";
	cout << "(4) Find files by keyword (trapdoor should have been genrated first.)\n";
	cout << "(5) Return to the main menu\n";
	cout << "Choice: ";
}

void serverMenu(){
	cout << "\n\n\n\n\n\n\n\n\n\n";
	cout << "Select one of the following: \n";
	cout << "(1) Receive Trapdoor\n";
	cout << "(2) Receive PEKS\n";
	cout << "(3) Return to the main menu\n";

	cout << "Choice: ";

}

int main()
{

	bool flagEnc = false; bool flagTrapdoor = false;
	time_t seed;
	time(&seed);
    irand((long)seed);
    clock_t t1, t2;
	float diff;
	G1 g,h,PA,PA1;
	Big alpha,r,PB;
	GT t;
	G2 HW,TW;
	TYPE_KEYWORD_DICTIONARY listOfFiles;
	string keyword;
	const unsigned int nb_trdb = 100;
    const unsigned int nb_crypb = 100;
	unsigned int i;
	string path = "/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles";
	string file_name;
	int keywordCounter;
	int choiceMain, choiceSender,choiceReceiver,serverChoice;
	auto start = time_now;
    auto end = time_now;
	float sum = 0.0;
// This function is run by Alice, the reciever,  to generate pk and sk.

	labelMain: mainMenu( );
	string fname_with_path_key;
	fstream ifiles;
	fname_with_path_key = "";
	fname_with_path_key.append(path);

	fname_with_path_key.append("/keyfile");
	ifiles.open(fname_with_path_key.c_str());
	if (ifiles.is_open()){
	readKeyFromFile (path,g,alpha,h);
	}

	cin >> choiceMain;
	cout << "\n\n\n\n\n\n\n\n\n\n";

if (choiceMain == 1){
	labelSenderMenu:
	senderMenu( path,   flagEnc, numOfFiles);
	cin >> choiceSender;
	if (choiceSender == 1){
		cout << "Please keyin the new path: ";
		cin >> path;
		goto labelSenderMenu;
	}
	else if (choiceSender == 2){
		cout << "Please keyin the number of files: ";
		cin >> numOfFiles;
		goto labelSenderMenu;
	}
	else if (choiceSender == 3){

		for (int j = 1; j < numOfFiles; j++){

			string keywordExtracted[25];
			string temp;
			int rep[numOfKeywords] = {0};
			TYPE_KEYWORD_DICTIONARY myset;
			file_name = "/";
			temp = std::to_string(j);
			file_name.append(temp);
		//	file_name.append(".");
			extractKeywords(myset, file_name, path);
			writeKeywords(myset, file_name, path);
			extractKeywordFile(keywordExtracted, keywordCounter, file_name,path);
			start = time_now;
			KeywordstoPEKS(PA, PB,file_name, path,g,h, keywordExtracted, keywordCounter);
			end = time_now;
			sum += (float)(std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count());
			cout << "Keywords have been successfully extracted and stored in .kw files. \n";
			cout << "Keywords have been encrypted for each file and stored in .sc files. \n";	cout << "\n\n\n\n\n\n\n\n\n\n";

		}
		cout<<"Generate PEKS, each file takes   "<<sum/numOfFiles<<" ms on average"<<endl;
		flagEnc = true;

	}
	else if (choiceSender == 4){
		sendPeksToServer(file_name,path);
		goto labelSenderMenu;
	}

	else if (choiceSender == 5){ goto labelMain;}
	sleep(1);
	goto labelSenderMenu;

}

if (choiceMain == 2){
	labelReceiverMenu:
	receiverMenu(flagTrapdoor,path);
	cin >> choiceReceiver;
	if (choiceReceiver == 1 ){
		keyGen(path, g, alpha,h);
		cout << "Keys have been succefully generated. \n";	cout << "\n\n\n\n\n\n\n\n\n\n";
		sleep (1);
		goto labelReceiverMenu;
	}

	if (choiceReceiver == 2 ){
		flagTrapdoor = true;
		myTrapdoorGenerator( alpha,TW, keyword);
		cout << "Trapdoor has been succefully generated. \n";	cout << "\n\n\n\n\n\n\n\n\n\n";
		sleep (1);
		goto labelReceiverMenu;
	}

	else if (choiceReceiver == 3){ sendTrapdoorToServer("trapdoor", "/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles/server/");  goto labelReceiverMenu;}

		else if (choiceReceiver == 4 ){
		//findFileByKeywords(listOfFiles,  file_name,  path,  TW);
		start = time_now;
		findFileByKeywordsServer( listOfFiles,  file_name,  "/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles/server", "/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles/server2/");
		end = time_now;

		cout<<"Search time    "<<std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count()/numOfFiles<<" ms"<<endl;
		for (set<string>::iterator it = listOfFiles.begin(); it != listOfFiles.end(); ++it) {
			cout <<"The file name is" << *it <<endl<<endl;
		}
		if (listOfFiles.size() == 0) cout << "No email was found for the keyword: "<< keyword << "\n";
			cout << "\n\n\n\n\n\n\n\n\n\n";
			sleep(1);
			 goto labelMain;
	}

	else if (choiceReceiver == 5){ goto labelMain;}

}

if (choiceMain == 3){
	labelServerMenu:
	serverMenu();
	cin >> serverChoice;

	if (serverChoice == 1){
		receivingTrapdoorServer("/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles/server2/"); goto labelServerMenu;
	}
	else if (serverChoice == 2 ){
	receivingPeksServer("/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles/server"); goto labelServerMenu;
	}
	else if (serverChoice == 3) {goto labelMain;}
}

if (choiceMain == 4){exit(1);}

    return 0;
}

void keyGen(string path,G1& g, Big& alpha, G1& h) {
	auto start = time_now;
    auto end = time_now;
	start = time_now;
	pfc.random(g);
	pfc.precomp_for_mult(g);  // precompute on fixed g
	pfc.random(alpha);		  // private key
	h=pfc.mult(g,alpha);      // public key
	pfc.precomp_for_mult(h);
	end = time_now;

	cout<<"Key Generation took    "<<std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count()<<" ms"<<endl;
	writekeystoFile(path,g,alpha,h);
}

void boneh_Peks(G1 g,G1 h, G1 &PA,Big &PB, string keyword)
{
	Big r;
	GT t;
	G2 HW;
	pfc.random(r);
	pfc.hash_and_map(HW,(char *)keyword.c_str());
	t=pfc.pairing(HW,pfc.mult(h,r));
	PA=pfc.mult(g,r);
	PB=pfc.hash_to_aes_key(t);    // [PA,PB] added to ciphertext
}

void boneh_Trapdoor(Big alpha,G2 &TW, string keyword)
{
	auto start = time_now;
    auto end = time_now;
	start = time_now;
	G2 HW;
	pfc.hash_and_map(HW,(char *)keyword.c_str()); // key word we are looking for
	TW=pfc.mult(HW,alpha);
	pfc.precomp_for_pairing(TW);

	end = time_now;

	cout<<"Trapdoor Generation took    "<<std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count()<<" ms"<<endl;

	cout << "This is what you want in file  "<< TW.g<<endl;
	writeTrapdoorFile("/home/bob/Desktop/80BonehPeks/80BonehPeks/textfiles/server/",TW);
}
bool boneh_Test(G2 TW, G1 PA, Big PB)
{
	if (pfc.hash_to_aes_key(pfc.pairing(TW,PA))==PB){
		cout << "yes it does work" << endl;
		return true;
	}

	else{
		cout << "It did not work"<<endl;
		return false;
	}

}


#include <iostream>
#include <cstdio>
#include <string>
#include <vector>
#include <fstream>
#include <Eigen/Eigenvalues>
#include <opencv2/opencv.hpp>
#include <algorithm>
#include <math.h>

#include "information.h"
#include "functionn.h"
#include "read.h"

using namespace std;
using Eigen::MatrixXd;
using Eigen::EigenSolver;
using Eigen::VectorXcd;
using Eigen::MatrixXcd;

string input,indicator1,indicator2 ;
string dataset = "ATT/";
string dash = "_";
string file_type = ".png";

double all_image_matrix[number_of_dataset][figure_height*figure_width] = { 0 };             //Create a vector*vector to store all image
vector<double> average_face;
vector<double> each_eigenvector(figure_height*figure_width,0);
vector<vector<double>> eigenvectors(number_of_eigenvectors,each_eigenvector);
vector<vector<double>> Wfldupeigenvectors(number_of_eigenvectors, each_eigenvector);
vector<vector<double>> Wflddowneigenvectors(number_of_eigenvectors, each_eigenvector);
double class_average_face[number_of_face_class][figure_height*figure_width] = { 0 };
double class_w[number_of_face_class][number_of_eigenvectors] = { 0 };
int test_index;
int main() {

	image_content image;                                                   //Create a class call "image_content" to store the information of image
	double each[figure_height*figure_width] = {0};                       //Create a vector to store one image

	//Data all_data;

	//Read and store the data from figure and store it into to image_matrix
	for (int i = 0, q = 1, w = 1; i < number_of_dataset && q < 41 && w < 11; i++) {
		indicator1 = to_string(q);
		indicator2 = to_string(w);
		input = dataset + indicator1 + dash + indicator2 + file_type;      //Read the file by substituting the character.
		image = read::figure(input);                                       //Via opencv to read the image and acquire the data in class "image_content" form.
		for (int j = 0; j < figure_height*figure_width; j++) {
			each[j] = (image.content[j]);                                  // Assign the data from class "image_content" to vector variable "each".
		}

		//Store information of each figure to the image_matrix
		for (int k = 0; k < figure_height*figure_width; k++) {
			all_image_matrix[i][k] = each[k];
		}

		//This is just for the pointer.
		if (w < 11) { w++; }
		if (w == 11) { w = 1; q++; }
	}

	average_face = functionn::averagee(all_image_matrix);    //Feed the image_matrix to the function and acquire the average face.

	functionn::save_one_face(average_face);                     //This part is saving the average face to a txt, in order to double check the average face is normal or not.

	//********
	functionn::calculate_St();
	functionn::calculate_Sb();
	functionn::calculate_Sw();
	//return Wopt

	//We got A!  (A = A_matrix)
	//********

	///////////////////////////////////////////////////////////////////////////////
	// Let's calculate eigenvectors!

	//We have two ways to calculate eigenvectors, first is via Matlab, the other is eigen.

	//This part is reading the eigenvector and eigenvalue which are calculated from matlab.
	//Read 6 eigenvalues and its relevant eigenvetors.
	string file_name = "For_matlab/eigenvector";
	string file_type = ".txt";
	ifstream eigenvector_file;
	for (int i = 1; i <= number_of_eigenvectors; i++) {
		int j = 0;
		cout << file_name + to_string(i) + file_type << endl;
		eigenvector_file.open(file_name + to_string(i) + file_type, ios::in);
		string line;
		double tem;
		while (getline(eigenvector_file, line)) {
			tem = stod(line);
			eigenvectors[i - 1][j] = tem;
			j++;
		}
		eigenvector_file.close();
	}

	//Now we have the essiential item to recognize face!
	int per = 0;
	int test_index = 0;
	while (true) {
		//cout << "Please tell me which image do you want to test? (0~49) or enter -1 to exit" << endl;
		//cin >> test_index;
		//cout << endl;

		class_information all;
		//First calculate the aveverge face of each class
		all.calculate_all_average_face();

		//Let's calculate each omega of all classes
		all.calculate_all_w();

		classify result;

		result.calculate_w(all_image_matrix[test_index]);
		result.criteria_each();                          //Calculate the Euclidian distance
		int d_ans=result.determine_result();

		int ans;
		if (test_index < 10) {
			ans = 0;
		}
		else if (test_index < 20) {
			ans = 1;
		}
		else if (test_index < 30) {
			ans = 2;
		}
		else if (test_index < 40) {
			ans = 3;
		}
		else if (test_index < 50) {
			ans = 4;
		}
		else if (test_index < 60) {
			ans = 5;
		}
		else if (test_index < 70) {
			ans = 6;
		}
		else if (test_index < 80) {
			ans = 7;
		}
		else if (test_index < 90) {
			ans = 8;
		}
		else if (test_index < 100) {
			ans = 9;
		}

		//cout << "And the ground truth is " << ans << " !" << endl;

		//cout << endl;

		if (ans == d_ans) {
			per = per + 1;
		}

		if (test_index == number_of_dataset) {
			double acc = double(per) / double(number_of_dataset);
			cout << "Accuracy=" << acc*100 << " %" << " !" << endl;
			break;
		}
		test_index = test_index + 1;
	}
	system("pause");
}


/*
 * CommutingSetGenerator.hpp
 *
 *  Created on: Aug 4, 2017
 *      Author: aqw
 */

#ifndef VQE_TRANSFORMATION_COMMUTINGSETGENERATOR_HPP_
#define VQE_TRANSFORMATION_COMMUTINGSETGENERATOR_HPP_

#include "PauliOperator.hpp"
#include <Eigen/Core>
#include <numeric>

namespace xacc {

namespace vqe {
class CommutingSetGenerator {

private:

	std::pair<Eigen::VectorXi, Eigen::VectorXi> bv(Term& op, int nQubits) {
		Eigen::VectorXi vx = Eigen::VectorXi::Zero(nQubits);
		Eigen::VectorXi vz = Eigen::VectorXi::Zero(nQubits);

		for (auto term : op.ops()) {
			if (term.second == "X") {
				vx(term.first) += 1;
			} else if (term.second == "Z") {
				vz(term.first) += 1;
			} else if (term.second == "Y") {
				vx(term.first) += 1;
				vz(term.first) += 1;
			}
		}

		return std::make_pair(vx, vz);
	};

	int bv_commutator(Term& term1, Term& term2, int nQubits) {
			auto pair1 = bv(term1, nQubits);
			auto pair2 = bv(term2, nQubits);
			auto scalar = pair1.first.dot(pair2.second) + pair1.second.dot(pair2.first);
			return scalar % 2;
		};

public:

	std::vector<std::vector<Term>> getCommutingSet(
			PauliOperator& composite, int n_qubits) {

		std::vector<std::vector<Term>> commuting_ops;
		std::vector<Term> allTerms;
		for (auto& kv : composite.getTerms()) {
			allTerms.push_back(kv.second);
		}

		for (int i = 0; i < allTerms.size(); i++) {

			auto t_i = allTerms[i];

			if (i == 0) {
				commuting_ops.push_back({t_i});
			} else {
				auto comm_ticker = 0;
				for (int j = 0; j < commuting_ops.size(); j++) {
					auto j_op_list = commuting_ops[j];
					int sum = 0;
					int innerCounter = 0;
					for (auto j_op : j_op_list) {
						auto t_jopPtr = allTerms[innerCounter];
						sum += bv_commutator(t_i, t_jopPtr,
								n_qubits);
						innerCounter++;
					}

					if (sum == 0) {
						commuting_ops[j].push_back(t_i);
						comm_ticker += 1;
						break;
					}
				}

				if (comm_ticker == 0) {
					commuting_ops.push_back({t_i});
				}
			}
		}

		return commuting_ops;
	}

};

}
}

#endif


#include <NTL/lzz_pE.h>

#include <NTL/new.h>

NTL_START_IMPL

zz_pEInfoT::zz_pEInfoT(const zz_pX& NewP)
{
   build(p, NewP);

   _card_base = zz_p::modulus();
   _card_exp = deg(NewP);
}

const ZZ& zz_pE::cardinality()
{
   if (!zz_pEInfo) LogicError("zz_pE::cardinality: undefined modulus");

   do { // NOTE: thread safe lazy init
      Lazy<ZZ>::Builder builder(zz_pEInfo->_card);
      if (!builder()) break;
      UniquePtr<ZZ> p;
      p.make();
      power(*p, zz_pEInfo->_card_base, zz_pEInfo->_card_exp);
      builder.move(p);
   } while (0);

   return *zz_pEInfo->_card;
}

NTL_THREAD_LOCAL SmartPtr<zz_pEInfoT> zz_pEInfo = 0;

void zz_pE::init(const zz_pX& p)
{
   zz_pEContext c(p);
   c.restore();
}

void zz_pEContext::save()
{
   ptr = zz_pEInfo;
}

void zz_pEContext::restore() const
{
   zz_pEInfo = ptr;
}

zz_pEBak::~zz_pEBak()
{
   if (MustRestore) c.restore();
}

void zz_pEBak::save()
{
   c.save();
   MustRestore = true;
}

void zz_pEBak::restore()
{
   c.restore();
   MustRestore = false;
}

const zz_pE& zz_pE::zero()
{
   NTL_THREAD_LOCAL static zz_pE z(INIT_NO_ALLOC);
   return z;
}

istream& operator>>(istream& s, zz_pE& x)
{
   zz_pX y;

   NTL_INPUT_CHECK_RET(s, s >> y);
   conv(x, y);

   return s;
}

void div(zz_pE& x, const zz_pE& a, const zz_pE& b)
{
   zz_pE t;

   inv(t, b);
   mul(x, a, t);
}

void div(zz_pE& x, const zz_pE& a, long b)
{
   NTL_zz_pRegister(B);
   B = b;
   inv(B, B);
   mul(x, a, B);
}

void div(zz_pE& x, const zz_pE& a, const zz_p& b)
{
   NTL_zz_pRegister(B);
   B = b;
   inv(B, B);
   mul(x, a, B);
}

void div(zz_pE& x, long a, const zz_pE& b)
{
   zz_pE t;
   inv(t, b);
   mul(x, a, t);
}

void div(zz_pE& x, const zz_p& a, const zz_pE& b)
{
   zz_pE t;
   inv(t, b);
   mul(x, a, t);
}

void inv(zz_pE& x, const zz_pE& a)
{
   InvMod(x._zz_pE__rep, a._zz_pE__rep, zz_pE::modulus());
}

NTL_END_IMPL


#ifndef GRAPH_HPP
#define GRAPH_HPP

#include <algorithm>           // std::all_of
#include <Eigen/Dense>
#include <limits>

#include "readGraphOBJ.hpp"
#include "writeGraphOBJ.hpp"
#include "graphOptions.hpp"

/* TODO: check adjacency_list
 *       add linear triconnectivity implementation
 *
 * 		- merge 	Graph(Eigen::MatrixXd nodes, Eigen::MatrixXi adjacency_matrix)
 * 					Graph(Eigen::MatrixXd nodes, Eigen::Matrix<int, Eigen::Dynamic, 2> edges)
 */
namespace libgraphcpp
{

	class Graph
	{
	private:
		graphOptions opts_;                                                      // Store all visualization infos

		// basic structure for edges and nodes
		Eigen::MatrixXd nodes_;                                             // should be a N by 3 matrix of doubles
		Eigen::MatrixXi edges_;                                             // should be a M by 2 matrix of integers
		int num_nodes_;                                                     // set to N
		int num_edges_;                                                     // set to M
		double scale_;                                                      // used to trim the tree in simplify_tree
		double cycle_ratio_ = 2;
        double triangle_ratio_ = 0.9;

		// structures used for fast circulation through data
		std::vector< std::vector<int> > adjacency_list_;                    // contains for each nodes, its nodes neighbors
		std::vector< std::vector<int> > adjacency_edge_list_;               // contains for each nodes, its edges neighbors
		Eigen::VectorXd edges_length_;                                      // used only for Dijkstra
		Eigen::MatrixXi adjacency_matrix_;

		// connectivity properties
		int is_connected_ = -1;                                             // 0 no, 1 yes, -1 undefined
		int is_biconnected_ = -1;                                           // 0 no, 1 yes, -1 undefined
		int is_triconnected_ = -1;                                          // 0 no, 1 yes, -1 undefined
		int has_bridges_ = -1;                                              // 0 no, 1 yes, -1 undefined
		int has_cycle_ = -1;                                                // 0 no, 1 yes, -1 undefined

		// storing of the cut sets
		std::vector< int > one_cut_vertices_;                               // set of articulation points : vector of nodes ids
		std::vector< std::pair<int, int> > two_cut_vertices_;               // set of two-cut vertices    : vector of nodes ids pair
		std::vector<int> bridges_;                                          // set of briges              : vector of edges ids
		std::vector< std::vector<int> > cycles_;                            // set of cycles

		// internal functions: tools (defined at the bottom of the file)
		inline void removeRow(Eigen::MatrixXd& matrix, unsigned int rowToRemove);
		inline void removeRow(Eigen::MatrixXi& matrix, unsigned int rowToRemove);
		inline void removeDuplicates(std::vector<std::pair<int, int>>& v);
		inline bool is_element_in_vector(int a, std::vector<int> & A);

		// internal functions: iterative functions (defined at the bottom of the file)
		inline void DFSUtil(int u, std::vector< std::vector<int> > adj, std::vector<bool> &visited);
		inline void APUtil(int u, std::vector<bool> & visited, int disc[], int low[], std::vector<int> & parent, std::vector<bool> & ap);
		inline void bridgeUtil(int u, std::vector<bool> & visited, int disc[], int low[], std::vector<int> & parent, std::vector<int> & bridges);

	public:
		// creators
		inline Graph(std::string file_name);
		inline Graph(std::string file_name, graphOptions opts);
		inline Graph(Eigen::MatrixXd nodes, Eigen::MatrixXi edges);
		inline Graph(Eigen::MatrixXd nodes, Eigen::MatrixXi edges, graphOptions opts);

		// destructor
		inline ~Graph(){};

		// initialisation of the private variables
		inline void init();
		inline void set_adjacency_lists();

		// save graph as OBJ file
		inline void save(std::string output_file);
		inline void print_isolated_vertices();

		// accessors
		int num_nodes();
		int num_edges();
		Eigen::MatrixXd get_nodes();
		Eigen::Vector3d get_node(int i);
		Eigen::MatrixXi get_edges();
		Eigen::Vector2i get_edge(int i);
		std::vector <int> get_adjacency_list(int i);
		int get_adjacency_list(int i, int j);
        int find_edge_from_nodes(int node_1, int node_2);

		// modifiers for nodes
		inline void add_node(Eigen::Vector3d node, std::vector<int> neighbours);
		inline void remove_node(int nodeToRemove);
		inline void merge_nodes(std::vector<int> nodes);
		inline void update_node(int node_id, Eigen::Vector3d new_node);
		inline void update_nodes(Eigen::MatrixXd new_nodes);

		// modifiers for edges
		inline void add_edge(Eigen::Vector2i edge);
		inline void remove_edge(int edgeToRemove);
		inline void collapse_edge(int edge_id);

		/* CONNECTIVITY TESTS */
		inline void connectivity_tests();
		inline bool is_connected();
		inline bool is_biconnected(std::vector<int>& one_cut_vertices);
		inline bool is_biconnected();
		inline bool is_triconnected(std::vector< std::pair<int, int> >& two_cut_vertices);
		inline bool is_triconnected();
		inline bool has_bridges(std::vector<int>& bridges);
		inline bool has_bridges();
		inline bool has_cycles(std::vector <std::vector<int>>& cycle_basis, std::vector<double>& cycle_lengths);
		inline bool has_cycles();

		// graph manipulation
		inline std::vector<std::vector <int> > make_tree(Eigen::MatrixXi& deleted_edges);
		inline std::vector<std::vector <int> > make_tree();
		inline void simplify_tree();
		inline void symplify_graph();
        inline void remove_flat_triangles();
		inline void transform (double scale, Eigen::Vector3d move);
		inline double dijkstra(int source, int target, std::vector<int>& node_path);
		inline double dijkstra(int source, int target);

		/* TO BE REMOVED? This apply only for directional graph */
		inline int edge_source(int i);
		inline int edge_target(int i);
		inline void swap_edge(int i);

	};

	inline Graph::Graph(std::string file_name)
	{
		readGraphOBJ(file_name, nodes_, edges_);

		init();
	};

	// overload with options
	inline Graph::Graph(std::string file_name, graphOptions opts) : Graph(file_name)
	{
		opts_ = opts;
	};

	inline Graph::Graph(Eigen::MatrixXd nodes, Eigen::MatrixXi edges)
	{
		// test if edges is m by 2 (explicit edges) or n by n (adjacency matrix)
		if ( edges.cols() == 2 )
		{
			nodes_ = nodes;
			edges_ = edges;
		}
		else
		{
			Eigen::MatrixXi adjacency_matrix;
			adjacency_matrix = edges;

			if (adjacency_matrix.rows() != adjacency_matrix.cols() || adjacency_matrix.rows() != nodes.rows()) {
				std::cout << "\nLibGraphCpp error: wrong input size in the class definition\n";
				std::cout << "Error thrown while assessing: adjacency_matrix.rows() != adjacency_matrix.cols() || adjacency_matrix.rows() != nodes.rows()\n ";
				std::cout << "size of the nodes matrix: " << nodes.rows() << ", " << nodes.cols() << "\n";
				std::cout << "size of the edges matrix: " << edges.rows() << ", " << edges.cols() << "\n";
				std::exit(EXIT_FAILURE);
			}
			if (adjacency_matrix.transpose() != adjacency_matrix)
			{
				std::cout << "\nLibGraphCpp error: the adjacency_matrix should be symmetric\n ";
				std::exit(EXIT_FAILURE);
			}

			nodes_ = nodes;
			Eigen::MatrixXi edges((adjacency_matrix.array() != 0).count(), 2);

			// explore the upper triangle of the adjacency_matrix (without the diagonal)
			int num_edges = 0;
			for (int i=0; i<adjacency_matrix.rows(); i++)
				for (int j=i+1; j<adjacency_matrix.cols(); j++) {
					if (adjacency_matrix(i,j) != 0)
					{
						edges(num_edges, 0) = i;
						edges(num_edges, 1) = j;
						num_edges ++;
					}
				}
			edges.conservativeResize(num_edges, 2);

			edges_ = edges;
			adjacency_matrix_  = adjacency_matrix;
		}

		init();
	};

	// overload with options
	inline Graph::Graph(Eigen::MatrixXd nodes, Eigen::MatrixXi edges, graphOptions opts) : Graph(nodes, edges)
	{
		opts_ = opts;
	};

	// initialisation of the private variables
	inline void Graph::init()
	{
		if (nodes_.cols()!=3 || edges_.cols()!=2) {
			std::cout << "\nLibGraphCpp error: wrong graph dimensions in the class initialization" << std::endl;
			std::cout << "nodes size: " << nodes_.rows() << " * " << nodes_.cols()<< std::endl;
			std::cout << "edges size: " << edges_.rows() << " * " << edges_.cols()<< std::endl;
			std::exit(EXIT_FAILURE);
		}

		one_cut_vertices_.clear();
		two_cut_vertices_.clear();
		bridges_.clear();

		// set up properties
		num_nodes_ = nodes_.rows();
		num_edges_ = edges_.rows();

		// get scale
		Eigen::MatrixXd max_point = nodes_.colwise().maxCoeff();
		Eigen::MatrixXd min_point = nodes_.colwise().minCoeff();
		scale_ = (max_point - min_point).norm();

		// set up edges_length
		edges_length_ = Eigen::VectorXd::Zero(num_edges_);
		for (int i=0; i<num_edges_; i++) {
			if ( (edges_(i, 0) >= num_nodes_) || (edges_(i, 1) >= num_nodes_) ||
					(edges_(i, 0) < 0) || (edges_(i, 1) < 0) ) {
				std::cout << "\nLibGraphCpp error: wrong edge given in the class initialization" << std::endl;
				std::cout << "the edge: (" << edges_(i, 0) << ", " << edges_(i, 1) << ") does not works for " << num_nodes_ << " nodes." << std::endl;
				std::exit(EXIT_FAILURE);
			}
			edges_length_(i) = (nodes_.row(edges_(i,0)) - nodes_.row(edges_(i,1)) ).norm();
		}

		// set up the adjacency list
		set_adjacency_lists();
	};

	inline void Graph::set_adjacency_lists()
	{
		// TODO: - the if statement should be removed for undirected graphs as they are not needed
		//       - is the adjacency_edge_list_ needed?

		// make sure the lists are empty to start with
		adjacency_list_.clear();
		adjacency_edge_list_.clear();

		adjacency_list_.resize(num_nodes_);
		adjacency_edge_list_.resize(num_nodes_);

		for (int i=0; i<num_edges_; i++)
		{
			// the if statements check if the node has already been inserted
			if (std::find (adjacency_list_[edges_(i, 0)].begin(), adjacency_list_[edges_(i, 0)].end(), edges_(i, 1))==adjacency_list_[edges_(i, 0)].end())
				adjacency_list_[edges_(i, 0)].push_back(edges_(i, 1));
			if (std::find (adjacency_list_[edges_(i, 1)].begin(), adjacency_list_[edges_(i, 1)].end(), edges_(i, 0))==adjacency_list_[edges_(i, 1)].end())
				adjacency_list_[edges_(i, 1)].push_back(edges_(i, 0));
			adjacency_edge_list_[edges_(i, 0)].push_back(i);
			adjacency_edge_list_[edges_(i, 1)].push_back(i);
		}
	};

	// save graph as OBJ file
	inline void Graph::save(std::string output_file)
	{
		writeGraphOBJ(nodes_, edges_, output_file);
	};

	inline void Graph::print_isolated_vertices()
	{
		for (int i=0; i<num_nodes_; i++)
			if (adjacency_list_[i].size() == 0)
				std::cout << "Isolated vertices at: " << i << std::endl;
	};

	// accessors
	inline int Graph::num_nodes() { return num_nodes_; };
	inline int Graph::num_edges() { return num_edges_; };
	inline Eigen::MatrixXd Graph::get_nodes() { return nodes_; };
	inline Eigen::Vector3d Graph::get_node(int i) { return nodes_.row(i); };
	inline Eigen::MatrixXi Graph::get_edges() { return edges_; };
	inline Eigen::Vector2i Graph::get_edge(int i) { return edges_.row(i); };
	inline std::vector <int> Graph::get_adjacency_list(int i) { return adjacency_list_[i]; };
	inline int Graph::get_adjacency_list(int i, int j) { return adjacency_list_[i][j]; };

	inline int Graph::find_edge_from_nodes(int node_1, int node_2)
	{
		for (int i=0; i<num_edges_; i++) {
			if (edges_(i, 0) == node_1 && edges_(i, 1) == node_2)
				return i;
			if (edges_(i, 0) == node_2 && edges_(i, 1) == node_1)
				return i;
		}
		return -1;
	};

	// modifiers for nodes
	inline void Graph::add_node(Eigen::Vector3d node, std::vector<int> neighbours)
	{
		// add node
		Eigen::MatrixXd temp_nodes = nodes_;
		nodes_.resize(nodes_.rows()+1, 3);
		nodes_ << temp_nodes, node.transpose();

		// add related edges
		Eigen::MatrixXi temp_edges = edges_;
		Eigen::MatrixXi edges_to_add(neighbours.size(), 2);
		for (int edge_iterator=0; edge_iterator<neighbours.size(); edge_iterator++)
			edges_to_add.row(edge_iterator) << nodes_.rows()-1, neighbours[edge_iterator];

		edges_.resize(temp_edges.rows()+edges_to_add.rows(), 2);
		edges_ << temp_edges, edges_to_add;

		init();
	};

	inline void Graph::remove_node(int nodeToRemove)
	{
		// remove node
		removeRow(nodes_, nodeToRemove);

		// remove related edges
		std::vector<int> edges_to_remove = adjacency_edge_list_[nodeToRemove];
		std::sort(edges_to_remove.begin(), edges_to_remove.end(), std::greater<int>());
		for (int edge_iterator=0; edge_iterator<edges_to_remove.size(); edge_iterator++)
			removeRow(edges_, edges_to_remove[edge_iterator]);

		// update the edge nodes index (we have one less node now)
		for (int i=0; i<edges_.rows(); i++)
			for (int j=0; j<edges_.cols(); j++)
				if (edges_(i,j) >= nodeToRemove)
					edges_(i,j)--;

		init();
	};

	inline void Graph::merge_nodes(std::vector<int> nodes)
	{
		// make sure there is no duplicates
		sort( nodes.begin(), nodes.end() );
		nodes.erase( unique( nodes.begin(), nodes.end() ), nodes.end() );

		// store connected elements first
		std::vector<int> neighbours;
		for (int node : nodes)
			for (int neighour : adjacency_list_[node])
				neighbours.push_back(neighour);

		std::sort(neighbours.begin(), neighbours.end());
		neighbours.erase( std::unique( neighbours.begin(), neighbours.end() ), neighbours.end() );

		// find node position
		Eigen::Vector3d merged_node;
		merged_node << 0,0,0;
		for (int node : nodes)
			merged_node += nodes_.row(node);
		merged_node /= nodes.size();

		// add merged node (it is stacked at the end so it has to be done before deleting the nodes)
		add_node(merged_node, neighbours);

		// delete other nodes
		std::sort(nodes.begin(), nodes.end(), std::greater<int> ());
		for (int node : nodes)
			remove_node(node);
	};

	inline void Graph::update_node(int node_id, Eigen::Vector3d new_node)
	{
		nodes_.row(node_id) = new_node;
	}

	inline void Graph::update_nodes(Eigen::MatrixXd new_nodes)
	{
		if (nodes_.rows() == new_nodes.rows() && nodes_.cols() == new_nodes.cols()) {
			nodes_ = new_nodes;
		} else {
			std::cout << "Error: wrong dimension:\n";
			std::cout << "nodes_.rows() == new_nodes.rows() && nodes_.cols() == new_nodes.cols() returned False \n ";
			std::exit(0);
		}
	};

	// modifiers for edges
	inline void Graph::add_edge(Eigen::Vector2i edge)
	{
		// add node
		Eigen::MatrixXi temp_edges = edges_;
		edges_.resize(edges_.rows()+1, 3);
		edges_ << temp_edges, edge.transpose();

		init();
	};

	inline void Graph::remove_edge(int edgeToRemove)
	{
		// remove edge
		removeRow(edges_, edgeToRemove);

		init();
	};

	// replace an edge and its two connected nodes by a single node
	inline void Graph::collapse_edge(int edge_id)
	{
		// get nodes_id:
		int node_1 = edges_(edge_id, 0);
		int node_2 = edges_(edge_id, 1);

		// merge the two previous ones
		Eigen::Vector3d fused_node = (nodes_.row(node_1) + nodes_.row(node_2) ) / 2;

		// get the connected neighbours
		std::vector<int> neighbours;
		neighbours = adjacency_list_[node_1];
		neighbours.insert( neighbours.end(), adjacency_list_[node_2].begin(), adjacency_list_[node_2].end() );

		// remove old nodes
		neighbours.erase(std::remove(neighbours.begin(), neighbours.end(), node_1), neighbours.end());
		neighbours.erase(std::remove(neighbours.begin(), neighbours.end(), node_2), neighbours.end());

		sort( neighbours.begin(), neighbours.end() );
		neighbours.erase( unique( neighbours.begin(), neighbours.end() ), neighbours.end() );

		// add node
		add_node(fused_node, neighbours);

		// remove the previous nodes (the order matter)
		if (node_2<node_1) {
			remove_node(node_1);
			remove_node(node_2);
		} else {
			remove_node(node_2);
			remove_node(node_1);
		}
	};

	/* CONNECTIVITY TESTS */
	inline void Graph::connectivity_tests()
	{
		is_connected();
		is_biconnected();
		is_triconnected();
		has_bridges();
	};

	inline bool Graph::is_connected()
	{
		if (is_connected_ == -1) {
			// check for graph connectivity using DFS:
			std::vector<bool> visited(num_nodes_, false);
			DFSUtil(0, adjacency_list_, visited);
			is_connected_ = std::all_of(visited.begin(), visited.end(), [](bool v) { return v; });

			if (opts_.verbose)
				std::cout << "graph is connected: " << is_connected_ << std::endl;

			// if not connected, update the next ones
			if (is_connected_ == 0) {
				is_biconnected_ = 0;
				is_triconnected_ = 0;
			}
		}
		return is_connected_;
	};

	// return the set of one cut vertices
	inline bool Graph::is_biconnected(std::vector<int>& one_cut_vertices)
	{
		if (is_biconnected_ == -1) {
			// check for 2 node connectivity:
			std::vector<bool> visited(num_nodes_, false);
			std::vector<int> parent(num_nodes_, -1);
			std::vector<bool> ap(num_nodes_, false);
			int *disc = new int[num_nodes_];
			int *low = new int[num_nodes_];
			// Call the recursive helper function to find articulation points
			// in DFS tree rooted with vertex '0'
			for (int i = 0; i < num_nodes_; i++)
				if (visited[i] == false)
					APUtil(i, visited, disc, low, parent, ap);

			// Now ap[] contains articulation points, print them and store them into one_cut_vertices_
			one_cut_vertices.clear();
			for (int i = 0; i < num_nodes_; i++)
				if (ap[i] == true) {
					one_cut_vertices.push_back(i);
					if (opts_.verbose)
						std::cout << "Articulation point " << i << " at: "  << nodes_.row(i) << std::endl;
				}
			is_biconnected_ = std::none_of(ap.begin(), ap.end(), [](bool v) { return v; });

			one_cut_vertices_ = one_cut_vertices;
		} else {
			one_cut_vertices = one_cut_vertices_;
		}

		if (opts_.verbose)
			std::cout << "graph is 2-connected: " << is_biconnected_ << std::endl;

		return is_biconnected_;
	};

	// overload is_biconnected
	inline bool Graph::is_biconnected()
	{
		std::vector< int > one_cut_vertices;
		return is_biconnected(one_cut_vertices);
	};

	// return the set of two cut vertices
	inline bool Graph::is_triconnected(std::vector< std::pair<int, int> >& two_cut_vertices)
	{
		/* this function run in quadratic time, this is not the most efficient way to do it
		* for alternative, see these papers:
		* - Finding the Triconnected Components of a Graph (1972)
		* - A Linear Time Implementation of SPQR-Trees (2000)
		* and these implementations:
		* - http://www.ogdf.net/doku.php
		* - https://github.com/adrianN/Triconnectivity
		*/
		graphOptions opts = opts_;
		opts.verbose = false;
		if (is_triconnected_ == -1) {
			is_triconnected_ = true;
			for (int i=0; i<num_nodes_; i++) {
				std::vector< int > one_cut_vertices;
				Graph reduced_graph(nodes_, edges_, opts);
				reduced_graph.init();
				reduced_graph.remove_node(i);
				bool reduced_graph_is_biconnected = reduced_graph.is_biconnected(one_cut_vertices);

				if (not reduced_graph_is_biconnected)
					for (int j=0; j<one_cut_vertices.size(); j++) {
						int node_offset = (one_cut_vertices[j]>=i); // offset needed because a node has been deleted
						two_cut_vertices.push_back(std::make_pair(i, one_cut_vertices[j]+node_offset));
					}
				is_triconnected_ &= reduced_graph_is_biconnected;
			}

			removeDuplicates(two_cut_vertices);
			two_cut_vertices_ = two_cut_vertices;
		} else {
			two_cut_vertices = two_cut_vertices_;
		}

		if (opts_.verbose) {
			std::cout << "graph is 3-connected: " << is_triconnected_ << std::endl;
			for (int i = 0; i<two_cut_vertices.size(); i++)
				std::cout << "two_cut_vertices between node " << two_cut_vertices[i].first <<" and node " << two_cut_vertices[i].second << std::endl;
		}

		return is_triconnected_;
	};

	// overload is_triconnected
	inline bool Graph::is_triconnected()
	{
		std::vector< std::pair<int, int> > two_cut_vertices;
		return is_triconnected(two_cut_vertices);
	};

	// return the set of bridges
	inline bool Graph::has_bridges(std::vector<int>& bridges)
	{
		if (has_bridges_ == -1) {
			// check for bridges:

			std::vector<bool> visited(num_nodes_, false);
			std::vector<int> parent(num_nodes_, -1);
			int *disc = new int[num_nodes_];
			int *low = new int[num_nodes_];

			// Call the recursive helper function to find Bridges
			// in DFS tree rooted with vertex 'i'
			for (int i = 0; i < num_nodes_; i++)
				if (visited[i] == false)
					bridgeUtil(i, visited, disc, low, parent, bridges);

			delete[] disc;
			delete[] low;

			if (bridges.size() == 0)
				has_bridges_ = 0;
			else
				has_bridges_ = 1;

			bridges_ = bridges;
		} else {
			bridges = bridges_;
		}

		if (opts_.verbose)
			std::cout << "graph has bridges: " << has_bridges_ << std::endl;

		return has_bridges_;
	};

	// overload has_bridges
	inline bool Graph::has_bridges()
	{
		std::vector<int> bridges;
		return has_bridges(bridges);
	};

	// return a fundamental cycles basis (see: An Algorithm for Finding a Fundamental Set of Cycles of a Graph)
	// should use DFS or BFS for generating the tree
	inline bool Graph::has_cycles(std::vector <std::vector<int>>& cycle_basis, std::vector<double>& cycle_lengths)
	{
		graphOptions opts = opts_;
		opts.verbose = false;
		Graph spanning_tree(nodes_, edges_, opts);
		Eigen::MatrixXi deleted_edges;
		spanning_tree.make_tree(deleted_edges);

		for (int i=0; i<deleted_edges.rows(); i++) {
			std::vector<int> cycle;
			double cycle_length;
			cycle_length = spanning_tree.dijkstra(deleted_edges(i, 0), deleted_edges(i, 1), cycle);

			double deleted_edge_length = (spanning_tree.get_node(deleted_edges(i, 0)) - spanning_tree.get_node(deleted_edges(i, 1))).norm();
			cycle_length = cycle_length + deleted_edge_length;
			cycle_basis.push_back(cycle);
			cycle_lengths.push_back(cycle_length);
		}

		if (cycle_basis.size() != 0)
			return true;
		else
			return false;
	};

	// overload has_cycle
	inline bool Graph::has_cycles()
	{
		std::vector <std::vector <int>> cycles;
		std::vector<double> cycle_lengths;
		return has_cycles(cycles, cycle_lengths);
	};

	inline std::vector<std::vector <int> > Graph::make_tree(Eigen::MatrixXi& deleted_edges)
	{
		std::vector<std::vector <int> > nodes_references(num_nodes_);
		for (int i=0; i<num_nodes_; i++)
			nodes_references[i].push_back(i);

		bool verbose_temp = opts_.verbose;
		opts_.verbose = false;

		// check for bridges
		has_bridges_ = -1;
		has_bridges();

		deleted_edges.resize(num_edges_ - num_nodes_ + 1, 2); // cycle rank * 2
		int counter = 0;

		while (bridges_.size() != num_edges_)
		{

			// list all non bridges
			std::vector <int> edges_to_edit;
			std::vector <double> edges_length;
			for (int i=0; i<num_edges_; i++)
				if (find (bridges_.begin(), bridges_.end(), i) == bridges_.end()) {
					edges_to_edit.push_back(i);
					edges_length.push_back(edges_length_(i));
				}

			// sort edges by decreasing length
			std::vector<std::pair<double, int>> sorting_container;
			sorting_container.reserve(edges_to_edit.size());
			std::transform(edges_length.begin(), edges_length.end(), edges_to_edit.begin(), std::back_inserter(sorting_container),
				[](double a, int b) { return std::make_pair(a, b); });

			std::sort(sorting_container.begin(), sorting_container.end());
			std::reverse(sorting_container.begin(), sorting_container.end());

			// store edges
			deleted_edges.row(counter) << edges_.row(sorting_container[0].second);
			counter ++;

			// actually store the edges
			//collapse_edge(edges_to_edit[0]);
			remove_edge(sorting_container[0].second);

			// check for bridges
			has_bridges_ = -1;
			has_bridges();
		}

		opts_.verbose = verbose_temp;
		return nodes_references;
	};

	// overload make_tree if deleted edges are not needed
	inline std::vector<std::vector <int> > Graph::make_tree()
	{
		Eigen::MatrixXi deleted_edges;
		return make_tree(deleted_edges);
	};

	inline void Graph::simplify_tree()
	{
		/*
			* for each junctions:
			* if the distance between two junction is "small"
			* then merge all the nodes in the path
			*/
		std::vector<int> junction_list;
		start_junction_checking:
		junction_list.clear();
		for (int i=0; i<adjacency_list_.size(); i++)
			if (adjacency_list_[i].size()>2)
				junction_list.push_back(i);

		for (int i = 0; i<junction_list.size(); i++) {
			for (int j = i+1; j<junction_list.size(); j++) {
				std::vector<int> path;
				double junctions_distance = dijkstra(junction_list[i], junction_list[j], path);

				if (junctions_distance < scale_/10)
				{
					merge_nodes(path);
					goto start_junction_checking; // went throwing up after writing this
				}
			}
		}

		/*
			* for each leaf:
			* if the distance to the closest junction is "small"
			* then delete all nodes until this node is reached
			*/
		std::vector<int> leaves_list;
		junction_list.clear();
		for (int i=0; i<adjacency_list_.size(); i++){
			if (adjacency_list_[i].size()==1)
				leaves_list.push_back(i);

			if (adjacency_list_[i].size()>2)
				junction_list.push_back(i);
		}

		// for each leaves check the distance to each junction
		std::vector<int> nodes_to_delete;
		for (int leaf: leaves_list) {
			for (int junction: junction_list) {
				std::vector<int> path;
				double leaf_to_junction_distance = dijkstra(leaf, junction, path);

				if (leaf_to_junction_distance < scale_/10)
				{
					for (int node_id : path)
						if (node_id != junction)
							nodes_to_delete.push_back(node_id);
				}
			}
		}

		if (nodes_to_delete.size() != 0) {
			std::sort(nodes_to_delete.begin(), nodes_to_delete.end(), std::greater<int>());
			nodes_to_delete.erase( std::unique( nodes_to_delete.begin(), nodes_to_delete.end() ), nodes_to_delete.end() );
			for (int node_id : nodes_to_delete)
				remove_node(node_id);
		}

	};

	inline void Graph::symplify_graph()
	{
		// list all cycles
		std::vector< std::vector< int > > cycle_basis;
		std::vector< double > cycle_lengths;
		has_cycles(cycle_basis, cycle_lengths);

		// remove large cycles
		for(int i=0; i<cycle_lengths.size();) {
			if(cycle_lengths[i] > scale_/cycle_ratio_) {
				cycle_lengths.erase(cycle_lengths.begin()+i);
				cycle_basis.erase(cycle_basis.begin() + i);
			} else {
				++i;
			}
		}

		// find set of clustered nodes
		std::vector< std::vector< int > > clusters;
		bool clusters_found = false;
		while (!clusters_found) {
			std::vector <int> cluster_temp;
			cluster_temp = cycle_basis[0];
			cycle_basis.erase(cycle_basis.begin());

			for(int i=0; i<cycle_basis.size(); )
			{
				// check for intersection
				std::vector <int> v1, v2;
				v1 = cluster_temp;
				v2 = cycle_basis[i];

				sort(v1.begin(), v1.end());
				sort(v2.begin(), v2.end());

				std::vector<int> v(v1.size() + v2.size());
				std::vector<int>::iterator it, st;

				it = set_intersection(v1.begin(),
										v1.end(),
										v2.begin(),
										v2.end(),
										v.begin());

				bool each_cycle_have_common_elements = false;
				for (st = v.begin(); st != it; ++st)
					each_cycle_have_common_elements = true;

				if(each_cycle_have_common_elements) {
					cluster_temp.insert( cluster_temp.end(), cycle_basis[i].begin(), cycle_basis[i].end() );
					cycle_basis.erase(cycle_basis.begin()+i);
					i = 0;
				} else {
					++i;
				}
			}

			// add sort + unique
			sort( cluster_temp.begin(), cluster_temp.end() );
			cluster_temp.erase( unique( cluster_temp.begin(), cluster_temp.end() ), cluster_temp.end() );
			clusters.push_back(cluster_temp);

			if (cycle_basis.size() == 0)
				clusters_found = true;
		}

		// merge nodes
		for (int i=0; i<clusters.size(); i++) {
			std::vector<int> empty_set;
			merge_nodes(clusters[i]);
			// each time a node is removed, the other lists should be updated to avoid deleting the wrong nodes
			for (int j=i+1; j<clusters.size(); j++)
				for (int k=0; k<clusters[j].size(); k++) {
					int offset = 0;
					for (int merged_node : clusters[i]) {
						// first check if the point is shared (it would then be the last node)
						if (clusters[j][k] == merged_node)
						{
							offset = 0;
							clusters[j][k] = num_nodes_-1;
							break;
						}
						// otherwise check if the merged point is lower, then the node has to be shifted of one position
						if (clusters[j][k] > merged_node)
							offset ++;
					}
					clusters[j][k] -= offset;
				}
		}
	};

	// the listing of the triangles should be performed differently
	// -> listing the fundamental cycles basis can be inacurate
	// -> or replace by the minimal cycle basis
	inline void Graph::remove_flat_triangles()
	{
		// list all cycles
		std::vector< std::vector< int > > cycle_basis;
		std::vector< double > cycle_lengths;
		has_cycles(cycle_basis, cycle_lengths);

		// go through each cycles
		for (int i=0; i<cycle_basis.size(); i++) {
			if (cycle_basis[i].size() == 3) {
				int edge_1 = find_edge_from_nodes(cycle_basis[i][0], cycle_basis[i][1]);
				int edge_2 = find_edge_from_nodes(cycle_basis[i][1], cycle_basis[i][2]);
				int edge_3 = find_edge_from_nodes(cycle_basis[i][2], cycle_basis[i][0]);

				std::vector<std::pair<double, int>> triangle_distance(3);
				triangle_distance[0] = std::make_pair( edges_length_(edge_1), edge_1);
				triangle_distance[1] = std::make_pair( edges_length_(edge_2), edge_2);
				triangle_distance[2] = std::make_pair( edges_length_(edge_3), edge_3);

				sort(triangle_distance.begin(), triangle_distance.end());

				if (triangle_distance[2].first > (triangle_distance[0].first + triangle_distance[1].first)*triangle_ratio_)
					remove_edge(triangle_distance[2].second);

			}
		}
	};

	inline void Graph::transform (double scale, Eigen::Vector3d move)
	{
		nodes_ /= scale;
		nodes_ += move.transpose();
	};

	inline double Graph::dijkstra(int source, int target, std::vector<int>& node_path)
	{
		// initialization
		double distance_source_to_target;
		std::vector<double> min_distance(num_nodes_, std::numeric_limits<double>::infinity());
		std::vector<int> previous_node(num_nodes_, -1);

		// set the set of visited nodes:
		std::vector<int> visited;
		std::vector<int> to_visit;

		// initialize the node to start from
		int u = source;
		min_distance.at(source) = 0;

		// start searching
		bool target_found;
		while (!target_found) {
			// check all neighbours of "u"
			for (int i = 0; i < adjacency_list_.at(u).size(); ++i) {
				int neighbour_node = adjacency_list_.at(u).at(i);
				double edge_length = edges_length_(adjacency_edge_list_.at(u).at(i));
				if (not is_element_in_vector(neighbour_node, to_visit))
					if (not is_element_in_vector(neighbour_node, visited))
						to_visit.push_back(neighbour_node);

				if ( min_distance.at(u) + edge_length < min_distance.at( neighbour_node ) ) {
					min_distance.at( neighbour_node ) = min_distance.at(u) +  edge_length;
					previous_node.at( neighbour_node ) = u;
				}
			}

			visited.push_back(u);

			// check if the visited point is in sub_V
			target_found = is_element_in_vector(target, visited);

			// set next u
			int index_of_next_point = 0;
			for (int i = 0; i < to_visit.size(); ++i)
				if (min_distance.at( to_visit.at(i) ) < min_distance.at( to_visit.at( index_of_next_point) ) )
					index_of_next_point = i;

			// check if all vertices have been visited:
			if (to_visit.size() == 0)
				break;

			u = to_visit.at(index_of_next_point);
			to_visit.erase(to_visit.begin() + index_of_next_point);
		}

		// backtracking to generate path
		if (min_distance.at(target) != std::numeric_limits<double>::infinity()) {
			int backtracking_node = target;
			node_path.insert(node_path.begin(), backtracking_node);

			while (backtracking_node != source) {
				node_path.insert(node_path.begin(), previous_node[backtracking_node]);
				backtracking_node = previous_node[backtracking_node];
			}
		}

		return min_distance.at(target);
	};

	inline double Graph::dijkstra(int source, int target)
	{
		std::vector<int> node_path;
		return  dijkstra(source, target, node_path);
	};

	/* TO BE REMOVED? This apply just for directional graph */

	inline int Graph::edge_source(int i)
	{
		return edges_(i, 0);
	};

	inline int Graph::edge_target(int i)
	{
		return edges_(i, 1);
	};

	inline void Graph::swap_edge(int i)
	{
		int temp = edges_(i,0);
		edges_(i,0) = edges_(i,1);
		edges_(i,1) = temp;
	};

	/*
	 * PRIVATE FUNCTIONS:
	 */

	//https://stackoverflow.com/a/46303314/2562693
	void Graph::removeRow(Eigen::MatrixXd& matrix, unsigned int rowToRemove)
	{
		unsigned int numRows = matrix.rows()-1;
		unsigned int numCols = matrix.cols();

		if( rowToRemove < numRows )
			matrix.block(rowToRemove,0,numRows-rowToRemove,numCols) = matrix.bottomRows(numRows-rowToRemove);

		matrix.conservativeResize(numRows,numCols);
	};

	void Graph::removeRow(Eigen::MatrixXi& matrix, unsigned int rowToRemove)
	{
		unsigned int numRows = matrix.rows()-1;
		unsigned int numCols = matrix.cols();

		if( rowToRemove < numRows )
			matrix.block(rowToRemove,0,numRows-rowToRemove,numCols) = matrix.bottomRows(numRows-rowToRemove);

		matrix.conservativeResize(numRows,numCols);
	};

	//https://stackoverflow.com/a/32842128/2562693
	void Graph::removeDuplicates(std::vector<std::pair<int, int>>& v)
	{

		//Normalize == sort the pair members
		for(auto& p : v){
			int x = std::max(p.first, p.second), y = std::min(p.first, p.second);
			p.first = x; p.second = y;
		}

		//Sort the pairs
		std::sort(v.begin(), v.end());

		//Unique the vector
		auto last = unique(v.begin(), v.end() );
		v.erase(last, v.end());
	};

	bool Graph::is_element_in_vector(int a, std::vector<int> & A)
	{
		auto it = std::find(A.begin(), A.end(), a);
		return it != A.end();
	}

	// used for checking connectivity:
	//
	// for reference, see Kosaraju's algorithm (https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm)
	//
	// from: https://www.geeksforgeeks.org/graph-implementation-using-stl-for-competitive-programming-set-1-dfs-of-unweighted-and-undirected/
	// A utility function to do DFS of graph
	// recursively from a given vertex u.
	void Graph::DFSUtil(int u, std::vector< std::vector<int> > adj, std::vector<bool> &visited)
	{
		visited[u] = true;
		for (int i=0; i<adj[u].size(); i++)
			if (visited[adj[u][i]] == false)
				DFSUtil(adj[u][i], adj, visited);
	};

	// used for finding articulation points (1-node-connectivity):
	//
	// for reference, see Tarjan’s algorithm
	//
	// from: https://www.geeksforgeeks.org/articulation-points-or-cut-vertices-in-a-graph/
	// A recursive function that find articulation points using DFS traversal
	// u --> The vertex to be visited next
	// visited[] --> keeps tract of visited vertices
	// disc[] --> Stores discovery times of visited vertices
	// parent[] --> Stores parent vertices in DFS tree
	// ap[] --> Store articulation points
	void Graph::APUtil(int u, std::vector<bool> & visited, int disc[],
										int low[], std::vector<int> & parent, std::vector<bool> & ap)
	{
		// A static variable is used for simplicity, we can avoid use of static
		// variable by passing a pointer.
		static int time = 0;

		// Count of children in DFS Tree
		int children = 0;

		// Mark the current node as visited
		visited[u] = true;

		// Initialize discovery time and low value
		disc[u] = low[u] = ++time;

		for (int i=0; i<adjacency_list_[u].size(); i++) {
			int v = adjacency_list_[u][i];  // v is current adjacent of u

			// If v is not visited yet, then make it a child of u
			// in DFS tree and recur for it
			if (!visited[v]) {
				children++;
				parent[v] = u;
				APUtil(v, visited, disc, low, parent, ap);

				// Check if the subtree rooted with v has a connection to
				// one of the ancestors of u
				low[u]  = std::min(low[u], low[v]);

				// u is an articulation point in following cases

				// (1) u is root of DFS tree and has two or more chilren.
				if (parent[u] == -1 && children > 1)
				ap[u] = true;

				// (2) If u is not root and low value of one of its child is more
				// than discovery value of u.
				if (parent[u] != -1 && low[v] >= disc[u])
				ap[u] = true;
			}

			// Update low value of u for parent function calls.
			else if (v != parent[u])
				low[u]  = std::min(low[u], disc[v]);
		}
	};

	// used for finding articulation points (1-node-connectivity):
	//
	// for reference, see Tarjan’s algorithm
	//
	// from: https://www.geeksforgeeks.org/bridge-in-a-graph/
	// A recursive function that finds and prints bridges using
	// DFS traversal
	// u --> The vertex to be visited next
	// visited[] --> keeps tract of visited vertices
	// disc[] --> Stores discovery times of visited vertices
	// parent[] --> Stores parent vertices in DFS tree
	void Graph::bridgeUtil(int u,
						std::vector<bool> & visited,
						int disc[],
						int low[],
						std::vector<int> & parent,
						std::vector<int> & bridges)
	{
		// A static variable is used for simplicity, we can
		// avoid use of static variable by passing a pointer.
		static int time = 0;

		// Mark the current node as visited
		visited[u] = true;

		// Initialize discovery time and low value
		disc[u] = low[u] = ++time;

		// Go through all vertices aadjacent to this
		for (int i=0; i<adjacency_list_[u].size(); i++) {

			int v = adjacency_list_[u][i];

			// If v is not visited yet, then recur for it
			if (!visited[v]) {
				parent[v] = u;

				bridgeUtil(v, visited, disc, low, parent, bridges);

				// Check if the subtree rooted with v has a
				// connection to one of the ancestors of u
				low[u]  = std::min(low[u], low[v]);

				if (low[v] > disc[u]) {
					bridges.push_back(adjacency_edge_list_[u][i]);
				}

				// If the lowest vertex reachable from subtree
				// under v is  below u in DFS tree, then u-v
				// is a bridge
				if (low[v] > disc[u] && opts_.verbose)
				std::cout << "Bridge at: " << u << " " << v << std::endl;
			}

			// Update low value of u for parent function calls.
			else if (v != parent[u])
				low[u]  = std::min(low[u], disc[v]);
		}
	};

};

#endif


/**
* \file auxiliaryFunctions.cpp
* \brief functions for the module auxiliaryFunctions
* \author Guillaume St-Onge
* \version 1.0
* \date 08/11/2017
*/

#include "auxiliaryFunctions.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <cmath>
#include <boost/random/uniform_int_distribution.hpp>
#include <boost/random/discrete_distribution.hpp>

using namespace std;

namespace DynNet
{//start of namespace DynNet

typedef boost::random::uniform_int_distribution<> uniform_int;

/**
 * \fn void order(edge p_edge)
 * \brief Assure the edge is in ascending order
 * \param[in] p_edge
 */
void order(edge& p_edge)
{
	if (p_edge.first > p_edge.second)
	{
		swap(p_edge.first,p_edge.second);
	}
}

/**
 * \fn vector<edge> input_edgeList(string p_path)
 * \brief Input the edge list from file
 * \param[in] p_path path name to the file
 */
vector<edge> input_edgeList(string p_path)
{
	ifstream inStream;
	inStream.open(p_path, ios::in);
	string line;

	vector<edge> edgeList;

	while(getline(inStream,line))
	{
		edge e;
		stringstream line_stream(line);
		line_stream >> e.first >> e.second;
		//swap edge to assure ascending order
		order(e);
		edgeList.push_back(e);
	}
	inStream.close();

	return edgeList;
}

/**
 * \fn pair<double,double> update_estimator(DynamicNetwork& p_net, edge& p_chosenEdge,
	pair<double,double>& p_est_param)ble,double>& p_model_param,
	edge& p_chosenEdge, double& p_degreeNormalization)
 * \brief Get likelihood for the new edge and update normalisation.
 * \param[in] p_net Structure of a network in reconstruction.
 * \param[in] p_chosenEdge new edge
 * \param[in] p_est_param Estimated parameter
 */
pair<double,double> update_estimator(DynamicNetwork& p_net, edge& p_chosenEdge,
	pair<double,double>& p_est_param)
{
	double n = p_net.get_currentDegreeMap().size();
	double m = p_net.get_currentEdgeList().size();
	double kavg = 2*m/n;
	double ksum = kavg*n;
	double k2avg = p_est_param.first + kavg*kavg;
	double k2sum = n*k2avg;

	unordered_map<node, unsigned int> currentDegreeMap = p_net.get_currentDegreeMap();

	//update parameter according to new node
	m += 1;
	ksum += 2;
	if (currentDegreeMap.find(p_chosenEdge.first) !=
		currentDegreeMap.end())
	{
		if (currentDegreeMap.find(p_chosenEdge.second) !=
		currentDegreeMap.end())
		{
			//2 old nodes, n stays the same
			//check if self-loop
			if (p_chosenEdge.first == p_chosenEdge.second)
			{
				k2sum -= (currentDegreeMap[p_chosenEdge.first]) * (currentDegreeMap[p_chosenEdge.first]);
				k2sum += (currentDegreeMap[p_chosenEdge.first] + 2) * (currentDegreeMap[p_chosenEdge.first] + 2);
				// k2sum -= pow(currentDegreeMap[p_chosenEdge.first],2.);
				// k2sum += pow(currentDegreeMap[p_chosenEdge.first] + 2,2.);
			}
			else
			{
			// 	k2sum -= pow(currentDegreeMap[p_chosenEdge.first],2.);
			// 	k2sum += pow(currentDegreeMap[p_chosenEdge.first] + 1,2.);
			// 	k2sum -= pow(currentDegreeMap[p_chosenEdge.second],2.);
			// 	k2sum += pow(currentDegreeMap[p_chosenEdge.second] + 1,2.);
				k2sum -= (currentDegreeMap[p_chosenEdge.first]) * (currentDegreeMap[p_chosenEdge.first]);
				k2sum += (currentDegreeMap[p_chosenEdge.first] + 1) * (currentDegreeMap[p_chosenEdge.first] + 1);
				k2sum -= (currentDegreeMap[p_chosenEdge.second]) * (currentDegreeMap[p_chosenEdge.second]);
				k2sum += (currentDegreeMap[p_chosenEdge.second] + 1) * (currentDegreeMap[p_chosenEdge.second] + 1);
			}
		}
		else
		{
			//first is old, second is new
			n += 1;
			// k2sum -= pow(currentDegreeMap[p_chosenEdge.first],2.);
			// k2sum += pow(currentDegreeMap[p_chosenEdge.first] + 1,2.);
			k2sum -= (currentDegreeMap[p_chosenEdge.first]) *  (currentDegreeMap[p_chosenEdge.first]);
			k2sum += (currentDegreeMap[p_chosenEdge.first] + 1) * (currentDegreeMap[p_chosenEdge.first] + 1);
			k2sum += 1;
		}
	}
	else
	{
		//first is new, second is old
		n += 1;
		// k2sum -= pow(currentDegreeMap[p_chosenEdge.second],2.);
		// k2sum += pow(currentDegreeMap[p_chosenEdge.second] + 1,2.);
		k2sum -= (currentDegreeMap[p_chosenEdge.second]) * (currentDegreeMap[p_chosenEdge.second]);
		k2sum += (currentDegreeMap[p_chosenEdge.second] + 1) * (currentDegreeMap[p_chosenEdge.second] + 1);
		k2sum += 1;
	}

	// pair<double,double> new_est_param(k2sum/n - pow(ksum/n, 2.),(n-2)/(m-1));
	pair<double,double> new_est_param(k2sum/n - (ksum/n) * (ksum/n), (n-2)/(m-1));

	return new_est_param;
}

/**
 * \fn double update_modelLikelihood(DynamicNetwork& p_net, pair<double,
 	double>& p_model_param,	edge& p_chosenEdge, double& p_degreeNormalization)
 * \brief Get likelihood for the new edge and update normalisation.
 * \param[in] p_net Structure of a network in reconstruction.
 * \param[in] p_model_param Model parameters
 * \param[in] p_chosenEdge new edge
 * \param[in] p_degreeNormalization Normalization factor for probability
 */
double update_modelLikelihood(DynamicNetwork& p_net,
	pair<double,double>& p_model_param, edge& p_chosenEdge,
	double& p_degreeNormalization)
{
	double model_likelihood = 1.;
	if (p_net.get_currentDegreeMap().find(p_chosenEdge.first) !=
		p_net.get_currentDegreeMap().end())
	{
		if (p_net.get_currentDegreeMap().find(p_chosenEdge.second) !=
		p_net.get_currentDegreeMap().end())
		{
			//2 old nodes
			double k1 = p_net.get_currentDegreeMap().at(p_chosenEdge.first);
			double k2 = p_net.get_currentDegreeMap().at(p_chosenEdge.second);
			model_likelihood *= (1 - p_model_param.second)*pow(k1*k2,
				p_model_param.first)/pow(p_degreeNormalization,2.);
			//check if self-loop
			if (p_chosenEdge.first == p_chosenEdge.second)
			{
				p_degreeNormalization -= pow(k1,p_model_param.first);
				p_degreeNormalization += pow(k1+2,p_model_param.first);
			}
			else
			{
				p_degreeNormalization -= pow(k1,p_model_param.first);
				p_degreeNormalization -= pow(k2,p_model_param.first);
				p_degreeNormalization += pow(k1+1,p_model_param.first);
				p_degreeNormalization += pow(k2+1,p_model_param.first);
			}
		}
		else
		{
			//first is old, second is new
			double k1 = p_net.get_currentDegreeMap().at(p_chosenEdge.first);
			model_likelihood *= p_model_param.second*pow(k1,
				p_model_param.first)/p_degreeNormalization;
			p_degreeNormalization -= pow(k1,p_model_param.first);
			p_degreeNormalization += pow(k1+1,p_model_param.first)+1;
		}
	}
	else
	{
		//first is new, second is old
		double k2 = p_net.get_currentDegreeMap().at(p_chosenEdge.second);
		model_likelihood *= p_model_param.second*pow(k2,
			p_model_param.first)/p_degreeNormalization;
		p_degreeNormalization -= pow(k2,p_model_param.first);
		p_degreeNormalization += pow(k2+1,p_model_param.first)+1;
	}

	return model_likelihood;
}

/**
 * \fn void add_edge(DynamicNetwork& p_net, pair<double,double>& p_model_param,
	pair<double,double>& p_target_param, pair<double,double>& p_est_param,
	pair<double,double>& p_bias_param, RNGType& gen)
 * \brief Add a new edge to the network following to the growth model.
 * \param[in] p_net Structure of a network in reconstruction.
 * \param[in] p_model_param Model parameters
 * \param[in] p_target_param Target parameter for bias
 * \param[in] p_est_param Estimated parameter for bias
 * \param[in] p_bias_param Tunable parameter for bias
 * \param[in] p_degreeNormalization Normalization factor for probability
 * \param[in] gen Random number generator.
 */
void add_edge(DynamicNetwork& p_net, pair<double,double>& p_model_param,
	pair<double,double>& p_target_param, pair<double,double>& p_est_param,
	pair<double,double>& p_bias_param, double& p_degreeNormalization,
	RNGType& gen)
{
	//this add_edge method is used with important_sampling

	edgeSet reachableEdgeSet = p_net.get_reachableEdgeSet();
	unordered_map<unsigned int, edge> indexMap;
	vector<double> weightList(reachableEdgeSet.size(), 0);
	// vector<double> weightList;
	double weightNorm = 0.;
	bool oldSeed = false;
	bool oldTarget = false;

	//get weights
	unsigned int index = 0;
	for (auto iter = reachableEdgeSet.begin();
		iter != reachableEdgeSet.end(); ++iter)
	{
		indexMap[index] = *iter;

		double weight = p_net.get_remainingEdgeMap().at(*iter); //bias for multiple edges
		edge possibleEdge = *iter;
		pair<double,double> new_est_param = update_estimator(p_net, possibleEdge,
			p_est_param);
		// weight *= exp(-p_bias_param.first*(new_est_param.second
		// 	- p_target_param.second));
		// weight *= exp(-p_bias_param.second*(new_est_param.first
		// 	- p_target_param.first));
		weight *= exp(-p_bias_param.first*(new_est_param.second- p_target_param.second) * (new_est_param.second- p_target_param.second));  // density
		weight *= exp(-p_bias_param.second*(new_est_param.first- p_target_param.first) * (new_est_param.first- p_target_param.first));  // variance

		// weightList.push_back(weight);

		weightList[index] = weight;
		weightNorm += weight;

		index += 1;
	}

	//Get a new random edge
	boost::random::discrete_distribution<int> edgeDist(weightList.begin(), weightList.end());
	unsigned int newEdgeIndex = edgeDist(gen);
	edge chosenEdge = indexMap[newEdgeIndex];

	//determine the model likelihood
	double model_likelihood = update_modelLikelihood(p_net, p_model_param, chosenEdge,
		p_degreeNormalization);

	//update the estimator
	p_est_param = update_estimator(p_net, chosenEdge, p_est_param);

	// cout << p_est_param.first << " " << p_est_param.second << "\n";

	//add the edge with according probability ratio (model_likelihood/bias_prob)
	double ratio = model_likelihood*weightNorm/(weightList[newEdgeIndex]);
	//account for multiple edge indistinguishability
	ratio *= p_net.get_remainingEdgeMap().at(chosenEdge);

	p_net.add(chosenEdge, ratio);
}

/**
 * \fn void add_edge(DynamicNetwork& p_net, pair<double,double>& p_param,
	double& p_degreeNormalization, RNGType& gen)
 * \brief Add a new edge to the network following to the growth model.
 * \param[in] p_net Structure of a network in reconstruction.
 * \param[in] p_param Model parameters
 * \param[in] p_degreeNormalization Normalization factor for probability
 * \param[in] gen Random number generator.
 */
void add_edge(DynamicNetwork& p_net, pair<double,double>& p_param,
	double& p_degreeNormalization, RNGType& gen)
{
	edgeSet reachableEdgeSet = p_net.get_reachableEdgeSet();
	uniform_int indexDist(0, p_net.get_reachableEdge()-1);

	// cout << reachableEdgeSet.size() << " " << p_net.get_reachableEdge() << endl;
	int index = indexDist(gen);

	auto iter = reachableEdgeSet.begin();
	//count multiple edge with remaining map
	while (index >= 0)
	{
		unsigned int remaining = p_net.get_remainingEdgeMap().at(*iter);
		index -= remaining;
		if (index >= 0)
		{
			iter++ ;
		}
	}

	bool oldSeed = false;
	bool oldTarget = false;
	if (p_net.get_currentDegreeMap().find(iter->first) !=
		p_net.get_currentDegreeMap().end())
	{
		oldSeed = true;
	}
	else
	{
		oldSeed = false;
	}
	if (p_net.get_currentDegreeMap().find(iter->second) !=
		p_net.get_currentDegreeMap().end())
	{
		oldTarget = true;
	}
	else
	{
		oldTarget = false;
	}

	double weight = p_net.get_reachableEdge();
	if (oldSeed)
	{
		if (oldTarget)
		{
			double k1 = p_net.get_currentDegreeMap().at(iter->first);
			double k2 = p_net.get_currentDegreeMap().at(iter->second);
			weight *= (1- p_param.second)/pow(p_degreeNormalization,2.);
			weight *= pow(k1, p_param.first);
			weight *= pow(k2, p_param.first);
			if (iter->second == iter->first)
			{
				p_degreeNormalization -= pow(k1, p_param.first);
				p_degreeNormalization += pow(k1+2, p_param.first);
			}
			else
			{
				p_degreeNormalization -= pow(k1, p_param.first);
				p_degreeNormalization -= pow(k2, p_param.first);
				p_degreeNormalization += pow(k1+1., p_param.first);
				p_degreeNormalization += pow(k2+1., p_param.first);
			}
		}
		else
		{
			double k1 = p_net.get_currentDegreeMap().at(iter->first);
			weight *= p_param.second/p_degreeNormalization;
			weight *= pow(k1, p_param.first);
			p_degreeNormalization -= pow(k1, p_param.first);
			p_degreeNormalization += pow(k1+1., p_param.first) + 1;
		}
	}
	else
	{
		if (oldTarget)
		{
			double k2 = p_net.get_currentDegreeMap().at(iter->second);
			weight *= p_param.second/p_degreeNormalization;
			weight *= pow(k2, p_param.first);
			// weight *= p_net.get_remainingEdgeMap().at(*iter);//bias to account
			p_degreeNormalization -= pow(k2, p_param.first);
			p_degreeNormalization += pow(k2+1., p_param.first) + 1;
		}
		else
		{
			//impossible to have 2 new nodes
			cout << "ERROR : two new nodes" << endl;
		}
	}

	p_net.add(*iter, weight);
}

/**
 * \fn void update_meanMarginal(DynamicNetwork& p_net, unordered_map< edge,
 	vector<double>, boost::hash<edge> > p_meanMarginalMap,
 	double meanLogweight, double& p_effectiveWeightSum)
 * \brief Update mean marginal evaluation from a network sample.
 * \param[in] p_net Structure of a network in reconstruction.
 * \param[in] p_meanMarginalMap Map of the mean marginal.
 * \param[in] p_meanLogweight Mean value of the logweight.
 * \param[in] p_effectiveWeightSum Normalization value to update.
 */
void update_meanMarginal(DynamicNetwork& p_net, unordered_map< edge,
	vector<double>, boost::hash<edge> >& p_meanMarginalMap,
	double meanLogweight, double& p_effectiveWeightSum)
{
	edgeIntMap edgeCountMap;
	double effectiveWeight = exp(p_net.get_logweight()-meanLogweight);
	double t = 0.;
	for (auto iter = p_net.get_currentEdgeList().begin();
		iter != p_net.get_currentEdgeList().end(); ++iter)
	{
		p_meanMarginalMap[*iter][edgeCountMap[*iter]] += t*effectiveWeight;
		edgeCountMap[*iter] += 1;
		t += 1.;
	}
	p_effectiveWeightSum += effectiveWeight;
}

/**
 * \fn bool update_meanMarginal(DynamicNetwork& p_net, unordered_map< edge,
 	vector<double>, boost::hash<edge> > p_meanMarginalMap,
 	double meanLogweight, double& p_effectiveWeightSum, double p_rtol)
 * \brief Update mean marginal evaluation from a network sample.
 * \param[in] p_net Structure of a network in reconstruction.
 * \param[in] p_meanMarginalMap Map of the mean marginal.
 * \param[in] p_meanLogweight Mean value of the logweight.
 * \param[in] p_effectiveWeightSum Normalization value to update.
 * \param[in] p_rtol Tolerance for convergence.
 */
bool update_meanMarginal(DynamicNetwork& p_net, unordered_map< edge,
	vector<double>, boost::hash<edge> >& p_meanMarginalMap,
	double meanLogweight, double& p_effectiveWeightSum, double p_rtol,
	unsigned int sampleSize)
{
	edgeIntMap edgeCountMap;
	bool converged = true;
	double effectiveWeight = exp(p_net.get_logweight()-meanLogweight);
	p_effectiveWeightSum += effectiveWeight;
	double t = 0.;
	for (auto iter = p_net.get_currentEdgeList().begin();
		iter != p_net.get_currentEdgeList().end(); ++iter)
	{
		p_meanMarginalMap[*iter][edgeCountMap[*iter]] += t*effectiveWeight;
		double avg_t = (p_meanMarginalMap[*iter][edgeCountMap[*iter]]
			/p_effectiveWeightSum);
		if (abs(t-avg_t)/sqrt(sampleSize) > p_rtol) //comportement moyen
		{
			converged = false;
		}
		edgeCountMap[*iter] += 1;
		t += 1.;
	}

	return converged;
}

}//end of namespace DynNet