#include "stdafx.h"
#include <string>
// mine
#include "pop.h"
#include "params.h"
#include "rnd.h"
#include "data.h"
#include "state.h"
#include "logger.h"

#include "InitPop.h"
#include "FitnessEstimator.h"
#include "Fitness.h"
#include "Generation.h"
#include "instructionset.h"
//#include "omp.h"
#include "Generationfns.h"
#include "strdist.h"
#include <time.h>
#include <cstring>
#include "p_archive.h"
#include "Eqn2Line.h"
#include "general_fns.h"
#include "load_params.h"
#include "load_data.h"
#include "printing.h"
// #include "runEllenGP.h"
//#define _CRTDBG_MAP_ALLOC
//#include <stdlib.h>
//#include <crtdbg.h>

using namespace std;
#if defined(_WIN32)
	#include <direct.h>
	#define GetCurrentDir _getcwd
#else
	#include <unistd.h>
	#include <iomanip>
	#define GetCurrentDir getcwd
#endif

// includes for python integration
#include <boost/python.hpp>
namespace bp = boost::python;
#include <numpy/ndarrayobject.h>
//#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
// global parameters structure

bool check_genty(vector<ind>& pop,params& p){

	for(int count = 0; count<pop.size(); ++count)
	{
		float tmp = abs(pop[count].fitness-pop[count].fitness_v)/pop[count].fitness;
		if (pop.at(count).genty != tmp && pop.at(count).genty != p.max_fit)
			return 0;
	}
	return 1;
}
void printGenome(tribe& T,int gen,string& logname,Data& d,params& p)
{
	// print genome to file in csv format
	// create Data file
	// print format: print in head-to-tail fashion with grid locations
	// x y gene
	// gene encoding: +:1,-:2,*:3,/:4,sin:5,cos:6,exp:7,log:8,constant:9,vars:10-length(vars)

	//total genes
	 std::ofstream gout_all;
	 std::ofstream gout_on;
	 std::ofstream gout_off;
	 string filename0 = logname.substr(0,logname.size()-4)+"_g_all.csv."+std::to_string(static_cast<long long>(gen));
	 gout_all.open(filename0);
	 gout_all << "x,y,fit,age,g,e\n";
	// on genes
	 if (p.eHC_on){

		 string filename = logname.substr(0,logname.size()-4)+"_g_on.csv."+std::to_string(static_cast<long long>(gen));
		 gout_on.open(filename);
		 gout_on << "x,y,fit,age,g,e\n";
		 // off genes

		 string filename2 = logname.substr(0,logname.size()-4)+"_g_off.csv."+std::to_string(static_cast<long long>(gen));
		 gout_off.open(filename2);
		 gout_off << "x,y,fit,age,g,e\n";
	 }
	 string out;
	 string tmp;
	 int k;
	 float y,y_on, y_off;
	 float max_fit=0;
	 float min_fit=10000;
	 float max_age=0;
	 float min_age=100000;
	 float max_line=0;
	 float min_line = 1000;
	 for (size_t i =0; i<T.pop.size(); ++i){
		if (log(1+T.pop[i].fitness)>max_fit) max_fit=log(1+T.pop[i].fitness);
		if (log(1+T.pop[i].fitness)<min_fit) min_fit=log(1+T.pop[i].fitness);
		if (T.pop[i].age>max_age) max_age=T.pop[i].age;
		if (T.pop[i].age<min_age) min_age=T.pop[i].age;
		for (size_t j = 0; j<T.pop[i].line.size(); ++j){
			if (T.pop[i].line.size()>max_line) max_line=T.pop[i].line.size();
			if (T.pop[i].line.size()<min_line) min_line=T.pop[i].line.size();
		}
	}
	if(p.sel==4) T.sortpop_age();
	else T.sortpop();

	 for (size_t i=0; i<T.pop.size(); ++i){
		 y=0;
		 y_on=0;
		 y_off=0;
		 for (int j=T.pop[i].line.size()-1; j>=0; --j){
			 switch(T.pop[i].line[j].type){
			 case '+':
				 out="1";
				 break;
			 case '-':
				 out="2";
				 break;
			 case '*':
				 out="3";
				 break;
			 case '/':
				 out="4";
				 break;
			 case 's':
				 out="5";
				 break;
			 case 'c':
				 out="6";
				 break;
			 case 'e':
				 out="7";
				 break;
			 case 'l':
				 out="8";
				 break;
			 case 'n':
				 out="9";
				 break;
			 case 'v':
				 k=0;
				 while (T.pop[i].line[j].varname.compare(d.label[k])!=0)
					 ++k;
				 tmp=std::to_string(static_cast<long long>(k+10));
				 out = tmp;
				 break;
			 }
			  gout_all << float(float(i)/float(T.pop.size())) << "," << y/p.max_len << "," << (log(1+T.pop[i].fitness)-min_fit)/(max_fit-min_fit) << "," << (float(T.pop[i].age)-min_age)/(max_age-min_age) << "," << out << "," << T.pop[i].line[j].on << "\n";
			 ++y;
			 if (p.eHC_on){
				 if (T.pop[i].line[j].on){
				 gout_on << float(float(i)/float(T.pop.size())) << "," << y_on/p.max_len << "," << (log(1+T.pop[i].fitness)-min_fit)/(max_fit-min_fit) << "," << (float(T.pop[i].age)-min_age)/(max_age-min_age) << "," << out << "," << T.pop[i].line[j].on << "\n";
				 ++y_on;
				 }
				 else{
				  gout_off << float(float(i)/float(T.pop.size())) << "," << y_off/p.max_len << "," << (log(1+T.pop[i].fitness)-min_fit)/(max_fit-min_fit) << "," << (float(T.pop[i].age)-min_age)/(max_age-min_age) << "," << out << "," << T.pop[i].line[j].on << "\n";
				  ++y_off;
				 }
			//++y;
			 }
		 }
	 }
	 gout_all.close();
	 gout_off.close();
		 gout_on.close();
	 /*if (p.eHC_on) {

	 }*/
}
void printstats(tribe& T,int &i,state& s,params& p,paretoarchive& A){
//boost::progress_timer timer;

s.out << "--- Generation " << i << "---------------------------------------------------------------" << "\n";
s.out << "population size: " << T.pop.size() << "\n";
s.out << "Number of evals: " << s.genevals.back() << "\n";
s.out << "Best Fitness: " << T.bestFit() <<"\n";
s.out << "Best Fitness (v): " << T.bestFit_v() <<"\n";
s.out << "Median Fitness: " << T.medFit_v()<<"\n";
s.out << "Median Fitness (v): " << T.medFit()<<"\n";
s.out << "Mean Size: " << T.meanSize() << "\n";
s.out << "Mean Eff Size: " << T.meanEffSize() << "\n";
s.out << "Pareto Front Equations: " << A.optimal_size << "\n";
if (p.pHC_on) {
	s.out << "Parameter updates: " << float(s.setPHCupdates()) / float(p.popsize) * 100 << "%\n";
	s.out << "Epigenetic updates: " << float(s.setEHCupdates()) / float(p.popsize) * 100 << "%\n";
	s.out << "Epigenetic ties: " << float(s.setEHCties()) / float(p.popsize) * 100 << "%\n";
}
s.out << "Beneficial Genetics: " << s.getGoodCrossPct() << "%\n";
s.out << "Neutral Genetics: " << s.getNeutCrossPct() << "%\n";
s.out << "Bad Genetics: " << s.getBadCrossPct() << "%\n";

if (p.classification){
	s.out << "Fitness \t Equation \n";
	vector <sub_ind> besteqns;
	T.topTen(besteqns);
	//for(unsigned int j=0;j<min(10,int(A.pop.size()));++j)
	//	s.out <<A.pop.at(j).abserror_v << "\t" << A.pop.at(j).corr_v << "\t" << A.pop.at(j).eqn <<"\n";
	for(unsigned int j=0;j<besteqns.size();++j)
		s.out << besteqns.at(j).fitness << "\t" << besteqns.at(j).eqn <<"\n";

}
else{
	s.out << "MAE \t R^2 \t Fitness \t Equation \n";
	vector <sub_ind> besteqns;
	T.topTen(besteqns);
	//for(unsigned int j=0;j<min(10,int(A.pop.size()));++j)
	//	s.out <<A.pop.at(j).abserror_v << "\t" << A.pop.at(j).corr_v << "\t" << A.pop.at(j).eqn <<"\n";
	for(unsigned int j=0;j<besteqns.size();++j){
		s.out << setprecision(3) << besteqns.at(j).abserror << "\t" << setprecision(3) << besteqns.at(j).corr << "\t" << setprecision(3) << besteqns.at(j).fitness << "\t" << besteqns.at(j).eqn <<"\n";
		/*if(boost::math::isnan(besteqns.at(j).abserror))
		{
			cout << "equation with NaN error: " + besteqns.at(j).eqn + "\n";
		}*/
	}
}
//if (p.classification && p.class_m4gp){
//	ind best_ind;
//	T.getbestind(best_ind);
//	s.out << "best M:\n";
//	s.out << best_ind.M << "\n";
//	for (unsigned i = 0; i<p.number_of_classes; ++i){
//		s.out << "best C[" << i << "]:\n";
//		s.out << best_ind.C[i] << "\n";
//	}
//}
s.out << "-------------------------------------------------------------------------------" << "\n";
}

int load_pop(vector<ind>& pop,params& p,state& s)
{
	// load population from file filename.
	ifstream fs(p.pop_restart_path);
	if(!fs.is_open())
	{
		std::cerr << "Error: couldn't open population file " + p.pop_restart_path + ".\n";
		exit(1);
	}

	string s0;
	string varname;
	float tmpf;
	//int tmpi;
	string tmps;
	bool tmpb;

	//string trash;
	//s.erase();
    //s.reserve(is.rdbuf()->in_avail());
	int i=0;
	int j=0;
	while(!fs.eof() && i<=p.popsize)
    {
		getline(fs,s0,'\n');
		istringstream ss(s0);
		ss >> varname;

		if(varname.compare(0,6,"gline:") == 0 && i<p.popsize)
		{
			//pop.push_back(ind());

			while (ss>>tmps){
				if (tmps.compare("+")==0 || tmps.compare("-")==0 || tmps.compare("*")==0 || tmps.compare("/")==0 || tmps.compare("s")==0 || tmps.compare("c")==0 || tmps.compare("e")==0 || tmps.compare("l")==0 || tmps.compare("!") == 0 || tmps.compare("=") == 0 || tmps.compare("<") == 0 || tmps.compare(">") == 0 || tmps.compare("{") == 0 || tmps.compare("}")==0 || tmps.compare("i") == 0 || tmps.compare("t") == 0 || tmps.compare("&") == 0 || tmps.compare("|") == 0) // operator node
					pop[i].line.push_back(node(char(tmps[0])));
				else if (isdigit(tmps[0]) || tmps[0]=='-') // constant node
					pop[i].line.push_back(node(std::stof(tmps)));
				else //variable node
					pop[i].line.push_back(node(tmps));

			}
			++i;
		}
		else if(p.eHC_on && varname.compare(0,6,"eline:") == 0)
		{
			while (ss>>tmpb){
				pop[i-1].line[j].on = tmpb;
				++j;
			}
			j=0;
		}
		/*else if(varname.compare(0,4,"age:") == 0)
			ss>>pop[i-1].age;*/
	}
	if (!fs.eof()) s.out << "WARNING: truncating population from file to first " + to_string(static_cast<long long>(p.popsize)) + " individuals...\n";
	return i; // size of loaded population
}

