#include "polyFFT.h"
#include <cryptoTools/Common/Log.h>
#include <cryptoTools/Common/Timer.h>
#include <cryptoTools/Crypto/RandomOracle.h>
#include <vector>
#include <thread>
#include <NTL/BasicThreadPool.h>
#include <omp.h>
//#define fftTest

namespace osuCrypto
{
#define LEFT(X) (2*X+1)
#define RIGHT(X) (2*X+2)
#define PAPA(X) ((X-1)/2)

	void print_poly(ZZ_pX& P)
	{
		long degree = deg(P);
		if (-1 == degree) {
			cout << "0";
			return;
		}
		for (long i = 0; i <= degree; i++) {
			cout << coeff(P, i);
			if (i == 1)
				cout << "X";
			else if (i > 1)
				cout << "X^" << i;
			if (i < degree) {
				cout << " + ";
			}
		}
		//    cout << endl << "random poly:" << endl << P << endl;
	}

	void build_tree(ZZ_pX* tree, ZZ_p* points, unsigned int tree_size, int numThreads, ZZ &prime) {

		ZZ_p negated;
		unsigned int point_index;

		//build all leaves whose index starts at treesize/2 => tree[i]= x-xi
		for (u32 i = tree_size / 2; i< tree_size; i++) {
			point_index = i - (tree_size - 1) / 2;
			NTL::negate(negated, points[point_index]); //get -xi
			SetCoeff(tree[i], 0, negated);
			SetCoeff(tree[i], 1, 1);
		}

		vector<vector<int>> subs(numThreads);
		generateSubTreeArrays(subs, tree_size / 2, numThreads - 1);

		vector<thread> threads(numThreads);

		for (int t = 0; t < numThreads; t++)
			threads[t] = thread(&buildSubTree, tree, ref(subs[t]), ref(prime));

		for (int t = 0; t < numThreads; t++)
			threads[t].join();

		vector<thread> threadsLastPart(numThreads * 2);

		for (int i = log2(numThreads) - 1; i >= 1; i--) {
			//cout<<"layer "<<i<< " : " <<endl;
			for (int j = pow(2, i) - 1; j <= pow(2, i + 1) - 2; j++) {
				//cout<<" index to process is "<< j<<endl;

				threadsLastPart[j] = thread(&buildTreeSpecific, tree, j, ref(prime));
			}

			for (int j = pow(2, i) - 1; j <= pow(2, i + 1) - 2; j++) {
				//cout<<" index join to process is "<< j<<endl;
				threadsLastPart[j].join();
			}
		}

		tree[0] = tree[LEFT(0)] * tree[RIGHT(0)];

	}

	void generateSubTreeArrays(vector<vector<int>> &subArrays, int totalNodes, int firstIndex) {

		int numOfSubTrees = subArrays.size();

		//generate subtree array
		int maxElement = totalNodes;

		for (int i = 0; i < numOfSubTrees; i++) {
			subArrays[i].resize(maxElement);//this is overkill, but will be reduces later
			subArrays[i][0] = firstIndex + i;
		}

		for (int i = 0; i < numOfSubTrees; i++) {
			for (int j = 1; j < totalNodes; j++) {

				if (j % 2 == 1)
					subArrays[i][j] = LEFT(subArrays[i][PAPA(j)]);
				else
					subArrays[i][j] = RIGHT(subArrays[i][PAPA(j)]);

				if (subArrays[i][j] >= maxElement) {
					subArrays[i].resize(j);
					//cout << "j is in first loop " << j << endl;
					break;

				}

				//cout << subArrays[i][j] << "-";
			}
			//cout << "size of "<<i<<" array" << subArrays[i].size() << endl;
			//cout << "-------------------" << endl;

		}

	}

	void buildSubTree(ZZ_pX* tree, vector<int> &subTree, ZZ &prime) {

		ZZ_p::init(prime);
		int index;
		for (int i = subTree.size() - 1; i >= 0; i--) {
			index = subTree[i];

#ifdef fftTest
			FFTMul(tree[index], tree[LEFT(index)], tree[RIGHT(index)]);
#else
			tree[index] = tree[LEFT(index)] * tree[RIGHT(index)];
#endif // fftTest

			//

		}
	}

	void interSubTree(ZZ_pX* temp, ZZ_pX* M, vector<int> &subTree, ZZ &prime) {

		//cout<<"Thread indices";

		ZZ_p::init(prime);
		int index;
		for (int i = subTree.size() - 1; i >= 0; i--) {
			index = subTree[i];
			//cout<<"--"<<index;
#ifdef fftTest
			ZZ_pX tempfft1;
			FFTMul(tempfft1, temp[LEFT(index)], M[RIGHT(index)]);

			ZZ_pX tempfft2;
			FFTMul(tempfft2, temp[RIGHT(index)], M[LEFT(index)]);

			temp[index] = tempfft1 + tempfft2;
#else
			temp[index] = temp[LEFT(index)] * M[RIGHT(index)] + temp[RIGHT(index)] * M[LEFT(index)];
#endif // fftTest

		}

	}

	void evalSubTree(ZZ_pX* reminders, ZZ_pX* tree, vector<int> &subTree, ZZ &prime) {

		//cout<<"Thread indices";
		ZZ_p::init(prime);
		int index;

		unsigned int i = 1;
		for (i = 0; i < subTree.size(); i++) {
			index = subTree[i];
			//        cout << "i="<<i <<": ";
			//
#ifdef fftTest
			FFTRem(reminders[index], reminders[PAPA(index)], tree[index]);
#else
			reminders[index] = reminders[PAPA(index)] % tree[index];
#endif
		}

	}

	void test_tree(ZZ_pX& final_polynomial, ZZ_p* points, unsigned int npoints) {
		ZZ_p result;
		bool error = false;
		for (unsigned int i = 0; i < npoints; i++) {
			result = eval(final_polynomial, points[i]);
			if (0 != result) {
				cout << "FATAL ERROR: polynomials tree is incorrect!" << endl;
				error = true;
				break;
			}
		}
		if (!error)
			cout << "polynomials tree is correct." << endl;
	}

	void evaluate_main(ZZ_pX& P, ZZ_pX* tree, ZZ_pX* reminders, unsigned int tree_size, ZZ_p* results) {

		reminders[0] = P%tree[0];

		unsigned int i = 1;
		for (; i < tree_size / 2; i++) {
			reminders[i] = reminders[PAPA(i)] % tree[i];
		}

		unsigned int result_index;
		for (; i < tree_size; i++) {
			reminders[i] = reminders[PAPA(i)] % tree[i];
			result_index = i - (tree_size - 1) / 2;
			results[result_index] = coeff(reminders[i], 0);
		}
	}

	void evaluate(ZZ_pX& P, ZZ_pX* tree, ZZ_pX* reminders, unsigned int tree_size, ZZ_p* results, int numThreads, ZZ &prime) {

		auto begin1 = steady_clock::now();
		//set the reminder of the root
		//reminders[0] = P%tree[0];
		reminders[0] = P;
		reminders[1] = P;
		reminders[2] = P;

		auto end1 = steady_clock::now();
		//cout << "eval - not paralleled "<<" threads " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		//    print_poly(tree[0]);
		//
		//    cout<<endl<<"---Reminder"<<endl;
		//    print_poly(reminders[0]);
		//
		//    cout<<endl<<"---P"<<endl;
		//    print_poly(P);

		begin1 = steady_clock::now();
		vector<vector<int>> subs(numThreads);
		generateSubTreeArrays(subs, tree_size, numThreads - 1);
		end1 = steady_clock::now();
		//cout << "eval - generate sub trees: " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		begin1 = steady_clock::now();
		vector<thread> threadsFirstPart(numThreads * 2);

		for (int i = 2; i < log2(numThreads); i++) {
			//cout<<"layer "<<i<< " : " <<endl;
			for (int j = pow(2, i) - 1; j <= pow(2, i + 1) - 2; j++) {
				//cout<<" index to process is "<< j<<endl;

				threadsFirstPart[j] = thread(&evalReminder, tree, reminders, j, ref(prime));

			}

			for (int j = pow(2, i) - 1; j <= pow(2, i + 1) - 2; j++) {
				//cout<<" index join to process is "<< j<<endl;
				threadsFirstPart[j].join();
			}
		}

		end1 = steady_clock::now();
		//cout << "eval - first part for max "<< numThreads/2<<" threads " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		begin1 = steady_clock::now();
		vector<thread> threads(numThreads);

		for (int t = 0; t < numThreads; t++) {

			threads[t] = thread(&evalSubTree, reminders, tree, ref(subs[t]), ref(prime));

		}

		for (int t = 0; t < numThreads; t++) {
			threads[t].join();
		}

		end1 = steady_clock::now();
		//cout << "eval - thread part for "<< numThreads<<" threads " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		begin1 = steady_clock::now();
		unsigned int result_index;
		for (int i = tree_size / 2; i < tree_size; i++) {
			result_index = i - (tree_size - 1) / 2;
			results[result_index] = coeff(reminders[i], 0);
		}

		end1 = steady_clock::now();
		//cout << "eval - assign last part: " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

	}

	void evalReminder(ZZ_pX *tree, ZZ_pX *reminders, int i, ZZ &prime) {
		ZZ_p::init(prime);
#ifdef fftTest
		FFTRem(reminders[i], reminders[PAPA(i)], tree[i]);
#else
		reminders[i] = reminders[PAPA(i)] % tree[i];
#endif

	}

	void interSpecific(ZZ_pX *temp, ZZ_pX *M, int i, ZZ &prime) {
		ZZ_p::init(prime);

#ifdef fftTest
		ZZ_pX tempfft1;
		FFTMul(tempfft1, temp[LEFT(i)], M[RIGHT(i)]);

		ZZ_pX tempfft2;
		FFTMul(tempfft2, temp[RIGHT(i)], M[LEFT(i)]);

		temp[i] = tempfft1 + tempfft2;
#else
		temp[i] = temp[LEFT(i)] * M[RIGHT(i)] + temp[RIGHT(i)] * M[LEFT(i)];
#endif // fftTest

	}

	void buildTreeSpecific(ZZ_pX *tree, int i, ZZ &prime) {
		ZZ_p::init(prime);

#ifdef fftTest
		FFTMul(tree[i], tree[LEFT(i)], tree[RIGHT(i)]);
#else
		tree[i] = tree[LEFT(i)] * tree[RIGHT(i)];
#endif
	}

	void test_evaluate(ZZ_pX& P, ZZ_p* points, ZZ_p* results, unsigned int npoints) {
		bool error = false;
		for (unsigned int i = 0; i < npoints; i++) {
			ZZ_p y = eval(P, points[i]);
			if (y != results[i]) {
				cout << "y=" << y << " and results[i]=" << results[i] << endl;
				error = true;
			}
		}
		if (error)
			cout << "ERROR: evaluation results do not match real evaluation!" << endl;
		else
			cout << "All evaluation results computed correctly!" << endl;
	}

