/*
 * This binary enables the training and testing of aromaticity models using
 * the NSPDK kernel within the SGD framework.
 *
 *      Author: mmann
 */

#include <algorithm>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
#include <vector>
#include <string>
#include <exception>
#include <iterator>
#include <cctype>
#include <cstdlib>
#include <ctime>
#include <map>

#include <boost/algorithm/string.hpp>

#include "biu/OptionParser.hh"
#include "biu/Timer.hh"

#include <sgm/Graph_boost.hh>

#include <ggl/Graph.hh>
#include <ggl/chem/Molecule.hh>
#include <ggl/chem/AP_NSPDK.hh>
#include <ggl/chem/SMILESparser.hh>
#include <ggl/chem/SMILESwriter.hh>
#include <ggl/Graph_GML_writer.hh>

#include <sgd/svmsgd.h>

#include "version.hh"

	using namespace ggl;
	using namespace ggl::chem;

//////////////////////////////////////////////////////////////////////////
#ifndef ARGEXCEPTION_
#define ARGEXCEPTION_

	  /*! Exception class for exeptions thrown during argument and input parsing.
	   */
	class ArgException : public std::exception {
	public:
		  //! the error message
		std::string errorMsg;
		ArgException( std::string errorMsg_ ) : errorMsg(errorMsg_) {}
		virtual ~ArgException() throw (){}
		virtual const char* what() const throw() {
			return errorMsg.c_str();
		}
	};

#endif
//////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////

bool
SVector_less(const sgd::SVector& v, const sgd::SVector & vCompare);

/**
 * Dummy pointer class that represents an index within a vector and enables
 * vector element comparison/sorting without vector alteration.
 */
class FeatureInfo {

	size_t posInVector;
	sgd::xvec_t * features;

public:

	FeatureInfo()
	: posInVector(0), features(NULL)
	{}

	FeatureInfo( size_t posInVector
				, sgd::xvec_t * features )
	: posInVector(posInVector)
		, features(features)
	{}

	size_t getPosition() const {
		return posInVector;
	}

	bool
	operator < ( const FeatureInfo & toComp ) const {
		assert( toComp.features == this->features );
		return SVector_less( this->features->at(posInVector), toComp.features->at(toComp.posInVector) );
	}
};

/////////////////////////////////////////////////////////////////////////////

/**
 * IN CLUSTERING MODE : Collects information for each ring cluster.
 */
class ClusterInfo {
public:
	  //! position of the representative within the feature vector
	size_t posInVector;
	  //! number of features for this cluster
	size_t clusterSize;
	  //! number of molecules with this feature
	size_t molNumber;

	ClusterInfo()
	  : posInVector(0), clusterSize(0), molNumber(0)
	{}

	ClusterInfo( const size_t posInVector, const size_t clusterSize, const size_t molNumber)
	  : posInVector(posInVector), clusterSize(clusterSize), molNumber(molNumber)
	{}

};

/////////////////////////////////////////////////////////////////////////////

void
annotateRing( Molecule & mol, const sgm::RingReporter::RingList & ring, bool addAnnotation );

/////////////////////////////////////////////////////////////////////////////

void
initAllowedArguments( biu::OptionMap & allowedArgs, std::string &infoText );

//////////////////////////////////////////////////////////////////////////

void getFeatures( AP_NSPDK & ap
				, const Molecule & mol
				, sgd::xvec_t& trainFeatures
				, sgd::yvec_t& trainTarget
				, std::vector< sgm::RingReporter::RingList > * trainRing = NULL
				);

//////////////////////////////////////////////////////////////////////////

void getFeatures(	AP_NSPDK & ap
					, const std::string & molSMILES
					, sgd::xvec_t& trainFeatures
					, sgd::yvec_t& trainTarget );

/////////////////////////////////////////////////////////////////////////////

typedef std::vector< std::string > SMILES_container;
typedef std::back_insert_iterator< SMILES_container > SMILES_inserter;

void parseSMILES(	std::istream & in
					, SMILES_inserter & toFill
					, const size_t linesRead ) throw(std::exception);

/////////////////////////////////////////////////////////////////////////////

void
readModel( std::istream & in, AP_NSPDK_Model & model );

/////////////////////////////////////////////////////////////////////////////

void
printModel( std::ostream & out, const AP_NSPDK_Model & model );

/////////////////////////////////////////////////////////////////////////////

void
printWeightedGML( std::ostream & out, AP_NSPDK & apNSPDK, const Molecule & mol, const bool multilineGML );

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

enum Mode { MODE_TRAIN, MODE_TEST, MODE_APPLY, MODE_CLUST, MODE_XVAL, MODE_UNDEF };