void shuffle_data(Data& d, params& p, vector<Randclass>& r,state& s)
{
	vector<int> shuffler;
	vector<float> newtarget;
	vector<vector<float>> newvals;

	for(int i=0;i<d.vals.size();++i)
		shuffler.push_back(i);

	std::random_shuffle(shuffler.begin(),shuffler.end(),r[omp_get_thread_num()]);

	if (p.EstimateFitness){
		bool tmp = s.out.print_to_file;
		s.out.print_to_file=1; // keep output from going to console
		s.out << "data shuffle index: ";
		for (int i=0; i<shuffler.size(); ++i)
			s.out << shuffler.at(i) << " ";
		s.out << "\n";
		s.out.print_to_file=tmp;
	}
	for(int i=0;i<d.vals.size();++i)
	{
		newtarget.push_back(d.target.at(shuffler.at(i)));
		newvals.push_back(d.vals.at(shuffler.at(i)));

	}
	using std::swap;
	std::swap(d.target,newtarget);
	std::swap(d.vals,newvals);
}
bool stopcondition(tribe& T,params p,Data& d,state& s,FitnessEstimator& FE)
{
	if (!p.stop_condition)
		return false;
	if (!p.EstimateFitness){
		if (T.bestFit() <= p.stop_threshold){
			s.out << "best fitness criterion achieved: " << T.bestFit() << "<" << p.stop_threshold << "\n";
			return true;
		}
		else
			return false;
	}
	else{
		vector<ind> best(1);
		T.getbestind(best[0]);

		p.EstimateFitness=0;
		Fitness(best,p,d,s,FE);
		p.EstimateFitness=1;
		if (best[0].fitness <= p.stop_threshold){
			s.out << "best fitness criterion achieved: " << best[0].fitness << "<" << p.stop_threshold << "\n";
			return true;
		}
		else
			return false;
	}
}
int get_next_task(int& index,vector<int>& task_assignments)
{
	// 0: evolve solution
	// 1: evolve fitness estimation
	// 2: print output (after all 0 tasks finish)
	// 3: update pareto archive (after all 0 tasks finish)
	if (index == task_assignments.size()-1)
		return -1;
	else{
		return task_assignments.at(++index);
	}
}
// template <size_t samples, size_t features>
// void runEllenGP(dict& param_dict, float (&X)[samples][features], float (&Y)[samples],string pname,string dname)
// numeric::array& features, numeric::array& target

void reference_contiguous_array(PyObject* in, float* &ptr, vector<int>& dims)
{ // returns pointer to underlying c array (ptr) from PyObject in, checking for contiguos memory storage
	  PyArrayObject* in_con;
		PyArray_Descr* x = PyArray_DESCR(in);
		// std::cout << "array dtype: " << (*x).type << "\n";
		// std::cout << "array kind: " << (*x).kind << "\n";
		// std::cout << "array byteorder: " << (*x).byteorder << "\n";
		// std::cout << "array flags: " << (*x).flags << "\n";
		// make sure data is contigous
    in_con = PyArray_GETCONTIGUOUS((PyArrayObject*)in);

		// get pointer to c array
    ptr = (float*)PyArray_DATA(in_con);

		// get dimensions
		int num_dim = PyArray_NDIM(in_con);
    npy_intp* pdim = PyArray_DIMS(in_con);
		// std::cout << "PyArray_NDIM:" << num_dim << "\n";

    for (int i = 0; i < num_dim; i++){
        dims.push_back(pdim[i]);
				// std::cout << "dim" << i << ": " << dims[i] << "\n";
			}
		// for (int i = 0; i < dims[0]; ++i){
		// 	std::cout << "*(ptr + " << i << "): " << *(ptr+i) << "\n";
		// }
}
void dereference(PyObject* o)
{
    Py_DECREF(o);
}

void line_to_py(vector<node>& line,bp::list& prog){
	// converts program to tuple for export to python.
	// cout << "in line to py\n";
	for (auto n: line){
		if (n.on){
			switch(n.type)
			{
			case 'v':
				if(n.varname.compare(0,6,"target")==0){
					// cout << "autoregressing " << n.varname << "\n";
					prog.append(bp::make_tuple("y", n.arity_float + n.arity_bool, stoi(string(n.varname.begin()+7,n.varname.end()))));
				}
				else{
					if (n.varname.find("_d") != std::string::npos) {
						size_t pos = n.varname.find("_d");
						// this is an auto-regressive variable
						// cout << "auto-regressive variable index: " << string(n.varname.begin()+2,n.varname.begin()+pos) << "\n";
						// cout << "auto-regressive variable delay: " << string(n.varname.begin()+pos+2,n.varname.end()) << "\n";
						prog.append(bp::make_tuple("xd", n.arity_float + n.arity_bool, stoi(string(n.varname.begin()+2,n.varname.begin()+pos)),stoi(string(n.varname.begin()+pos+2,n.varname.end()))));
					}
					else{
						// cout << n.varname << " regular\n";
						prog.append(bp::make_tuple("x", n.arity_float + n.arity_bool, stoi(string(n.varname.begin()+2,n.varname.end()))));
					}
				}
				break;
			case 'n':
				prog.append(bp::make_tuple("k", n.arity_float + n.arity_bool, n.value));
				break;
			default:
				prog.append(bp::make_tuple(n.type, n.arity_float + n.arity_bool));
			}
		}
	}
}
void pop_to_py(vector<ind>& archive,bp::list& arch_list){
	// converts program to tuple for export to python.
	// cout << "pop_to_py\n";
	// cout << "size of vector<ind> archive: " << archive.size() << "\n";
	for (auto i: archive){
	// for (unsigned int i = 0; i < archive.size(); ++i){
		// cout << i << "\n";
		// cout << i.eqn << "\n";
		bp::list prog;
		line_to_py(i.line,prog);
		arch_list.append(bp::make_tuple(prog,i.fitness,i.fitness_v));
	}
	// cout << "size of arch_list: " << bp::len(arch_list) << "\n";
	// cout <<"beepbopboop";
}