	void multipoint_evaluate_zp(ZZ_pX& P, ZZ_p* x, ZZ_p* y, long degree, int numThreads, ZZ &prime)
	{
		//    cout << "P:" <<endl; print_poly(P); cout << endl;
		// we want to evaluate P on 'degree+1' values.
		ZZ_pX* p_tree = new ZZ_pX[degree * 2 + 1];
		steady_clock::time_point begin1 = steady_clock::now();
		build_tree(p_tree, x, degree * 2 + 1, numThreads, prime);
		steady_clock::time_point end1 = steady_clock::now();
		//test_tree(p_tree[0], x, degree+1);

		ZZ_pX* reminders = new ZZ_pX[degree * 2 + 1];
		steady_clock::time_point begin2 = steady_clock::now();
		evaluate(P, p_tree, reminders, degree * 2 + 1, y, numThreads, prime);
		chrono::steady_clock::time_point end2 = steady_clock::now();

		//cout << "Building tree: " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;
		//cout << "Evaluating points: " << duration_cast<milliseconds>(end2 - begin2).count() << " ms" << endl;
		//cout << "Total: " << duration_cast<milliseconds>(end1 - begin1).count()+ duration_cast<milliseconds>(end2 - begin2).count() << " ms" << endl;

		//test_evaluate(P,x,y,10);
	}

	void test_multipoint_eval_zp(ZZ prime, long degree, int numThreads)
	{
		// init underlying prime field
		ZZ_p::init(ZZ(prime));

		// the given polynomial
		ZZ_pX P;
		random(P, degree + 1);
		SetCoeff(P, degree, random_ZZ_p());

		// evaluation points:
		ZZ_p* x = new ZZ_p[degree + 1];
		ZZ_p* y = new ZZ_p[degree + 1];

		for (unsigned int i = 0; i <= degree; i++) {
			random(x[i]);
		}

		multipoint_evaluate_zp(P, x, y, degree, numThreads, prime);
	}

	/*
	* expects an "empty" polynomial 'resultP'
	*/
	void iterative_interpolate_zp_main(ZZ_pX& resultP, ZZ_pX* temp, ZZ_p* y, ZZ_p* a, ZZ_pX* M, unsigned int tree_size)
	{
		int i = tree_size - 1;
		ZZ_p inv_a;
		unsigned int y_index;
		for (; i >= tree_size / 2; i--) {
			y_index = i - (tree_size - 1) / 2;
			inv(inv_a, a[y_index]); // inv_a = 1/a[y_index]
			SetCoeff(temp[i], 0, y[y_index] * inv_a);
		}

		for (; i >= 0; i--) {
			temp[i] = temp[LEFT(i)] * M[RIGHT(i)] + temp[RIGHT(i)] * M[LEFT(i)];
		}

		resultP = temp[LEFT(0)] * M[RIGHT(0)] + temp[RIGHT(0)] * M[LEFT(0)];
	}

	void iterative_interpolate_zp(ZZ_pX& resultP, ZZ_pX* temp, ZZ_p* y, ZZ_p* a, ZZ_pX* M, unsigned int tree_size, int numThreads, ZZ &prime)
	{
		unsigned int i = tree_size - 1;
		ZZ_p inv_a;
		unsigned int y_index;
		for (; i >= tree_size / 2; i--) {
			y_index = i - (tree_size - 1) / 2;
			inv(inv_a, a[y_index]); // inv_a = 1/a[y_index]
			SetCoeff(temp[i], 0, y[y_index] * inv_a);
		}

		auto begin1 = steady_clock::now();
		vector<vector<int>> subs(numThreads);
		generateSubTreeArrays(subs, tree_size / 2, numThreads - 1);
		auto end1 = steady_clock::now();
		//cout << "inter - generate sub trees: " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		begin1 = steady_clock::now();
		vector<thread> threads(numThreads);

		for (int t = 0; t < numThreads; t++) {

			threads[t] = thread(&interSubTree, temp, M, ref(subs[t]), ref(prime));

		}

		for (int t = 0; t < numThreads; t++) {
			threads[t].join();
		}

		end1 = steady_clock::now();
		//cout << "interpolate - threads part: " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		//    begin1 = steady_clock::now();
		//    for(int i=numThreads-2; i>0; i--)
		//        temp[i] = temp[LEFT(i)] * M[RIGHT(i)] + temp[RIGHT(i)] * M[LEFT(i)] ;
		//
		//    end1 = steady_clock::now();
		//    cout << "inter - first part SERIAL "<< duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;
		//

		begin1 = steady_clock::now();
		vector<thread> threadsLastPart(numThreads * 2);

		for (int i = log2(numThreads) - 1; i >= 1; i--) {
			//cout<<"layer "<<i<< " : " <<endl;
			for (int j = pow(2, i) - 1; j <= pow(2, i + 1) - 2; j++) {
				//cout<<" index to process is "<< j<<endl;

				threadsLastPart[j] = thread(&interSpecific, temp, M, j, ref(prime));
			}

			for (int j = pow(2, i) - 1; j <= pow(2, i + 1) - 2; j++) {
				//cout<<" index join to process is "<< j<<endl;
				threadsLastPart[j].join();
			}
		}

		end1 = steady_clock::now();
		//cout << "inter - last part for max "<< numThreads/2<<" threads " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

		begin1 = steady_clock::now();
		//last iteration
		resultP = temp[LEFT(0)] * M[RIGHT(0)] + temp[RIGHT(0)] * M[LEFT(0)];

		end1 = steady_clock::now();
		//cout << "interpolate - last iteration for root: " << duration_cast<milliseconds>(end1 - begin1).count() << " ms" << endl;

	}

	void interpolate_zp(ZZ_pX& resultP, ZZ_p* x, ZZ_p* y, long degree, int numThreads, ZZ &prime)
	{
		system_clock::time_point begin[4];
		system_clock::time_point end[4];
		ZZ_pX *M = new ZZ_pX[degree * 2 + 1];;
		ZZ_p *a = new ZZ_p[degree + 1];;

		prepareForInterpolate(x, degree, M, a, numThreads, prime);

		//now we can apply the formula
		ZZ_pX* temp = new ZZ_pX[degree * 2 + 1];
		iterative_interpolate_zp(resultP, temp, y, a, M, degree * 2 + 1, numThreads, prime);

		//cout << "Interpolation: " << duration_cast<milliseconds>(end[4] - begin[4]).count() << " ms" << endl;
		//cout << "Total: " << duration_cast<milliseconds>(end[1]-begin[1] + end[2]-begin[2] + end[3]-begin[3] + end[4]-begin[4]).count() << " ms" << endl;
	}

	void prepareForInterpolate(ZZ_p *x, long degree, ZZ_pX *M, ZZ_p *a, int numThreads, ZZ &prime) {

		system_clock::time_point begin[4];
		system_clock::time_point end[4];
		begin[1] = system_clock::now();
		build_tree(M, x, degree * 2 + 1, numThreads, prime);
		end[1] = system_clock::now();

		ZZ_pX D;
		//we construct a preconditioned global structure for the a_k for all 1<=k<=(degree+1)ZZ_pX D;
		begin[2] = system_clock::now();
		diff(D, M[0]);
		end[2] = system_clock::now();

		//evaluate d(x) to obtain the results in the array a
		ZZ_pX* reminders = new ZZ_pX[degree * 2 + 1];
		begin[3] = system_clock::now();
		evaluate(D, M, reminders, degree * 2 + 1, a, numThreads, prime);
		end[3] = system_clock::now();
		//    test_evaluate(D,x,a,degree+1);

		/*cout << "Building tree: " << duration_cast<milliseconds>(end[1] - begin[1]).count() << " ms" << endl;
		cout << "Differentiate: " << duration_cast<milliseconds>(end[2] - begin[2]).count() << " ms" << endl;
		cout << "Evaluate diff: " << duration_cast<milliseconds>(end[3] - begin[3]).count() << " ms" << endl;*/

	}

	void test_interpolation_result_zp(ZZ_pX& P, ZZ_p* x, ZZ_p* y, long degree)
	{
		cout << "Testing result polynomial" << endl;
		ZZ_p res;
		for (long i = 0; i < degree + 1; i++) {
			eval(res, P, x[i]);
			if (res != y[i]) {
				cout << "Error! x = " << x[i] << ", y = " << y[i] << ", res = " << res << endl;
				return;
			}
		}
		cout << "Polynomial is interpolated correctly!" << endl;
	}

	void test_interpolate_zp(ZZ prime, long degree, int numThreads)
	{
		// init underlying prime field
		ZZ_p::init(ZZ(prime));

		// interpolation points:
		ZZ_p* x = new ZZ_p[degree + 1];
		ZZ_p* y = new ZZ_p[degree + 1];
		for (unsigned int i = 0; i <= degree; i++) {
			random(x[i]);
			random(y[i]);
		}

		ZZ_pX P;
		interpolate_zp(P, x, y, degree, numThreads, prime);
		//cout << "P: "; print_poly(P); cout << endl;
		//test_interpolation_result_zp(P, x, y, degree);
	}

	void BytesToZZ_px(unsigned char *bytesArr, ZZ_pX& poly, long numOfElements, long sizeOfElement) {

		//turn each byte to zz_p element in a vector

		vec_ZZ repFromBytes;
		repFromBytes.SetLength(numOfElements);

		for (int i = 0; i < numOfElements; i++) {

			ZZ zz;

			//translate the bytes into a ZZ element
			ZZFromBytes(zz, bytesArr + i*sizeOfElement, sizeOfElement);

			repFromBytes[i] = zz;
		}

		//turn the vec_zzp to the polynomial

		poly = to_ZZ_pX(to_vec_ZZ_p(repFromBytes));

	}
	void ZZ_pxToBytes(ZZ_pX& poly, unsigned char *bytesArr, long numOfElements, long sizeOfElement) {

		//get the zz_p vector

		for (int i = 0; i < numOfElements; i++) {

			BytesFromZZ(bytesArr + i*sizeOfElement, rep(poly.rep[i]), sizeOfElement);
		}

	}
}