int main( int argc, char** argv ) {

	//////////////////////////////////////////////////////////////
	// variables
	//////////////////////////////////////////////////////////////

	std::istream* inSMILES = NULL;
	std::ifstream* inSMILESFile = NULL;
	size_t inSMILESLine = 0;

	std::istream* inModel = NULL;
	std::ifstream* inModelFile = NULL;

	std::ostream* out = &std::cout;
	std::ofstream* outFile = NULL;

	std::ostream* log = &std::cout;
	std::ofstream* logFile = NULL;

	std::ostream* prediction = NULL;
	std::ofstream* predictionFile = NULL;

	SMILES_container SMILES;
	Mode mode = MODE_UNDEF;

	double lambda = 0.0;
	size_t epochs = 0;

	std::string modelID = "";
	size_t maxRingSize = 0;
	size_t nspdkMaxRingDistance = 0;
	size_t nspdkMaxDistance = 0;
	size_t nspdkMaxRadius = 0;
	size_t nspdkFeatureBitSize = 0;
	size_t randomClusterLists = 0;

	//////////////////////////////////////////////////////////////
	// parameter parsing and checking
	//////////////////////////////////////////////////////////////

	biu::OptionMap allowedArgs;	//< map of allowed arguments
	std::string infoText;		//< info string of the program
	initAllowedArguments(allowedArgs,infoText);	// init

		// parse programm arguments
	biu::COptionParser opts = biu::COptionParser(	allowedArgs, argc,
													argv, infoText);
		// check arguments parseable and all mandatory arguments given
		// + help output if needed
	if (opts.argExist("help")) {
		opts.coutUsage();
		return 0;
	}
	if (opts.argExist("version")) {
		giveVersion();
		return 0;
	}
	if (!opts.noErrors()) {
		return -1;
	}

	int exitValue = 0;

	try {

		  // set SMILES input stream
		if (boost::iequals(opts.getStrVal("smiles"),"STDIN")) {
			  // read from STDIN
			inSMILES = &std::cin;
		} else {
			  // read from file
			inSMILESFile = new std::ifstream( opts.getStrVal("smiles").c_str()
										, std::ifstream::in );
			if (!inSMILESFile->is_open()) {
				std::ostringstream oss;
				oss	<<"cannot open SMILES input file '" <<opts.getStrVal("smiles") <<"'";
				throw ArgException(oss.str());
			}
			inSMILES = inSMILESFile;
		}
		  // set output stream
		if (opts.getStrVal("out").size() == 0) {
			throw ArgException("no output file given");
		} else if ( !boost::iequals(opts.getStrVal("out"),"STDOUT")) {
			outFile = new std::ofstream(	opts.getStrVal("out").c_str()
											, std::ofstream::out );
			if (!outFile->is_open()) {
				std::ostringstream oss;
				oss	<<"cannot open output file '" <<opts.getStrVal("out") <<"'";
				throw ArgException(oss.str());
			}
			out = outFile;
		}
		  // set logfile stream
		if (opts.getStrVal("log").size() == 0) {
			throw ArgException("no logfile file given");
		} else if ( boost::iequals(opts.getStrVal("log"),"STDOUT")) {
			log = &std::cout;
		} else if ( boost::iequals(opts.getStrVal("log"),"STDERR")) {
			log = &std::cerr;
		} else {
			logFile = new std::ofstream(	opts.getStrVal("log").c_str()
											, std::ofstream::out );
			if (!logFile->is_open()) {
				std::ostringstream oss;
				oss	<<"cannot open logfile file '" <<opts.getStrVal("log") <<"'";
				throw ArgException(oss.str());
			}
			log = logFile;
		}
		  // set prediction stream if needed
		if (opts.argExist("prediction") && opts.getStrVal("prediction").size() >= 0) {
			if ( boost::iequals(opts.getStrVal("prediction"),"STDOUT")) {
				prediction = &std::cout;
			} else if ( boost::iequals(opts.getStrVal("prediction"),"STDERR")) {
				prediction = &std::cerr;
			} else {
				predictionFile = new std::ofstream(	opts.getStrVal("prediction").c_str()
												, std::ofstream::out );
				if (!predictionFile->is_open()) {
					std::ostringstream oss;
					oss	<<"cannot open prediction file '" <<opts.getStrVal("prediction") <<"'";
					throw ArgException(oss.str());
				}
				prediction = predictionFile;
			}
		}
		  // set mode
		if (boost::iequals(opts.getStrVal("mode"), "train")) {
			mode = MODE_TRAIN;
		} else if (boost::iequals(opts.getStrVal("mode"), "test")) {
			mode = MODE_TEST;
		} else if (boost::iequals(opts.getStrVal("mode"), "apply")) {
			mode = MODE_APPLY;
		} else if (boost::iequals(opts.getStrVal("mode"), "cluster")) {
			mode = MODE_CLUST;
		} else if (boost::iequals(opts.getStrVal("mode"), "xval")) {
			mode = MODE_XVAL;
		} else {
			std::ostringstream oss;
			oss	<<"mode '" <<opts.getStrVal("mode") <<"' is unknown";
			throw ArgException(oss.str());
		}

		  // set SGD lambda
		lambda = opts.getDoubleVal("lambda");
		  // set number of epochs
		if (opts.getIntVal("epochs") <= 0) {
			throw ArgException("number of epochs has to be >= 1");
		} else {
			epochs = (size_t)opts.getIntVal("epochs");
		}

		  // set model ID
		modelID = opts.getStrVal("modelID");
		  // set max ring size
		if (opts.getIntVal("maxRingSize") <= 2) {
			throw ArgException("maximal ring size has to be >= 3");
		} else {
			maxRingSize = (size_t)opts.getIntVal("maxRingSize");
		}
		  // set the NSPDK maximal distance
		if (opts.getIntVal("nspdkMaxDistance") < 0) {
			throw ArgException("NSPDK maximal distance has to be >= 0");
		} else {
			nspdkMaxDistance = (size_t)opts.getIntVal("nspdkMaxDistance");
		}
		  // set the NSPDK maximal radius
		if (opts.getIntVal("nspdkMaxRadius") < 0) {
			throw ArgException("NSPDK maximal radius has to be >= 0");
		} else {
			nspdkMaxRadius = (size_t)opts.getIntVal("nspdkMaxRadius");
		}
		  // set the NSPDK maximal radius
		if (opts.argExist("nspdkMaxRingDistance")) {
			if (!opts.argExist("ringCentered")) {
				throw ArgException("Giving 'nspdkMaxRingDistance' without 'ringCentered' makes no sense..");
			}
			if (opts.getIntVal("nspdkMaxRingDistance") < 0) {
				throw ArgException("The maximal distance from the ring for feature generation has to be >= 0");
			} else {
				nspdkMaxRingDistance = (size_t)opts.getIntVal("nspdkMaxRingDistance");
			}
		}
		  // set the NSPDK feature bit size
		if (opts.getIntVal("nspdkFeatureBitSize") < 8) {
			throw ArgException("NSPDK maximal radius has to be >= 8");
		} else {
			nspdkFeatureBitSize = (size_t)opts.getIntVal("nspdkFeatureBitSize");
		}

		  // set number of random cluster lists
		if (opts.getIntVal("outClustLists") < 0) {
			throw ArgException("The number of random cluster sampling lists has to be at least 1.");
		} else {
			randomClusterLists = (size_t)opts.getIntVal("outClustLists");
		}

		/////////////////////////////////////////////////////////////
		// parse all SMILES
		/////////////////////////////////////////////////////////////

		  // parse SMILES
		{
			SMILES_inserter inserter(SMILES);
			inSMILESLine = 0;
			parseSMILES( *inSMILES, inserter, inSMILESLine );
		}

		switch (mode) {

		case MODE_TRAIN : {

			/////////////////////////////////////////////////////////////
			// training mode
			/////////////////////////////////////////////////////////////

			  // setup new model to be filled
			AP_NSPDK_Model newModel;

			newModel.ringCentered = opts.argExist("ringCentered");
			newModel.modelID = modelID;
			newModel.maxRingSize = maxRingSize;
			newModel.nspdk_maxRingDistance = nspdkMaxRingDistance;
			newModel.nspdk_maxDistance = nspdkMaxDistance;
			newModel.nspdk_maxRadius = nspdkMaxRadius;
			newModel.nspdk_featureBitSize = nspdkFeatureBitSize;

			newModel.ringEdgeLabel = "*~";
			newModel.ringNodeLabelPrefix = "*";
			newModel.ringViewLabelPrefix = "?";

			  // create AP_NSPDK instance using the model
			AP_NSPDK apNSPDK( & newModel );
			  // create features
			sgd::xvec_t trainFeatures;
			sgd::yvec_t trainTarget;
			biu::Timer timer;
			timer.start();
			for (size_t i=0; i<SMILES.size(); ++i ) {
				getFeatures( apNSPDK, SMILES.at(i), trainFeatures, trainTarget );
			}
			*log <<" time generating    = " <<(size_t)timer.stop() <<" ms" <<std::endl;

			  // create SGD instance
			sgd::SvmSgd trainer( lambda );
			  // run training
			timer.start();
			trainer.train( trainFeatures, trainTarget, 0, trainFeatures.size(), epochs );
			*log <<" time training      = " <<(size_t)timer.stop() <<" ms" <<std::endl;

			  // store resulting model
			newModel.bias = trainer.getModel().bias;
			newModel.wscale = trainer.getModel().wscale;
			newModel.w = trainer.getModel().w;

			  // output model
			printModel( *out, newModel );

			  // print statistics
			size_t countArom = 0;
			for (sgd::yvec_t::const_iterator it = trainTarget.begin();
					it != trainTarget.end(); ++it )
			{
				if (*it > 0)
					countArom++;
			}
			*log
				<<" molecules          = " <<SMILES.size()
				<<"\n overall rings      = " <<trainTarget.size()
				<<"\n aromatic rings     = " <<countArom
				<<" = " <<(double(countArom)/double(trainTarget.size())*100.0) <<" %"
				<<"\n non-aromatic rings = " <<(trainTarget.size()-countArom)
				<<" = " <<(double(trainTarget.size()-countArom)/double(trainTarget.size())*100.0) <<" %"
				<<"\n" <<std::endl;

		}; break;
		case MODE_XVAL : {

			/////////////////////////////////////////////////////////////
			// cross validation mode
			/////////////////////////////////////////////////////////////

			  // set number of chunks to randomly split the data into
			if (opts.getIntVal("xval") < 2) {
				throw ArgException("number of epochs has to be >= 2");
			}
			const size_t xval = (size_t)opts.getIntVal("xval");

			  // setup new model to be filled
			AP_NSPDK_Model newModel;

			newModel.ringCentered = opts.argExist("ringCentered");
			newModel.modelID = modelID;
			newModel.maxRingSize = maxRingSize;
			newModel.nspdk_maxRingDistance = nspdkMaxRingDistance;
			newModel.nspdk_maxDistance = nspdkMaxDistance;
			newModel.nspdk_maxRadius = nspdkMaxRadius;
			newModel.nspdk_featureBitSize = nspdkFeatureBitSize;

			newModel.ringEdgeLabel = "*~";
			newModel.ringNodeLabelPrefix = "*";
			newModel.ringViewLabelPrefix = "?";

			  // create AP_NSPDK instance using the model
			AP_NSPDK apNSPDK( & newModel );
			  // create features
			sgd::xvec_t trainFeatures;
			sgd::yvec_t trainTarget;
			biu::Timer timer;
			timer.start();
			for (size_t i=0; i<SMILES.size(); ++i ) {
//				std::cerr <<" generating for " <<SMILES.at(i) <<"\n";
				getFeatures( apNSPDK, SMILES.at(i), trainFeatures, trainTarget );
			}
			*log <<" time generating    = " <<(size_t)timer.stop() <<" ms" <<std::endl;
			*log <<" ring number        = " <<trainFeatures.size() <<std::endl;

			  // create randomized chunks
			std::vector<size_t> randomTrainIds(trainFeatures.size(),0);
			for (size_t i=0; i<randomTrainIds.size(); ++i) {
				randomTrainIds[i] = i;
			}
			 // initialize random seed:
			std::srand( unsigned (std::time(NULL)) );
			 // shuffle indices
			std::random_shuffle(randomTrainIds.begin(),randomTrainIds.end());
			 // get chunk size rounded to ceiling
			const size_t chunkSize = (size_t)std::ceil((double)randomTrainIds.size() / (double)xval);

			 // generate test annotation for each feature vector via cross-validation
			sgd::yvec_t testValue(trainTarget.size(),-17.0);
			  // create SGD instance
			sgd::SvmSgd trainer( lambda );
			for (size_t chunk = 0; chunk < xval; ++chunk) {

				  // GENERATE TRAIN AND TEST DATA

				size_t chunkStart = chunk*chunkSize;
				sgd::xvec_t curTestFeatures( chunkSize - ( chunk+1 == xval ? (chunkStart+chunkSize) - trainFeatures.size() : 0) );
				  // collect train chunks
				sgd::xvec_t curTrainFeatures( trainFeatures.size() - curTestFeatures.size() );
				sgd::yvec_t curTrainTargets( trainFeatures.size() - curTestFeatures.size() );
				  // collect leading chunks
				size_t curPos = 0;
				for (size_t c=0; c<chunk; ++c) {
					chunkStart = c*chunkSize;
					for (size_t i=0; i<chunkSize; ++i) {
						curTrainFeatures[curPos] = trainFeatures.at(randomTrainIds.at(chunkStart+i));
						curTrainTargets[curPos]  = trainTarget.at(randomTrainIds.at(chunkStart+i));
						++curPos;
					}
				}
				  // collect trailing chunks
				for (size_t c=chunk+1; c<xval; ++c) {
					chunkStart = c*chunkSize;
					for (size_t i=0; i<chunkSize && (chunkStart+i) < trainFeatures.size(); ++i) {
						curTrainFeatures[curPos] = trainFeatures.at(randomTrainIds.at(chunkStart+i));
						curTrainTargets[curPos]  = trainTarget.at(randomTrainIds.at(chunkStart+i));
						++curPos;
					}
				}

				  // GENERATE MODEL

				  // run training
				timer.start();
				trainer.train( curTrainFeatures, curTrainTargets, 0, curTrainFeatures.size(), epochs );
				*log <<chunk <<" time training      = " <<(size_t)timer.stop() <<" ms" <<std::endl;

				  // store resulting model
				newModel.bias = trainer.getModel().bias;
				newModel.wscale = trainer.getModel().wscale;
				newModel.w = trainer.getModel().w;

				  // RUN AND STORE PREDICTION ON TEST
				chunkStart = chunk*chunkSize;
				  // collect test chunk
				double timeTesting = 0;
				for (size_t i=0; i<chunkSize && (chunkStart+i) < trainFeatures.size(); ++i) {
					  // test instance
					timer.start();
					testValue[randomTrainIds.at(chunkStart+i)] = newModel.predictValue( trainFeatures.at(randomTrainIds.at(chunkStart+i)) );
					timeTesting += timer.stop();
				}
				*log <<chunk <<" time testing       = " <<(size_t)timer.stop() <<" ms" <<std::endl;

			}

			 // GATHER STATISTICS

			size_t truePositive  = 0; // correctly aromatic
			size_t trueNegative  = 0; // correctly non-aromatic
			size_t falsePositive = 0; // incorrectly aromatic (is non-aromatic)
			size_t falseNegative = 0; // incorrectly non-aromatic (is aromatic)

			  // compare each target and test value
			  // and print target-test value pairs to out
			*out <<"targetVal\tpredVal\n";
			for (size_t i=0; i<testValue.size(); ++i) {
				*out <<trainTarget.at(i) <<"\t" <<testValue.at(i) <<"\n";
				  // correct prediction
				if (trainTarget.at(i)*testValue.at(i) > 0) {
					if (trainTarget.at(i) > 0) {
						++truePositive;
					} else {
						++trueNegative;
					}
				} // incorrect prediction
				else {
					if (trainTarget.at(i) > 0) {
						++falseNegative;
					} else {
						++falsePositive;
					}
				}
			}

			  // print statistics
			size_t countArom = (truePositive+falseNegative);
			*log
				<<" molecules          = " <<SMILES.size()
				<<"\n overall rings      = " <<trainTarget.size()
				<<"\n aromatic rings     = " <<countArom
				<<" = " <<(double(countArom)/double(trainTarget.size())*100.0) <<" %"
				<<"\n non-aromatic rings = " <<(trainTarget.size()-countArom)
				<<" = " <<(double(trainTarget.size()-countArom)/double(trainTarget.size())*100.0) <<" %"
				<<"\n" <<std::endl
				<<"\n TP (arom)            = " <<truePositive
				<<"\n TN (non-arom)        = " <<trueNegative
				<<"\n FN (arom)            = " <<falseNegative
				<<"\n FP (non-arom)        = " <<falsePositive
				<<"\n" <<std::endl
				;

		}; break;
		case MODE_CLUST : {

			/////////////////////////////////////////////////////////////
			// clustering mode
			/////////////////////////////////////////////////////////////

			 // generate Molecule representation of all SMILES
			std::vector< Molecule > molecules(SMILES.size());
			for (size_t i=0; i<SMILES.size(); ++i) {

				  // get molecule graph
				std::pair< Molecule, int > parse = SMILESparser::parseSMILES( SMILES.at(i) );
				if (parse.second >= 0) {
					throw ArgException("SMILES '"+SMILES.at(i)+"' cannot be parsed.");
				}
				  // copy to container
				MoleculeUtil::copy( parse.first, molecules[i] );
				  // fill protons
				MoleculeUtil::fillProtons( molecules[i] );

			}

			  // setup new model to be filled
			AP_NSPDK_Model newModel;

			newModel.ringCentered = opts.argExist("ringCentered");
			newModel.modelID = modelID;
			newModel.maxRingSize = maxRingSize;
			newModel.nspdk_maxRingDistance = nspdkMaxRingDistance;
			newModel.nspdk_maxDistance = nspdkMaxDistance;
			newModel.nspdk_maxRadius = nspdkMaxRadius;
			newModel.nspdk_featureBitSize = nspdkFeatureBitSize;

			newModel.ringEdgeLabel = "*~";
			newModel.ringNodeLabelPrefix = "*";
			newModel.ringViewLabelPrefix = "?";

			  // create AP_NSPDK instance using the model
			AP_NSPDK apNSPDK( & newModel );
			  // create features
			sgd::xvec_t trainFeatures;
			sgd::yvec_t trainTarget;
			  // the ring information for each feature vector
			std::vector< sgm::RingReporter::RingList > trainRing;
			typedef std::vector< size_t > SizetVec;
			SizetVec trainSMILES;
			biu::Timer timer;
			timer.start();
			for (size_t i=0; i<molecules.size(); ++i ) {
				getFeatures( apNSPDK, molecules.at(i), trainFeatures, trainTarget, &trainRing );
				// store SMILES/molecule ID for each added feature
				trainSMILES.resize(trainFeatures.size(),i);
			}
			*log <<" time generating    = " <<(size_t)timer.stop() <<" ms" <<std::endl;
			timer.start();

			std::vector< FeatureInfo > featureInfo( trainFeatures.size() );
			for (size_t i=0; i<featureInfo.size(); ++i) {
				featureInfo[i] = FeatureInfo(i, &trainFeatures);
			}

			  // sort all features but preserve ID information for the features
			std::sort( featureInfo.begin(), featureInfo.end());

			*log <<" time sorting       = " <<(size_t)timer.stop() <<" ms" <<std::endl;

			  // gather histogram data for cluster size
			  // and output at most two random samples per cluster
			typedef std::map<size_t,size_t> HIST;
			HIST clusterSizeHistogram;
			HIST clusterMolHistogram;
			size_t cStart = 0, cNext=0, cID = 0;

//			std::cerr <<"\n#############  ITERATE  ########## ";

			std::vector< std::vector<ClusterInfo> > selectedRings(randomClusterLists);
			/* initialize random seed: */
			std::srand ( unsigned (std::time(NULL)) );
			std::set<size_t> molIDs;
			while( cNext<featureInfo.size() ) {
				molIDs.clear();
				  // insert index of first cluster molecule
				molIDs.insert(trainSMILES.at(featureInfo.at(cNext).getPosition()));
				  // find end of current cluster
				++cNext;
				while(cNext<featureInfo.size()
						&& ! (featureInfo.at(cStart) < featureInfo.at(cNext)))
				{
					  // insert index of next cluster molecule
					molIDs.insert(trainSMILES.at(featureInfo.at(cNext).getPosition()));
					++cNext;
				}

				// update size histogram information
				size_t clusterSize = cNext-cStart;
				if (clusterSizeHistogram.find(clusterSize) == clusterSizeHistogram.end()) {
					clusterSizeHistogram[clusterSize] = 0;
				}
				clusterSizeHistogram[clusterSize]++;

				  // update molecule number histogram information
				size_t molNumber = molIDs.size();
				if (clusterMolHistogram.find(molNumber) == clusterMolHistogram.end()) {
					clusterMolHistogram[molNumber] = 0;
				}
				clusterMolHistogram[molNumber]++;

				  // pick random element of this cluster
				for (size_t i=0; i<randomClusterLists; ++i) {
					if (clusterSize == 1) {
						selectedRings[i].push_back( ClusterInfo(featureInfo.at(cStart).getPosition(), clusterSize, molNumber) );
					} else {
						  // pick element at random
						size_t pos1 = rand() % clusterSize;
						  // first element
						selectedRings[i].push_back( ClusterInfo(featureInfo.at(cStart+pos1).getPosition(), clusterSize, molNumber) );
					}
				}
				  // go to next cluster
				cStart = cNext;
				++cID;
			}

			  // output histogram data to log
			*log <<"\n";
			*log <<" cluster size histogram :\n\tclusterSize\t#cluster\n";
			for (HIST::const_iterator c=clusterSizeHistogram.begin();c!=clusterSizeHistogram.end(); ++c) {
				*log <<"\t" <<c->first <<"\t" <<c->second <<"\n";
			}
			*log <<std::endl;
			*log <<" molecules per cluster histogram :\n\tmolPerCluster\t#cluster\n";
			for (HIST::const_iterator c=clusterMolHistogram.begin();c!=clusterMolHistogram.end(); ++c) {
				*log <<"\t" <<c->first <<"\t" <<c->second <<"\n";
			}
			*log <<std::endl;

			  // output selected SMILES to out stream
			for (size_t i=0; i<randomClusterLists; ++i) {
				*out <<"# cluster sample list " <<(i+1) <<"\n";
				for (std::vector<ClusterInfo>::const_iterator it=selectedRings.at(i).begin(); it!=selectedRings.at(i).end(); ++it) {

					  // get molecule/SMILES index of representative
					const size_t molID = trainSMILES.at(it->posInVector);

					  // get ring annotation within the representative
					annotateRing(molecules[molID], trainRing.at(it->posInVector), true);
					  // get ring annotated SMILES for output
					const std::string ringSMILES = SMILESwriter::getSMILES(molecules[molID]);
					  // undo ring annotation
					annotateRing(molecules[molID], trainRing.at(it->posInVector), false);

					  // output original SMILES
					*out <<SMILES.at(molID)
					  // output ring annotated SMILES
						<<" "
						<<ringSMILES
					  // output cluster size
						<<" "
						<<it->clusterSize
					  // output molecule number
						<<" "
						<<it->molNumber
					  // line break
						<<"\n";
				}
				*out <<std::endl;
			}

		}; break;
		case MODE_TEST :
		{

			/////////////////////////////////////////////////////////////
			// test mode
			/////////////////////////////////////////////////////////////

			  // check input stream
			if (boost::iequals(opts.getStrVal("smiles"),opts.getStrVal("model"))) {
				throw ArgException("cannot read both SMILES and model from same stream");
			}

			  // read model
			AP_NSPDK_Model model;
			model.modelID = modelID;

			  // check if default model to be used
			if (opts.getStrVal("model").size() == 1
				&& std::string("OoMmCc").find(opts.getStrVal("model").at(0)) != std::string::npos )
			{
				// load default model
				switch ( opts.getStrVal("model").at(0) ) {
				case 'O' :
				case 'o' :
					model = *(AP_NSPDK_Model::getInstance("OpenBabel:2013"));
					break;
				case 'M' :
				case 'm' :
					model = *(AP_NSPDK_Model::getInstance("Marvin:general:2013"));
					break;
				default :
					throw ArgException("unhandled default model character '"+opts.getStrVal("model")+"'");
					break;
				}
			} else {
				  // get model from stream

				  // set model input stream
				if (boost::iequals(opts.getStrVal("model"),"STDIN")) {
					  // read from STDIN
					inModel = &std::cin;
				} else {
					  // check if any model input given
					if (opts.getStrVal("model").size() == 0) {
						throw ArgException("test mode but no model input specified");
					}
					  // read from file
					inModelFile = new std::ifstream( opts.getStrVal("model").c_str()
												, std::ifstream::in );
					if (!inModelFile->is_open()) {
						std::ostringstream oss;
						oss	<<"cannot open model input file '" <<opts.getStrVal("model") <<"'";
						throw ArgException(oss.str());
					}
					inModel = inModelFile;
				}
				  // read model from stream
				readModel( *inModel, model );
			}
			if (opts.argExist("ringCentered") && !model.ringCentered) {
				throw ArgException("Parameter 'ringCentered' is not applicable for the used model");
			}

			  // create AP_NSPDK instance for feature generation using the model
			AP_NSPDK apNSPDK( & model );

			  // timing setup
			biu::Timer timer;
			double timeGenerating = 0.0;
			double timeTesting = 0.0;

			  // result counting
			enum Result { OVERALL, CORRECT
						, OVERALL_AROM, CORRECT_AROM
						, OVERALL_NON, CORRECT_NON
						, OVERALL_MOL, CORRECT_MOL
						, RES_NUM };
			std::vector< size_t > count( RES_NUM, 0 );

			  // test each ring of each molecule
			for (size_t i=0; i<SMILES.size(); ++i ) {

				count[OVERALL_MOL]++;
				  // create features of all rings within current molecule
				sgd::xvec_t testFeatures;
				sgd::yvec_t testTarget;
				timer.start();
				getFeatures( apNSPDK, SMILES.at(i), testFeatures, testTarget );
				timeGenerating += timer.stop();

				  // test each ring independently
				bool allRingsCorrect = true;
				for (size_t t=0; t<testFeatures.size(); ++t) {
					  // test instance
					timer.start();
					const double predictionValue = model.predictValue( testFeatures.at(t) );
					timeTesting += timer.stop();
					  // count instance
					count[OVERALL]++;
					  // report target and prediction if needed
					if (prediction != NULL) {
						*prediction <<testTarget.at(t) <<" " <<predictionValue <<"\n";
					}
					const bool isAromatic = predictionValue >= 0;
					if (testTarget.at(t) > 0) {
						count[OVERALL_AROM]++;
						if (isAromatic) {
							count[CORRECT]++;
							count[CORRECT_AROM]++;
						} else {
							allRingsCorrect = false;
						}
					} else {
						count[OVERALL_NON]++;
						if (!isAromatic) {
							count[CORRECT]++;
							count[CORRECT_NON]++;
						} else {
							allRingsCorrect = false;
						}
					}
				}

				  // count overall molecule prediction
				if (allRingsCorrect) {
					count[CORRECT_MOL]++;
				}
			}
			  // print statistics
			*out
				<<" time generating test   = " <<(size_t)timeGenerating <<" ms"
				<<"\n time testing           = " <<(size_t)timeTesting <<" ms"
				<<"\n overall rings correct  = " << count[CORRECT] << " / " <<count[OVERALL]
				<<" = " <<(double(count[CORRECT])/double(count[OVERALL])*100.0)
				<<" %"
				<<"\n aromatic rings correct = " << count[CORRECT_AROM] << " / " <<count[OVERALL_AROM]
				<<" = " <<(double(count[CORRECT_AROM])/double(count[OVERALL_AROM])*100.0)
				<<" %"
				<<"\n non-aromatic r correct = " << count[CORRECT_NON] << " / " <<count[OVERALL_NON]
				<<" = " <<(double(count[CORRECT_NON])/double(count[OVERALL_NON])*100.0)
				<<" %"
				<<"\n whole molecule correct = " << count[CORRECT_MOL] << " / " <<count[OVERALL_MOL]
				<<" = " <<(double(count[CORRECT_MOL])/double(count[OVERALL_MOL])*100.0)
				<<" %"
				<<"\n" <<std::endl;

		}; break;
		case MODE_APPLY :
		{

			/////////////////////////////////////////////////////////////
			// application mode
			/////////////////////////////////////////////////////////////

			  // check input stream
			if (boost::iequals(opts.getStrVal("smiles"),opts.getStrVal("model"))) {
				throw ArgException("cannot read both SMILES and model from same stream");
			}

			  // read model
			AP_NSPDK_Model model;
			model.modelID = modelID;

			  // check if default model to be used
			if (opts.getStrVal("model").size() == 1
				&& std::string("OoMmCc").find(opts.getStrVal("model").at(0)) != std::string::npos )
			{
				// load default model
				switch ( opts.getStrVal("model").at(0) ) {
				case 'O' :
				case 'o' :
					model = *(AP_NSPDK_Model::getInstance("OpenBabel:2013"));
					break;
				case 'M' :
				case 'm' :
					model = *(AP_NSPDK_Model::getInstance("Marvin:general:2013"));
					break;
				default :
					throw ArgException("unhandled default model character '"+opts.getStrVal("model")+"'");
					break;
				}
			} else {
				  // get model from stream

				  // set model input stream
				if (boost::iequals(opts.getStrVal("model"),"STDIN")) {
					  // read from STDIN
					inModel = &std::cin;
				} else {
					  // check if any model input given
					if (opts.getStrVal("model").size() == 0) {
						throw ArgException("test mode but no model input specified");
					}
					  // read from file
					inModelFile = new std::ifstream( opts.getStrVal("model").c_str()
												, std::ifstream::in );
					if (!inModelFile->is_open()) {
						std::ostringstream oss;
						oss	<<"cannot open model input file '" <<opts.getStrVal("model") <<"'";
						throw ArgException(oss.str());
					}
					inModel = inModelFile;
				}
				  // read model from stream
				readModel( *inModel, model );
			}

			if (opts.argExist("ringCentered") && !model.ringCentered) {
				throw ArgException("Parameter 'ringCentered' is not applicable for the used model");
			}

			  // create AP_NSPDK instance for feature generation using the model
			AP_NSPDK apNSPDK( & model );

			  // whether or not weighted GML output or SMILES is to be generated
			bool outputWeightGML = opts.argExist("outNodeWeight");
			bool multilineGML = false;

			  // run aromaticity perception for each molecule
			for (size_t i=0; i<SMILES.size(); ++i ) {

				  // get molecule graph
				std::pair< Molecule, int > parse = SMILESparser::parseSMILES( SMILES.at(i) );

				  // proton handling
				Molecule mol;
				MoleculeUtil::copy( parse.first, mol );
				MoleculeUtil::fillProtons( mol );

				  // apply aromaticity perception
				apNSPDK.correctAromaticity( mol, false );

				if (outputWeightGML) {
					  // write weighted GML output
					printWeightedGML( *out, apNSPDK, mol, multilineGML );
				} else {
					  // write SMILES output
					*out <<ggl::chem::SMILESwriter::getSMILES( mol );
				}
				  // line break after each molecule
				*out <<std::endl;
			}

		}; break;
		case MODE_UNDEF : {
			throw ArgException("no mode selected"); // should never occur
		}; break;
		default:
			throw ArgException("unknown mode selected"); // should never occur
		}

	} catch (std::exception& ex) {
		std::cerr <<"\n\n ERROR : " <<ex.what() <<"\n"<<std::endl;
		exitValue = -1;
	}

	//////////////////////////////////////////////////////////////
	// final stream handling
	//////////////////////////////////////////////////////////////

	inSMILES = &std::cin;
	inModel = &std::cin;
	out = &std::cout;
	log = &std::cerr;
	prediction = NULL;
	if (inSMILESFile != NULL)	{ inSMILESFile->close(); delete inSMILESFile; }
	if (inModelFile != NULL)	{ inModelFile->close(); delete inModelFile; }
	if (outFile != NULL)		{ outFile->close(); delete outFile; }
	if (logFile != NULL)		{ logFile->close(); delete logFile; }
	if (predictionFile != NULL)	{ predictionFile->close(); delete predictionFile; }

	return exitValue;
}