void runEllenGP(bp::dict& param_dict, PyObject* features, PyObject* target, bp::list& best_prog) //string pname,string dname
{
	// MARK_FUNCTION
	try{
		bool trials = 0;
		int trialnum = 0;
		string pname = "ellenGP";
		string dname = "d";
		// string paramfile = "ellyn";
		// string datafile = "d";
		//string paramfile(param_in);
		//string datafile(data_in);
		/* ===================================
		steps:
		Initialize population
			make genotypes
			genotype to phenotype
			calculate fitness
			hill climb
		Next Generation
			Select Parents
			Create children genotypes
			genotype to phenotype
			calculate fitness
			hill climb
		Store Statistics
		Print Update

		INPUTS
		paramfile: parameter file
		datafile: data set: target in first column, dependent variables in second column
		=================================== */

		struct params p;
		struct Data d;
		struct state s;

		vector <Randclass> r;

		// load parameter file
		p.set(param_dict);

		// get the input array
		float* feat_ptr;
		vector<int> dims;
		reference_contiguous_array(features, feat_ptr, dims);

		// set up features
		d.set_train(feat_ptr,dims[0],dims[1]);

		float* target_ptr;
	  dims.resize(0);

		reference_contiguous_array(target, target_ptr, dims);
		d.set_target(target_ptr,dims[0]);
		Py_DECREF(features);
		Py_DECREF(target);

		d.set_dependencies(p);
		// load_data(d,ds,p);

		std::time_t t =  std::time(NULL);

	#if defined(_WIN32)
		std::tm tm;
		localtime_s(&tm,&t);
		char tmplog[100];
		strftime(tmplog,100,"%Y-%m-%d_%H-%M-%S",&tm);
	#else
		std::tm * tm = localtime(&t);
		char tmplog[100];
		strftime(tmplog,100,"%F_%H-%M-%S",tm);
	#endif

	   // string tmplog = "777";
		const char * c = p.resultspath.c_str();
		#if defined(_WIN32)
			_mkdir(c);
		#else
			mkdir(c, 0777); // notice that 777 is different than 0777
		#endif
		 int thrd = omp_get_thread_num();
		 string thread;
		 if (trials) thread = std::to_string(static_cast<long long>(trialnum));
		 else thread = std::to_string(static_cast<long long>(thrd));
	// define save file name
	string logname;
	#if defined(_WIN32)
		if (!p.savename.empty())
			logname = p.resultspath + '/' + p.savename + ".log";
		else
	  	logname = p.resultspath + '\\' + "ellyn_" + tmplog + "_" + pname + "_" + dname + "_" + thread + ".log";
	#else
		if (!p.savename.empty())
			logname = p.resultspath + '/' + p.savename + ".log";
		else
			logname = p.resultspath + '/' + "ellyn_" + tmplog + "_" + pname + "_" + dname + "_" + thread + ".log";
	#endif

		 s.out.set_ptf(trials && p.print_log); // only print to file if print_log

		 s.out.set_v(p.verbosity>1); // only print a log file to screen if verbosity > 1
		 // print log setup
		 if (p.print_log){
			 s.out.open(logname);
			 if (!s.out.is_open()){
				 cerr << "Write-to File " << p.resultspath + '/' + logname << " did not open correctly.\n";
				 exit(1);
			 }
		 	}
		 // initialize data file
		 std::ofstream dfout;
		 if (p.print_data)	initdatafile(dfout,logname,p);

		 if (p.verbosity>0){
			 s.out << "_______________________________________________________________________________ \n";
			 s.out << "                                    ellenGP                                     \n";
			 s.out << "_______________________________________________________________________________ \n";
			 //s.out << "Time right now is " << std::put_time(&tm, "%c %Z") << '\n';
			 s.out<< "Results Path: " << p.resultspath  << "\n";
			 if (!p.savename.empty())
			 	s.out << "Save filename: " << p.savename << "\n";
			 s.out << "parameter name: " << pname << "\n";
			 s.out << "data file: " << dname << "\n";
			 if(trials) {s.out << "Running in Trial Mode\n";
			 s.out << "Trial ID: " << trialnum << "\n";
			 }
			 s.out << "Settings: \n";
			 // get evolutionary method
			 s.out << "Evolutionary Method: ";
			 switch(p.sel){
				 case 1:
					 s.out << "Standard Tournament\n";
					 break;
				 case 2:
					 s.out << "Deterministic Crowding\n";
					 break;
				 case 3:
					 s.out << "Lexicase Selection\n";
					 break;
				 case 4:
					 s.out << "Age-Fitness Pareto\n";
					 break;
			 }
			 if (p.sel==3){
				 s.out << "Lexicase metacases:\n";
				// add metacases if needed
				for (auto i: p.lex_metacases)
					s.out << "\t" << i << "\n";
			 }
			 if (p.ERC) s.out << "ERCs on\n";
			 if (p.pHC_on) s.out << p.pHC_its << " iterations of Parameter Hill Climbing\n";
			 if (p.eHC_on) s.out << p.eHC_its << " iterations of Epigenetic Hill Climbing\n";
			 if(p.train) s.out << "Data split " << p.train_pct << "/" << 1-p.train_pct << " for training and validation.\n";
			 s.out << "Total Population Size: " << p.popsize << "\n";
			 if (p.limit_evals) s.out << "Maximum Point Evals: " << p.max_evals << "\n";
			 else s.out << "Maximum Generations: " << p.g << "\n";
			 s.out << "Number of log points: " << p.num_log_pts << " (0 means log all points)\n";
			 if (trials && p.islands){
				s.out << "WARNING: cannot run island populations in trial mode. This trial will run on one core.\n";
				p.islands = false;
			 }
			 s.out << "fitness type: " << p.fit_type << "\n";
			 s.out << "verbosity: " << p.verbosity << "\n";
		  }
		 // allow threads (release the Python GIL) python gil unlock
		// Py_BEGIN_ALLOW_THREADS{
		int nt=0;
		//int ntt=0;
		// if specified by the user, override the default omp number of threads
		if (p.islands && p.num_islands !=0){
			nt = p.num_islands;
			omp_set_num_threads(nt);
		}
		else{
			#pragma omp parallel
			{
				nt = omp_get_num_threads();
			}
		}
		if (p.verbosity>0) s.out << "Number of threads: " << nt << "\n";
		//s.out << "OMP Number of threads: " << omp_get_num_threads() << "\n";
		p.nt = nt;

		//initialize random number generator
		unsigned int seed1 = int(time(NULL));
		if (p.verbosity>0) s.out << "seeds: \n";
		r.resize(omp_get_max_threads());
		#pragma omp parallel
		{
				//cout << "seeder: " << seeder <<endl;
				//cout << "seed1: " << seed1*seeder <<endl;
				if(!trials){
					if (p.verbosity>0) s.out << to_string(static_cast<long long>(seed1*(omp_get_thread_num()+1))) + "\n";
					//r.at(seeder).SetSeed(seed1*(seeder+1));
					r.at(omp_get_thread_num()).SetSeed(seed1*(omp_get_thread_num()+1));
				}
				else
					r.at(omp_get_thread_num()).SetSeed(seed1*(omp_get_thread_num()+1)*trialnum);
		}
		if(trials)
			if (p.verbosity>0) s.out << (omp_get_thread_num()+1)*seed1*trialnum << "\n";

		//shuffle data for training
		if (p.shuffle_data)
			shuffle_data(d,p,r,s);

		// define class weights for wighted F1 fitness
		if (p.classification && (p.fit_type.compare("2")==0 || p.fit_type.compare("F1W")==0))
			d.define_class_weights(p);

		boost::timer time;
		paretoarchive A;
		if (p.prto_arch_on){
			A.resize(p.prto_arch_size);
			// tribe FinalArchive(p.prto_arch_size,p.max_fit,p.min_fit);
			// FinalArchive.pop = A.pop;
		}

		vector<FitnessEstimator> FE(1);
		vector<ind> trainers;
	  //////////////////////////////////////////////////////////////////////////////
		// islands
		//////////////////////////////////////////////////////////////////////////////
		if (p.islands)
		{
			//p.parallel=false;
			// determine number of threads

			//int num_islands=omp_get_max_threads(); //
			int num_islands=nt;
			int subpops = p.popsize/num_islands;
			p.popsize = subpops*num_islands;
			vector<tribe> T;
			tribe World(subpops*num_islands,p.max_fit,p.min_fit); //total population of tribes
			if (p.verbosity>0) s.out << num_islands << " islands of " << subpops << " individuals, total pop " << p.popsize <<"\n";

			for(int i=0;i<num_islands;++i)
				T.push_back(tribe(subpops,p.max_fit,p.min_fit));
			// run separate islands
			if (p.pop_restart) // initialize population from file
			{
				if (p.verbosity>0) s.out << "loading pop from " + p.pop_restart_path + "...\n";
				int tmp = load_pop(World.pop,p,s);
				if (tmp < p.popsize){
					if (p.verbosity>0) s.out << "WARNING: population size loaded from file (" << tmp << ") is smaller than set pop size (" << p.popsize << "). " << (p.popsize-tmp) << " randomly initiated individuals will be added...\n";
					vector<ind> tmppop(p.popsize-tmp);
					InitPop(tmppop,p,r);
					swap_ranges(World.pop.end()-(p.popsize-tmp),World.pop.end(),tmppop.begin());
				}
				// initialize fitness estimation pop
				if (p.EstimateFitness)
					InitPopFE(FE,World.pop,trainers,p,r,d,s);

				Fitness(World.pop,p,d,s,FE[0]);

				std::random_shuffle(World.pop.begin(),World.pop.end(),r[0]);
				//assign population to islands
				#pragma omp parallel for
				for (int q = 0; q<num_islands; ++q)
					T[q].pop.assign(World.pop.begin()+q*subpops,World.pop.begin()+(q+1)*subpops);
			}
			else // initialize population from random
			{
				if (p.init_validate_on)
				{
					if (p.verbosity>0) s.out << "Initial validation...";

					#pragma omp parallel for
					for(int i=0;i<num_islands;++i)
						InitPop(T.at(i).pop,p,r);

					if (p.EstimateFitness)
					{
						// construct world population
						for(int j=0;j<T.size();++j){
							for(int k=0;k<T[0].pop.size();++k){
								World.pop.at(j*T[0].pop.size()+k)=T.at(j).pop.at(k);
								//makenew(World.pop[j*T[0].pop.size()+k]);
							}
						}
						// initialize fitness estimation pop
						InitPopFE(FE,World.pop,trainers,p,r,d,s);
					}
					//discard invalid individuals
					#pragma omp parallel for
					for(int i=0;i<num_islands;++i){
						float worstfit;
						float bestfit;
						vector<ind> tmppop;

						Fitness(T.at(i).pop,p,d,s,FE[0]);
						worstfit = T.at(i).worstFit();
						bestfit = T.at(i).bestFit();

						int counter=0;
						while(worstfit == p.max_fit && counter<100)
						{
							for (vector<ind>::iterator j=T.at(i).pop.begin();j!=T.at(i).pop.end();)
							{
								if ( (*j).fitness == p.max_fit)
								{
									j=T.at(i).pop.erase(j);
									tmppop.push_back(ind());
								}
								else
									++j;
							}

							InitPop(tmppop,p,r);
							Fitness(tmppop,p,d,s,FE[0]);
							T.at(i).pop.insert(T.at(i).pop.end(),tmppop.begin(),tmppop.end());
							tmppop.clear();
							worstfit = T.at(i).worstFit();
							counter++;
							if(counter==100)
								if (p.verbosity>0) s.out << "initial population count exceeded. Starting evolution...\n";
						}
					}
					s.setgenevals();
					if (p.verbosity>0) s.out << " number of evals: " << s.getgenevals() << "\n";

				}
				else // normal population initialization
				{
					/*bool tmp = p.EstimateFitness;
					p.EstimateFitness=0;*/
					if (p.verbosity>1) s.out << "InitPop...\n";
                    #pragma omp parallel for
					for(int i=0;i<num_islands;++i)
						InitPop(T.at(i).pop,p,r);

					if (p.EstimateFitness){
						// construct world population
						for(int j=0;j<T.size();++j){
							for(int k=0;k<T[0].pop.size();++k){
								World.pop.at(j*T[0].pop.size()+k)=T.at(j).pop.at(k);
								//makenew(World.pop[j*T[0].pop.size()+k]);
							}
						}
						InitPopFE(FE,World.pop,trainers,p,r,d,s);
					}

					#pragma omp parallel for
					for(int i=0;i<num_islands;++i)
						Fitness(T.at(i).pop,p,d,s,FE[0]);

					// construct world population with assigned fitness values
					for(int j=0;j<T.size();++j){
						for(int k=0;k<T[0].pop.size();++k){
							World.pop.at(j*T[0].pop.size()+k)=T.at(j).pop.at(k);
							//makenew(World.pop[j*T[0].pop.size()+k]);
						}
					}

				}
			}
			// use tmpFE for updating in parallel
			vector<FitnessEstimator> tmpFE = FE;

			int gen=0;
			long long termits,term, print_trigger;
			if (p.limit_evals){
				termits = s.totalptevals();
				term = p.max_evals;
				if (p.num_log_pts ==0) print_trigger=0;
				else print_trigger = p.max_evals/p.num_log_pts;

			}
			else {
				print_trigger=0;
				termits=1;
				term = p.g;
			}
			bool pass=1;
			int mixtrigger=p.island_gens*(p.popsize+p.popsize*p.eHC_on+p.popsize*p.pHC_on);
			//int trainer_trigger=0;
			int trainer_trigger=p.FE_train_gens;//p.FE_train_gens*(p.popsize+p.popsize*p.eHC_on+p.popsize*p.pHC_on);

			bool migrate=false;
			int q;

			// construct world population
			if (!p.EstimateFitness || !p.pop_restart){
				int cntr=0;
				for (int q0 = 0; q0<nt;++q0){
					for(int k=q0*subpops;k<(q0+1)*subpops;++k){
						World.pop.at(k)=T.at(q0).pop.at(cntr);
						//makenew(World.pop.at(k));
						++cntr;
					}
					cntr=0;
				}
			}
			if (p.print_init_pop) printpop(World.pop,p,s,logname,3);
			if (p.print_genome) printGenome(World,0,logname,d,p);
			if (p.print_db) printDB(World.pop,logname,d,p);
			#pragma omp parallel private(q) shared(pass)
			{

				q = omp_get_thread_num();

				while(termits<=term)
				{

					if(pass){
						// cout << "thread " + std::to_string(static_cast<long long>(q)) + " Generation\n";
						Generation(T[q].pop,p,r,d,s,FE[0]);

						if (stopcondition(T[q],p,d,s,FE[0])){
							pass=0;
							// cout << "thread " + std::to_string(static_cast<long long>(q)) + " solution\n";
						}
					}

					if (pass) {
						if (p.pHC_on && p.ERC)
						{
								for(int k=0; k<T[q].pop.size(); ++k)
									HillClimb(T[q].pop.at(k),p,r,d,s,FE[0]);
						}
						if (p.eHC_on && !p.eHC_mut)
						{
								for(int m=0; m<T[q].pop.size(); m++)
									EpiHC(T[q].pop.at(m),p,r,d,s,FE[0]);
						}
                        if (p.SGD && p.ERC)
                        {
                            for(int k=0; k<T[q].pop.size(); ++k)
									StochasticGradient(T[q].pop.at(k),p,r,d,s,FE[0],gen);

                        }
						if (stopcondition(T[q],p,d,s,FE[0])){
							pass=0;
							// cout << "thread " + std::to_string(static_cast<long long>(q)) + " solution\n";
						}
					}
					// cout << "thread " + std::to_string(static_cast<long long>(q)) + " pass val: " + std::to_string(static_cast<long long>(pass)) + "\n";

						// construct world population
						int cntr=0;
						//std::vector<ind>::iterator it = T[q].begin();
						//World.pop.assign(
						for(int k=q*subpops;k<(q+1)*subpops;++k){
							World.pop.at(k)=T[q].pop.at(cntr);
							//makenew(World.pop.at(k));
							cntr++;
						}
						// cout << "thread " + std::to_string(static_cast<long long>(q)) + " World pop constructed\n";

						#pragma omp barrier

						if (!pass)
							break;
						// cout << "thread " + std::to_string(static_cast<long long>(q)) + " past the barrier\n";
						#pragma omp single  nowait //coevolve fitness estimators
						{

							if (p.EstimateFitness){

								float aveFEfit=0;
								for (int u=0;u<FE.size();u++)
									aveFEfit+=FE[u].fitness;
								aveFEfit /= FE.size();
								if (aveFEfit==0)
									std::random_shuffle(tmpFE.begin(),tmpFE.end(),r[omp_get_thread_num()]);
								else
									EvolveFE(World.pop,tmpFE,trainers,p,d,s,r);
								if (!p.limit_evals || s.totalptevals() >= print_trigger){
									if (p.verbosity>0) s.out << "Evolving fitness estimators...\n";
									if (p.verbosity>0) s.out << "Best FE fit: " << FE[0].fitness <<"\n";
									if (p.estimate_generality) s.out << "Best FE genty: " << FE[0].genty <<"\n";
									if (p.verbosity>0) s.out << "Ave FE fit: " << aveFEfit << "\n";
									if (p.verbosity>0) s.out << "Current Fitness Estimator:\n";

									if (p.verbosity>0){
										for (int b=0;b<FE[0].FEpts.size();b++)
											 s.out << FE[0].FEpts[b] << " ";
										s.out << "\n";
									}
								}

							}
						}
						#pragma omp single
						{
							s.setgenevals();
							if(s.totalevals()>mixtrigger)
							{
								//shuffle population
								std::random_shuffle(World.pop.begin(),World.pop.end(),r[q]);
								//redistribute populations to islands
								if (p.verbosity>0) s.out << "Shuffling island populations...\n";
								migrate = true;
								mixtrigger+=p.island_gens*(p.popsize+p.popsize*p.eHC_on+p.popsize*p.pHC_on);
							}
							else
								migrate=false;
						}
						#pragma omp single
						{
							if(p.prto_arch_on){
								A.update(World.pop);
								if (p.print_archive) printpop(A.pop,p,s,logname,1);
							}
							if (!p.limit_evals || s.totalptevals() >= print_trigger){
								if (p.print_data) printdatafile(World,s,p,r,dfout,gen,time.elapsed());
								if (p.print_every_pop) printpop(World.pop,p,s,logname,2);
								if (p.verbosity > 1) {
									printstats(World,gen,s,p,A);
									s.out << "Total Time: " << (int)floor(time.elapsed()/num_islands/3600) << " hr " << ((int)(time.elapsed()/num_islands) % 3600)/60 << " min " << (int)(time.elapsed()/num_islands) % 60 << " s\n";
									s.out << "Total Evals: " << s.totalevals() << "\n";
									s.out << "Point Evals: " << s.totalptevals() << "\n";
									s.out << "Average evals per second: " << (float)s.totalevals()/time.elapsed() << "\n";
									s.out << "Average point evals per second: " << (float)s.totalptevals()/(time.elapsed()/num_islands) << "\n";
								}
								if (print_trigger!=0) print_trigger += p.max_evals/p.num_log_pts;
								if (p.print_genome) printGenome(World,gen,logname,d,p);
								if (p.print_db) printDB(World.pop,logname,d,p);
							}
							++gen;
							if (p.limit_evals) termits = s.totalptevals();
							else ++termits;

							if (!p.EstimateFitness && p.estimate_generality && p.G_shuffle)
								shuffle_data(d,p,r,s);

						}

						#pragma omp single
						{
							if (p.EstimateFitness){
								// assign tmpFE to FE
								FE.assign(tmpFE.begin(),tmpFE.end());
								/*float avefit=0;
								for (int u=0;u<FE.size();u++)
									avefit+=FE[u].fitness;
								avefit /= FE.size();
								if (avefit>1)
									cout <<"avefit error\n";*/
								if(gen>trainer_trigger) { //pick new trainers when FE pop has converged or when it has been enough generations
									if (p.verbosity>0) s.out << "Picking trainers...\n";
									PickTrainers(World.pop,FE,trainers,p,d,s);
									trainer_trigger = gen + p.FE_train_gens; //p.island_gens*(p.popsize+p.popsize*p.eHC_on+p.popsize*p.pHC_on);
								}
							}
						}

						if (migrate)
							T[q].pop.assign(World.pop.begin()+q*subpops,World.pop.begin()+(q+1)*subpops);

						//if (gen>p.g) pass=0;

				} if (p.verbosity>1) s.out << "thread " + std::to_string(static_cast<long long>(q)) + " exited while loop...\n";
				if (!pass){ //make sure archive was updated and generation prints out (may be redundant)
					#pragma omp single
					{
						if(p.prto_arch_on){
							A.update(World.pop);
							if (p.print_archive) printpop(A.pop,p,s,logname,1);
						}
						if (!p.limit_evals || s.totalptevals() >= print_trigger){
							if (p.print_data) printdatafile(World,s,p,r,dfout,gen,time.elapsed());
							if (p.print_every_pop) printpop(World.pop,p,s,logname,2);
							if (p.verbosity > 1) {
								printstats(World,gen,s,p,A);
								s.out << "Total Time: " << (int)floor(time.elapsed()/num_islands/3600) << " hr " << ((int)(time.elapsed()/num_islands) % 3600)/60 << " min " << (int)(time.elapsed()/num_islands) % 60 << " s\n";
								s.out << "Total Evals: " << s.totalevals() << "\n";
								s.out << "Point Evals: " << s.totalptevals() << "\n";
								s.out << "Average evals per second: " << (float)s.totalevals()/time.elapsed() << "\n";
								s.out << "Average point evals per second: " << (float)s.totalptevals()/time.elapsed() << "\n";
							}
							if (print_trigger!=0) print_trigger += p.max_evals/p.num_log_pts;
							if (p.print_genome) printGenome(World,gen,logname,d,p);
							if (p.print_db) printDB(World.pop,logname,d,p);
						}
						++gen;
						if (p.limit_evals) termits = s.totalptevals();
						else ++termits;

						if (!p.EstimateFitness && p.estimate_generality && p.G_shuffle)
							shuffle_data(d,p,r,s);

					}
				}
			} if (p.verbosity>1) s.out << "exited parallel region ...\n";

			if (p.EstimateFitness || p.test_at_end){// assign real fitness values to final population and archive
				p.EstimateFitness=0;
				p.test_at_end = 0;
				Fitness(World.pop,p,d,s,FE[0]);
				if (p.prto_arch_on) Fitness(A.pop,p,d,s,FE[0]);
				p.EstimateFitness=1;
			}

			if (p.print_data) printdatafile(World,s,p,r,dfout,gen,time.elapsed());
			if (p.print_best_ind) printbestind(World,p,s,logname);
			if (p.print_last_pop) printpop(World.pop,p,s,logname,0);

			if (p.prto_arch_on){
				// if (A.pop.empty()) A.update(World.pop);

				if (p.print_archive) printpop(A.pop,p,s,logname,1);
				// save archive to best_prog for python
				if (p.return_pop){
					World.sortpop();
					pop_to_py(World.pop,best_prog);
				}
				else
					pop_to_py(A.pop,best_prog);
			}
			else{
				// save best individual to best_prog for python
				if (p.return_pop){
					World.sortpop();
					pop_to_py(World.pop,best_prog);
				}
				else{
					vector<ind> best(1);
					World.getbestind(best[0]);
					line_to_py(best[0].line,best_prog);
				}
			}
		}
		/////////////////////////////////////////////////////////////////////////////
		// no islands
		//////////////////////////////////////////////////////////////////////////////
		else //no islands
		{
	 		tribe T(p.popsize,p.max_fit,p.min_fit);
			if (p.pop_restart) // initialize population from file
			{
				if (p.verbosity>0) s.out << "loading pop from " + p.pop_restart_path + "...\n";
				int tmp = load_pop(T.pop,p,s);
				if (tmp < p.popsize){
					if (p.verbosity>0) s.out << "WARNING: population size loaded from file (" << tmp << ") is smaller than set pop size (" << p.popsize << "). " << (p.popsize-tmp) << " randomly initiated individuals will be added...\n";
					vector<ind> tmppop(p.popsize-tmp);
					InitPop(tmppop,p,r);
					swap_ranges(T.pop.end()-(p.popsize-tmp),T.pop.end(),tmppop.begin());
				}
				if (p.EstimateFitness)
					InitPopFE(FE,T.pop,trainers,p,r,d,s);
				Fitness(T.pop,p,d,s,FE[0]);
			}
			else{
				if (p.init_validate_on)
				{
					if (p.verbosity>0) s.out << "Initial validation...";
					//bool tmp = p.EstimateFitness;
					//p.EstimateFitness=0;

					float worstfit;
					int cnt=0;
					//float bestfit;
					vector<ind> tmppop;
					// s.out << "Initialize Population..." << "\n";
					InitPop(T.pop,p,r);
					assert (T.pop.size()== p.popsize) ;
					// s.out << "Gen 2 Phen..." << "\n";
					// s.out << "Fitness..." << "\n";
					if (p.EstimateFitness)
						InitPopFE(FE,T.pop,trainers,p,r,d,s);

					Fitness(T.pop,p,d,s,FE[0]);
					assert (T.pop.size()== p.popsize) ;

					worstfit = T.worstFit();
					while(worstfit == p.max_fit && cnt<100)
					{
						for (vector<ind>::iterator j=T.pop.begin();j!=T.pop.end();)
						{
							if ( (*j).fitness == p.max_fit)
							{
								j=T.pop.erase(j);
								tmppop.push_back(ind());
							}
							else
								++j;
						}

						if (p.verbosity>0) s.out << "\ntmppop size: " << tmppop.size();
						InitPop(tmppop,p,r);
						Fitness(tmppop,p,d,s,FE[0]);

						T.pop.insert(T.pop.end(),tmppop.begin(),tmppop.end());
						tmppop.clear();
						worstfit = T.worstFit();
						cnt++;
						if(cnt==100)
							if (p.verbosity>0) s.out << "initial population count exceeded. Starting evolution...\n";
					}
					//p.EstimateFitness=tmp;
				}
				else // normal population initialization
				{
					InitPop(T.pop,p,r);
					if (p.EstimateFitness)
						InitPopFE(FE,T.pop,trainers,p,r,d,s);
					//bool tmp = p.EstimateFitness;
					//p.EstimateFitness=0;
					Fitness(T.pop,p,d,s,FE[0]);
					//p.EstimateFitness=tmp;
				}
			}
			s.setgenevals();
			if (p.verbosity>0) s.out << " number of evals: " << s.getgenevals() << "\n";
			int its = 1;
			long long termits;
			int gits=1;

			if (p.limit_evals) termits = s.totalptevals();
			else termits=1;
			int trigger=0;
			int trainer_trigger=p.FE_train_gens;//*(p.popsize+p.popsize*p.eHC_on+p.popsize*p.pHC_on);

			int gen=0;
			int counter=0;
			//if(p.sel==2) // if using deterministic crowding, increase gen size
			//{
			//	gen = p.g*(p.popsize*(p.rt_mut+p.rt_rep) + p.popsize*p.rt_cross/2);
			//	trigger = p.popsize*(p.rt_mut+p.rt_rep)+p.popsize*p.rt_cross/2;
			//}
			//else
				gen=p.g;
			long long term, print_trigger;
			bool printed=false;

			if (p.limit_evals) {
				term = p.max_evals;
				if (p.num_log_pts ==0) print_trigger=0;
				else print_trigger = p.max_evals/p.num_log_pts;
			}
			else{
				print_trigger=0;
				term = gen;
			}

			float etmp;
			//print initial population
			if (p.print_init_pop) printpop(T.pop,p,s,logname,3);
			if (p.print_genome) printGenome(T,0,logname,d,p);
			if (p.print_db) printDB(T.pop,logname,d,p);

			while (termits<=term && !stopcondition(T,p,d,s,FE[0]))
			{

				 etmp = s.numevals[omp_get_thread_num()];

				 if (!p.EstimateFitness && p.estimate_generality && p.G_shuffle)
					shuffle_data(d,p,r,s);

				 assert (T.pop.size() == p.popsize);

				 try{
				 	Generation(T.pop,p,r,d,s,FE[0]);
				}
				catch(std::exception& e){
					std::cerr << e.what() << std::endl;
				}
				catch(...){
					std::cerr << "not a standard error\n";
				}
				 assert (T.pop.size()== p.popsize);
				 //s.out << "Generation evals = " + to_string(static_cast<long long>(s.numevals[omp_get_thread_num()]-etmp)) + "\n";

				 if (its>trigger)
				 {
					 if (p.pHC_on && p.ERC)
					 {
						 etmp = s.numevals[omp_get_thread_num()];
						//#pragma omp parallel for
			 			for(int k=0; k<T.pop.size(); ++k)
			 				HillClimb(T.pop.at(k),p,r,d,s,FE[0]);
			 			 //s.out << "Hill climb evals = " + to_string(static_cast<long long>(s.numevals[omp_get_thread_num()]-etmp)) + "\n";

			 		 }

					 if (p.eHC_on&& !p.eHC_mut)
					 {
						 etmp = s.numevals[omp_get_thread_num()];
						// boost::progress_timer tm1;
						//#pragma omp parallel for
						for(int m=0; m<T.pop.size(); m++)
							EpiHC(T.pop.at(m),p,r,d,s,FE[0]);
						 //s.out << "EHC evals = " + to_string(static_cast<long long>(s.numevals[omp_get_thread_num()]-etmp)) + "\n";
					 }
                     if (p.SGD && p.ERC)
                    {
                        for(int k=0; k<T.pop.size(); ++k)
                                StochasticGradient(T.pop.at(k),p,r,d,s,FE[0],gen);

                    }
					s.setgenevals();
					//s.out << "Elapsed time: \n";
					if (p.prto_arch_on){
						A.update(T.pop);
						if (p.print_archive) printpop(A.pop,p,s,logname,1);
					}
					if (!p.limit_evals || s.totalptevals() >= print_trigger){
						if (p.print_data) printdatafile(T,s,p,r,dfout,counter,time.elapsed());
						if (p.print_every_pop) printpop(T.pop,p,s,logname,2);
						if (p.verbosity > 1){
							printstats(T,counter,s,p,A);
							s.out << "Total Time: " << (int)floor(time.elapsed()/3600) << " hr " << ((int)time.elapsed() % 3600)/60 << " min " << (int)time.elapsed() % 60 << " s\n";
							s.out << "Total Evals: " << s.totalevals() << "\n";
							s.out << "Point Evals: " << s.totalptevals() << "\n";
							s.out << "Average evals per second: " << (float)s.totalevals()/time.elapsed() << "\n";
							s.out << "Average point evals per second: " << (float)s.totalptevals()/time.elapsed() << "\n";
						}
						if (print_trigger!=0) print_trigger += p.max_evals/p.num_log_pts;
						if (p.print_genome) printGenome(T,counter,logname,d,p);
						if (p.print_db) printDB(T.pop,logname,d,p);

						printed=true;
					}

					//if (p.sel==2)
						//trigger+=p.popsize*(p.rt_mut+p.rt_rep)+p.popsize*p.rt_cross/2;
					++counter;

				 }

				if (p.EstimateFitness){
					EvolveFE(T.pop,FE,trainers,p,d,s,r);
					if (!p.limit_evals || printed){
						if (p.verbosity>0) s.out << "Evolving fitness estimators...\n";
						if (p.verbosity>0) s.out << "Best FE fit: " << FE[0].fitness <<"\n";
						if (p.estimate_generality) s.out << "Best FE genty: " << FE[0].genty <<"\n";
						if (p.verbosity>0) s.out << "Current Fitness Estimator:\n";

						if (p.verbosity>0) {
							for (int b=0;b<FE[0].FEpts.size();b++)
								s.out << FE[0].FEpts[b] << " ";
							s.out << "\n";
						}
					}
				}

				if (p.EstimateFitness){
					if(counter>trainer_trigger) {
						if (p.verbosity>0) s.out << "Picking trainers...\n";
						PickTrainers(T.pop,FE,trainers,p,d,s);
						trainer_trigger= counter + p.FE_train_gens; //*(p.popsize+p.popsize*p.eHC_on+p.popsize*p.pHC_on);
						//trainer_trigger=0;
					}
				}

				if (p.limit_evals) termits = s.totalptevals();
				else termits++;
				its++;
				printed=false;
			}

			if (p.EstimateFitness || p.test_at_end){// assign real fitness values to final population and archive
				p.EstimateFitness=0;
				p.test_at_end = 0;
				Fitness(T.pop,p,d,s,FE[0]);
				if (p.prto_arch_on) Fitness(A.pop,p,d,s,FE[0]);
				p.EstimateFitness=1;
			}

				if (p.print_data) printdatafile(T,s,p,r,dfout,counter,time.elapsed());
				if (p.print_best_ind) printbestind(T,p,s,logname);
				if (p.print_last_pop) printpop(T.pop,p,s,logname,0);

			if (p.prto_arch_on){
				if (A.pop.empty()) A.update(T.pop);
				if (p.print_archive) printpop(A.pop,p,s,logname,1);
				// save archive to best_prog for python
				if (p.return_pop){
					T.sortpop();
					pop_to_py(T.pop,best_prog);
				}
				else
					pop_to_py(A.pop,best_prog);
			}
			else{
				// save best individual to best_prog for python

				if (p.return_pop){
					T.sortpop();
					pop_to_py(T.pop,best_prog);
				}
				else{
					vector<ind> best(1);
					T.getbestind(best[0]);
					line_to_py(best[0].line,best_prog);
				}
			}
		}

		if (p.verbosity>0) s.out << "\n Program finished sucessfully.\n";

// Py_END_ALLOW_THREADS	} //end python gil unlock
	}
	catch(std::exception& e){
		std::cerr << e.what();

	}

}