/*
	Copyright (c) 2019,	Mobile Robots Laboratory:
	-Jan Wietrzykowski (jan.wietrzykowski@put.poznan.pl).
	Poznan University of Technology
	All rights reserved.

	Redistribution and use in source and binary forms, with or without modification,
	are permitted provided that the following conditions are met:

	1. Redistributions of source code must retain the above copyright notice,
	this list of conditions and the following disclaimer.

	2. Redistributions in binary form must reproduce the above copyright notice,
	this list of conditions and the following disclaimer in the documentation
	and/or other materials provided with the distribution.

	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
	AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
	THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
	ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
	FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
	DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
	LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
	AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
	OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
	OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <iostream>
#include <thread>
#include <random>
#include <chrono>
#include <string>
#include <fstream>

#include <boost/serialization/serialization.hpp>
#include <boost/serialization/vector.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>

#include "pgm/ParamEstSGD.h"
#include "pgm/Exceptions.h"
#include "pgm/Inference.h"

using namespace std;

std::vector<double> ParamEstSGD::estimate(std::shared_ptr<PgmCreator> pgmCreator,
										Params estParams,
										const std::vector<double>& initParams)
{
	static const int maxThreads = 4;

	std::default_random_engine randGen;

	int m = pgmCreator->getM();

	//generate random permutation
	vector<int> perm;
	for(int i = 0; i < m; ++i){
		perm.push_back(i);
	}
	for(int i = 0; i < m - 1; ++i){
		std::uniform_int_distribution<int> dist(i, m - 1);
		int swapIdx = dist(randGen);
		int tmp = perm[i];
		perm[i] = perm[swapIdx];
		perm[swapIdx] = tmp;
	}

	int mVal = estParams.valSetFrac * perm.size();
	vector<int> permVal = vector<int>(perm.begin(), perm.begin() + mVal);

	int mTrain = perm.size() - mVal;
	vector<int> permTrain = vector<int>(perm.begin() + mVal, perm.end());

	vector<double> curParams = initParams;

	vector<double> paramsGrad;

	int iter = 0;
	bool stopFlag = false;
	while(!stopFlag){
		cout << "iteration " << iter << endl;
		cout << "curParams = " << curParams << endl;

		double curLearnRate = 1 / ((estParams.sigma * estParams.sigma) * (estParams.learnRateM0 + iter));
		cout << "curLearnRate = " << curLearnRate << endl;

		//generate random permutation
		vector<int> curPerm = permTrain;

		for(int i = 0; i < mTrain - 1; ++i){
			std::uniform_int_distribution<int> dist(i, mTrain - 1);
			int swapIdx = dist(randGen);
			int tmp = curPerm[i];
			curPerm[i] = curPerm[swapIdx];
			curPerm[swapIdx] = tmp;
		}

		vector<double> lhoodNumers;
		vector<double> lhoodDenoms;
		vector<vector<double>> Efis;
		vector<vector<double>> Eds;

		//take first n samples
		evaluateSamples(vector<int>(curPerm.begin(), curPerm.begin() + estParams.n),
						maxThreads,
						pgmCreator,
						curParams,
						lhoodNumers,
						lhoodDenoms,
						Efis,
						Eds);

		vector<double> curGrad(curParams.size(), 0.0);
		// compute current gradient
		for(int i = 0; i < Efis.size(); ++i){
			for(int p = 0; p < curGrad.size(); ++p){
				curGrad[p] = Eds[i][p] - Efis[i][p];
			}
		}
		for(int p = 0; p < curGrad.size(); ++p){
			curGrad[p] /= Efis.size();
		}
		for(int p = 0; p < curGrad.size(); ++p){
			curGrad[p] -= curParams[p] / (estParams.sigma * estParams.sigma);
		}

		cout << "curGrad = " << curGrad << endl;

		// if it is first iteration and previous gradient is empty
		if(paramsGrad.empty()){
			paramsGrad = curGrad;
		}

		for(int p = 0; p < paramsGrad.size(); ++p){
			paramsGrad[p] = curLearnRate * (paramsGrad[p]*estParams.momentum + curGrad[p]);
		}

		for(int p = 0; p < curParams.size(); ++p){
			curParams[p] += paramsGrad[p];
		}

		//evaluate on validation set
		if(iter % 10 == 0){
			double scoreVal = 0.0;

			vector<double> lhoodNumers;
			vector<double> lhoodDenoms;
			vector<vector<double>> Efis;
			vector<vector<double>> Eds;

			//take first n samples
			evaluateSamples(permVal,
							maxThreads,
							pgmCreator,
							curParams,
							lhoodNumers,
							lhoodDenoms,
							Efis,
							Eds);

			for(int i = 0; i < lhoodNumers.size(); ++i){
				cout << "scoreVal += " << lhoodNumers[i] << " - " << lhoodDenoms[i] << endl;
				scoreVal += lhoodNumers[i] - lhoodDenoms[i];
			}
			scoreVal /= lhoodNumers.size();

			cout << endl << endl << "scoreVal = " << scoreVal << endl << endl << endl;

			//saving a snapshot
			{
				ofstream paramsFile(string("log/params_") + to_string(iter));
				boost::archive::text_oarchive paramsArch(paramsFile);
				paramsArch << curParams;
			}
			{
				ofstream gradFile(string("log/grad_") + to_string(iter));
				boost::archive::text_oarchive gradArch(gradFile);
				gradArch << curGrad;
			}
			{
				ofstream scoreFile(string("log/score"), ios::app);
				scoreFile << iter << " " << scoreVal << endl;
			}
		}

		//stop condition
		//TODO use better stop condition
		if(curLearnRate < 0.005){
			stopFlag = true;
		}

		iter++;
	}

	return curParams;
}

void ParamEstSGD::evaluateSamples(const std::vector<int>& sampIdxs,
							int maxThreads,
							std::shared_ptr<PgmCreator> pgmCreator,
							const std::vector<double>& curParams,
							std::vector<double>& lhoodNumers,
							std::vector<double>& lhoodDenoms,
							std::vector<std::vector<double>>& Efis,
							std::vector<std::vector<double>>& Eds)
{
	int threadCnt = 0;
	vector<shared_ptr<ParamEstSGD::EvaluateThread>> threads(sampIdxs.size());
	vector<shared_ptr<Pgm>> pgms(sampIdxs.size());
	vector<shared_ptr<vector<double>>> obsVecs(sampIdxs.size());
	vector<shared_ptr<vector<double>>> varVals(sampIdxs.size());
	vector<shared_ptr<vector<int>>> varIds(sampIdxs.size());
	vector<shared_ptr<vector<double>>> paramVals(sampIdxs.size());
	lhoodNumers.resize(sampIdxs.size());
	lhoodDenoms.resize(sampIdxs.size());
	Efis.resize(sampIdxs.size());
	Eds.resize(sampIdxs.size());

	//take first n elements from permutation
	for(int i = 0; i < sampIdxs.size(); ++i){
		while(threadCnt == maxThreads){
			endThreadIfAny(threadCnt,
							threads,
							pgms,
							obsVecs,
							varVals,
							varIds,
							paramVals);

			std::this_thread::sleep_for(chrono::milliseconds(50));
		}

		pgms[i] = shared_ptr<Pgm>(new Pgm);
		obsVecs[i] = shared_ptr<vector<double>>(new vector<double>());
		varVals[i] = shared_ptr<vector<double>>(new vector<double>());
		varIds[i] = shared_ptr<vector<int>>(new vector<int>());
		paramVals[i] = shared_ptr<vector<double>>(new vector<double>(curParams));
		lhoodNumers[i] = 0.0;
		lhoodDenoms[i] = 0.0;
		Efis[i] = vector<double>(curParams.size(), 0.0);
		Eds[i] = vector<double>(curParams.size(), 0.0);

		pgmCreator->create(sampIdxs[i],
						*pgms[i],
						*obsVecs[i],
						*varVals[i],
						*varIds[i]);

//		for(int j = 0; j < Efis.size(); ++j){
//			cout << "Efis[" << j << "].data() = " << Efis[j].data() << endl;
//			cout << "&Efis[" << j << "] = " << &Efis[j] << endl;
//		}

		threads[i] = shared_ptr<EvaluateThread>(new EvaluateThread(pgms[i],
												obsVecs[i],
												varVals[i],
												varIds[i],
												paramVals[i],
												lhoodNumers[i],
												lhoodDenoms[i],
												Efis[i],
												Eds[i]));
		++threadCnt;
	}

	while(threadCnt > 0){
		endThreadIfAny(threadCnt,
						threads,
						pgms,
						obsVecs,
						varVals,
						varIds,
						paramVals);
		std::this_thread::sleep_for(chrono::milliseconds(50));
	}
}

void ParamEstSGD::endThreadIfAny(int& threadCnt,
						std::vector<std::shared_ptr<ParamEstSGD::EvaluateThread>>& threads,
						std::vector<std::shared_ptr<Pgm>>& pgms,
						std::vector<std::shared_ptr<std::vector<double>>>& obsVecs,
						std::vector<std::shared_ptr<std::vector<double>>>& varVals,
						std::vector<std::shared_ptr<std::vector<int>>>& varIds,
						std::vector<std::shared_ptr<std::vector<double>>>& paramVals)
{
	for(int t = 0; t < threads.size(); ++t){
		if(threads[t]){
			if(threads[t]->hasFinished()){
				cout << "Ending thread " << t << endl;
				threads[t].reset();
				pgms[t].reset();
				obsVecs[t].reset();
				varVals[t].reset();
				varIds[t].reset();
				paramVals[t].reset();

				--threadCnt;
			}
		}
	}
}

ParamEstSGD::EvaluateThread::EvaluateThread(std::shared_ptr<Pgm> ipgm,
										std::shared_ptr<const std::vector<double>> iobsVec,
										std::shared_ptr<const std::vector<double>> ivarVals,
										std::shared_ptr<const std::vector<int>> ivarIds,
										std::shared_ptr<const std::vector<double>> iparamVals,
										double& ilhoodNumer,
										double& ilhoodDenom,
										std::vector<double>& iEfi,
										std::vector<double>& iEd)
		: pgm(ipgm),
		  obsVec(iobsVec),
		  varVals(ivarVals),
		  varIds(ivarIds),
		  paramVals(iparamVals),
		  lhoodNumer(ilhoodNumer),
		  lhoodDenom(ilhoodDenom),
		  Efi(iEfi),
		  Ed(iEd),
		  finishedFlag(false)
{
//	cout << "Efi.data() constr = " << Efi.data() << ", this = " << this << endl;
//	cout << "&Efi constr = " << &Efi << ", this = " << this << endl;
	runThread = thread(&ParamEstSGD::EvaluateThread::run, this);
}

ParamEstSGD::EvaluateThread::~EvaluateThread()
{
	if(runThread.joinable()){
		runThread.join();
	}
}

bool ParamEstSGD::EvaluateThread::hasFinished()
{
	return finishedFlag;
}

void ParamEstSGD::EvaluateThread::run()
{
	// inference without clamped variables - denominator and model expectation
	{
//		cout << "Efi.data() 1 = " << Efi.data() << ", this = " << this <<  endl;
//		cout << "&Efi 1 = " << &Efi << ", this = " << this << endl;

		vector<vector<double>> marg;
		vector<vector<vector<double>>> msgs;
		double logPartFunc;

//		cout << "Efi.data() 2 = " << Efi.data() << ", this = " << this << endl;
		bool calibrated = Inference::compMarginalsParam(*pgm, marg, logPartFunc, msgs, *paramVals, *obsVec);
//		cout << "Efi.data() 3 = " << Efi.data() << endl;

		if(!calibrated){
			cout << "Warning - inference without clamped variables didn't calibrate" << endl;
		}

		lhoodDenom = logPartFunc;
		cout << "loodDenom = " << lhoodDenom << endl;

		for(int c = 0; c < (int)pgm->constClusters().size(); ++c){
//			cout << "Cluster " << c << endl;
			std::shared_ptr<Cluster> curCluster = pgm->clusters()[c];
//			cout << "curCluster->id() = " << curCluster->id() << endl;
			const vector<vector<double> >& curClustMsgs = msgs[curCluster->id()];
//			cout << "curClustMsgs = " << curClustMsgs << endl;
			vector<double> curEfi = curCluster->compSumModelExpectation(curClustMsgs, *paramVals, *obsVec);
//			cout << "Efi.size() = " << Efi.size() << ", curEfi.size() = " << curEfi.size() << endl;

			for(int f = 0; f < (int)curCluster->feats().size(); ++f){
//				cout << "f = " << f << endl;
				int paramNum = curCluster->feats()[f]->paramNum();
//				cout << "paramNum = " << paramNum << endl;
				Efi[paramNum] += curEfi[f];
			}
		}
	}

	// clamping variables
//	cout << "clamping variables" << endl;
	int posRv = 0;
	for(int v = 0; v < varIds->size(); ++v){
		bool stopFlag = false;
		while(!stopFlag && posRv < pgm->randVars().size() - 1){
			if(pgm->randVars()[posRv]->id() < varIds->at(v)){
				++posRv;
			}
			else{
				stopFlag = true;
			}
		}
		if(pgm->randVars()[posRv]->id() == varIds->at(v)){
			pgm->randVars()[posRv]->makeObserved(varVals->at(v));
		}
		else{
			cout << "pgm->randVars()[posRv]->id() = " << pgm->randVars()[posRv]->id() << endl;
			cout << "varIds->at(v) = " << varIds->at(v) << endl;
			throw PGM_EXCEPTION("Rand var not found");
		}
	}
//	cout << "end clamping variables" << endl;

	// inference with clamped variables - numerator and distribution expectation
	{
		vector<vector<double>> margClamp;
		vector<vector<vector<double>>> msgsClamp;
		double logPartFuncClamp;

		bool calibratedClamp = Inference::compMarginalsParam(*pgm, margClamp, logPartFuncClamp, msgsClamp, *paramVals, *obsVec);

		if(!calibratedClamp){
			cout << "Warning - inference with clamped variables didn't calibrate" << endl;
		}

		lhoodNumer = logPartFuncClamp;
		cout << "loodNumer = " << lhoodNumer << endl;

		// a model expectation with clamped variables is a distribution expectation
		for(int c = 0; c < (int)pgm->constClusters().size(); ++c){
//			cout << "Cluster " << c << endl;
			std::shared_ptr<Cluster> curCluster = pgm->clusters()[c];
			const vector<vector<double> >& curClustMsgs = msgsClamp[curCluster->id()];
			vector<double> curEfi = curCluster->compSumModelExpectation(curClustMsgs, *paramVals, *obsVec);

			for(int f = 0; f < (int)curCluster->feats().size(); ++f){
				int paramNum = curCluster->feats()[f]->paramNum();
				Ed[paramNum] += curEfi[f];
			}
		}
	}

	finishedFlag = true;
}


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

#ifdef _MSC_VER
#  pragma warning (disable : 4127) // conditional expression is constant
#  pragma warning (disable : 4180) // qualifier applied to function type has no meaning; ignored
#  pragma warning (disable : 4503) // decorated name length exceeded, name was truncated
#  pragma warning (disable : 4512) // assignment operator could not be generated
#  pragma warning (disable : 4224) // nonstandard extension used : formal parameter 'function_ptr' was previously defined as a type
#endif

// #define BOOST_SVG_DIAGNOSTICS // define to provide diagnostic output from plotting.

#include <boost/math/special_functions.hpp>
#include <boost/math/tools/roots.hpp>
#include <boost/function.hpp>
#include <boost/bind.hpp>

#include <list>
#include <map>
#include <string>
#include <boost/svg_plot/svg_2d_plot.hpp>
#include <boost/svg_plot/show_2d_settings.hpp>

class function_arity1_plotter
{
public:
   function_arity1_plotter() : m_min_x(0), m_max_x(0), m_min_y(0), m_max_y(0), m_has_legend(false) {}

   //! Add a function to the plotter, compute the axes using range a to b and compute & add data points to map.

   void add(boost::function<double(double)> f, double x_lo, double x_hi, const std::string& name)
   {
     std::cout << "Adding function " << name << ", x range " << x_lo << " to " << x_hi << std::endl;
      if(name.size())
         m_has_legend = true;
      //
      // Now set our x-axis limits:
      if(m_max_x == m_min_x)
      {
         m_max_x = x_hi;
         m_min_x = x_lo;
      }
      else
      {
         if(x_lo < m_min_x)
            m_min_x = x_lo;
         if(x_hi > m_max_x)
            m_max_x = x_hi;
      }
      m_points.push_back(std::pair<std::string, std::map<double,double> >(name, std::map<double,double>()));
      std::map<double,double>& points = m_points.rbegin()->second;
      double interval = (x_hi - x_lo) / 200;
      for(double x = x_lo; x <= x_hi; x += interval)
      {
         double y = f(x); // Evaluate the function.
         // Set the Y axis limits if needed.
         if((m_min_y == m_max_y) && (m_min_y == 0))
            m_min_y = m_max_y = y;
         if(m_min_y > y)
            m_min_y = y;
         if(m_max_y < y)
            m_max_y = y;
         points[x] = y; // Store the pair of points values.
      } // for x

#ifdef BOOST_SVG_DIAGNOSTICS
  std::cout << "Added function " << name
    << ", x range " << x_lo << " to " << x_hi
    << ", x min = " << m_min_x << ", x max = " << m_max_x
    << ", y min = " << m_min_y << ", y max = " << m_max_y
    << ", interval = " << interval
    << std::endl;
#endif
   } // void add(boost::function<double(double)> f, double a, double b, const std::string& name)

   //! Compute x and y min and max from a map of pre-computed data points.
   void add(const std::map<double, double>& m, const std::string& name)
   {
     if (name.size() != 0)
     {
       m_has_legend = true;
     }
      m_points.push_back(std::pair<std::string, std::map<double,double> >(name, m));

      std::map<double, double>::const_iterator i = m.begin();
      while(i != m.end())
      {
         if((m_min_x == m_min_y) && (m_min_y == 0))
         {
            m_min_x = m_max_x = i->first;
         }
         if(i->first < m_min_x)
         {
            m_min_x = i->first;
         }
         if(i->first > m_max_x)
         {
            m_max_x = i->first;
         }

         if((m_min_y == m_max_y) && (m_min_y == 0))
         {
            m_min_y = m_max_y = i->second;
         }
         if(i->second < m_min_y)
         {
            m_min_y = i->second;
         }
         if(i->second > m_max_y)
         {
            m_max_y = i->second;
         }

         ++i;
      }
   } // void add(const std::map<double, double>& m, const std::string& name)

   //! Plot pre-computed m_points data for function.
   void plot(const std::string& title, const std::string& file,
      const std::string& x_lable = std::string(), const std::string& y_lable = std::string())
   {
      using namespace boost::svg;

      static const svg_color colors[5] =
      { // Colors for plot curves, used in turn.
         darkblue,
         darkred,
         darkgreen,
         darkorange,
         chartreuse
      };

      std::cout << "Plotting Special Function " << title << " to file " << file << std::endl;

      svg_2d_plot plot;
      plot.image_x_size(600);
      plot.image_y_size(400);
      plot.copyright_holder("John Maddock").copyright_date("2008").boost_license_on(true);
      plot.coord_precision(4); // Could be 3 for smaller plots?
      plot.title(title).title_font_size(20).title_on(true);
      plot.legend_on(m_has_legend);

      double x_delta = (m_max_x - m_min_x) / 50;
      double y_delta = (m_max_y - m_min_y) / 50;
      plot.x_range(m_min_x, m_max_x + x_delta)
         .y_range(m_min_y, m_max_y + y_delta);
      plot.x_label_on(true).x_label(x_lable);
      plot.y_label_on(true).y_label(y_lable);
      plot.y_major_grid_on(false).x_major_grid_on(false);
      plot.x_num_minor_ticks(3);
      plot.y_num_minor_ticks(3);
      //
      // Work out axis tick intervals:
      double l = std::floor(std::log10((m_max_x - m_min_x) / 10) + 0.5);
      double interval = std::pow(10.0, (int)l);
      if(((m_max_x - m_min_x) / interval) > 10)
         interval *= 5;
      plot.x_major_interval(interval);
      l = std::floor(std::log10((m_max_y - m_min_y) / 10) + 0.5);
      interval = std::pow(10.0, (int)l);
      if(((m_max_y - m_min_y) / interval) > 10)
         interval *= 5;
      plot.y_major_interval(interval);
      plot.plot_window_on(true);
      plot.plot_border_color(lightslategray)
          .background_border_color(lightslategray)
          .legend_border_color(lightslategray)
          .legend_background_color(white);

      int color_index = 0; // Cycle through the colors for each curve.

      for(std::list<std::pair<std::string, std::map<double,double> > >::const_iterator i = m_points.begin();
         i != m_points.end(); ++i)
      {
         plot.plot(i->second, i->first)
            .line_on(true)
            .line_color(colors[color_index])
            .line_width(1.)
            .shape(none);
         if(i->first.size())
            ++color_index;
         color_index = color_index % (sizeof(colors)/sizeof(colors[0]));
      }
      plot.write(file);
   } //    void plot(const std::string& title, const std::string& file,

   void clear()
   {
      m_points.clear();
      m_min_x = m_min_y = m_max_x = m_max_y = 0;
      m_has_legend = false;
   } // clear

private:
   std::list<std::pair<std::string, std::map<double, double> > > m_points;
   double m_min_x, m_max_x, m_min_y, m_max_y;
   bool m_has_legend;
};

template <class F>
struct location_finder
{
   location_finder(F _f, double t, double x0) : f(_f), target(t), x_off(x0){}

   double operator()(double x)
   {
      try
      {
         return f(x + x_off) - target;
      }
      catch(const std::overflow_error&)
      {
         return boost::math::tools::max_value<double>();
      }
      catch(const std::domain_error&)
      {
         if(x + x_off == x_off)
            return f(x_off + boost::math::tools::epsilon<double>() * x_off);
         throw;
      }
   }

private:
   F f;
   double target;
   double x_off;
};

template <class F>
double find_end_point(F f, double x0, double target, bool rising, double x_off = 0)
{
   boost::math::tools::eps_tolerance<double> tol(50);
   boost::uintmax_t max_iter = 1000;
   return x_off + boost::math::tools::bracket_and_solve_root(
      location_finder<F>(f, target, x_off),
      x0,
      1.5,
      rising,
      tol,
      max_iter).first;
}

double sqrt1pm1(double x)
{
   return boost::math::sqrt1pm1(x);
}

double lbeta(double a, double b)
{
   return std::log(boost::math::beta(a, b));
}

int main()
{
  try
  {
    function_arity1_plotter plot;

    // Functions may have varying numbers and types of parameters.
    // plot.add calls must use the appropriate function type.
    // Not all function types may be used, so can ignore any warning like
    // "C4101: 'f4': unreferenced local variable"
    double(*f)(double); // Simplest function type, suits most functions.
    double(*f2)(double, double);
    double(*f2u)(unsigned, double);
    double(*f2i)(int, double);
    double(*f3)(double, double, double);
    double(*f4)(double, double, double, double);
    double max_val; // Hold evaluated value of function for use in find_end_point.

       f = boost::math::zeta;
       plot.add(f, find_end_point(f, 0.1, 40.0, false, 1.0), 10, "");
       plot.add(f, -20, find_end_point(f, -0.1, -40.0, false, 1.0), "");
       plot.plot("Zeta Function Over [-20,10]", "zeta1.svg", "z", "zeta(z)");

       plot.clear();
       plot.add(f, -14, 0, "");
       plot.plot("Zeta Function Over [-14,0]", "zeta2.svg", "z", "zeta(z)");

       f = boost::math::tgamma;
       max_val = f(6);
       plot.clear();
       plot.add(f, find_end_point(f, 0.1, max_val, false), 6, "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -1), find_end_point(f, -0.1, -max_val, false), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -2), find_end_point(f, -0.1, max_val, true, -1), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -3), find_end_point(f, -0.1, -max_val, false, -2), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -4), find_end_point(f, -0.1, max_val, true, -3), "");
       plot.plot("tgamma", "tgamma.svg", "z", "tgamma(z)");

       f = boost::math::lgamma;
       max_val = f(10);
       plot.clear();
       plot.add(f, find_end_point(f, 0.1, max_val, false), 10, "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -1), find_end_point(f, -0.1, max_val, true), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -2), find_end_point(f, -0.1, max_val, true, -1), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -3), find_end_point(f, -0.1, max_val, true, -2), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -4), find_end_point(f, -0.1, max_val, true, -3), "");
       plot.add(f, find_end_point(f, 0.1, max_val, false, -5), find_end_point(f, -0.1, max_val, true, -4), "");
       plot.plot("lgamma", "lgamma.svg", "z", "lgamma(z)");

       f = boost::math::digamma;
       max_val = 10;
       plot.clear();
       plot.add(f, find_end_point(f, 0.1, -max_val, true), 10, "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -1), find_end_point(f, -0.1, max_val, true), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -2), find_end_point(f, -0.1, max_val, true, -1), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -3), find_end_point(f, -0.1, max_val, true, -2), "");
       plot.add(f, find_end_point(f, 0.1, -max_val, true, -4), find_end_point(f, -0.1, max_val, true, -3), "");
      plot.plot("digamma", "digamma.svg", "z", "digamma(z)");

      f = boost::math::erf;
      plot.clear();
      plot.add(f, -3, 3, "erf");
      plot.plot("erf", "erf.svg", "z", "erf(z)");

      f = boost::math::erfc;
      plot.clear();
      plot.add(f, -3, 3, "erfc");
      plot.plot("erfc", "erfc.svg", "z", "erfc(z)");

    f = boost::math::erf_inv;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -3, true, -1), find_end_point(f, -0.1, 3, true, 1), "");
    plot.plot("erf_inv", "erf_inv.svg", "z", "erf_inv(z)");
    f = boost::math::erfc_inv;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, 3, false), find_end_point(f, -0.1, -3, false, 2), "");
    plot.plot("erfc_inv", "erfc_inv.svg", "z", "erfc_inv(z)");

    f = boost::math::log1p;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -10, true, -1), 10, "");
    plot.plot("log1p", "log1p.svg", "z", "log1p(z)");

    f = boost::math::expm1;
    plot.clear();
    plot.add(f, -4, 2, "");
    plot.plot("expm1", "expm1.svg", "z", "expm1(z)");

    f = boost::math::cbrt;
    plot.clear();
    plot.add(f, -10, 10, "");
    plot.plot("cbrt", "cbrt.svg", "z", "cbrt(z)");

    f = sqrt1pm1;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -10, true, -1), 5, "");
    plot.plot("sqrt1pm1", "sqrt1pm1.svg", "z", "sqrt1pm1(z)");

    f2 = boost::math::powm1;
    plot.clear();
    plot.add(boost::bind(f2, 0.0001, _1), find_end_point(boost::bind(f2, 0.0001, _1), -1, 10, false), 5, "a=0.0001");
    plot.add(boost::bind(f2, 0.001, _1), find_end_point(boost::bind(f2, 0.001, _1), -1, 10, false), 5, "a=0.001");
    plot.add(boost::bind(f2, 0.01, _1), find_end_point(boost::bind(f2, 0.01, _1), -1, 10, false), 5, "a=0.01");
    plot.add(boost::bind(f2, 0.1, _1), find_end_point(boost::bind(f2, 0.1, _1), -1, 10, false), 5, "a=0.1");
    plot.add(boost::bind(f2, 0.75, _1), -5, 5, "a=0.75");
    plot.add(boost::bind(f2, 1.25, _1), -5, 5, "a=1.25");
    plot.plot("powm1", "powm1.svg", "z", "powm1(a, z)");

    f = boost::math::sinc_pi;
    plot.clear();
    plot.add(f, -10, 10, "");
    plot.plot("sinc_pi", "sinc_pi.svg", "z", "sinc_pi(z)");

    f = boost::math::sinhc_pi;
    plot.clear();
    plot.add(f, -5, 5, "");
    plot.plot("sinhc_pi", "sinhc_pi.svg", "z", "sinhc_pi(z)");

    f = boost::math::acosh;
    plot.clear();
    plot.add(f, 1, 10, "acosh");
    plot.plot("acosh", "acosh.svg", "z", "acosh(z)");

    f = boost::math::asinh;
    plot.clear();
    plot.add(f, -10, 10, "");
    plot.plot("asinh", "asinh.svg", "z", "asinh(z)");

    f = boost::math::atanh;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -5, true, -1), find_end_point(f, -0.1, 5, true, 1), "");
    plot.plot("atanh", "atanh.svg", "z", "atanh(z)");

    f2 = boost::math::tgamma_delta_ratio;
    plot.clear();
    plot.add(boost::bind(f2, _1, -0.5), 1, 40, "delta = -0.5");
    plot.add(boost::bind(f2, _1, -0.2), 1, 40, "delta = -0.2");
    plot.add(boost::bind(f2, _1, -0.1), 1, 40, "delta = -0.1");
    plot.add(boost::bind(f2, _1, 0.1), 1, 40, "delta = 0.1");
    plot.add(boost::bind(f2, _1, 0.2), 1, 40, "delta = 0.2");
    plot.add(boost::bind(f2, _1, 0.5), 1, 40, "delta = 0.5");
    plot.add(boost::bind(f2, _1, 1.0), 1, 40, "delta = 1.0");
    plot.plot("tgamma_delta_ratio", "tgamma_delta_ratio.svg", "z", "tgamma_delta_ratio(delta, z)");

    f2 = boost::math::gamma_p;
    plot.clear();
    plot.add(boost::bind(f2, 0.5, _1), 0, 20, "a = 0.5");
    plot.add(boost::bind(f2, 1.0, _1), 0, 20, "a = 1.0");
    plot.add(boost::bind(f2, 5.0, _1), 0, 20, "a = 5.0");
    plot.add(boost::bind(f2, 10.0, _1), 0, 20, "a = 10.0");
    plot.plot("gamma_p", "gamma_p.svg", "z", "gamma_p(a, z)");

    f2 = boost::math::gamma_q;
    plot.clear();
    plot.add(boost::bind(f2, 0.5, _1), 0, 20, "a = 0.5");
    plot.add(boost::bind(f2, 1.0, _1), 0, 20, "a = 1.0");
    plot.add(boost::bind(f2, 5.0, _1), 0, 20, "a = 5.0");
    plot.add(boost::bind(f2, 10.0, _1), 0, 20, "a = 10.0");
    plot.plot("gamma_q", "gamma_q.svg", "z", "gamma_q(a, z)");

    f2 = lbeta;
    plot.clear();
    plot.add(boost::bind(f2, 0.5, _1), 0.00001, 5, "a = 0.5");
    plot.add(boost::bind(f2, 1.0, _1), 0.00001, 5, "a = 1.0");
    plot.add(boost::bind(f2, 5.0, _1), 0.00001, 5, "a = 5.0");
    plot.add(boost::bind(f2, 10.0, _1), 0.00001, 5, "a = 10.0");
    plot.plot("beta", "beta.svg", "z", "log(beta(a, z))");

    f = boost::math::expint;
    max_val = f(4);
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, -max_val, true), 4, "");
    plot.add(f, -3, find_end_point(f, -0.1, -max_val, false), "");
    plot.plot("Exponential Integral Ei", "expint_i.svg", "z", "expint(z)");

    f2u = boost::math::expint;
    max_val = 1;
    plot.clear();
    plot.add(boost::bind(f2u, 1, _1), find_end_point(boost::bind(f2u, 1, _1), 0.1, max_val, false), 2, "n = 1 ");
    plot.add(boost::bind(f2u, 2, _1), find_end_point(boost::bind(f2u, 2, _1), 0.1, max_val, false), 2, "n = 2 ");
    plot.add(boost::bind(f2u, 3, _1), 0, 2, "n = 3 ");
    plot.add(boost::bind(f2u, 4, _1), 0, 2, "n = 4 ");
    plot.plot("Exponential Integral En", "expint2.svg", "z", "expint(n, z)");

    f3 = boost::math::ibeta;
    plot.clear();
    plot.add(boost::bind(f3, 9, 1, _1), 0, 1, "a = 9, b = 1");
    plot.add(boost::bind(f3, 7, 2, _1), 0, 1, "a = 7, b = 2");
    plot.add(boost::bind(f3, 5, 5, _1), 0, 1, "a = 5, b = 5");
    plot.add(boost::bind(f3, 2, 7, _1), 0, 1, "a = 2, b = 7");
    plot.add(boost::bind(f3, 1, 9, _1), 0, 1, "a = 1, b = 9");
    plot.plot("ibeta", "ibeta.svg", "z", "ibeta(a, b, z)");

    f2i = boost::math::legendre_p;
    plot.clear();
    plot.add(boost::bind(f2i, 1, _1), -1, 1, "l = 1");
    plot.add(boost::bind(f2i, 2, _1), -1, 1, "l = 2");
    plot.add(boost::bind(f2i, 3, _1), -1, 1, "l = 3");
    plot.add(boost::bind(f2i, 4, _1), -1, 1, "l = 4");
    plot.add(boost::bind(f2i, 5, _1), -1, 1, "l = 5");
    plot.plot("Legendre Polynomials", "legendre_p.svg", "x", "legendre_p(l, x)");

    f2u = boost::math::legendre_q;
    plot.clear();
    plot.add(boost::bind(f2u, 1, _1), -0.95, 0.95, "l = 1");
    plot.add(boost::bind(f2u, 2, _1), -0.95, 0.95, "l = 2");
    plot.add(boost::bind(f2u, 3, _1), -0.95, 0.95, "l = 3");
    plot.add(boost::bind(f2u, 4, _1), -0.95, 0.95, "l = 4");
    plot.add(boost::bind(f2u, 5, _1), -0.95, 0.95, "l = 5");
    plot.plot("Legendre Polynomials of the Second Kind", "legendre_q.svg", "x", "legendre_q(l, x)");

    f2u = boost::math::laguerre;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), -5, 10, "n = 0");
    plot.add(boost::bind(f2u, 1, _1), -5, 10, "n = 1");
    plot.add(boost::bind(f2u, 2, _1),
       find_end_point(boost::bind(f2u, 2, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 2, _1), 4, 20, true),
       "n = 2");
    plot.add(boost::bind(f2u, 3, _1),
       find_end_point(boost::bind(f2u, 3, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 3, _1), 1, 20, false, 8),
       "n = 3");
    plot.add(boost::bind(f2u, 4, _1),
       find_end_point(boost::bind(f2u, 4, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 4, _1), 1, 20, true, 8),
       "n = 4");
    plot.add(boost::bind(f2u, 5, _1),
       find_end_point(boost::bind(f2u, 5, _1), -2, 20, false),
       find_end_point(boost::bind(f2u, 5, _1), 1, 20, true, 8),
       "n = 5");
    plot.plot("Laguerre Polynomials", "laguerre.svg", "x", "laguerre(n, x)");

    f2u = boost::math::hermite;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), -1.8, 1.8, "n = 0");
    plot.add(boost::bind(f2u, 1, _1), -1.8, 1.8, "n = 1");
    plot.add(boost::bind(f2u, 2, _1), -1.8, 1.8, "n = 2");
    plot.add(boost::bind(f2u, 3, _1), -1.8, 1.8, "n = 3");
    plot.add(boost::bind(f2u, 4, _1), -1.8, 1.8, "n = 4");
    plot.plot("Hermite Polynomials", "hermite.svg", "x", "hermite(n, x)");

    f2 = boost::math::cyl_bessel_j;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -20, 20, "v = 0");
    plot.add(boost::bind(f2, 1, _1), -20, 20, "v = 1");
    plot.add(boost::bind(f2, 2, _1), -20, 20, "v = 2");
    plot.add(boost::bind(f2, 3, _1), -20, 20, "v = 3");
    plot.add(boost::bind(f2, 4, _1), -20, 20, "v = 4");
    plot.plot("Bessel J", "cyl_bessel_j.svg", "x", "cyl_bessel_j(v, x)");

    f2 = boost::math::cyl_neumann;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), find_end_point(boost::bind(f2, 0, _1), 0.1, -5, true), 20, "v = 0");
    plot.add(boost::bind(f2, 1, _1), find_end_point(boost::bind(f2, 1, _1), 0.1, -5, true), 20, "v = 1");
    plot.add(boost::bind(f2, 2, _1), find_end_point(boost::bind(f2, 2, _1), 0.1, -5, true), 20, "v = 2");
    plot.add(boost::bind(f2, 3, _1), find_end_point(boost::bind(f2, 3, _1), 0.1, -5, true), 20, "v = 3");
    plot.add(boost::bind(f2, 4, _1), find_end_point(boost::bind(f2, 4, _1), 0.1, -5, true), 20, "v = 4");
    plot.plot("Bessel Y", "cyl_neumann.svg", "x", "cyl_neumann(v, x)");

    f2 = boost::math::cyl_bessel_i;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), find_end_point(boost::bind(f2, 0, _1), -0.1, 20, false), find_end_point(boost::bind(f2, 0, _1), 0.1, 20, true), "v = 0");
    plot.add(boost::bind(f2, 2, _1), find_end_point(boost::bind(f2, 2, _1), -0.1, 20, false), find_end_point(boost::bind(f2, 2, _1), 0.1, 20, true), "v = 2");
    plot.add(boost::bind(f2, 5, _1), find_end_point(boost::bind(f2, 5, _1), -0.1, -20, true), find_end_point(boost::bind(f2, 5, _1), 0.1, 20, true), "v = 5");
    plot.add(boost::bind(f2, 7, _1), find_end_point(boost::bind(f2, 7, _1), -0.1, -20, true), find_end_point(boost::bind(f2, 7, _1), 0.1, 20, true), "v = 7");
    plot.add(boost::bind(f2, 10, _1), find_end_point(boost::bind(f2, 10, _1), -0.1, 20, false), find_end_point(boost::bind(f2, 10, _1), 0.1, 20, true), "v = 10");
    plot.plot("Bessel I", "cyl_bessel_i.svg", "x", "cyl_bessel_i(v, x)");

    f2 = boost::math::cyl_bessel_k;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), find_end_point(boost::bind(f2, 0, _1), 0.1, 10, false), 10, "v = 0");
    plot.add(boost::bind(f2, 2, _1), find_end_point(boost::bind(f2, 2, _1), 0.1, 10, false), 10, "v = 2");
    plot.add(boost::bind(f2, 5, _1), find_end_point(boost::bind(f2, 5, _1), 0.1, 10, false), 10, "v = 5");
    plot.add(boost::bind(f2, 7, _1), find_end_point(boost::bind(f2, 7, _1), 0.1, 10, false), 10, "v = 7");
    plot.add(boost::bind(f2, 10, _1), find_end_point(boost::bind(f2, 10, _1), 0.1, 10, false), 10, "v = 10");
    plot.plot("Bessel K", "cyl_bessel_k.svg", "x", "cyl_bessel_k(v, x)");

    f2u = boost::math::sph_bessel;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), 0, 20, "v = 0");
    plot.add(boost::bind(f2u, 2, _1), 0, 20, "v = 2");
    plot.add(boost::bind(f2u, 5, _1), 0, 20, "v = 5");
    plot.add(boost::bind(f2u, 7, _1), 0, 20, "v = 7");
    plot.add(boost::bind(f2u, 10, _1), 0, 20, "v = 10");
    plot.plot("Bessel j", "sph_bessel.svg", "x", "sph_bessel(v, x)");

    f2u = boost::math::sph_neumann;
    plot.clear();
    plot.add(boost::bind(f2u, 0, _1), find_end_point(boost::bind(f2u, 0, _1), 0.1, -5, true), 20, "v = 0");
    plot.add(boost::bind(f2u, 2, _1), find_end_point(boost::bind(f2u, 2, _1), 0.1, -5, true), 20, "v = 2");
    plot.add(boost::bind(f2u, 5, _1), find_end_point(boost::bind(f2u, 5, _1), 0.1, -5, true), 20, "v = 5");
    plot.add(boost::bind(f2u, 7, _1), find_end_point(boost::bind(f2u, 7, _1), 0.1, -5, true), 20, "v = 7");
    plot.add(boost::bind(f2u, 10, _1), find_end_point(boost::bind(f2u, 10, _1), 0.1, -5, true), 20, "v = 10");
    plot.plot("Bessel y", "sph_neumann.svg", "x", "sph_neumann(v, x)");

    f4 = boost::math::ellint_rj;
    plot.clear();
    plot.add(boost::bind(f4, _1, _1, _1, _1), find_end_point(boost::bind(f4, _1, _1, _1, _1), 0.1, 10, false), 4, "RJ");
    f3 = boost::math::ellint_rf;
    plot.add(boost::bind(f3, _1, _1, _1), find_end_point(boost::bind(f3, _1, _1, _1), 0.1, 10, false), 4, "RF");
    plot.plot("Elliptic Integrals", "ellint_carlson.svg", "x", "");

    f2 = boost::math::ellint_1;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.5), -0.9, 0.9, "φ=0.5");
    plot.add(boost::bind(f2, _1, 0.75), -0.9, 0.9, "φ=0.75");
    plot.add(boost::bind(f2, _1, 1.25), -0.9, 0.9, "φ=1.25");
    plot.add(boost::bind(f2, _1, boost::math::constants::pi<double>() / 2), -0.9, 0.9, "φ=π/2");
    plot.plot("Elliptic Of the First Kind", "ellint_1.svg", "k", "ellint_1(k, phi)");

    f2 = boost::math::ellint_2;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.5), -1, 1, "φ=0.5");
    plot.add(boost::bind(f2, _1, 0.75), -1, 1, "φ=0.75");
    plot.add(boost::bind(f2, _1, 1.25), -1, 1, "φ=1.25");
    plot.add(boost::bind(f2, _1, boost::math::constants::pi<double>() / 2), -1, 1, "φ=π/2");
    plot.plot("Elliptic Of the Second Kind", "ellint_2.svg", "k", "ellint_2(k, phi)");

    f3 = boost::math::ellint_3;
    plot.clear();
    plot.add(boost::bind(f3, _1, 0, 1.25), -1, 1, "n=0 φ=1.25");
    plot.add(boost::bind(f3, _1, 0.5, 1.25), -1, 1, "n=0.5 φ=1.25");
    plot.add(boost::bind(f3, _1, 0.25, boost::math::constants::pi<double>() / 2),
       find_end_point(
          boost::bind(f3, _1, 0.25, boost::math::constants::pi<double>() / 2),
          0.5, 4, false, -1),
       find_end_point(
          boost::bind(f3, _1, 0.25, boost::math::constants::pi<double>() / 2),
          -0.5, 4, true, 1), "n=0.25 φ=π/2");
    plot.add(boost::bind(f3, _1, 0.75, boost::math::constants::pi<double>() / 2),
       find_end_point(
          boost::bind(f3, _1, 0.75, boost::math::constants::pi<double>() / 2),
          0.5, 4, false, -1),
       find_end_point(
          boost::bind(f3, _1, 0.75, boost::math::constants::pi<double>() / 2),
          -0.5, 4, true, 1), "n=0.75 φ=π/2");
    plot.plot("Elliptic Of the Third Kind", "ellint_3.svg", "k", "ellint_3(k, n, phi)");

    f2 = boost::math::jacobi_sn;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic sn", "jacobi_sn.svg", "k", "jacobi_sn(k, u)");

    f2 = boost::math::jacobi_cn;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic cn", "jacobi_cn.svg", "k", "jacobi_cn(k, u)");

    f2 = boost::math::jacobi_dn;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic dn", "jacobi_dn.svg", "k", "jacobi_dn(k, u)");

    f2 = boost::math::jacobi_cd;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic cd", "jacobi_cd.svg", "k", "jacobi_cd(k, u)");

    f2 = boost::math::jacobi_cs;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), 0.1, 3, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), 0.1, 3, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), 0.1, 3, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), 0.1, 3, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), 0.1, 3, "k=1");
    plot.plot("Jacobi Elliptic cs", "jacobi_cs.svg", "k", "jacobi_cs(k, u)");

    f2 = boost::math::jacobi_dc;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -10, 10, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -10, 10, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -10, 10, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -10, 10, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -10, 10, "k=1");
    plot.plot("Jacobi Elliptic dc", "jacobi_dc.svg", "k", "jacobi_dc(k, u)");

    f2 = boost::math::jacobi_ds;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), 0.1, 3, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), 0.1, 3, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), 0.1, 3, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), 0.1, 3, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), 0.1, 3, "k=1");
    plot.plot("Jacobi Elliptic ds", "jacobi_ds.svg", "k", "jacobi_ds(k, u)");

    f2 = boost::math::jacobi_nc;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -5, 5, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -5, 5, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -5, 5, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -5, 5, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -5, 5, "k=1");
    plot.plot("Jacobi Elliptic nc", "jacobi_nc.svg", "k", "jacobi_nc(k, u)");

    f2 = boost::math::jacobi_ns;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), 0.1, 4, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), 0.1, 4, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), 0.1, 4, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), 0.1, 4, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), 0.1, 4, "k=1");
    plot.plot("Jacobi Elliptic ns", "jacobi_ns.svg", "k", "jacobi_ns(k, u)");

    f2 = boost::math::jacobi_nd;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -2, 2, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -2, 2, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -2, 2, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -2, 2, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -2, 2, "k=1");
    plot.plot("Jacobi Elliptic nd", "jacobi_nd.svg", "k", "jacobi_nd(k, u)");

    f2 = boost::math::jacobi_sc;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -5, 5, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -5, 5, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -5, 5, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -5, 5, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -5, 5, "k=1");
    plot.plot("Jacobi Elliptic sc", "jacobi_sc.svg", "k", "jacobi_sc(k, u)");

    f2 = boost::math::jacobi_sd;
    plot.clear();
    plot.add(boost::bind(f2, 0, _1), -2.5, 2.5, "k=0");
    plot.add(boost::bind(f2, 0.5, _1), -2.5, 2.5, "k=0.5");
    plot.add(boost::bind(f2, 0.75, _1), -2.5, 2.5, "k=0.75");
    plot.add(boost::bind(f2, 0.95, _1), -2.5, 2.5, "k=0.95");
    plot.add(boost::bind(f2, 1, _1), -2.5, 2.5, "k=1");
    plot.plot("Jacobi Elliptic sd", "jacobi_sd.svg", "k", "jacobi_sd(k, u)");

    plot.clear();
    f2 = boost::math::jacobi_theta1;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2081");
    f2 = boost::math::jacobi_theta2;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2082");
    f2 = boost::math::jacobi_theta3;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2083");
    f2 = boost::math::jacobi_theta4;
    plot.add(boost::bind(f2, _1, 0.15), 0.0, boost::math::constants::two_pi<double>(), "\u03B8\u2084");
    plot.plot("Jacobi Theta Functions", "jacobi_theta.svg", "x", "jacobi_theta(x, q=0.15)");

    f2 = boost::math::jacobi_theta1;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2081", "jacobi_theta1.svg", "x", "jacobi_theta1(x, q)");

    f2 = boost::math::jacobi_theta2;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2082", "jacobi_theta2.svg", "x", "jacobi_theta2(x, q)");

    f2 = boost::math::jacobi_theta3;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2083", "jacobi_theta3.svg", "x", "jacobi_theta3(x, q)");

    f2 = boost::math::jacobi_theta4;
    plot.clear();
    plot.add(boost::bind(f2, _1, 0.05), 0.0, boost::math::constants::two_pi<double>(), "q=0.05");
    plot.add(boost::bind(f2, _1, 0.5), 0.0, boost::math::constants::two_pi<double>(), "q=0.5");
    plot.add(boost::bind(f2, _1, 0.7), 0.0, boost::math::constants::two_pi<double>(), "q=0.7");
    plot.add(boost::bind(f2, _1, 0.9), 0.0, boost::math::constants::two_pi<double>(), "q=0.9");
    plot.plot("Jacobi Theta Function \u03B8\u2084", "jacobi_theta4.svg", "x", "jacobi_theta4(x, q)");

    f = boost::math::airy_ai;
    plot.clear();
    plot.add(f, -20, 20, "");
    plot.plot("Ai", "airy_ai.svg", "z", "airy_ai(z)");

    f = boost::math::airy_bi;
    plot.clear();
    plot.add(f, -20, 3, "");
    plot.plot("Bi", "airy_bi.svg", "z", "airy_bi(z)");

    f = boost::math::airy_ai_prime;
    plot.clear();
    plot.add(f, -20, 20, "");
    plot.plot("Ai'", "airy_aip.svg", "z", "airy_ai_prime(z)");

    f = boost::math::airy_bi_prime;
    plot.clear();
    plot.add(f, -20, 3, "");
    plot.plot("Bi'", "airy_bip.svg", "z", "airy_bi_prime(z)");

    f = boost::math::trigamma;
    max_val = 30;
    plot.clear();
    plot.add(f, find_end_point(f, 0.1, max_val, false), 5, "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -1), find_end_point(f, -0.1, max_val, true), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -2), find_end_point(f, -0.1, max_val, true, -1), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -3), find_end_point(f, -0.1, max_val, true, -2), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -4), find_end_point(f, -0.1, max_val, true, -3), "");
    plot.add(f, find_end_point(f, 0.1, max_val, false, -5), find_end_point(f, -0.1, max_val, true, -4), "");
    plot.plot("Trigamma", "trigamma.svg", "x", "trigamma(x)");

    f2i = boost::math::polygamma;
    max_val = -50;
    plot.clear();
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true), 5, "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -1), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -2), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -1), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -3), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -2), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -4), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -3), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -5), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -4), "");
    plot.add(boost::bind(f2i, 2, _1), find_end_point(boost::bind(f2i, 2, _1), 0.1, max_val, true, -6), find_end_point(boost::bind(f2i, 2, _1), -0.1, -max_val, true, -5), "");
    plot.plot("Polygamma", "polygamma2.svg", "x", "polygamma(2, x)");

    max_val = 800;
    plot.clear();
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false), 5, "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -1), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -2), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -1), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -3), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -2), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -4), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -3), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -5), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -4), "");
    plot.add(boost::bind(f2i, 3, _1), find_end_point(boost::bind(f2i, 3, _1), 0.1, max_val, false, -6), find_end_point(boost::bind(f2i, 3, _1), -0.1, max_val, true, -5), "");
    plot.plot("Polygamma", "polygamma3.svg", "x", "polygamma(3, x)");

  }
  catch (const std::exception& ex)
  {
    std::cout << ex.what() << std::endl;
  }
   return 0;
}


/** Copyright (c) 2011, Edgar Solomonik, all rights reserved.
  * \addtogroup benchmarks
  * @{
  * \addtogroup bench_redistributions
  * @{
  * \brief Benchmarks arbitrary NS redistribution
  */