/////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////

//////////////////////////////////////////////////////////////////////////

/*!
 * Initialises allowed parameters and their default values and prepares an
 * information string for the tool.
 */
void
initAllowedArguments(biu::OptionMap & allowedArgs, std::string &infoText )
{
	infoText = "\n"
		"Reads a list of SMILES and trains/tests/applies an aromaticity"
		" model using NSPDK and the SGD framework.\n"
		"\n"
		;

	allowedArgs.push_back(biu::COption(
							"mode", false, biu::COption::STRING,
							"Running mode : 'train' : train a model using the given parameters;"
							" 'xval' : does an 'xval'-cross validation using the given model training parameters and prints target-prediction value pairs to 'out';"
							" 'cluster' : clusters the training data based on feature identity for a model using the given parameters;"
							" 'test' : tests the prediction of ring wise and for whole molecules (model needed);"
							" 'apply' : applies the given model to each molecule and prints the corrected SMILES (model needed)"
							));
	allowedArgs.push_back(biu::COption(
							"smiles", true, biu::COption::STRING,
							"SMILES input file name or 'STDIN' when to read from standard input",
							"STDIN"));
	allowedArgs.push_back(biu::COption(
							"out", true, biu::COption::STRING,
							"Output file name or 'STDOUT' when to write to standard output",
							"STDOUT"));
	allowedArgs.push_back(biu::COption(
							"log", true, biu::COption::STRING,
							"Log file name or 'STDERR'/'STDOUT' when to write to standard error / output",
							"STDERR"));
	allowedArgs.push_back(biu::COption(
							"prediction", true, biu::COption::STRING,
							"Prediction file name or 'STDERR'/'STDOUT' when to write to standard error / output"));
	allowedArgs.push_back(biu::COption(
							"model", true, biu::COption::STRING,
							"models input file name, or 'STDIN' when to read from standard input, or a single letter code for one of the default models : (O)penBabel or (M)arvin general mode.",
							"STDIN"));
	allowedArgs.push_back(biu::COption(
							"modelID", true, biu::COption::STRING,
							"the models ID to set",
							"NSPDK-SGD-aromaticity-model"));
	allowedArgs.push_back(biu::COption(
							"maxRingSize", true, biu::COption::INT,
							"the maximal ring size to be considered for aromatic ring training/testing",
							"13"));
	allowedArgs.push_back(biu::COption(
							"ringCentered", true, biu::COption::BOOL,
							"NSPDK: if given, the NSPDK features have to touch the ring of interest in at least one node"));
	allowedArgs.push_back(biu::COption(
							"nspdkMaxRingDistance", true, biu::COption::INT,
							"NSPDK: IF(ringCentered) : the distance from ring nodes to be considered for feature generation"));
	allowedArgs.push_back(biu::COption(
							"nspdkMaxDistance", true, biu::COption::INT,
							"NSPDK: the maximal distance between nodes to be considered for feature generation",
							"4"));
	allowedArgs.push_back(biu::COption(
							"nspdkMaxRadius", true, biu::COption::INT,
							"NSPDK: the maximal radius around nodes to be considered for feature generation",
							"2"));
	allowedArgs.push_back(biu::COption(
							"nspdkFeatureBitSize", true, biu::COption::INT,
							"NSPDK: the number of bits used to encode the NSPDK features",
							"22"));
	allowedArgs.push_back(biu::COption(
							"lambda", true, biu::COption::DOUBLE,
							"[mode=train] the lambda factor used by the SGD framework",
							"1e-4"));
	allowedArgs.push_back(biu::COption(
							"epochs", true, biu::COption::INT,
							"[mode=train] the number of training epochs performed by the SGD framework",
							"5"));
	allowedArgs.push_back(biu::COption(
							"outClustLists", true, biu::COption::INT,
							"[mode=cluster] number of random cluster sample lists to be reported",
							"1"));
	allowedArgs.push_back(biu::COption(
							"xval", true, biu::COption::INT,
							"[mode=xval] sets the X for X-cross validation, ie. the number of groups to randomly split the input into",
							"10"));
	allowedArgs.push_back(biu::COption(
							"outNodeWeight", true, biu::COption::BOOL,
							"[mode=apply] output is given in GML format where for each ring the normalized feature weights for view point nodes are provided"));
	allowedArgs.push_back(biu::COption(
							"help", true, biu::COption::BOOL,
							"Displays help on all parameters"));
	allowedArgs.push_back(biu::COption(
							"version", true, biu::COption::BOOL,
							"Version information"));
}