BOOST_PYTHON_MODULE(elgp)
{
    def("runEllenGP", runEllenGP);
		// import_array();
}


//------------------------------------------------------------------------------
// Copyright (c) 2018 Hsienchi Kuo (Allen Institute, Hanchuan Peng's team)
// All rights reserved.
//------------------------------------------------------------------------------

/*******************************************************************************
*
*  Most of NeuronStructUtil class methods intend to operate on the whole neuron struct level.
*  As 'utility' it is called, the functionalities provided in this class include:
*    a. [Basic neuron struct operations]                   -- cropping SWC, scaling SWC, swc registration, etc.
*    b. [Tree - subtree operations]                        -- extracting upstream or downstream of a given tree.
*    c. [Neuron struct profiling methods]                  -- node-tile mapping, node-location mapping, etc.
*    d. [SWC - ImgAnalyzer::connectedComponent operations] -- Methods of this category convert SWC into vector<ImgAnalyzer::connectedComponent>
*
*  Most of NeuronStructUtil class methods are implemented as static functions. The input NeuronTree is always set to be const so that it will not be modified.
*  A typical function call would need at least three input arguments:
*
*		NeuronStructUtil::func(const NeuronTree& inputTree, NeuronTree& outputTree, other input arguments);
*
********************************************************************************/