//#include <boost/math/distributions/normal.hpp>
//
//boost::math::normal dist(0.0, 1.0);
//
//// 95% of distribution is below q:
//double q = quantile(dist, 0.95);

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <math.h>
#include <assert.h>
#include <algorithm>
#include <ctf.hpp>
#include <iostream>
#include <fstream>
#include "../src/shared/util.h"

using namespace CTF;

void bench_redistribution(int          niter,
                          World &      dw,
                          int          order,
                          int const *  lens,
                          char const * idx,
                          int          prl1_ord,
                          int const *  prl1_lens,
                          char const * prl1_idx,
                          int          prl2_ord,
                          int const *  prl2_lens,
                          char const * prl2_idx,
                          int          blk1_ord,
                          int const *  blk1_lens,
                          char const * blk1_idx,
                          int          blk2_ord,
                          int const *  blk2_lens,
                          char const * blk2_idx){

  int rank, num_pes;

  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &num_pes);

  int sym[order];
  int64_t N = 1;
  for (int i=0; i<order; i++){
    N*=lens[i];
    sym[i] = NS;
  }

  Partition prl1(prl1_ord, prl1_lens);
  Partition prl2(prl2_ord, prl2_lens);
  Partition blk1(blk1_ord, blk1_lens);
  Partition blk2(blk2_ord, blk2_lens);

  Tensor<> A(order, lens, sym, dw, idx, prl1[prl1_idx], blk1[blk1_idx], "A", 1);

  A.fill_random(-.5, .5);

  double t = 0.0;
  double t_min;
  double t_max;

  double btime;
  MPI_Barrier(MPI_COMM_WORLD);
  btime = MPI_Wtime();
  MPI_Barrier(MPI_COMM_WORLD);
  btime -= MPI_Wtime();

  double * data_ref = A.read(idx, prl2[prl2_idx], blk2[blk2_idx]);