//////////////////////////////////////////////////////////////////////////

void getFeatures( AP_NSPDK & ap
				, const std::string & molSMILES
				, sgd::xvec_t& trainFeatures
				, sgd::yvec_t& trainTarget )
{

	  // get molecule graph
	std::pair< Molecule, int > parse = SMILESparser::parseSMILES( molSMILES );

	  // fill protons
	Molecule mol;
	MoleculeUtil::copy( parse.first, mol );
	MoleculeUtil::fillProtons( mol );

	  // call final function
	getFeatures(ap, mol, trainFeatures, trainTarget, NULL);

}

//////////////////////////////////////////////////////////////////////////

void getFeatures( AP_NSPDK & ap
				, const Molecule & mol
				, sgd::xvec_t& trainFeatures
				, sgd::yvec_t& trainTarget
				, std::vector< sgm::RingReporter::RingList > * trainRing
				)
{

	  // get features
	std::vector< std::pair< sgm::RingReporter::RingList, nspdk::SVector> > features = ap.getFeatures( mol );

	  // create a dummy graph to get access to the ring labels
	ggl::chem::Molecule_Graph molGraph(mol);

	  // feed all rings to the feature vector
	for (size_t i=0; i<features.size(); ++i ) {

		  // check if aromatic ring via bond labels
		sgm::RingReporter::RingList::const_iterator cur = features.at(i).first.begin();
		sgm::RingReporter::RingList::const_iterator last = features.at(i).first.begin();
		bool isAromatic = true;
		for (++cur; isAromatic && cur!=features.at(i).first.end();++cur,++last) {
			assert( molGraph.getEdgesBegin(*last,*cur) != molGraph.getEdgesEnd(*last,*cur) /*no edge present*/);
			isAromatic = MoleculeUtil::getBondData(molGraph.getEdgesBegin(*last,*cur)->getEdgeLabel())->isAromatic != 0;
		}

		  // store features
		trainFeatures.push_back( features.at(i).second );
		  // store training target
		trainTarget.push_back( isAromatic ? +1 : -1 );
		  // store ring information
		if (trainRing != NULL) {
			trainRing->push_back( features.at(i).first );
		}

	}
}

//////////////////////////////////////////////////////////////////////////

/*!
 * Parses SMILES strings from stream. Each line should contain only ONE SMILES
 * string. Leading and tailing whitespaces are ignored.
 *
 * @param in the stream to read from
 * @param toFill the STL inserter to add the found SMILES to
 *
 */