#include <iostream>
#include <iterator>
#include <set>
#include <cmath>

#include <boost/filesystem.hpp>

#include "basic_4dimage.h"

#include "NeuronStructUtilities.h"
#include "ImgProcessor.h"

using namespace boost;

/* ======================================== Segment Operations ========================================= */
segUnit NeuronStructUtil::segUnitConnect_executer(const segUnit& segUnit1, const segUnit& segUnit2, connectOrientation connOrt)
{
	if (segUnit1.tails.size() > 1 || segUnit2.tails.size() > 1)
		throw invalid_argument("Currently forked segment connection is not supported. Do nothing and return");

	segUnit newSeg;
	QList<NeuronSWC> newSegNodes;
	QList<NeuronSWC> endEditedNodes;

	switch (connOrt)
	{
	case head_tail:
	{
		int connTailID = *segUnit2.tails.cbegin();
		endEditedNodes = segUnit1.nodes;
		endEditedNodes.begin()->parent = connTailID; // In current implementation, the 1st element of a seg must be a root.
		newSegNodes.append(segUnit2.nodes);
		newSegNodes.append(endEditedNodes);
		newSeg.nodes = newSegNodes;
		break;
	}
	case tail_head:
	{
		int connTailID = *segUnit1.tails.cbegin();
		endEditedNodes = segUnit2.nodes;
		endEditedNodes.begin()->parent = connTailID; // In current implementation, the 1st element of a seg must be a root.
		newSegNodes.append(segUnit1.nodes);
		newSegNodes.append(endEditedNodes);
		newSeg.nodes = newSegNodes;
		break;
	}
	case head_head:
	{
		int connTailID = segUnit2.head;
		for (map<int, vector<size_t>>::const_iterator it = segUnit2.seg_childLocMap.cbegin(); it != segUnit2.seg_childLocMap.cend(); ++it)
		{
			size_t nodeLoc = segUnit2.seg_nodeLocMap.at(it->first);
			NeuronSWC newNode = segUnit2.nodes.at(nodeLoc);
			if (it->second.empty())
			{
				//cout << newNode.x << " " << newNode.y << " " << newNode.z << endl;
				newNode.parent = -1;
			}
			else newNode.parent = segUnit2.nodes.at(*(it->second.cbegin())).n;
			endEditedNodes.push_back(newNode);
		}
		newSegNodes.append(segUnit1.nodes);
		newSegNodes.begin()->parent = connTailID;
		newSegNodes.append(endEditedNodes);
		newSeg.nodes = newSegNodes;
		break;
	}
	case tail_tail:
	{
		int connTailID = *segUnit2.tails.cbegin();
		for (map<int, vector<size_t>>::const_iterator it = segUnit1.seg_childLocMap.cbegin(); it != segUnit1.seg_childLocMap.cend(); ++it)
		{
			size_t nodeLoc = segUnit1.seg_nodeLocMap.at(it->first);
			NeuronSWC newNode = segUnit1.nodes.at(nodeLoc);
			if (it->second.empty()) newNode.parent = connTailID;
			else newNode.parent = segUnit1.nodes.at(*(it->second.cbegin())).n;
			endEditedNodes.push_back(newNode);
		}
		newSegNodes.append(segUnit2.nodes);
		newSegNodes.append(endEditedNodes);
		newSeg.nodes = newSegNodes;
		break;
	}
	default:
		break;
	}

	return newSeg;
}
/* ===================================================================================================== */

/* ===================================== Neuron Struct Processing ====================================== */
NeuronTree NeuronStructUtil::swcRegister(NeuronTree& inputTree, const NeuronTree& refTree)
{
	double xShift, yShift, zShift;
	double xScale, yScale, zScale;

	double xmin = 10000, ymin = 10000, zmin = 10000;
	double xmax = 0, ymax = 0, zmax = 0;
	for (QList<NeuronSWC>::iterator it = inputTree.listNeuron.begin(); it != inputTree.listNeuron.end(); ++it)
	{
		if (it->x < xmin) xmin = it->x;
		if (it->x > xmax) xmax = it->x;
		if (it->y < ymin) ymin = it->y;
		if (it->y > ymax) ymax = it->y;
		if (it->z < zmin) zmin = it->z;
		if (it->z > zmax) zmax = it->z;
	}
	double refXmin = 10000, refYmin = 10000, refZmin = 10000;
	double refXmax = 0, refYmax = 0, refZmax = 0;
	for (QList<NeuronSWC>::const_iterator refIt = refTree.listNeuron.begin(); refIt != refTree.listNeuron.end(); ++refIt)
	{
		if (refIt->x < refXmin) refXmin = refIt->x;
		if (refIt->x > refXmax) refXmax = refIt->x;
		if (refIt->y < refYmin) refYmin = refIt->y;
		if (refIt->y > refYmax) refYmax = refIt->y;
		if (refIt->z < refZmin) refZmin = refIt->z;
		if (refIt->z > refZmax) refZmax = refIt->z;
	}

	xScale = (refXmax - refXmin) / (xmax - xmin);
	yScale = (refYmax - refYmin) / (ymax - ymin);
	zScale = (refZmax - refZmin) / (zmax - zmin);
	xShift = refXmin - xmin * xScale;
	yShift = refYmin - ymin * yScale;
	zShift = refZmin - zmin * zScale;

	NeuronTree outputTree;
	for (int i = 0; i < inputTree.listNeuron.size(); ++i)
	{
		NeuronSWC newNode = inputTree.listNeuron.at(i);
		newNode.x = newNode.x * xScale + xShift;
		newNode.y = newNode.y * yScale + yShift;
		newNode.z = newNode.z * zScale + zShift;
		outputTree.listNeuron.push_back(newNode);
	}

	return outputTree;
}

NeuronTree NeuronStructUtil::swcCombine(const vector<NeuronTree>& inputTrees)
{
	NeuronTree outputTree;
	ptrdiff_t listSize = 0;
	int nodeIDmax = 0;
	for (vector<NeuronTree>::const_iterator it = inputTrees.begin(); it != inputTrees.end(); ++it)
	{
		for (QList<NeuronSWC>::iterator outputNodeIt = outputTree.listNeuron.begin(); outputNodeIt != outputTree.listNeuron.end(); ++outputNodeIt)
			if (outputNodeIt->n > nodeIDmax) nodeIDmax = outputNodeIt->n;

		outputTree.listNeuron.append(it->listNeuron);
		for (QList<NeuronSWC>::iterator nodeIt = outputTree.listNeuron.begin() + listSize; nodeIt != outputTree.listNeuron.end(); ++nodeIt)
		{
			nodeIt->n = nodeIt->n + nodeIDmax;
			if (nodeIt->parent == -1) continue;
			else nodeIt->parent = nodeIt->parent + nodeIDmax;
		}

		listSize = ptrdiff_t(outputTree.listNeuron.size());
	}

	return outputTree;
}

void NeuronStructUtil::swcSlicer(const NeuronTree& inputTree, vector<NeuronTree>& outputTrees, int thickness)
{
	QList<NeuronSWC> inputList = inputTree.listNeuron;
	int zMax = 0;
	ptrdiff_t thicknessPtrDiff = ptrdiff_t(thickness); // Determining largest number of z in inputTree.
	for (QList<NeuronSWC>::const_iterator it = inputTree.listNeuron.begin(); it != inputTree.listNeuron.end(); ++it)
	{
		int z = round(it->z);
		if (z >= zMax) zMax = z;
	}

	int treeNum = zMax / thickness + 1;
	vector<ptrdiff_t> delLocs;
	for (int i = 0; i < treeNum; ++i)
	{
		NeuronTree outputTree;
		outputTrees.push_back(outputTree);
		for (QList<NeuronSWC>::iterator it = inputList.begin(); it != inputList.end(); ++it)
		{
			if (it->z <= thickness * (i + 1))
			{
				outputTrees.at(i).listNeuron.push_back(*it);
				delLocs.push_back(it - inputList.begin());
			}
		}

		sort(delLocs.rbegin(), delLocs.rend());
		for (vector<ptrdiff_t>::iterator delIt = delLocs.begin(); delIt != delLocs.end(); ++delIt) inputList.erase(inputList.begin() + *delIt);
		delLocs.clear();
	}
}

map<int, QList<NeuronSWC>> NeuronStructUtil::swcSplitByType(const NeuronTree& inputTree)
{
	map<int, QList<NeuronSWC>> outputNodeTypeMap;
	map<int, boost::container::flat_set<int>> nodeIDsetMap;
	for (QList<NeuronSWC>::const_iterator it = inputTree.listNeuron.begin(); it != inputTree.listNeuron.end(); ++it)
	{
		outputNodeTypeMap[it->type].append(*it);
		nodeIDsetMap[it->type].insert(it->n);
	}
	for (map<int, QList<NeuronSWC>>::iterator mapIt = outputNodeTypeMap.begin(); mapIt != outputNodeTypeMap.end(); ++mapIt)
	{
		for (QList<NeuronSWC>::iterator nodeIt = mapIt->second.begin(); nodeIt != mapIt->second.end(); ++nodeIt)
		{
			if (nodeIt->parent == -1) continue;
			else if (nodeIDsetMap.at(nodeIt->type).find(nodeIt->parent) == nodeIDsetMap.at(nodeIt->type).end()) nodeIt->parent = -1;
		}
	}

	return outputNodeTypeMap;
}