#ifdef USE_FOMPI
  int N_DGTOG = 6;
#else
  int N_DGTOG = 5;
#endif

  std::ofstream f;
  if (rank == 0){
    char fname[1000];
    sprintf(fname, "bench_redist.p%d.o%d.N%d.pst-%s.vst-%s.ped-%s.ved-%s.dat", num_pes, order, lens[0], prl1_idx, blk1_idx, prl2_idx, blk2_idx);
    f.open(fname);
  }

  std::vector<double> times[N_DGTOG];
  for (int D=0; D<N_DGTOG; D++){
    DGTOG_SWITCH = D;
    char const * str_name;
    switch (D){
      case 0:
        str_name = "NAIVE";
        break;
      case 1:
        str_name = "ROR";
        break;
      case 2:
        str_name = "ROR_ISR";
        break;
      case 3:
        str_name = "ROR_PUT";
        break;
      case 4:
        str_name = "ROR_ISR_ANY";
        break;
      case 5:
        str_name = "ROR_PUT_ANY";
        break;
    }
    if (rank == 0) printf("Testing redistribution via kernel %s\n", str_name);

    double * data = A.read(idx, prl2[prl2_idx], blk2[blk2_idx]);
    int pass = 1;
    for (int64_t j=0; j<N/num_pes; j++){
      if (data[j] != data_ref[j]){
        pass = 0;
        printf("[%d] Incorrect! data[%ld] = %lf instead of %lf\n",rank,j, data[j],data_ref[j]);
      }
    }
    free(data);
    MPI_Allreduce(MPI_IN_PLACE, &pass, 1, MPI_INT, MPI_MIN, MPI_COMM_WORLD);
    if (pass){
      if (rank == 0) printf("Correctness test passed.\n");
      MPI_Barrier(MPI_COMM_WORLD);
      Timer_epoch te(str_name);
      te.begin();
      for (int i=0; i<niter; i++){
        double t_st = MPI_Wtime();
        double * data = A.read(idx, prl2[prl2_idx], blk2[blk2_idx]);
        MPI_Barrier(MPI_COMM_WORLD);
        times[D].push_back(MPI_Wtime() - t_st - btime);
        free(data);
      }
      te.end();
      std::sort(&times[D][0], &times[D][0]+niter);
      if (rank == 0){
        printf("Performed %d redistributions via kernel %s sec/iter: median = %lf (median effective end-to-end bandwidth, N/(t*p) = %lf GB/s), range = [%lf, %lf]\n",
                niter, str_name, times[D][niter/2], 1.E-9*N*sizeof(double)/(num_pes*times[D][niter/2]), times[D][0], times[D][niter-1]);
        f << str_name << " ";
        for (int i=0; i<niter; i++){
          f << times[D][i] << " ";
        }
        f << "\n";
      }
    }
  }
  if (rank == 0){
    printf("Data line kernel * [min, median max]:\n");
    for (int D=0; D<N_DGTOG; D++){
      printf("%lf %lf %lf ", times[D][0], times[D][niter/2], times[D][niter-1]);
    }
    printf("\n");
  }
  if (rank == 0){
    f.close();
  }

  free(data_ref);