void
parseSMILES(	std::istream & in
				, SMILES_inserter & toFill
				, const size_t linesRead ) throw(std::exception)
{
	std::string line;
	size_t lineNumber = linesRead;
	const std::string WHITESPACES = std::string(" \t\n");
	  // parse whole input stream
	while (in.good()) {
		  // read next line
		std::getline( in, line );
		lineNumber++;

		  // check if line is not empty
		if (line.size() > 0) {
			size_t start = line.find_first_not_of(WHITESPACES);
			  // check if line is not only filled with blanks
			if (start != std::string::npos ) {
				size_t end = line.find_last_not_of(WHITESPACES);
				  // add SMILES string to inserter
				*toFill = line.substr( start, end - start + 1 );
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////

void
readModel( std::istream & in, AP_NSPDK_Model & model )
{
	in
		>> model.ringCentered
		>> model.nspdk_maxRingDistance
		>> model.nspdk_maxDistance
		>> model.nspdk_maxRadius
		>> model.nspdk_featureBitSize
		>> model.maxRingSize
		>> model.bias
		>> model.wscale
		>> model.w
	;

	model.ringEdgeLabel = "*~";
	model.ringNodeLabelPrefix = "*";
	model.ringViewLabelPrefix = "?";
}

//////////////////////////////////////////////////////////////////////////

void
printModel( std::ostream & out, const AP_NSPDK_Model & model )
{
	out
		<< model.ringCentered <<" "
		<< model.nspdk_maxRingDistance <<" "
		<< model.nspdk_maxDistance <<" "
		<< model.nspdk_maxRadius <<" "
		<< model.nspdk_featureBitSize <<" "
		<< model.maxRingSize <<" "
		<< model.bias <<" "
		<< model.wscale <<" "
		<< model.w <<"\n"
	;
}

//////////////////////////////////////////////////////////////////////////

void
annotateRing( Molecule & mol, const sgm::RingReporter::RingList & ring, bool addAnnotation )
{

	const std::string ringClass = ":1";

	  // label access
	boost::property_map< ggl::chem::Molecule, ggl::chem::PropNodeLabel >::type
		nodeLabel = boost::get( ggl::chem::PropNodeLabel(), mol );

	  // update ring vertices
	sgm::RingReporter::RingList::const_iterator cur = ring.begin();
	for (++cur; cur!=ring.end();++cur) {

		  // get vertex
		Molecule::vertex_descriptor node = boost::vertex( *cur, mol );

		  // get current atom label
		std::string atomLabel = nodeLabel[node];

		  // alter the atom label accordingly
		if (addAnnotation) {
			  // ensure the ring class is not present
			assert( atomLabel.find(ringClass) == std::string::npos );
			atomLabel += ringClass;
		} else {
			  // ensure the ring class is present
			assert( atomLabel.find(ringClass) != std::string::npos );
			atomLabel = atomLabel.substr(0,atomLabel.find(ringClass));

		}

		  // set altered label
		nodeLabel[ node ] = atomLabel;
	}

}

//////////////////////////////////////////////////////////////////////////

void
printWeightedGML( std::ostream & out, AP_NSPDK & apNSPDK, const Molecule & mol, const bool multilineGML )
{

	std::string linesep = (multilineGML ? "\n" : "");
	std::string indent = (multilineGML ? "  " : "");

	  // get weights for each ring
	std::vector< std::pair< AP_NSPDK::RingDescriptor, AP_NSPDK::RingWeightVec> >
		weightsPerRing = apNSPDK.getNodeWeights( mol );

	  // generate additional GML information for each ring
	std::stringstream ringGmlEncoding;
	  // change bond and node labels
	  // for each predicted ring
	for (size_t i=0; i<weightsPerRing.size(); ++i)
	{
		const AP_NSPDK::RingDescriptor & ring = weightsPerRing.at(i).first;
		const AP_NSPDK::RingWeightVec & ringWeights = weightsPerRing.at(i).second;

		  // general ring informationring
		ringGmlEncoding <<"ring [" <<linesep
						<<indent <<"label \""
						<<(ring.predState == AP_NSPDK::RingDescriptor::Aromatic ? "aromatic" : "non-aromatic" )
						<<":"
						<<ring.predCertainty
						<<"\""<<linesep
		  // ring nodes
						<<" ringNodes [ "
						;
		  // start from second node, since first and last node are the same
		sgm::RingReporter::RingList::const_iterator ringNode = ring.ring.begin();
		for (ringNode++;ringNode != ring.ring.end(); ++ringNode )
		{
			ringGmlEncoding <<"id " <<*ringNode <<" ";
		}
		ringGmlEncoding	<<"]" <<linesep;

		  // print node weights
		for (AP_NSPDK::RingWeightVec::const_iterator weight = ringWeights.begin();
				weight != ringWeights.end(); ++weight )
		{
			ringGmlEncoding <<indent <<"nodeWeight [ id " <<weight->first <<" label \"" <<weight->second <<"\" ]" <<linesep;
		}

		  // print GML end
		ringGmlEncoding <<"]" <<linesep;

	}

	  // write final molecule to stream in GML format
	std::string additionalGML = ringGmlEncoding.str();
	Graph_GML_writer::write( out, mol, multilineGML, &additionalGML );

}

//////////////////////////////////////////////////////////////////////////

bool
SVector_less(const sgd::SVector& v, const sgd::SVector & vCompare)
{
		  // get iterators over sparse entries
		const sgd::SVector::Pair *p1 = v, *p2 = vCompare;

		// skip all equal elements
		for( ; p1->i >= 0 && p2->i >= 0 && p1->i == p2->i && p1->v == p2->v; p1++,p2++)
		{ /* just go to next entries */}

		  // check first non-equal element
		if (p1->i < p2->i ) {
			  // if (p1->i < 0) one additional larger entry in vCompare,
			  // else one entry in v existing that is 0 in vCompare
			return (p1->i < 0 && p2->v > 0) || (p1->i > 0 && p1->v < 0);
		} else if (p1->i > p2->i) {
			  // if (p2->i < 0) one additional larger entry in v,
			  // else one entry in vCompare existing that is 0 in v
			return (p2->i < 0 && p1->v < 0) || (p2->i > 0 && p2->v > 0);
		} else {
			// p1->i == p2->i
			if (p1->i < 0) {
				  // all the same
				return false;
			} else {
				  // check if v is smaller
				return p1->v < p2->v;
			}
		}

}

//////////////////////////////////////////////////////////////////////////


// Copyright (c) 2014 The Dacrs developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "base58.h"

#include "hash.h"
#include "uint256.h"

#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <vector>
#include <string>
#include <boost/variant/apply_visitor.hpp>
#include <boost/variant/static_visitor.hpp>

/* All alphanumeric characters except for "0", "I", "O", and "l" */
static const char* g_pstaBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

bool DecodeBase58(const char *psz, vector<unsigned char>& vch) {
	// Skip leading spaces.
	while (*psz && isspace(*psz)) {
		psz++;
	}

	// Skip and count leading '1's.
	int nZeroes = 0;
	while (*psz == '1') {
		nZeroes++;
		psz++;
	}

	// Allocate enough space in big-endian base256 representation.
	// log(58) / log(256), rounded up.
	vector<unsigned char> b256(strlen(psz) * 733 / 1000 + 1);
	// Process the characters.
	while (*psz && !isspace(*psz)) {
		// Decode base58 character
		const char *ch = strchr(g_pstaBase58, *psz);
		if (ch == NULL) {
			return false;
		}

		// Apply "b256 = b256 * 58 + ch".
		int carry = ch - g_pstaBase58;
		for (vector<unsigned char>::reverse_iterator it = b256.rbegin(); it != b256.rend(); it++) {
			carry += 58 * (*it);
			*it = carry % 256;
			carry /= 256;
		}
		assert(carry == 0);
		psz++;
	}

	// Skip trailing spaces.
	while (isspace(*psz)) {
		psz++;
	}

	if (*psz != 0) {
		return false;
	}

	// Skip leading zeroes in b256.
	vector<unsigned char>::iterator iter = b256.begin();
	while (iter != b256.end() && *iter == 0) {
		iter++;
	}

	// Copy result into output vector.
	vch.reserve(nZeroes + (b256.end() - iter));
	vch.assign(nZeroes, 0x00);
	while (iter != b256.end()) {
		vch.push_back(*(iter++));
	}

	return true;
}

string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) {
	// Skip & count leading zeroes.
	int nZeroes = 0;
	while (pbegin != pend && *pbegin == 0) {
		pbegin++;
		nZeroes++;
	}
	// Allocate enough space in big-endian base58 representation.
	vector<unsigned char> b58((pend - pbegin) * 138 / 100 + 1); // log(256) / log(58), rounded up.
	// Process the bytes.
	while (pbegin != pend) {
		int carry = *pbegin;
		// Apply "b58 = b58 * 256 + ch".
		for (vector<unsigned char>::reverse_iterator it = b58.rbegin(); it != b58.rend(); it++) {
			carry += 256 * (*it);
			*it = carry % 58;
			carry /= 58;
		}
		assert(carry == 0);
		pbegin++;
	}

	// Skip leading zeroes in base58 result.
	vector<unsigned char>::iterator iter = b58.begin();
	while (iter != b58.end() && *iter == 0) {
		iter++;
	}

	// Translate the result into a string.
	string str;
	str.reserve(nZeroes + (b58.end() - iter));
	str.assign(nZeroes, '1');
	while (iter != b58.end()) {
		str += g_pstaBase58[*(iter++)];
	}

	return str;
}

string EncodeBase58(const vector<unsigned char>& vch) {
	return EncodeBase58(&vch[0], &vch[0] + vch.size());
}

bool DecodeBase58(const string& str, vector<unsigned char>& vchRet) {
	return DecodeBase58(str.c_str(), vchRet);
}

string EncodeBase58Check(const vector<unsigned char>& vchIn) {
	// add 4-byte hash check to the end
	vector<unsigned char> vch(vchIn);
	uint256 hash = Hash(vch.begin(), vch.end());
	vch.insert(vch.end(), (unsigned char*) &hash, (unsigned char*) &hash + 4);
	return EncodeBase58(vch);
}

bool DecodeBase58Check(const char* psz, vector<unsigned char>& vchRet) {
	if (!DecodeBase58(psz, vchRet)) {
		return false;
	}

	if (vchRet.size() < 4) {
		vchRet.clear();
		return false;
	}

	// re-calculate the checksum, insure it matches the included 4-byte checksum
	uint256 hash = Hash(vchRet.begin(), vchRet.end() - 4);
	if (memcmp(&hash, &vchRet.end()[-4], 4) != 0) {
		vchRet.clear();
		return false;
	}
	vchRet.resize(vchRet.size() - 4);

	return true;
}

bool DecodeBase58Check(const string& str, vector<unsigned char>& vchRet) {
	return DecodeBase58Check(str.c_str(), vchRet);
}

CBase58Data::CBase58Data() {
	m_vchVersion.clear();
	m_vchData.clear();
}

void CBase58Data::SetData(const vector<unsigned char> &vchVersionIn, const void* pdata, size_t nSize) {
	m_vchVersion = vchVersionIn;
	m_vchData.resize(nSize);
	if (!m_vchData.empty()) {
		memcpy(&m_vchData[0], pdata, nSize);
	}
}

void CBase58Data::SetData(const vector<unsigned char> &vchVersionIn, const unsigned char *pbegin,
		const unsigned char *pend) {
	SetData(vchVersionIn, (void*) pbegin, pend - pbegin);
}

bool CBase58Data::SetString(const char* psz, unsigned int unVersionBytes) {
	vector<unsigned char> vchTemp;
	DecodeBase58Check(psz, vchTemp);
	if (vchTemp.size() < unVersionBytes) {
		m_vchData.clear();
		m_vchVersion.clear();
		return false;
	}

	m_vchVersion.assign(vchTemp.begin(), vchTemp.begin() + unVersionBytes);
	m_vchData.resize(vchTemp.size() - unVersionBytes);
	if (!m_vchData.empty()) {
		memcpy(&m_vchData[0], &vchTemp[unVersionBytes], m_vchData.size());
	}

	OPENSSL_cleanse(&vchTemp[0], m_vchData.size());
	return true;
}

bool CBase58Data::SetString(const string& str) {
	return SetString(str.c_str());
}

string CBase58Data::ToString() const {
	vector<unsigned char> vch = m_vchVersion;
	vch.insert(vch.end(), m_vchData.begin(), m_vchData.end());
	return EncodeBase58Check(vch);
}

int CBase58Data::CompareTo(const CBase58Data& cBase58) const {
	if (m_vchVersion < cBase58.m_vchVersion) {
		return -1;
	}

	if (m_vchVersion > cBase58.m_vchVersion) {
		return 1;
	}

	if (m_vchData < cBase58.m_vchData) {
		return -1;
	}

	if (m_vchData > cBase58.m_vchData) {
		return 1;
	}

	return 0;
}

namespace {
class CDacrsAddressVisitor: public boost::static_visitor<bool> {
 public:
	CDacrsAddressVisitor(CDacrsAddress *addrIn) :
		m_cDacrsAddr(addrIn) {
	}

	bool operator()(const CKeyID &id) const {
		return m_cDacrsAddr->Set(id);
	}

	bool operator()(const CNoDestination &no) const {
		return false;
	}

 private:
	CDacrsAddress *m_cDacrsAddr;
};
};

bool CDacrsAddress::Set(const CKeyID &cId) {
	SetData(SysCfg().Base58Prefix(CBaseParams::EM_PUBKEY_ADDRESS), &cId, 20);
	return true;
}

bool CDacrsAddress::Set(const CTxDestination &cDest) {
	return boost::apply_visitor(CDacrsAddressVisitor(this), cDest);
}

bool CDacrsAddress::IsValid() const {
	bool bValid = false;
	{
		bool bCorrectSize = m_vchData.size() == 20;
		bool bKnownVersion = m_vchVersion == SysCfg().Base58Prefix(CBaseParams::EM_PUBKEY_ADDRESS);
		bValid = bCorrectSize && bKnownVersion;
	}

	if (!bValid) {
		vector<unsigned char> vid;
		vid.push_back(CBaseParams::EM_ACC_ADDRESS);
		if (m_vchData.size() == 26 && m_vchVersion == vid) {
			bValid = true;
		}
	}

	return bValid;
}

CTxDestination CDacrsAddress::Get() const {
	if (!IsValid()) {
		return CNoDestination();
	}

	if (m_vchData.size() == 20) {
		uint160 cId;
		memcpy(&cId, &m_vchData[0], 20);

		if (m_vchVersion == SysCfg().Base58Prefix(CBaseParams::EM_PUBKEY_ADDRESS)) {
			return CKeyID(cId);
		} else {
			return CNoDestination();
		}
	} else {
		return CNoDestination();
	}
}

bool CDacrsAddress::GetKeyID(CKeyID &keyID) const {
	uint160 cId;

	if (m_vchVersion == SysCfg().Base58Prefix(CBaseParams::EM_PUBKEY_ADDRESS) && m_vchData.size() == 20) {
		memcpy(&cId, &m_vchData[0], 20);
		keyID = CKeyID(cId);
		return true;
	}

	vector<unsigned char> vuchId;
	vuchId.push_back(CBaseParams::EM_ACC_ADDRESS);
	if (m_vchData.size() == 26 && m_vchVersion == vuchId) {
		memcpy(keyID.begin(), &m_vchData[0], 20);
		return true;
	}

	return false;
}

//bool CDacrsAddress::GetRegID(CRegID &Regid) const {
//
////	vector<unsigned char> vid;
////	vid.push_back(CBaseParams::ACC_ADDRESS);
//	if (vchData.size() == 26 && vchVersion == vector<unsigned char>(CBaseParams::ACC_ADDRESS)) {
//		Regid.SetRegID(vector<unsigned char>(vchData.end()-6,vchData.end()));
//		return true;
//	}
//	return false;
//}

bool CDacrsAddress::IsScript() const {
	return IsValid() && m_vchVersion == SysCfg().Base58Prefix(CBaseParams::EM_SCRIPT_ADDRESS);
}

void CDacrsSecret::SetKey(const CKey& vchSecret) {
	assert(vchSecret.IsValid());
	SetData(SysCfg().Base58Prefix(CBaseParams::EM_SECRET_KEY), vchSecret.begin(), vchSecret.size());
	if (vchSecret.IsCompressed()) {
		m_vchData.push_back(1);
	}
}

CKey CDacrsSecret::GetKey() {
	CKey cRet;
	cRet.Set(&m_vchData[0], &m_vchData[32], m_vchData.size() > 32 && m_vchData[32] == 1);
	return cRet;
}

bool CDacrsSecret::IsValid() const {
	bool bExpectedFormat = m_vchData.size() == 32 || (m_vchData.size() == 33 && m_vchData[32] == 1);
	bool bCorrectVersion = m_vchVersion == SysCfg().Base58Prefix(CBaseParams::EM_SECRET_KEY);
	return bExpectedFormat && bCorrectVersion;
}

bool CDacrsSecret::SetString(const char* pszSecret) {
	return CBase58Data::SetString(pszSecret) && IsValid();
}

bool CDacrsSecret::SetString(const string& strSecret) {
	return SetString(strSecret.c_str());
}


/*
(c) 2012 Fengtao Fan
*/
#include "psbmReebGraph.h"
#include <iostream>
#include <vector>
#include <map>
#include <cmath>
#include <string>
#include "SimpleMesh.h"
#include "FilesOutputForOptimalCycles.h"

#include <time.h>
#include <sstream>
#include <boost/progress.hpp>
#include <boost/program_options.hpp>

//
bool CheckBoundaries(_SimpleMesh &inMesh, std::vector<std::vector<std::pair<int, int> > > &boundries) {
    bool ret = false;
    std::vector<bool> edge_flag(inMesh.vecEdge.size(), false);
    for (unsigned int i = 0; i < inMesh.vecEdge.size(); i++) {
        if (!edge_flag[i] && inMesh.vecEdge[i].AdjTriNum == 1) {// this is a boundary edge
            ret = true;
            std::vector<std::pair<int, int> > bdrEdges;
            //
            int terminate_vertex = inMesh.vecEdge[i].v0;
            int current_vertex = inMesh.vecEdge[i].v1;
            int current_edge = i;
            int loop_counter = 0;
            while (current_vertex != terminate_vertex) {
                bdrEdges.push_back(std::pair<int, int>(current_edge, inMesh.vecEdge[current_edge].AdjTri[0]));
                edge_flag[current_edge] = true;
                //
                unsigned int evid = 0;
                for (; evid < inMesh.vecVertex[current_vertex].adjEdges.size(); evid++) {
                    int rot_edge_id = inMesh.vecVertex[current_vertex].adjEdges[evid];
                    if (rot_edge_id != current_edge && inMesh.vecEdge[rot_edge_id].AdjTriNum == 1)
                        break;
                }
                if (evid < inMesh.vecVertex[current_vertex].adjEdges.size()) {
                    current_edge = inMesh.vecVertex[current_vertex].adjEdges[evid];
                    current_vertex = inMesh.vecEdge[current_edge].v0 + inMesh.vecEdge[current_edge].v1 - current_vertex;
                } else {
                    std::cout << "READING MESH MODEL ERROR : " << std::endl;
                    std::cout << "--- CAN NOT FIND ADJACENT BOUDNARY EDGE !" << std::endl;
                    std::cout << "--- PLEASE CHECK THE MESH MODEL" << std::endl;
                    exit(0);
                }
                loop_counter++;
                if (loop_counter > inMesh.vecEdge.size()) {
                    std::cout << "READING MESH MODEL ERROR : " << std::endl;
                    std::cout << "--- PLEASE CHECK THE MESH MODEL" << std::endl;
                    exit(0);
                }
            }
            edge_flag[current_edge] = true;
            bdrEdges.push_back(std::pair<int, int>(current_edge, inMesh.vecEdge[current_edge].AdjTri[0]));
            //
            boundries.push_back(bdrEdges);
        }
        edge_flag[i] = true;
    }
    return ret;
}

void FindEdgeOrientation(_SimpleMesh &inMesh, const int fid, std::vector<int> &OrientTriangles,
                         std::pair<int, int> &EdgeOrientation) {
    int start_index = 3 * fid;
    std::vector<int> TriOrientation(OrientTriangles.begin() + start_index, OrientTriangles.begin() + start_index + 3);
    TriOrientation.push_back(TriOrientation.front());
    //
    for (unsigned int i = 0; i < 3; i++) {
        if ((EdgeOrientation.first == TriOrientation[i] && EdgeOrientation.second == TriOrientation[i + 1]) ||
            (EdgeOrientation.second == TriOrientation[i] && EdgeOrientation.first == TriOrientation[i + 1])) {
            EdgeOrientation.first = TriOrientation[i + 1];
            EdgeOrientation.second = TriOrientation[i];
        }
    }
    return;
}

void CloseHoles(_SimpleMesh &inMesh, std::vector<std::vector<std::pair<int, int> > > &meshBoundaries,
                std::vector<Vector3> &inMeshNormal,
                std::vector<int> &OrientTriangles, std::set<int> &extraVertices) {
    for (unsigned int b = 0; b < meshBoundaries.size(); b++) {
        Vector3 centroid(0.0, 0.0, 0.0);
        int curEdge = 0;
        int nextEdge = 0;
        int curVertex = 0;
        for (unsigned int i = 0; i < meshBoundaries[b].size(); i++) {// the edges on the boundary is ordered
            curEdge = meshBoundaries[b][i].first;
            nextEdge = meshBoundaries[b][0].first;
            if (i < meshBoundaries[b].size() - 1)
                nextEdge = meshBoundaries[b][i + 1].first;
            // shared vertex
            curVertex = inMesh.vecEdge[curEdge].v0;
            if (curVertex != inMesh.vecEdge[nextEdge].v0 &&
                curVertex != inMesh.vecEdge[nextEdge].v1)
                curVertex = inMesh.vecEdge[curEdge].v1;
            //
            centroid = centroid + Vector3(inMesh.vecVertex[curVertex].x,
                                          inMesh.vecVertex[curVertex].y,
                                          inMesh.vecVertex[curVertex].z);
            //
        }
        centroid = centroid / meshBoundaries[b].size();
        //
        // add vertex
        int centroid_index = inMesh.vecVertex.size();
        extraVertices.insert(centroid_index);
        _SimpleMeshVertex tmpVer;
        tmpVer.x = centroid[0];
        tmpVer.y = centroid[1];
        tmpVer.z = centroid[2];
        //
        inMesh.vecVertex.push_back(tmpVer);
        //
        // add edges and triangles
        std::map<int, int> edge_mapping;
        for (unsigned int i = 0; i < meshBoundaries[b].size(); i++) {
            // Find orientation of this edge
            curEdge = meshBoundaries[b][i].first;
            std::pair<int, int> EdgeOrientation(inMesh.vecEdge[curEdge].v0, inMesh.vecEdge[curEdge].v1);
            FindEdgeOrientation(inMesh, meshBoundaries[b][i].second, OrientTriangles, EdgeOrientation);
            //
            OrientTriangles.push_back(EdgeOrientation.first);
            OrientTriangles.push_back(EdgeOrientation.second);
            OrientTriangles.push_back(centroid_index);
            //
            _SimpleMeshTriangle tmpTri;
            _SimpleMeshEdge tmpEdge;
            //
            tmpTri.v0 = EdgeOrientation.first;
            tmpTri.v1 = EdgeOrientation.second;
            tmpTri.v2 = centroid_index;
            //
            //
            Vector3 leftVec, rightVec;
            leftVec[0] = inMesh.vecVertex[tmpTri.v2].x - inMesh.vecVertex[tmpTri.v1].x;
            leftVec[1] = inMesh.vecVertex[tmpTri.v2].y - inMesh.vecVertex[tmpTri.v1].y;
            leftVec[2] = inMesh.vecVertex[tmpTri.v2].z - inMesh.vecVertex[tmpTri.v1].z;

            rightVec[0] = inMesh.vecVertex[tmpTri.v0].x - inMesh.vecVertex[tmpTri.v1].x;
            rightVec[1] = inMesh.vecVertex[tmpTri.v0].y - inMesh.vecVertex[tmpTri.v1].y;
            rightVec[2] = inMesh.vecVertex[tmpTri.v0].z - inMesh.vecVertex[tmpTri.v1].z;
            leftVec = leftVec ^ rightVec;
            unitize(leftVec);
            //leftVec = leftVec / norm(leftVec);
            inMeshNormal.push_back(leftVec);
            //
            tmpTri.sortVertices();
            // [v0 v1] existing edge
            inMesh.vecEdge[curEdge].AdjTriNum++;
            inMesh.vecEdge[curEdge].AdjTri[1] = inMesh.vecTriangle.size();
            //
            tmpTri.e01 = curEdge;
            //
            std::map<int, int>::iterator mIter;
            mIter = edge_mapping.find(tmpTri.v1);
            if (mIter == edge_mapping.end()) {// new edge
                tmpEdge.v0 = tmpTri.v1;
                tmpEdge.v1 = tmpTri.v2;
                tmpEdge.AdjTri[0] = inMesh.vecTriangle.size();
                tmpEdge.AdjTriNum = 1;
                //
                inMesh.vecEdge.push_back(tmpEdge);
                tmpTri.e12 = inMesh.vecEdge.size() - 1;
                edge_mapping[tmpTri.v1] = tmpTri.e12;
                //
                inMesh.vecVertex[tmpEdge.v0].adjEdges.push_back(tmpTri.e12);
                inMesh.vecVertex[tmpEdge.v1].adjEdges.push_back(tmpTri.e12);
            } else {// existed edge
                inMesh.vecEdge[mIter->second].AdjTriNum++;
                inMesh.vecEdge[mIter->second].AdjTri[1] = inMesh.vecTriangle.size();
                //
                tmpTri.e12 = mIter->second;
            }
            mIter = edge_mapping.find(tmpTri.v0);
            if (mIter == edge_mapping.end()) {
                // new edge
                tmpEdge.v0 = tmpTri.v0;
                tmpEdge.v1 = tmpTri.v2;
                tmpEdge.AdjTri[0] = inMesh.vecTriangle.size();
                tmpEdge.AdjTriNum = 1;
                //
                inMesh.vecEdge.push_back(tmpEdge);
                tmpTri.e02 = inMesh.vecEdge.size() - 1;
                edge_mapping[tmpTri.v0] = tmpTri.e02;
                //
                inMesh.vecVertex[tmpEdge.v0].adjEdges.push_back(tmpTri.e02);
                inMesh.vecVertex[tmpEdge.v1].adjEdges.push_back(tmpTri.e02);
            } else {// existed already
                inMesh.vecEdge[mIter->second].AdjTriNum++;
                inMesh.vecEdge[mIter->second].AdjTri[1] = inMesh.vecTriangle.size();
                //
                tmpTri.e02 = mIter->second;
            }
            //
            inMesh.vecTriangle.push_back(tmpTri);
        }
    }
    return;
}

int LoadData(_SimpleMesh &mesh, std::vector<Vector3> &meshNormal,
             const char *mesh_file_name, double &BoundingBoxRadius, std::vector<int> &tris,
             std::set<int> &extraVertices, int &orgTriangleSize,
             const float fEnlargeFactor) {
    int genus = 0;
    _SimpleMeshVertex minBd;
    _SimpleMeshVertex maxBd;
    mesh.LoadMeshInOFFformat(minBd, maxBd, meshNormal, mesh_file_name, tris,
                             fEnlargeFactor);//"E:\\RProgramming\\Models\\torus.off");// "D:\\MeshModels\\OFF-models\\HighGenusCubeHC1.off");//
    mesh.SetMeshNormalPtr(&meshNormal);
    // triangles in TRIS are in the same order as triangles in mesh.vecTriangle;
    //
    orgTriangleSize = mesh.vecTriangle.size();
    //
    std::vector<std::vector<std::pair<int, int> > > meshBoundaries;
    if (CheckBoundaries(mesh, meshBoundaries)) {// it is a mesh with bondary
        int EulerCharacteristic = mesh.vecVertex.size() + mesh.vecTriangle.size() - mesh.vecEdge.size();
        genus = 1 - (EulerCharacteristic + meshBoundaries.size()) / 2;
        if (genus) {
            CloseHoles(mesh, meshBoundaries, meshNormal, tris, extraVertices);
        }
    } else {// it is a closed mesh
        int EulerCharacteristic = mesh.vecVertex.size() + mesh.vecTriangle.size() - mesh.vecEdge.size();
        genus = 1 - EulerCharacteristic / 2;
    }
    if (!genus) {
        std::cout << "NOTHING IS COMPUTED : " << std::endl;
        std::cout << " ---- MESH HAS GENUS 0!" << std::endl;
        exit(1);
    }
    std::cout << "Mesh has genus : " << genus << std::endl;
    //

// compute the bounding box

    return genus;
}

void RandomUniqueDirection(_SimpleMesh &mesh, Vector3 &uniDirection) {
    //
    std::vector<Vector3> vecEdgeDirections(2 * mesh.vecEdge.size());
    // all vectors are pointed to positive Z direction
    for (unsigned int i = 0; i < mesh.vecEdge.size(); i++) {
        vecEdgeDirections[i][0] = mesh.vecVertex[mesh.vecEdge[i].v0].x - mesh.vecVertex[mesh.vecEdge[i].v1].x;
        vecEdgeDirections[i][1] = mesh.vecVertex[mesh.vecEdge[i].v0].y - mesh.vecVertex[mesh.vecEdge[i].v1].y;
        vecEdgeDirections[i][2] = mesh.vecVertex[mesh.vecEdge[i].v0].z - mesh.vecVertex[mesh.vecEdge[i].v1].z;
        //
        if (vecEdgeDirections[i][2] < 0) {
            for (int j = 0; j < 3; j++)
                vecEdgeDirections[i][j] = -vecEdgeDirections[i][j];
        }
        //
        unitize(vecEdgeDirections[i]);
    }
    int mesh_edge_size = mesh.vecEdge.size();
    int opp_v_0;
    int opp_v_1;
    for (unsigned int i = 0; i < mesh.vecEdge.size(); i++) {
        if (mesh.vecEdge[i].AdjTriNum == 2) {
            opp_v_0 = mesh.vecEdge[i].AdjTri[0];
            if (mesh.vecTriangle[opp_v_0].v0 != mesh.vecEdge[i].v0 &&
                mesh.vecTriangle[opp_v_0].v0 != mesh.vecEdge[i].v1) {
                opp_v_0 = mesh.vecTriangle[opp_v_0].v0;
            } else {
                if (mesh.vecTriangle[opp_v_0].v1 != mesh.vecEdge[i].v0 &&
                    mesh.vecTriangle[opp_v_0].v1 != mesh.vecEdge[i].v1) {
                    opp_v_0 = mesh.vecTriangle[opp_v_0].v1;
                } else {
                    opp_v_0 = mesh.vecTriangle[opp_v_0].v2;
                }
            }
            //
            opp_v_1 = mesh.vecEdge[i].AdjTri[1];
            if (mesh.vecTriangle[opp_v_1].v0 != mesh.vecEdge[i].v0 &&
                mesh.vecTriangle[opp_v_1].v0 != mesh.vecEdge[i].v1) {
                opp_v_1 = mesh.vecTriangle[opp_v_1].v0;
            } else {
                if (mesh.vecTriangle[opp_v_1].v1 != mesh.vecEdge[i].v0 &&
                    mesh.vecTriangle[opp_v_1].v1 != mesh.vecEdge[i].v1) {
                    opp_v_1 = mesh.vecTriangle[opp_v_1].v1;
                } else {
                    opp_v_1 = mesh.vecTriangle[opp_v_1].v2;
                }
            }
            //
            vecEdgeDirections[i + mesh_edge_size][0] = mesh.vecVertex[opp_v_0].x - mesh.vecVertex[opp_v_1].x;
            vecEdgeDirections[i + mesh_edge_size][1] = mesh.vecVertex[opp_v_0].y - mesh.vecVertex[opp_v_1].y;
            vecEdgeDirections[i + mesh_edge_size][2] = mesh.vecVertex[opp_v_0].z - mesh.vecVertex[opp_v_1].z;
            //
            if (vecEdgeDirections[i + mesh_edge_size][2] < 0) {
                for (int j = 0; j < 3; j++)
                    vecEdgeDirections[i + mesh_edge_size][j] = -vecEdgeDirections[i + mesh_edge_size][j];
            }
            //
            unitize(vecEdgeDirections[i + mesh_edge_size]);
        }
    }
    //
    srand(time(NULL));
    //
    int runTimesCounter = 0;
    int halfRandMax = RAND_MAX >> 2;
    bool bFindDirection = false;
    double minAangleValue = 1.0;
    double error_precision = 1e-7;
    double max_running_counter = 2000;
    while (!bFindDirection) {
        runTimesCounter++;
        for (int i = 0; i < 3; i++) {
            uniDirection[i] = rand() - halfRandMax;
        }
        if (uniDirection[2] < 0) {
            for (int i = 0; i < 3; i++)
                uniDirection[i] = -uniDirection[i];
        }
        //
        unitize(uniDirection);
        //
        bFindDirection = true;
        minAangleValue = 1.0;
        for (unsigned int i = 0; i < vecEdgeDirections.size(); i++) {
            double angleValue = uniDirection * vecEdgeDirections[i];
            if (angleValue < error_precision && angleValue > -error_precision) {
                bFindDirection = false;
                break;
            }
            if (std::abs(minAangleValue) > std::abs(angleValue))
                minAangleValue = angleValue;
        }
        if (runTimesCounter > max_running_counter)
            break;
    }
    //
    //std::cout << "run times : " << runTimesCounter << std::endl;
    //std::cout << "min angle : " << minAangleValue << std::endl;
    //
    return;
}

//
void CycleLocalOptimization(_SimpleMesh &locMesh, psbmReebGraph &reebgraph, std::vector<int> &OrientTriangles,
                            std::vector<std::set<int> > &v_basis,
                            std::vector<std::set<int> > &h_basis, const float fEnlargeFactor);

void CycleLocalOptimization_bdry(_SimpleMesh &locMesh, psbmReebGraph &reebgraph, std::vector<int> &OrientTriangles,
                                 std::vector<std::set<int> > &out_v_basis_loops,
                                 std::vector<std::set<int> > &out_h_basis_loops,
                                 std::set<int> extraVertices, const float fEnlargeFactor);

////
char *strLicense = "THIS SOFTWARE IS PROVIDED \"AS-IS\". THERE IS NO WARRANTY OF ANY KIND. "
                   "NEITHER THE AUTHORS NOR THE OHIO STATE UNIVERSITY WILL BE LIABLE FOR "
                   "ANY DAMAGES OF ANY KIND, EVEN IF ADVISED OF SUCH POSSIBILITY. \n"
                   "\n"
                   "This software was developed (and is copyrighted by) the Jyamiti group at "
                   "The Ohio State University. Please do not redistribute this software. "
                   "This program is for academic research use only. This software uses the "
                   "CGAL library (www.cgal.org), Boost library (www.boost.org) and Ann library "
                   "(www.cs.umd.edu/~mount/ANN/) which are covered under their own licenses.\n"
                   "\n"
                   "The CGAL library's license "
                   "(which applies to the CGAL library ONLY and NOT to this program itself) is "
                   "as follows:\n"
                   "\n"
                   "LICENSE\n"
                   "---------------------------------------------------------------------------\n"
                   "\n"
                   "The CGAL software consists of several parts, each of which is licensed under "
                   "an open source license. It is also possible to obtain commercial licenses "
                   "from GeometryFactory (www.geometryfactory.com) for all or parts of CGAL. \n"
                   "\n"
                   "The source code of the CGAL library can be found in the directories "
                   "\"src/CGAL\", \"src/CGALQt\" and \"include/CGAL\". It is specified in each file of "
                   "the CGAL library which license applies to it. This is either the GNU Lesser "
                   "General Public License (as published by the Free Software Foundation; "
                   "version 2.1 of the License) or the Q Public License (version 1.0). The texts "
                   "of both licenses can be found in the files LICENSE.LGPL and LICENSE.QPL. \n"
                   "\n"
                   "Distributed along with CGAL (for the users' convenience), but not part of "
                   "CGAL, are the following third-party libraries, available under their own "
                   "licenses: \n"
                   "\n"
                   "- CORE, in the directories \"include/CORE\" and \"src/Core\", is licensed under"
                   " the QPL (see LICENSE.QPL). \n"
                   "- OpenNL, in the directory \"include/OpenNL\", is licensed under the LGPL"
                   " (see include/OpenNL/LICENSE.OPENNL). \n"
                   "- ImageIO, in the directory \"examples/Surface_mesher/ImageIO\", is licensed"
                   " under the LGPL (see LICENSE.LGPL). \n"
                   "\n"
                   "All other files that do not have an explicit copyright notice (e.g., all "
                   "examples and some demos) are licensed under a very permissive license. The "
                   "exact license text can be found in the file LICENSE.FREE_USE. Note that some "
                   "subdirectories have their own copy of LICENSE.FREE_USE. These copies have "
                   "the same license text and differ only in the copyright holder.\n"
                   "---------------------------------------------------------------------------\n"
                   "\n"
                   "The Boost library's license "
                   "(which applies to the Boost library ONLY and NOT to this program itself) is "
                   "as follows:\n"
                   "\n"
                   "LICENSE\n"
                   "---------------------------------------------------------------------------\n"
                   "Boost Software License - Version 1.0 - August 17th, 2003\n"
                   "\n"
                   "Permission is hereby granted, free of charge, to any person or organization "
                   "obtaining a copy of the software and accompanying documentation covered by "
                   "this license (the \"Software\") to use, reproduce, display, distribute, "
                   "execute, and transmit the Software, and to prepare derivative works of the "
                   "Software, and to permit third-parties to whom the Software is furnished to "
                   "do so, all subject to the following: \n"
                   "\n"
                   "The copyright notices in the Software and this entire statement, including "
                   "the above license grant, this restriction and the following disclaimer, "
                   "must be included in all copies of the Software, in whole or in part, and "
                   "all derivative works of the Software, unless such copies or derivative "
                   "works are solely in the form of machine-executable object code generated by "
                   "a source language processor. \n"
                   "\n"
                   "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR "
                   "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, "
                   "FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT "
                   "SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE "
                   "FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, "
                   "ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER "
                   "DEALINGS IN THE SOFTWARE. \n"
                   "---------------------------------------------------------------------------\n"
                   "\n"
                   "ANN library's license "
                   "(which applies to the ANN library ONLY and NOT to this program itself) is "
                   "as follows: \n"
                   "\n"
                   "LICENSE\n"
                   "---------------------------------------------------------------------------\n"
                   "The ANN Library (all versions) is provided under the terms and "
                   "conditions of the GNU Lesser General Public Library, which is stated "
                   "below.  It can also be found at: \n"
                   "\n"
                   "   http:\//www.gnu.org/copyleft/lesser.html \n"
                   "---------------------------------------------------------------------------\n";

//char * strLicense = "THIS SOFTWARE IS PROVIDED \"AS-IS\". http:\//www.gnu.org/copyleft/lesser.html THERE IS NO WARRANTY OF ANY KIND. \n";
bool ParseCommand(int argc, char **argv, std::string &InputFile, std::string &OutputFile) {
    try {
        /* Define the program options description
        */
        namespace po = boost::program_options;
        po::options_description desc("ReebHanTun Usage");
        desc.add_options()
                (",h", "Help information")
                (",l", "License information")
                (",I", po::value<std::string>(&InputFile)->required(), "Input file name")
                (",O", po::value<std::string>(&OutputFile)->required(), "Output file name prefix");

        // Parser map
        po::variables_map vm;
        try {
            po::store(po::parse_command_line(argc, argv, desc), vm);

            //
            if (vm.count("-h")) {
                std::cout << desc << std::endl;
            }
            //
            if (vm.count("-l")) {
                std::cout << strLicense << std::endl;
            }
            //
            po::notify(vm);
        }
        catch (boost::program_options::required_option &e) {
            std::cerr << "ERROR: " << e.what() << std::endl;
            return false;
        }
        catch (boost::program_options::error &e) {
            std::cerr << "ERROR: " << e.what() << std::endl;
            return false;
        }
        //if (vm.count("-I"))
        //{
        //	std::cout << vm["-I"].as<std::string>() << std::endl;
        //}
        //if (vm.count("-O"))
        //{
        //	std::cout << vm["-O"].as<std::string>() << std::endl;
        //}
    }
    catch (std::exception &e) {
        std::cerr << "Unhandled Exception reached the top of main: "
                  << e.what() << ", application will now exit" << std::endl;
        return false;

    }
    return true;
}

int main(int argc, char **argv) {
    std::string InputFileName;
    std::string OutputFileName;
    Vector3 distinctDirection;
    //
    _SimpleMesh mesh;
    const float fEnlargeFactor = 10000.f;
    std::vector<std::set<int> > v_basis_loops;
    std::vector<std::set<int> > h_basis_loops;
    //
    psbmReebGraph reebGraph;
    std::vector<Vector3> meshNormal;
    //
    if (ParseCommand(argc, argv, InputFileName, OutputFileName)) {
        //
        std::vector<int> OrientTriangles;
        std::set<int> extraVertices;
        int nOrgTriangleSize = 0;
        double BoundingBoxRadius;
        LoadData(mesh, meshNormal, InputFileName.c_str(), BoundingBoxRadius, OrientTriangles, extraVertices,
                 nOrgTriangleSize, fEnlargeFactor);
        //////

        RandomUniqueDirection(mesh, distinctDirection);
        //
        //std::cout << distinctDirection[0] << " " <<
        //			 distinctDirection[1] << " " <<
        //			 distinctDirection[2] << std::endl;

        //

        reebGraph.ReserveSpaceForEdges(mesh.vecEdge.size());
        //
        double *scalarField = new double[mesh.vecVertex.size()];
        int perDir = 0;
        int rayDir = 0;
        for (unsigned int i = 0; i < mesh.vecVertex.size(); i++) {
            scalarField[i] = mesh.vecVertex[i].x * distinctDirection[0] +
                             mesh.vecVertex[i].y * distinctDirection[1] +
                             mesh.vecVertex[i].z * distinctDirection[2];
        }
        reebGraph.SetHeightDirection(distinctDirection);
        reebGraph.AssignData(&mesh, scalarField);
        reebGraph.scalarDir = 1;// x=0, y=1, z=2
        //
        //minimumDiffBetweenVertices(scalarField);
        //
        std::cout << std::endl;
        {
            boost::progress_timer t;
            //
            reebGraph.ComputeReebGraph();
            //
        }
        {
        	reebGraph.WriteReebGraphOBJ("casting.obj", mesh.vecVertex);
        	std::cout << "Vertices in RG : " << reebGraph.pVecReebNode->size() << std::endl;
        	std::cout << "Edges in RG : " << reebGraph.pListReebArc->size() << std::endl;
        	std::cout << "Genus is : " << reebGraph.pListReebArc->size() - reebGraph.pVecReebNode->size() + 1  << std::endl;
        }
        {
            boost::progress_timer t;
            ////
            std::cout << "Time for mapping and linking :" << std::endl;
            //reebGraph.ComputeCycleAndPairing();
            reebGraph.ComputingCycle_max_tree();

//            reebGraph.WriteSimplifiedReebGraphOBJ("casting-sim.obj", mesh.vecVertex);

            //std::cout << "mapping" << std::endl;
            //// computing the cycle on surface
            reebGraph.compute_path_on_mesh_for_each_simplified_arc();//pathArcOnMesh, offsetPathArcOnMesh);

            //std::cout << "embed" << std::endl;
            reebGraph.EmbedCycleAsEdgePathOnMesh();
            //std::cout << "linking" << std::endl;
            reebGraph.LinkNumberMatrixComputing();

        }
        //

        //
        if (extraVertices.empty())
            CycleLocalOptimization(mesh, reebGraph, OrientTriangles, v_basis_loops, h_basis_loops,
                                   1.f / fEnlargeFactor);
        else
            CycleLocalOptimization_bdry(mesh, reebGraph, OrientTriangles, v_basis_loops, h_basis_loops, extraVertices,
                                        1.f / fEnlargeFactor);
        //
        std::cout << "Handle and tunnel loops written in files :  \n";
        FilesOutputForOptimalCycles files_out_op;
        files_out_op.InitMeshPtr(&mesh);
        files_out_op.WriteCyclesInformation(OutputFileName.c_str(), v_basis_loops, h_basis_loops);
        int orgVertexSize = mesh.vecVertex.size();
        if (!extraVertices.empty())
            orgVertexSize = *extraVertices.begin();
        files_out_op.WriteGeomviewListFormat(OutputFileName.c_str(), v_basis_loops, h_basis_loops, OrientTriangles,
                                             orgVertexSize, nOrgTriangleSize, 1.f / fEnlargeFactor);

        ///////////////////////////////////////////////////
    }
    return 1;
}


#ifndef BOOST_GEOMETRY_PROJECTIONS_OCEA_HPP
#define BOOST_GEOMETRY_PROJECTIONS_OCEA_HPP

// Boost.Geometry - extensions-gis-projections (based on PROJ4)
// This file is automatically generated. DO NOT EDIT.

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017.
// Modifications copyright (c) 2017, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is 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)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 4.9.1

// Original copyright notice:

// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

#include <boost/geometry/util/math.hpp>

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry
{

namespace srs { namespace par4
{
    struct ocea {};

}} //namespace srs::par4

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace ocea
    {
            template <typename T>
            struct par_ocea
            {
                T    rok;
                T    rtk;
                T    sinphi;
                T    cosphi;
                T    singam;
                T    cosgam;
            };

            // template class, using CRTP to implement forward/inverse
            template <typename CalculationType, typename Parameters>
            struct base_ocea_spheroid : public base_t_fi<base_ocea_spheroid<CalculationType, Parameters>,
                     CalculationType, Parameters>
            {

                typedef CalculationType geographic_type;
                typedef CalculationType cartesian_type;

                par_ocea<CalculationType> m_proj_parm;

                inline base_ocea_spheroid(const Parameters& par)
                    : base_t_fi<base_ocea_spheroid<CalculationType, Parameters>,
                     CalculationType, Parameters>(*this, par) {}

                // FORWARD(s_forward)  spheroid
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(geographic_type& lp_lon, geographic_type& lp_lat, cartesian_type& xy_x, cartesian_type& xy_y) const
                {
                    static const CalculationType ONEPI = detail::ONEPI<CalculationType>();

                    CalculationType t;

                    xy_y = sin(lp_lon);
                /*
                    xy_x = atan2((tan(lp_lat) * this->m_proj_parm.cosphi + this->m_proj_parm.sinphi * xy_y) , cos(lp_lon));
                */
                    t = cos(lp_lon);
                    xy_x = atan((tan(lp_lat) * this->m_proj_parm.cosphi + this->m_proj_parm.sinphi * xy_y) / t);
                    if (t < 0.)
                        xy_x += ONEPI;
                    xy_x *= this->m_proj_parm.rtk;
                    xy_y = this->m_proj_parm.rok * (this->m_proj_parm.sinphi * sin(lp_lat) - this->m_proj_parm.cosphi * cos(lp_lat) * xy_y);
                }

                // INVERSE(s_inverse)  spheroid
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(cartesian_type& xy_x, cartesian_type& xy_y, geographic_type& lp_lon, geographic_type& lp_lat) const
                {
                    CalculationType t, s;

                    xy_y /= this->m_proj_parm.rok;
                    xy_x /= this->m_proj_parm.rtk;
                    t = sqrt(1. - xy_y * xy_y);
                    lp_lat = asin(xy_y * this->m_proj_parm.sinphi + t * this->m_proj_parm.cosphi * (s = sin(xy_x)));
                    lp_lon = atan2(t * this->m_proj_parm.sinphi * s - xy_y * this->m_proj_parm.cosphi,
                        t * cos(xy_x));
                }

                static inline std::string get_name()
                {
                    return "ocea_spheroid";
                }

            };

            // Oblique Cylindrical Equal Area
            template <typename Parameters, typename T>
            inline void setup_ocea(Parameters& par, par_ocea<T>& proj_parm)
            {
                static const T HALFPI = detail::HALFPI<T>();

                T phi_0=0.0, phi_1, phi_2, lam_1, lam_2, lonz, alpha;

                proj_parm.rok = 1. / par.k0;
                proj_parm.rtk = par.k0;
                if ( pj_param(par.params, "talpha").i) {
                    alpha    = pj_param(par.params, "ralpha").f;
                    lonz = pj_param(par.params, "rlonc").f;
                    proj_parm.singam = atan(-cos(alpha)/(-sin(phi_0) * sin(alpha))) + lonz;
                    proj_parm.sinphi = asin(cos(phi_0) * sin(alpha));
                } else {
                    phi_1 = pj_param(par.params, "rlat_1").f;
                    phi_2 = pj_param(par.params, "rlat_2").f;
                    lam_1 = pj_param(par.params, "rlon_1").f;
                    lam_2 = pj_param(par.params, "rlon_2").f;
                    proj_parm.singam = atan2(cos(phi_1) * sin(phi_2) * cos(lam_1) -
                        sin(phi_1) * cos(phi_2) * cos(lam_2),
                        sin(phi_1) * cos(phi_2) * sin(lam_2) -
                        cos(phi_1) * sin(phi_2) * sin(lam_1) );
                    if (lam_1 == -HALFPI)
                        proj_parm.singam = -proj_parm.singam;
                    proj_parm.sinphi = atan(-cos(proj_parm.singam - lam_1) / tan(phi_1));
                }
                par.lam0 = proj_parm.singam + HALFPI;
                proj_parm.cosphi = cos(proj_parm.sinphi);
                proj_parm.sinphi = sin(proj_parm.sinphi);
                proj_parm.cosgam = cos(proj_parm.singam);
                proj_parm.singam = sin(proj_parm.singam);
                par.es = 0.;
            }

    }} // namespace detail::ocea
    #endif // doxygen

    /*!
        \brief Oblique Cylindrical Equal Area projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Projection characteristics
         - Cylindrical
         - Spheroid
        \par Projection parameters
         - lonc: Longitude (only used if alpha (or gamma) is specified) (degrees)
         - alpha: Alpha (degrees)
         - lat_1: Latitude of first standard parallel (degrees)
         - lat_2: Latitude of second standard parallel (degrees)
         - lon_1 (degrees)
         - lon_2 (degrees)
        \par Example
        \image html ex_ocea.gif
    */
    template <typename CalculationType, typename Parameters>
    struct ocea_spheroid : public detail::ocea::base_ocea_spheroid<CalculationType, Parameters>
    {
        inline ocea_spheroid(const Parameters& par) : detail::ocea::base_ocea_spheroid<CalculationType, Parameters>(par)
        {
            detail::ocea::setup_ocea(this->m_par, this->m_proj_parm);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION(srs::par4::ocea, ocea_spheroid, ocea_spheroid)

        // Factory entry(s)
        template <typename CalculationType, typename Parameters>
        class ocea_entry : public detail::factory_entry<CalculationType, Parameters>
        {
            public :
                virtual base_v<CalculationType, Parameters>* create_new(const Parameters& par) const
                {
                    return new base_v_fi<ocea_spheroid<CalculationType, Parameters>, CalculationType, Parameters>(par);
                }
        };

        template <typename CalculationType, typename Parameters>
        inline void ocea_init(detail::base_factory<CalculationType, Parameters>& factory)
        {
            factory.add_to_factory("ocea", new ocea_entry<CalculationType, Parameters>);
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_OCEA_HPP