NeuronTree NeuronStructUtil::swcSubtraction(const NeuronTree& targetTree, const NeuronTree& refTree, int type)
{
	boost::container::flat_map<string, QList<NeuronSWC>> targetNodeTileMap;
	boost::container::flat_map<string, QList<NeuronSWC>> refNodeTileMap;
	NeuronStructUtil::nodeTileMapGen(targetTree, targetNodeTileMap);
	NeuronStructUtil::nodeTileMapGen(refTree, refNodeTileMap);

	if (type == 0)
	{
		vector<ptrdiff_t> delLocs;
		for (boost::container::flat_map<string, QList<NeuronSWC>>::iterator targetTileIt = targetNodeTileMap.begin(); targetTileIt != targetNodeTileMap.end(); ++targetTileIt)
		{
			if (refNodeTileMap.find(targetTileIt->first) != refNodeTileMap.end())
			{
				for (QList<NeuronSWC>::iterator checkIt1 = targetTileIt->second.begin(); checkIt1 != targetTileIt->second.end(); ++checkIt1)
				{
					for (QList<NeuronSWC>::iterator checkIt2 = refNodeTileMap.at(targetTileIt->first).begin(); checkIt2 != refNodeTileMap.at(targetTileIt->first).end(); ++checkIt2)
					{
						if (checkIt1->x == checkIt2->x && checkIt1->y == checkIt2->y && checkIt1->z == checkIt2->z)
							delLocs.push_back(ptrdiff_t(checkIt1 - targetTileIt->second.begin()));
					}
				}
			}
			else continue;

			sort(delLocs.rbegin(), delLocs.rend());
			for (vector<ptrdiff_t>::iterator delIt = delLocs.begin(); delIt != delLocs.end(); ++delIt) targetTileIt->second.erase(targetTileIt->second.begin() + *delIt);
			delLocs.clear();
		}
	}
	else
	{
		vector<ptrdiff_t> delLocs;
		for (boost::container::flat_map<string, QList<NeuronSWC>>::iterator targetTileIt = targetNodeTileMap.begin(); targetTileIt != targetNodeTileMap.end(); ++targetTileIt)
		{
			if (refNodeTileMap.find(targetTileIt->first) != refNodeTileMap.end())
			{
				for (QList<NeuronSWC>::iterator checkIt1 = targetTileIt->second.begin(); checkIt1 != targetTileIt->second.end(); ++checkIt1)
				{
					for (QList<NeuronSWC>::iterator checkIt2 = refNodeTileMap.at(targetTileIt->first).begin(); checkIt2 != refNodeTileMap.at(targetTileIt->first).end(); ++checkIt2)
					{
						if (checkIt1->x == checkIt2->x && checkIt1->y == checkIt2->y && checkIt1->z == checkIt2->z && checkIt2->type == type)
							delLocs.push_back(ptrdiff_t(checkIt1 - targetTileIt->second.begin()));
					}
				}
			}
			else continue;

			sort(delLocs.rbegin(), delLocs.rend());
			for (vector<ptrdiff_t>::iterator delIt = delLocs.begin(); delIt != delLocs.end(); ++delIt)
			{
				if (targetTileIt->second.begin() + *delIt >= targetTileIt->second.end()) continue;
				else targetTileIt->second.erase(targetTileIt->second.begin() + *delIt);
			}
			delLocs.clear();
		}
	}

	NeuronTree outputTree;
	boost::container::flat_set<int> nodeIDs;
	for (boost::container::flat_map<string, QList<NeuronSWC>>::iterator mapIt = targetNodeTileMap.begin(); mapIt != targetNodeTileMap.end(); ++mapIt)
	{
		outputTree.listNeuron.append(mapIt->second);
		for (QList<NeuronSWC>::iterator nodeIt = mapIt->second.begin(); nodeIt != mapIt->second.end(); ++nodeIt)
			nodeIDs.insert(nodeIt->n);
	}

	for (QList<NeuronSWC>::iterator nodeIt = outputTree.listNeuron.begin(); nodeIt != outputTree.listNeuron.end(); ++nodeIt)
	{
		if (nodeIt->parent == -1) continue;
		else
			if (nodeIDs.find(nodeIt->parent) == nodeIDs.end()) nodeIt->parent = -1;
	}

	return outputTree;
}

void NeuronStructUtil::treeUpSample(const profiledTree& inputProfiledTree, profiledTree& outputProfiledTree, float intervalLength)
{
	size_t maxNodeID = 0;
	for (QList<NeuronSWC>::const_iterator it = inputProfiledTree.tree.listNeuron.begin(); it != inputProfiledTree.tree.listNeuron.end(); ++it)
		if (it->n > maxNodeID) maxNodeID = it->n;

	for (map<int, segUnit>::const_iterator segIt = inputProfiledTree.segs.begin(); segIt != inputProfiledTree.segs.end(); ++segIt)
	{
		QList<NeuronSWC> newSegNodes;
		map<int, vector<QList<NeuronSWC>>> interpolatedNodeMap;
		for (QList<NeuronSWC>::const_iterator nodeIt = segIt->second.nodes.begin(); nodeIt != segIt->second.nodes.end() - 1; ++nodeIt)
		{
			vector<size_t> childLocs = segIt->second.seg_childLocMap.at(nodeIt->n);
			for (vector<size_t>::iterator childLocIt = childLocs.begin(); childLocIt != childLocs.end(); ++childLocIt)
			{
				float dist = sqrt((segIt->second.nodes.at(*childLocIt).x - nodeIt->x) * (segIt->second.nodes.at(*childLocIt).x - nodeIt->x) +
					(segIt->second.nodes.at(*childLocIt).y - nodeIt->y) * (segIt->second.nodes.at(*childLocIt).y - nodeIt->y) +
					(segIt->second.nodes.at(*childLocIt).z - nodeIt->z) * (segIt->second.nodes.at(*childLocIt).z - nodeIt->z));
				int intervals = int(dist / intervalLength);
				float intervalX = (segIt->second.nodes.at(*childLocIt).x - nodeIt->x) / float(intervals);
				float intervalY = (segIt->second.nodes.at(*childLocIt).y - nodeIt->y) / float(intervals);
				float intervalZ = (segIt->second.nodes.at(*childLocIt).z - nodeIt->z) / float(intervals);

				QList<NeuronSWC> interpolatedNodes;
				for (int i = 1; i < intervals; ++i)
				{
					NeuronSWC newNode;
					newNode.x = nodeIt->x + intervalX * float(i);
					newNode.y = nodeIt->y + intervalY * float(i);
					newNode.z = nodeIt->z + intervalZ * float(i);
					newNode.type = nodeIt->type;
					++maxNodeID;
					newNode.n = maxNodeID;
					newNode.parent = maxNodeID - 1;
					interpolatedNodes.push_back(newNode);
				}
				interpolatedNodes.push_back(segIt->second.nodes.at(*childLocIt));
				if (interpolatedNodes.size() >= 2)
				{
					interpolatedNodes.back().parent = (interpolatedNodes.end() - 2)->n;
					interpolatedNodes.begin()->parent = nodeIt->n;
				}

				interpolatedNodeMap[nodeIt->n].push_back(interpolatedNodes);
			}
		}
		for (map<int, vector<QList<NeuronSWC>>>::iterator mapIt = interpolatedNodeMap.begin(); mapIt != interpolatedNodeMap.end(); ++mapIt)
			for (vector<QList<NeuronSWC>>::iterator qlistIt = mapIt->second.begin(); qlistIt != mapIt->second.end(); ++qlistIt) newSegNodes.append(*qlistIt);
		newSegNodes.push_front(segIt->second.nodes.at(segIt->second.seg_nodeLocMap.at(segIt->second.head)));

		segUnit newSegUnit;
		newSegUnit.nodes = newSegNodes;
		outputProfiledTree.segs.insert(pair<int, segUnit>(segIt->first, newSegUnit));
		outputProfiledTree.tree.listNeuron.append(newSegNodes);
	}
}

profiledTree NeuronStructUtil::treeDownSample(const profiledTree& inputProfiledTree, int nodeInterval)
{
	// -- This method "down samples" the input tree segment by segment.
	// -- A recursive down sampling method [NeuronStructExplorer::rc_segDownSample] is called in this function to deal with all possible braching points in each segment.
	// -- NOTE, this method is essentially used for straightening / smoothing segments when there are too many zigzagging.

	NeuronTree outputTree;
	QList<NeuronSWC> currSegOutputList;
	for (map<int, segUnit>::const_iterator it = inputProfiledTree.segs.begin(); it != inputProfiledTree.segs.end(); ++it)
	{
		//if (it->second.seg_childLocMap.empty()) continue; => Using this line is not safe. Can occasionally result in program crash.
		// The safety of seg_childLocMap needs to be investigated later.
		if (it->second.nodes.size() <= 3) continue;

		currSegOutputList.clear();
		currSegOutputList.push_back(*(it->second.nodes.begin()));
		NeuronStructUtil::rc_segDownSample(it->second, currSegOutputList, it->second.head, nodeInterval);
		outputTree.listNeuron.append(currSegOutputList);
	}

	vector<size_t> delLocs;
	for (QList<NeuronSWC>::iterator nodeIt = outputTree.listNeuron.begin(); nodeIt != outputTree.listNeuron.end(); ++nodeIt)
		if (nodeIt->n == nodeIt->parent) delLocs.push_back(size_t(nodeIt - outputTree.listNeuron.begin()));
	sort(delLocs.rbegin(), delLocs.rend());
	for (vector<size_t>::iterator delIt = delLocs.begin(); delIt != delLocs.end(); ++delIt) outputTree.listNeuron.erase(outputTree.listNeuron.begin() + ptrdiff_t(*delIt));
	profiledTree outputProfiledTree(outputTree);

	return outputProfiledTree;
}

void NeuronStructUtil::rc_segDownSample(const segUnit& inputSeg, QList<NeuronSWC>& outputNodeList, int branchigNodeID, int interval)
{
	int currNodeID = 0, count = 0;
	for (vector<size_t>::const_iterator childIt = inputSeg.seg_childLocMap.at(branchigNodeID).begin(); childIt != inputSeg.seg_childLocMap.at(branchigNodeID).end(); ++childIt)
	{
		outputNodeList.push_back(inputSeg.nodes.at(*childIt));
		outputNodeList.last().parent = branchigNodeID;
		currNodeID = inputSeg.nodes.at(*childIt).n;
		count = 0;
		while (inputSeg.seg_childLocMap.at(currNodeID).size() > 0)
		{
			if (inputSeg.seg_childLocMap.at(currNodeID).size() >= 2) // branching point found, function recursively called
			{
				outputNodeList.push_back(inputSeg.nodes.at(inputSeg.seg_nodeLocMap.at(currNodeID)));
				outputNodeList.last().parent = (outputNodeList.end() - 2)->n;
				NeuronStructUtil::rc_segDownSample(inputSeg, outputNodeList, currNodeID, interval);
				break;
			}

			++count;
			currNodeID = inputSeg.nodes.at(*(inputSeg.seg_childLocMap.at(currNodeID).begin())).n;
			if (count % interval == 0 || inputSeg.seg_childLocMap.at(currNodeID).size() == 0) // The tail(s) of the segment needs to stay.
			{
				outputNodeList.push_back(inputSeg.nodes.at(inputSeg.seg_nodeLocMap.at(currNodeID)));
				outputNodeList.last().parent = (outputNodeList.end() - 2)->n;
			}
		}
	}
}
/* ===================================== END of [Neuron Struct Processing] ===================================== */

/* ================================== SWC <-> ImgAnalyzer::connectedComponents ================================== */
vector<connectedComponent> NeuronStructUtil::swc2signal2DBlobs(const NeuronTree& inputTree)
{
	// -- Finds signal blobs "slice by slice" from input NeuronTree. Each slice is independent to one another.
	// -- Therefore, the same real blobs in 3D are consists of certain amount of 2D "blob slices" produced by this method.
	// -- Each 2D blob slice accounts for 1 ImgAnalyzer::connectedComponent.

	vector<NeuronSWC> allNodes;
	for (QList<NeuronSWC>::const_iterator it = inputTree.listNeuron.begin(); it != inputTree.listNeuron.end(); ++it) allNodes.push_back(*it);
	bool longList = false;

	vector<connectedComponent> connComps2D;
	int islandCount = 0;
	if (allNodes.size() >= 100000)
	{
		longList = true;
		cout << "number of SWC nodes processed: ";
	}
	for (vector<NeuronSWC>::iterator nodeIt = allNodes.begin(); nodeIt != allNodes.end(); ++nodeIt)
	{
		if (longList)
		{
			if (int(nodeIt - allNodes.begin()) % 10000 == 0) cout << int(nodeIt - allNodes.begin()) << " ";
		}

		for (vector<connectedComponent>::iterator connIt = connComps2D.begin(); connIt != connComps2D.end(); ++connIt)
		{
			if (connIt->coordSets.empty()) continue;
			else if (int(nodeIt->z) == connIt->coordSets.begin()->first)
			{
				for (set<vector<int>>::iterator dotIt = connIt->coordSets[int(nodeIt->z)].begin(); dotIt != connIt->coordSets[int(nodeIt->z)].end(); ++dotIt)
				{
					if (int(nodeIt->x) <= dotIt->at(0) + 1 && int(nodeIt->x) >= dotIt->at(0) - 1 &&
						int(nodeIt->y) <= dotIt->at(1) + 1 && int(nodeIt->y) >= dotIt->at(1) - 1)
					{
						vector<int> newCoord(3);
						newCoord[0] = int(nodeIt->x);
						newCoord[1] = int(nodeIt->y);
						newCoord[2] = int(nodeIt->z);
						connIt->coordSets[newCoord[2]].insert(newCoord);
						connIt->size = connIt->size + 1;

						if (newCoord[0] < connIt->xMin) connIt->xMin = newCoord[0];
						else if (newCoord[0] > connIt->xMax) connIt->xMax = newCoord[0];

						if (newCoord[1] < connIt->yMin) connIt->yMin = newCoord[1];
						else if (newCoord[1] > connIt->yMax) connIt->yMax = newCoord[1];

						goto NODE_INSERTED;
					}
				}
			}
		}

		{
			++islandCount;
			connectedComponent newIsland;
			newIsland.islandNum = islandCount;
			vector<int> newCoord(3);
			newCoord[0] = int(nodeIt->x);
			newCoord[1] = int(nodeIt->y);
			newCoord[2] = int(nodeIt->z);
			set<vector<int>> coordSet;
			coordSet.insert(newCoord);
			newIsland.coordSets.insert(pair<int, set<vector<int>>>(newCoord[2], coordSet));
			newIsland.xMax = newCoord[0];
			newIsland.xMin = newCoord[0];
			newIsland.yMax = newCoord[1];
			newIsland.yMin = newCoord[1];
			newIsland.zMin = newCoord[2];
			newIsland.zMax = newCoord[2];
			newIsland.size = 1;
			connComps2D.push_back(newIsland);
		}

	NODE_INSERTED:
		continue;
	}
	//cout << endl << endl;

	vector<float> center(3);
	for (vector<connectedComponent>::iterator it = connComps2D.begin(); it != connComps2D.end(); ++it)
		ChebyshevCenter_connComp(*it);

	return connComps2D;
}