/*  if (rank == 0)
    printf("Performed %d redistributions in %lf time/iter %lf mem GB/sec\n",
            niter, (end_time-st_time)/niter, (2*N*1.E-9/((end_time-st_time)/niter))/num_pes);*/
}

char* getCmdOption(char ** begin,
                   char ** end,
                   const   std::string & option){
  char ** itr = std::find(begin, end, option);
  if (itr != end && ++itr != end){
    return *itr;
  }
  return 0;
}

int main(int argc, char ** argv){
  int rank, np, niter, n, phase;
  int const in_num = argc;
  char ** input_str = argv;
  char const * idx;
  char const * prl1_idx;
  char const * prl2_idx;
  char const * blk1_idx;
  char const * blk2_idx;
  int64_t prl1, prl2, blk1, blk2;
  int order;
  int prl1_ord;
  int prl2_ord;
  int blk1_ord;
  int blk2_ord;
  int * lens;
  int * prl1_lens;
  int * prl2_lens;
  int * blk1_lens;
  int * blk2_lens;

  MPI_Init(&argc, &argv);
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  MPI_Comm_size(MPI_COMM_WORLD, &np);

  if (getCmdOption(input_str, input_str+in_num, "-n")){
    n = atoi(getCmdOption(input_str, input_str+in_num, "-n"));
    if (n < 0) n = 4;
  } else n = 4;

  if (getCmdOption(input_str, input_str+in_num, "-phase")){
    phase = atoi(getCmdOption(input_str, input_str+in_num, "-phase"));
    if (phase < 0) phase = 10;
  } else phase = 10;

  if (getCmdOption(input_str, input_str+in_num, "-prl1")){
    prl1 = atoi(getCmdOption(input_str, input_str+in_num, "-prl1"));
    if (prl1 < 0) prl1 = np;
  } else prl1 = np;

  if (getCmdOption(input_str, input_str+in_num, "-prl2")){
    prl2 = atoi(getCmdOption(input_str, input_str+in_num, "-prl2"));
    if (prl2 < 0) prl2 = np;
  } else prl2 = np;

  if (getCmdOption(input_str, input_str+in_num, "-blk1")){
    blk1 = atoi(getCmdOption(input_str, input_str+in_num, "-blk1"));
    if (blk1 < 0) blk1 = np;
  } else blk1 = np;

  if (getCmdOption(input_str, input_str+in_num, "-blk2")){
    blk2 = atoi(getCmdOption(input_str, input_str+in_num, "-blk2"));
    if (blk2 < 0) blk2 = np;
  } else blk2 = np;

  if (getCmdOption(input_str, input_str+in_num, "-niter")){
    niter = atoi(getCmdOption(input_str, input_str+in_num, "-niter"));
    if (niter < 0) niter = 3;
  } else niter = 3;

  if (getCmdOption(input_str, input_str+in_num, "-idx")){
    idx = getCmdOption(input_str, input_str+in_num, "-idx");
  } else idx = "ij";
  if (getCmdOption(input_str, input_str+in_num, "-prl1_idx")){
    prl1_idx = getCmdOption(input_str, input_str+in_num, "-prl1_idx");
  } else prl1_idx = "i";
  if (getCmdOption(input_str, input_str+in_num, "-prl2_idx")){
    prl2_idx = getCmdOption(input_str, input_str+in_num, "-prl2_idx");
  } else prl2_idx = "j";
  if (getCmdOption(input_str, input_str+in_num, "-blk1_idx")){
    blk1_idx = getCmdOption(input_str, input_str+in_num, "-blk1_idx");
  } else blk1_idx = "";
  if (getCmdOption(input_str, input_str+in_num, "-blk2_idx")){
    blk2_idx = getCmdOption(input_str, input_str+in_num, "-blk2_idx");
  } else blk2_idx = "";

  order = strlen(idx);
  prl1_ord = strlen(prl1_idx);
  prl2_ord = strlen(prl2_idx);
  blk1_ord = strlen(blk1_idx);
  blk2_ord = strlen(blk2_idx);

  if (rank==0){
    printf("Redistributing order %d tensor with all dims %d and idx %s from order %d proc grid with dims %ld and idx %s, to order %d proc grid with dims %ld and idx %s\n", order, n, idx, prl1_ord, prl1, prl1_idx, prl2_ord, prl2, prl2_idx);
    printf("Initial blocking order %d dims %ld and idx %s, to final blocking order %d dims %ld and idx %s\n", blk1_ord, blk1, blk1_idx, blk2_ord, blk2, blk2_idx);
  }

  lens = (int*)malloc(order*sizeof(int));
  for (int i=0; i<order; i++){
    lens[i] = n;
  }
  prl1_lens = (int*)malloc(prl1_ord*sizeof(int));
  for (int i=0; i<prl1_ord; i++){
    prl1_lens[prl1_ord-i-1] = prl1%phase;
    prl1 = prl1/phase;
  }
  if (rank == 0){
    printf("start topology:");
    for (int i=0; i<prl1_ord; i++){
      printf(" %d", prl1_lens[i]);
    }
    printf("\n");
  }
  prl2_lens = (int*)malloc(prl2_ord*sizeof(int));
  for (int i=0; i<prl2_ord; i++){
    prl2_lens[prl2_ord-i-1] = prl2%phase;
    prl2 = prl2/phase;
  }
  if (rank == 0){
    printf("end topology:");
    for (int i=0; i<prl2_ord; i++){
      printf(" %d", prl2_lens[i]);
    }
    printf("\n");
  }

  blk1_lens = (int*)malloc(blk1_ord*sizeof(int));
  for (int i=0; i<blk1_ord; i++){
    blk1_lens[blk1_ord-i-1] = blk1%phase;
    blk1 = blk1/phase;
  }
  if (rank == 0){
    printf("start blocking:");
    for (int i=0; i<blk1_ord; i++){
      printf(" %d", blk1_lens[i]);
    }
    printf("\n");
  }

  blk2_lens = (int*)malloc(blk2_ord*sizeof(int));
  for (int i=0; i<blk2_ord; i++){
    blk2_lens[blk2_ord-i-1] = blk2%phase;
    blk2 = blk2/phase;
  }
  if (rank == 0){
    printf("end blocking:");
    for (int i=0; i<blk2_ord; i++){
      printf(" %d", blk2_lens[i]);
    }
    printf("\n");
  }

  {
    CTF_World dw(argc, argv);
    bench_redistribution(niter, dw, order, lens, idx,
                         prl1_ord, prl1_lens, prl1_idx,
                         prl2_ord, prl2_lens, prl2_idx,
                         blk1_ord, blk1_lens, blk1_idx,
                         blk2_ord, blk2_lens, blk2_idx);
  }

  MPI_Finalize();
  return 0;
}
/**
 * @}
 * @}
 */


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|  Phycas: Python software for phylogenetic analysis                          |
|  Copyright (C) 2006 Mark T. Holder, Paul O. Lewis and David L. Swofford     |
|                                                                             |
|  This program is free software; you can redistribute it and/or modify       |
|  it under the terms of the GNU General Public License as published by       |
|  the Free Software Foundation; either version 2 of the License, or          |
|  (at your option) any later version.                                        |
|                                                                             |
|  This program is distributed in the hope that it will be useful,            |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of             |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              |
|  GNU General Public License for more details.                               |
|                                                                             |
|  You should have received a copy of the GNU General Public License along    |
|  with this program; if not, write to the Free Software Foundation, Inc.,    |
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                |
\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

#ifndef SLICE_SAMPLER_HPP
#define SLICE_SAMPLER_HPP

#include <cstdlib>
#include <climits>
#include <cfloat>
#include "basic_lot.hpp"
#include "probability_distribution.hpp"
#include "xprobdist.hpp"
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#if defined(PYTHON_ONLY)
#	include <boost/python/call_method.hpp>
#endif
typedef std::pair<double, double> ParamAndLnProb;
typedef std::pair<double, double> SliceInterval;

namespace phycas
{

struct SliceStats
	{
	SliceStats() : nsamples(0), value(0.0), width(0.0), diff(0.0), failed(0.0), evals(0.0) {}

	unsigned nsamples;
	double value;
	double width;
	double diff;
	double failed;
	double evals;
	};

// Note: AdHocDensity is defined in probablity_distribution.hpp
typedef boost::shared_ptr<AdHocDensity> FuncToSampleShPtr;

#undef WEAK_FUNCTOSAMPLE
#if defined(WEAK_FUNCTOSAMPLE)
typedef boost::weak_ptr<AdHocDensity> FuncToSampleWkPtr;
#endif

/*----------------------------------------------------------------------------------------------------------------------
|	Implements the univariate slice sampler described in Neal, Radford M. 2003. Slice sampling. Annals of Statistics
|	31:705-741.
*/
class SliceSampler
	{
	public:
								SliceSampler();
#if defined(WEAK_FUNCTOSAMPLE)
								SliceSampler(LotShPtr rnd, FuncToSampleWkPtr f);
#else
								SliceSampler(LotShPtr rnd, FuncToSampleShPtr f);
#endif
		virtual					~SliceSampler();