vector<connectedComponent> NeuronStructUtil::swc2signal3DBlobs(const NeuronTree& inputTree)
{
	// -- This method is a wrapper of NeuronStructUtil::swc2signal2DBlobs and NeuronStructUtil::merge2DConnComponent.
	// -- It produces 3D signal blobs by calling the two swc2signal2DBlobs and merge2DConnComponent sequentially.

	vector<connectedComponent> inputConnCompList = NeuronStructUtil::swc2signal2DBlobs(inputTree);
	vector<connectedComponent> outputConnCompList = NeuronStructUtil::merge2DConnComponent(inputConnCompList);

	return outputConnCompList;
}

vector<connectedComponent> NeuronStructUtil::merge2DConnComponent(const vector<connectedComponent>& inputConnCompList)
{
	// -- This method finds 3D signal blobs by grouping 2D signal blobs together, which are generated by NeuronStructUtil::swc2signal2DBlobs.
	// -- This method is typically called by NeuronStructUtil::swc2signal2DBlobs when identifying 3D blobs from 2D ones.
	// -- The approach is consists of 2 stages:
	//		1. Identifying the same 3D blobs slice by slice.
	//		2. Merging 3D blobs that contain the same 2D blobs.

	cout << "Merging 2D signal blobs.." << endl;
	cout << "-- processing slice ";

	vector<connectedComponent> outputConnCompList;

	int zMax = 0;

	// -- I notice that boost's container templates are able to lift up the performace by ~30%.
	boost::container::flat_map<int, boost::container::flat_set<int>> b2Dtob3Dmap;
	b2Dtob3Dmap.clear();
	boost::container::flat_map<int, boost::container::flat_set<int>> b3Dcomps;  // a map from 3D connected components to all of its associated 2D connected components
	b3Dcomps.clear();
	// ---------------------------------------------------------------------------------------

	// --------- First slice, container initialization --------------
	int sliceBlobCount = 0;
	for (vector<connectedComponent>::const_iterator it = inputConnCompList.begin(); it != inputConnCompList.end(); ++it)
	{
		if (it->coordSets.begin()->first > zMax) zMax = it->coordSets.begin()->first;

		if (it->coordSets.begin()->first == 0) // 1st slice connected components profile initialization
		{
			++sliceBlobCount;
			boost::container::flat_set<int> blob3D;
			blob3D.insert(sliceBlobCount);
			b2Dtob3Dmap.insert(pair<int, boost::container::flat_set<int>>(it->islandNum, blob3D));
			boost::container::flat_set<int> comps;
			comps.insert(it->islandNum);
			b3Dcomps[sliceBlobCount] = comps;
		}
	}
	// -----------------------------------------------------------

	// ------------------------------------------- Merge 2D blobs from 2 adjacent slices -------------------------------------------
	vector<connectedComponent> currSliceConnComps;
	vector<connectedComponent> preSliceConnComps;
	size_t increasedSize;
	for (int i = 1; i <= zMax; ++i)
	{
		currSliceConnComps.clear();
		preSliceConnComps.clear();

		increasedSize = 0;
		for (vector<connectedComponent>::const_iterator it = inputConnCompList.begin(); it != inputConnCompList.end(); ++it)
			if (it->coordSets.begin()->first == i) currSliceConnComps.push_back(*it); // collect all connected components from the current slice
		if (currSliceConnComps.empty())
		{
			//cout << i << "->0 ";
			continue;
		}

		cout << i << "->";
		for (vector<connectedComponent>::const_iterator it = inputConnCompList.begin(); it != inputConnCompList.end(); ++it)
			if (it->coordSets.begin()->first == i - 1) preSliceConnComps.push_back(*it);  // collect all connected components from the previous slice
		if (preSliceConnComps.empty())
		{
			// If the previous slice is empty, all 2D components found in the current slice will be part of new 3D components.
			for (vector<connectedComponent>::iterator newCompsIt = currSliceConnComps.begin(); newCompsIt != currSliceConnComps.end(); ++newCompsIt)
			{
				++sliceBlobCount;
				boost::container::flat_set<int> blob3D;
				blob3D.insert(sliceBlobCount);
				b2Dtob3Dmap.insert(pair<int, boost::container::flat_set<int>>(newCompsIt->islandNum, blob3D));
				boost::container::flat_set<int> comps;
				comps.insert(newCompsIt->islandNum);
				b3Dcomps[sliceBlobCount] = comps;
				increasedSize = increasedSize + comps.size();
			}
			continue;
		}

		for (vector<connectedComponent>::iterator currIt = currSliceConnComps.begin(); currIt != currSliceConnComps.end(); ++currIt)
		{
			bool merged = false;
			for (vector<connectedComponent>::iterator preIt = preSliceConnComps.begin(); preIt != preSliceConnComps.end(); ++preIt)
			{
				// First, use component boundaries to quickly exclude those pixels that can't be connected to any existing components.
				// And then create new components for these pixels.
				if (currIt->xMin > preIt->xMax + 2 || currIt->xMax < preIt->xMin - 2 ||
					currIt->yMin > preIt->yMax + 2 || currIt->yMax < preIt->yMin - 2) continue;

				for (set<vector<int>>::iterator currDotIt = currIt->coordSets.begin()->second.begin(); currDotIt != currIt->coordSets.begin()->second.end(); ++currDotIt)
				{
					for (set<vector<int>>::iterator preDotIt = preIt->coordSets.begin()->second.begin(); preDotIt != preIt->coordSets.begin()->second.end(); ++preDotIt)
					{
						if (currDotIt->at(0) >= preDotIt->at(0) - 1 && currDotIt->at(0) <= preDotIt->at(0) + 1 &&
							currDotIt->at(1) >= preDotIt->at(1) - 1 && currDotIt->at(1) <= preDotIt->at(1) + 1)
						{
							merged = true;
							// Find out to which 3D component the 2D component connected to the pixel belong.
							boost::container::flat_set<int> asso3Dblob = b2Dtob3Dmap[preIt->islandNum];

							// Register the component of the pixel in the current slice to b2Dtob3Dmap.
							b2Dtob3Dmap.insert(pair<int, boost::container::flat_set<int>>(currIt->islandNum, asso3Dblob));

							// Add a new entry of newly identified 2D component that is connected to the existing 3D component to b3Dcomps.
							for (boost::container::flat_set<int>::iterator blob3DIt = asso3Dblob.begin(); blob3DIt != asso3Dblob.end(); ++blob3DIt)
								b3Dcomps[*blob3DIt].insert(currIt->islandNum);

							goto BLOB_MERGED;
						}
					}
				}

				if (!merged) continue;

			BLOB_MERGED:
				merged = true;
			}

			if (!merged) // All 2D blobs in the current slice fail to find its associated 3D blobs. Create new 3D blobs for them here.
			{
				++sliceBlobCount;
				boost::container::flat_set<int> newBlob3D;
				newBlob3D.insert(sliceBlobCount);
				b2Dtob3Dmap.insert(pair<int, boost::container::flat_set<int>>(currIt->islandNum, newBlob3D));
				boost::container::flat_set<int> comps;
				comps.insert(currIt->islandNum);
				b3Dcomps[sliceBlobCount] = comps;
				increasedSize = increasedSize + comps.size();
			}
		}
		cout << increasedSize << ", ";
	}
	cout << endl;
	cout << "Done merging 2D blobs from every 2 slices." << endl;
	// ---------------------------------------- END of [Merge 2D blobs from 2 adjacent slices] -------------------------------------------

	// ------------------------------------------ Merge 3D blobs --------------------------------------------
	// Merge any 3D blobs if any of them share the same 2D blob members.
	cout << "Now merging 3D blobs.." << endl;
	cout << " -- original 3D blobs number: " << b3Dcomps.size() << endl;
	bool mergeFinish = false;
	int currBaseBlob = 1;
	while (!mergeFinish)
	{
		for (boost::container::flat_map<int, boost::container::flat_set<int>>::iterator checkIt1 = b3Dcomps.begin(); checkIt1 != b3Dcomps.end(); ++checkIt1)
		{
			if (checkIt1->first < currBaseBlob) continue;
			for (boost::container::flat_map<int, boost::container::flat_set<int>>::iterator checkIt2 = checkIt1 + 1; checkIt2 != b3Dcomps.end(); ++checkIt2)
			{
				//if (checkIt2 == checkIt1) continue;
				for (boost::container::flat_set<int>::iterator member1 = checkIt1->second.begin(); member1 != checkIt1->second.end(); ++member1)
				{
					for (boost::container::flat_set<int>::iterator member2 = checkIt2->second.begin(); member2 != checkIt2->second.end(); ++member2)
					{
						if (*member2 == *member1)
						{
							checkIt1->second.insert(checkIt2->second.begin(), checkIt2->second.end());
							b3Dcomps.erase(checkIt2);
							currBaseBlob = checkIt1->first;
							cout << "  merging blob " << checkIt1->first << " and blob " << checkIt2->first << endl;
							goto MERGED;
						}
					}
				}
			}
		}
		mergeFinish = true;

	MERGED:
		continue;
	}
	cout << " -- new 3D blobs number: " << b3Dcomps.size() << endl;
	cout << "    ------------------------------" << endl << endl;
	// --------------------------------------- END of [Merge 3D blobs] --------------------------------------

	// ------------------------------------- Create 3D connected component data -------------------------------------
	map<int, connectedComponent> compsMap;
	for (vector<connectedComponent>::const_iterator inputIt = inputConnCompList.begin(); inputIt != inputConnCompList.end(); ++inputIt)
		compsMap.insert(pair<int, connectedComponent>(inputIt->islandNum, *inputIt));
	int newLabel = 0;
	for (boost::container::flat_map<int, boost::container::flat_set<int>>::iterator it = b3Dcomps.begin(); it != b3Dcomps.end(); ++it)
	{
		++newLabel;
		connectedComponent newComp;
		newComp.islandNum = newLabel;
		newComp.size = 0;
		newComp.xMax = 0; newComp.xMin = 1000000;
		newComp.yMax = 0; newComp.yMin = 1000000;
		newComp.zMax = 0; newComp.zMin = 1000000;
		for (boost::container::flat_set<int>::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) // *it2 = 2D connected component's [islandNum]
		{
			// A 3D connected component may contain different 2D components from the same slice.
			if (newComp.coordSets.find(compsMap.at(*it2).coordSets.begin()->first) != newComp.coordSets.end())
			{
				for (set<vector<int>>::iterator it3 = compsMap.at(*it2).coordSets.begin()->second.begin(); it3 != compsMap.at(*it2).coordSets.begin()->second.end(); ++it3)
					newComp.coordSets.at(compsMap.at(*it2).coordSets.begin()->first).insert(*it3);

				newComp.xMax = getMax(newComp.xMax, compsMap.at(*it2).xMax);
				newComp.xMin = getMin(newComp.xMin, compsMap.at(*it2).xMin);
				newComp.yMax = getMax(newComp.yMax, compsMap.at(*it2).yMax);
				newComp.yMin = getMin(newComp.yMin, compsMap.at(*it2).yMin);
				newComp.zMax = getMax(newComp.zMax, compsMap.at(*it2).zMax);
				newComp.zMin = getMin(newComp.zMin, compsMap.at(*it2).zMin);
				newComp.size = newComp.size + compsMap.at(*it2).size;
			}
			else
			{
				newComp.coordSets.insert(pair<int, set<vector<int>>>(compsMap.at(*it2).coordSets.begin()->first, compsMap.at(*it2).coordSets.begin()->second));
				newComp.xMax = getMax(newComp.xMax, compsMap.at(*it2).xMax);
				newComp.xMin = getMin(newComp.xMin, compsMap.at(*it2).xMin);
				newComp.yMax = getMax(newComp.yMax, compsMap.at(*it2).yMax);
				newComp.yMin = getMin(newComp.yMin, compsMap.at(*it2).yMin);
				newComp.zMax = getMax(newComp.zMax, compsMap.at(*it2).zMax);
				newComp.zMin = getMin(newComp.zMin, compsMap.at(*it2).zMin);
				newComp.size = newComp.size + compsMap.at(*it2).size;
			}
		}

		outputConnCompList.push_back(newComp);
	}
	// --------------------------------- END of [Create 3D connected component data] ---------------------------------

	return outputConnCompList;
}