		double					Sample();
		std::vector<double>					DebugSample();

		double					OverrelaxedSample();
		std::vector<double>					DebugOverrelaxedSample();

#if defined(WEAK_FUNCTOSAMPLE)
		void					AttachFunc(FuncToSampleWkPtr f);
#else
		void					AttachFunc(FuncToSampleShPtr f);
#endif
		void					AttachRandomNumberGenerator(LotShPtr rnd);

		void					SetXValue(double x);
		void					SetMaxUnits(unsigned umax);

		void					SetSliceUnitWidth(double uwidth);
		double					GetSliceUnitWidth() const;

		double					AdaptSimple(double multiplier);
		double					AdaptNeal(double multiplier);
		void					AdaptYConditional(double from_ends, double multiplier);
		SliceInterval			FindSliceInterval(ParamAndLnProb x0, const double ln_y0, double tol, unsigned max_steps = UINT_MAX) const;
		double					CalcW(double y0) const;

		double					GetMode() const;
		double					GetLnDensityAtMode() const;

		double					GetLastSampledXValue();
		double					GetLastSampledYValue();
		double					GetSliceYValue();

		double					GetLnZero() const {return -DBL_MAX;}
        void                    UseDoublingMethod(bool d);

		// For diagnosing inefficiency
		//
		double					GetMinX();
		double					GetMaxX();
		double					GetOrigLeftEdgeOfSlice();
		double					GetOrigRightEdgeOfSlice();
		double					GetLeftEdgeOfSlice();
		double					GetRightEdgeOfSlice();
		unsigned				GetNumFuncEvals();
		unsigned				GetNumFailedSamples();
		unsigned				GetNumUnitsRequired();
		unsigned				GetNumSamples();

		SliceStats				SummarizeDiagnostics();
		void					ResetDiagnostics();

	protected:

		void					Init();
		ParamAndLnProb			GetNextSample(const ParamAndLnProb);
		ParamAndLnProb			GetNextOverrelaxedSample(const ParamAndLnProb);
		SliceInterval			BisectionSqueeze(double left, double lnf_left, double right, double lnf_right, const double ln_y0, double tol, unsigned max_steps) const;

#if defined(WEAK_FUNCTOSAMPLE)
		FuncToSampleWkPtr		func;				/**< is a functor representing the probability distribution to be sampled */
#else
		FuncToSampleShPtr		func;				/**< is a functor representing the probability distribution to be sampled */
#endif
		LotShPtr				r;					/**< is the random number generator */
		ParamAndLnProb			lastSampled;		/**< most recent valid sample and its relative density */
		double					w;					/**< unit size for interval I */
		unsigned				maxUnits;			/**< maximum number of units of size w to use for interval I (set to UINT_MAX for unlimited) */

		// These quantities are used for diagnostic purposes in GetNextSample()
		//
		double					orig_left_edge;		/**< x coordinate of left edge of most recent slice (before cropping) */
		double					orig_right_edge;	/**< x coordinate of right edge of most recent slice (before cropping) */
		double					left_edge;			/**< x coordinate of left edge of most recent slice (after cropping) */
		double					right_edge;			/**< x coordinate of right edge of most recent slice (after cropping) */
		double					ln_y;				/**< log of f(x) representing most recent slice */

		// For diagnosing inefficiency, all reset with call to ResetDiagnostics()
		//
		double					min_x;				/**< minimum x value tried */
		double					max_x;				/**< maximum x value tried */
		double					sumValues;			/**< sum of last `num_samples' sampled values */
		double					sumWidths;			/**< sum of last `num_samples' cropped slice widths (where slice width = right_edge - left_edge) */
		double					sumDiffs;			/**< sum of last `num_samples' differences between successively sampled values */
		unsigned				func_evals;			/**< counts number of function evaluations */
		unsigned				failed_samples;		/**< counts number of samples that failed because they were not in the slice */
		unsigned				realized_m;			/**< counts number of units, each of size w, that were required to bracket the slice */
		unsigned				num_samples;		/**< counts number of times GetNextSample called */
		std::vector<ParamAndLnProb>	most_recent;	/**< vector of all (x, lnx) pairs evaluated in most recent sampling effort */

		// These are needed for y-conditional adaptation
		bool					ycond_on;			/**< if true, w chosen anew for each sample based on y-coordinate of slice */
		double					ycond_a;
		double					ycond_b;
		double					ycond_multiplier;
		ParamAndLnProb			mode;

		// These are for overrelaxed sampling
		unsigned				num_overrelaxed_samples;	/**< counts number of times GetNextOverrelaxedSample called */

        bool                    doubling;           /**< if true, doubling method will be used to increase slice interval */
	};

typedef boost::shared_ptr<SliceSampler> SliceSamplerShPtr;

} // namespace phycas

#endif