NeuronTree NeuronStructUtil::blobs2tree(const vector<connectedComponent>& inputconnComp, bool usingRadius2compNum)
{
	// -- This method produces a NeuronTree that is used to store connected component information. The radius column is ususally used for component label.

	NeuronTree outputTree;
	for (vector<connectedComponent>::const_iterator it = inputconnComp.begin(); it != inputconnComp.end(); ++it)
	{
		for (map<int, set<vector<int>>>::const_iterator sliceIt = it->coordSets.begin(); sliceIt != it->coordSets.end(); ++sliceIt)
		{
			for (set<vector<int>>::const_iterator pointIt = sliceIt->second.begin(); pointIt != sliceIt->second.end(); ++pointIt)
			{
				NeuronSWC newNode;
				newNode.x = pointIt->at(0);
				newNode.y = pointIt->at(1);
				newNode.z = pointIt->at(2);
				newNode.type = it->islandNum % 500;
				newNode.parent = -1;

				if (usingRadius2compNum) newNode.radius = it->islandNum; // Use SWC's radius column to keep component label information.

				outputTree.listNeuron.push_back(newNode);
			}
		}
	}

	return outputTree;
}
/* =============================== END of [SWC <-> ImgAnalyzer::connectedComponents] =============================== */

/* =========================================== Miscellaneous =========================================== */
NeuronTree NeuronStructUtil::nodeSpheresGen(float sphereRadius, float density, float stepX, float stepY, float stepZ, float xRange, float yRange, float zRange)
{
	NeuronTree outputTree;
	for (float x = 0; x <= xRange; x += stepX)
	{
		for (float y = 0; y <= yRange; y += stepY)
		{
			for (float z = 0; z <= zRange; z += stepZ)
			{
				NeuronTree currSphereTree = NeuronStructUtil::sphereRandNodes(sphereRadius, x, y, z, density);
				size_t existingNodeNum = outputTree.listNeuron.size();
				for (QList<NeuronSWC>::iterator it = currSphereTree.listNeuron.begin(); it != currSphereTree.listNeuron.end(); ++it) it->n = it->n + existingNodeNum;
				outputTree.listNeuron.append(currSphereTree.listNeuron);
			}
		}
	}

	return outputTree;
}
/* ===================================================================================================== */

/* =================================== Volumetric SWC sampling methods =================================== */
void NeuronStructUtil::sigNode_Gen(const NeuronTree& inputTree, NeuronTree& outputTree, float ratio, float distance)
{
	// -- Randomly generate signal patches within given distance range
	//      ratio:    the ratio of targeted number of upsampling nodes to the number of maunal nodes in the inputTree
	//      distance: the radius allowed with SWC node centered

	cout << "target signal patch number: " << int(inputTree.listNeuron.size() * ratio) << endl;
	int nodeCount = 0;
	for (QList<NeuronSWC>::const_iterator it = inputTree.listNeuron.begin(); it != inputTree.listNeuron.end(); ++it)
	{
		outputTree.listNeuron.push_back(*it);
		(outputTree.listNeuron.end() - 1)->parent = -1;
		(outputTree.listNeuron.end() - 1)->type = 2;
		int foldCount = 2;
		while (foldCount <= ratio)
		{
			int randNumX = rand() % int(distance * 2) + int(it->x - distance);
			int randNumY = rand() % int(distance * 2) + int(it->y - distance);
			int randNumZ = rand() % int(distance * 2) + int(it->z - distance);

			++nodeCount;
			if (nodeCount % 10000 == 0) cout << nodeCount << " signal nodes generated." << endl;

			NeuronSWC newNode;
			newNode.x = randNumX;
			newNode.y = randNumY;
			newNode.z = randNumZ;
			newNode.type = 2;
			newNode.radius = 1;
			newNode.parent = -1;
			outputTree.listNeuron.push_back(newNode);

			++foldCount;
		}
	}
}

void NeuronStructUtil::bkgNode_Gen(const NeuronTree& inputTree, NeuronTree& outputTree, int dims[], float ratio, float distance)
{
	// -- Randomly generate background patches away from the forbidden distance
	//      dims:     image stack dimension
	//      ratio:    the ratio of targeted number of background nodes to the number of manual nodes in the inputTree
	//      distance: the forbidden distance from each SWC node

	QList<NeuronSWC> neuronList = inputTree.listNeuron;
	int targetBkgNodeNum = int(neuronList.size() * ratio);
	cout << targetBkgNodeNum << " targeted bkg nodes to ge generated." << endl;
	int bkgNodeCount = 0;
	while (bkgNodeCount <= targetBkgNodeNum)
	{
		int randNumX = rand() % (dims[0] - 20) + 10;
		int randNumY = rand() % (dims[1] - 20) + 10;
		int randNumZ = rand() % dims[2];

		bool flag = false;
		for (QList<NeuronSWC>::iterator it = neuronList.begin(); it != neuronList.end(); ++it)
		{
			float distSqr;
			float diffx = float(randNumX) - it->x;
			float diffy = float(randNumY) - it->y;
			float diffz = float(randNumZ) - it->z;
			distSqr = diffx * diffx + diffy * diffy + diffz * diffz;

			if (distSqr <= distance * distance)
			{
				flag = true;
				break;
			}
		}

		if (flag == false)
		{
			++bkgNodeCount;
			if (bkgNodeCount % 10000 == 0) cout << bkgNodeCount << " bkg nodes generated." << endl;

			NeuronSWC newBkgNode;
			newBkgNode.x = randNumX;
			newBkgNode.y = randNumY;
			newBkgNode.z = randNumZ;
			newBkgNode.type = 3;
			newBkgNode.radius = 1;
			newBkgNode.parent = -1;
			outputTree.listNeuron.push_back(newBkgNode);
		}
	}
}

void NeuronStructUtil::bkgNode_Gen_somaArea(const NeuronTree& intputTree, NeuronTree& outputTree, int xLength, int yLength, int zLength, float ratio, float distance)
{
	// -- Randomly generate background patches away from the forbidden distance. This method aims to reinforce the background recognition near soma area.
	//      xLength, yLength, zLength: decide the range to apply with soma centered
	//      ratio:    the ratio of targeted number of soma background nodes to the number of manual nodes in the inputTree
	//      distance: the forbidden distance from each SWC node

	NeuronSWC somaNode;
	for (QList<NeuronSWC>::const_iterator it = intputTree.listNeuron.begin(); it != intputTree.listNeuron.end(); ++it)
	{
		if (it->parent == -1)
		{
			somaNode = *it;
			break;
		}
	}
	float xlb = somaNode.x - float(xLength);
	float xhb = somaNode.x + float(xLength);
	float ylb = somaNode.y - float(yLength);
	float yhb = somaNode.y + float(yLength);
	float zlb = somaNode.z - float(zLength);
	float zhb = somaNode.z + float(zLength);

	list<NeuronSWC> confinedNodes;
	for (QList<NeuronSWC>::const_iterator it = intputTree.listNeuron.begin(); it != intputTree.listNeuron.end(); ++it)
		if (xlb <= it->x && xhb >= it->x && ylb <= it->y && yhb >= it->y && zlb <= it->z && zhb >= it->z) confinedNodes.push_back(*it);

	int targetBkgNodeNum = int(float(xLength) * float(yLength) * float(zLength) * ratio);
	cout << targetBkgNodeNum << " targeted bkg nodes to ge generated." << endl;
	int bkgNodeCount = 0;
	while (bkgNodeCount <= targetBkgNodeNum)
	{
		int randNumX = rand() % int(xhb - xlb + 1) + int(xlb);
		int randNumY = rand() % int(yhb - ylb + 1) + int(ylb);
		int randNumZ = rand() % int(zhb - zlb + 1) + int(zlb);

		bool flag = false;
		for (list<NeuronSWC>::iterator it = confinedNodes.begin(); it != confinedNodes.end(); ++it)
		{
			float distSqr;
			float diffx = float(randNumX) - it->x;
			float diffy = float(randNumY) - it->y;
			float diffz = float(randNumZ) - it->z;
			distSqr = diffx * diffx + diffy * diffy + diffz * diffz;

			if (distSqr <= distance * distance)
			{
				flag = true;
				break;
			}
		}

		if (flag == false)
		{
			++bkgNodeCount;
			if (bkgNodeCount % 10000 == 0) cout << bkgNodeCount << " bkg nodes generated within the soma area." << endl;

			NeuronSWC newBkgNode;
			newBkgNode.x = randNumX;
			newBkgNode.y = randNumY;
			newBkgNode.z = randNumZ;
			newBkgNode.type = 3;
			newBkgNode.radius = 1;
			newBkgNode.parent = -1;
			outputTree.listNeuron.push_back(newBkgNode);
		}
	}
}

void NeuronStructUtil::swcSlicer_DL(const NeuronTree& inputTree, vector<NeuronTree>& outputTrees, int thickness)
{
	// -- Dissemble SWC files into "slices." Each outputSWC file represents only 1 z slice.
	// thickness * 2 + 1 = the number of consecutive z slices for one SWC node to appear. This is for the purpose producing continous masks.

	QList<NeuronSWC> inputList = inputTree.listNeuron;
	int zMax = 0;
	ptrdiff_t thicknessPtrDiff = ptrdiff_t(thickness); // Determining how many z sections to be included in 1 single slice.
	for (QList<NeuronSWC>::iterator it = inputList.begin(); it != inputList.end(); ++it)
	{
		it->z = round(it->z);
		if (it->z >= zMax) zMax = it->z;
	}

	QList<NeuronTree> slicedTrees; // Determining number of sliced trees in the list.
	for (int i = 0; i < zMax; ++i)
	{
		NeuronTree nt;
		slicedTrees.push_back(nt);
	}

	for (QList<NeuronSWC>::iterator it = inputList.begin(); it != inputList.end(); ++it)
	{
		NeuronSWC currNode = *it;
		ptrdiff_t sliceNo = ptrdiff_t(it->z);
		(slicedTrees.begin() + sliceNo - 1)->listNeuron.push_back(currNode); // SWC starts with 1.
		float currZ = currNode.z;

		// -- Project +/- thickness slices onto the same plane, making sure the tube can be connected accross planes. -- //
		vector<ptrdiff_t> sectionNums;
		for (ptrdiff_t ptri = 1; ptri <= thicknessPtrDiff; ++ptri)
		{
			ptrdiff_t minusDiff = sliceNo - ptri;
			ptrdiff_t plusDiff = sliceNo + ptri;

			if (minusDiff < 0) continue;
			else sectionNums.push_back(minusDiff);

			if (plusDiff > ptrdiff_t(zMax)) continue;
			else sectionNums.push_back(plusDiff);
		}
		for (vector<ptrdiff_t>::iterator ptrIt = sectionNums.begin(); ptrIt != sectionNums.end(); ++ptrIt)
		{
			//cout << "current node z:" << currNode.z << " " << *ptrIt << "| ";
			NeuronSWC newNode = currNode;
			newNode.z = float(*ptrIt);
			(slicedTrees.begin() + *ptrIt - 1)->listNeuron.push_back(newNode);
		}
		//cout << endl;

		sectionNums.clear();
		// ------------------------------------------------------------------------------------------------------------- //
	}

	for (QList<NeuronTree>::iterator it = slicedTrees.begin(); it != slicedTrees.end(); ++it)
		outputTrees.push_back(*it);
}
/* =================================== Volumetric SWC sampling methods =================================== */