#ifndef STAN_MATH_PRIM_SCAL_FUN_TGAMMA_HPP
#define STAN_MATH_PRIM_SCAL_FUN_TGAMMA_HPP

#include <boost/math/special_functions/gamma.hpp>

namespace stan {
  namespace math {

    /**
     * Return the gamma function applied to the specified argument.
     *
     * @param x Argument.
     * @return The gamma function applied to argument.
     */
    inline double tgamma(double x) {
      return boost::math::tgamma(x);
    }

  }
}
#endif


#include <fstream>
#include <sstream>

#include <boost/algorithm/string.hpp>

#include "ImgProcessor.h"
#include "integratedDataStructures.h"

integratedDataStructures::morphStructElement2D::morphStructElement2D(shape selectedEleShape, int length) : structEleShape(selectedEleShape), xLength(length), yLength(length)
{
	if (this->structEleShape == morphStructElement2D::disk)
	{
		this->radius = length / 2;
		this->structElePtr = new unsigned char[(this->radius * 2 + 1) * (this->radius * 2 + 1)];
		if (length == 3)
		{
			this->structElePtr[0] = 0; this->structElePtr[1] = 1; this->structElePtr[2] = 0;
			this->structElePtr[3] = 1; this->structElePtr[4] = 1; this->structElePtr[5] = 1;
			this->structElePtr[6] = 0; this->structElePtr[7] = 1; this->structElePtr[8] = 0;
		}
		else
		{
			int count = 0;
			for (int j = -this->radius; j <= this->radius; ++j)
			{
				for (int i = -this->radius; i <= this->radius; ++i)
				{
					int rounded_dist = int(roundf(sqrt(float(i * i) + float(j * j))));
					if (rounded_dist > this->radius) this->structElePtr[(this->radius * 2 + 1) * (j + this->radius) + (i + this->radius)] = 0;
					else this->structElePtr[(this->radius * 2 + 1) * (j + this->radius) + (i + this->radius)] = 1;

					++count;
				}
			}
		}
	}
}

integratedDataStructures::morphStructElement2D::morphStructElement2D(string shape, int length) : eleShape(shape), xLength(length), yLength(length)
{
	if (!this->eleShape.compare("circle"))
	{
		this->radius = length / 2;
		this->structElePtr = new unsigned char[(this->radius * 2 + 1) * (this->radius * 2 + 1)];
		if (length == 3)
		{
			this->structElePtr[0] = 0; this->structElePtr[1] = 1; this->structElePtr[2] = 0;
			this->structElePtr[3] = 1; this->structElePtr[4] = 1; this->structElePtr[5] = 1;
			this->structElePtr[6] = 0; this->structElePtr[7] = 1; this->structElePtr[8] = 0;
		}
		else
		{
			int count = 0;
			for (int j = -this->radius; j <= this->radius; ++j)
			{
				for (int i = -this->radius; i <= this->radius; ++i)
				{
					int rounded_dist = int(roundf(sqrt(float(i * i) + float(j * j))));
					if (rounded_dist > this->radius) this->structElePtr[(this->radius * 2 + 1) * (j + this->radius) + (i + this->radius)] = 0;
					else this->structElePtr[(this->radius * 2 + 1) * (j + this->radius) + (i + this->radius)] = 1;

					++count;
				}
			}
		}
	}
}

integratedDataStructures::morphStructElement2D::~morphStructElement2D()
{
	if (this->structElePtr != nullptr) delete[] this->structElePtr;
}

void integratedDataStructures::connectedComponent::getConnCompSurface()
{
	boost::container::flat_set<vector<int>> topSliceSet;
	for (set<vector<int>>::iterator it = this->coordSets.begin()->second.begin(); it != this->coordSets.begin()->second.end(); ++it)
	{
		vector<int> newVec = { it->at(0), it->at(1), it->at(2) };
		topSliceSet.insert(newVec);
	}
	this->surfaceCoordSets.insert(pair<int, boost::container::flat_set<vector<int>>>(this->coordSets.begin()->first, topSliceSet));

	boost::container::flat_set<vector<int>> bottomSliceSet;
	for (set<vector<int>>::iterator it = this->coordSets.rbegin()->second.begin(); it != this->coordSets.rbegin()->second.end(); ++it)
	{
		vector<int> newVec = { it->at(0), it->at(1), it->at(2) };
		topSliceSet.insert(newVec);
	}
	this->surfaceCoordSets.insert(pair<int, boost::container::flat_set<vector<int>>>(this->coordSets.rbegin()->first, topSliceSet));

	if (this->coordSets.size() > 2)
	{
		for (map<int, set<vector<int>>>::iterator it = ++this->coordSets.begin(); it != --this->coordSets.end(); ++it)
		{
			boost::container::flat_set<vector<int>> currSliceSurf;
			for (set<vector<int>>::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2)
			{
				vector<int> vec1 = { it2->at(0) - 1, it2->at(1), it2->at(2) };
				vector<int> vec2 = { it2->at(0) + 1, it2->at(1), it2->at(2) };
				vector<int> vec3 = { it2->at(0), it2->at(1) - 1, it2->at(2) };
				vector<int> vec4 = { it2->at(0), it2->at(1) + 1, it2->at(2) };
				vector<int> vec5 = { it2->at(0), it2->at(1), it2->at(2) - 1 };
				vector<int> vec6 = { it2->at(0), it2->at(1), it2->at(2) + 1 };
				if (this->coordSets.at(it->first - 1).find(vec5) == this->coordSets.at(it->first - 1).end() ||
					this->coordSets.at(it->first + 1).find(vec6) == this->coordSets.at(it->first + 1).end() ||
					this->coordSets.at(it->first).find(vec1) == this->coordSets.at(it->first).end() ||
					this->coordSets.at(it->first).find(vec2) == this->coordSets.at(it->first).end() ||
					this->coordSets.at(it->first).find(vec3) == this->coordSets.at(it->first).end() ||
					this->coordSets.at(it->first).find(vec4) == this->coordSets.at(it->first).end()) currSliceSurf.insert(*it2);
			}
			this->surfaceCoordSets.insert(pair<int, boost::container::flat_set<vector<int>>>(it->first, currSliceSurf));
		}
	}
}

void integratedDataStructures::connectedComponent::getXYZprojections()
{
	for (boost::container::flat_map<int, boost::container::flat_set<vector<int>>>::iterator it = this->surfaceCoordSets.begin(); it != this->surfaceCoordSets.end(); ++it)
	{
		for (boost::container::flat_set<vector<int>>::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2)
		{
			vector<int> projectX(2);
			projectX[0] = it2->at(1);
			projectX[1] = it2->at(2);

			vector<int> projectY(2);
			projectY[0] = it2->at(0);
			projectY[1] = it2->at(2);

			vector<int> projectZ(2);
			projectZ[0] = it2->at(0);
			projectZ[1] = it2->at(1);

			this->yzProjection.insert(projectX);
			this->xzProjection.insert(projectY);
			this->xyProjection.insert(projectZ);
		}
	}
}

void integratedDataStructures::brainRegion::writeBrainRegion_file(string saveFileName)
{
	if (this->regionBodies.empty())
	{
		cout << "No connected components found. Do nothing and return." << endl;
		return;
	}

	ofstream outputFile(saveFileName);
	vector<string> splittedString;
	boost::algorithm::split(splittedString, saveFileName, boost::is_any_of("."));
	if (splittedString.back().compare("brg"))
	{
		cout << "Not supported file extension. Do nothing and return." << endl;
		return;
	}
	outputFile << "# " << this->name << endl;

	for (vector<connectedComponent>::iterator it = this->regionBodies.begin(); it != this->regionBodies.end(); ++it)
	{
		if (it->coordSets.empty())
		{
			cout << "No data stored in this connected componenet. Move to the next one." << endl;
			continue;
		}

		if (it->surfaceCoordSets.empty())
		{
			it->getConnCompSurface();
			it->getXYZprojections();
		}

		outputFile << "# Group " << int(it - this->regionBodies.begin()) + 1 << endl;
		for (boost::container::flat_map<int, boost::container::flat_set<vector<int>>>::iterator sliceIt = it->surfaceCoordSets.begin(); sliceIt != it->surfaceCoordSets.end(); ++sliceIt)
		{
			outputFile << sliceIt->first << " ";
			for (boost::container::flat_set<vector<int>>::iterator nodeIt = sliceIt->second.begin(); nodeIt != sliceIt->second.end(); ++nodeIt)
				outputFile << nodeIt->at(0) << " " << nodeIt->at(1) << " ";
			outputFile << endl;
		}
		outputFile << "---" << endl;
	}
}

void integratedDataStructures::brainRegion::readBrainRegion_file(string inputFileName)
{
	ifstream inputFile(inputFileName);
	if (!inputFile.good())
	{
		cout << "Cannot open the file " << inputFileName << ". The file either doesn't exist or is corrupted." << endl;
		return;
	}

	vector<string> splittedinputName1;
	boost::algorithm::split(splittedinputName1, inputFileName, boost::is_any_of("."));
	if (splittedinputName1.back().compare("brg"))
	{
		cout << "Not supported file extension. Do nothing and return." << endl;
		return;
	}
	vector<string> splittedinputName2;
	boost::algorithm::split(splittedinputName2, *(splittedinputName1.end() - 2), boost::is_any_of("/"));
	this->name = splittedinputName2.back();

	if (inputFile.is_open())
	{
		string line;
		string buffer;
		vector<string> splittedLine;
		int xMin = 100000, yMin = 100000;
		int xMax = 0, yMax = 0;
		while (getline(inputFile, line))
		{
			splittedLine.clear();
			stringstream ss(line);
			while (ss >> buffer) splittedLine.push_back(buffer);

			if (!splittedLine.begin()->compare("---"))
			{
				this->regionBodies.back().xMax = xMax;
				this->regionBodies.back().xMin = xMin;
				this->regionBodies.back().yMax = yMax;
				this->regionBodies.back().yMin = yMin;
				this->regionBodies.back().zMax = (this->regionBodies.back().surfaceCoordSets.end() - 1)->first;
				this->regionBodies.back().zMin = this->regionBodies.back().surfaceCoordSets.begin()->first;
				continue;
			}

			if (!splittedLine.begin()->compare("#"))
			{
				if (!(splittedLine.begin() + 1)->compare("Group"))
				{
					connectedComponent newComponent;
					newComponent.islandNum = stoi(*(splittedLine.begin() + 2));
					this->regionBodies.push_back(newComponent);
					continue;
				}
				continue;
			}

			boost::container::flat_set<vector<int>> currSliceNodeSet;
			for (vector<string>::iterator it = splittedLine.begin() + 1; it != splittedLine.end(); it = it + 2)
			{
				vector<int> nodeVec = { stoi(*it), stoi(*(it + 1)), stoi(*splittedLine.begin()) };
				currSliceNodeSet.insert(nodeVec);
				if (nodeVec.at(0) > xMax) xMax = nodeVec.at(0);
				if (nodeVec.at(0) < xMin) xMin = nodeVec.at(0);
				if (nodeVec.at(1) > yMax) yMax = nodeVec.at(1);
				if (nodeVec.at(1) < yMin) yMin = nodeVec.at(1);
			}
			this->regionBodies.back().surfaceCoordSets.insert(pair<int, boost::container::flat_set<vector<int>>>(stoi(*splittedLine.begin()), currSliceNodeSet));
		}
	}

	for (vector<connectedComponent>::iterator it = this->regionBodies.begin(); it != this->regionBodies.end(); ++it)
		it->getXYZprojections();
}

void integratedDataStructures::registeredImg::createBlankImg(const int imgDims[])
{
	myImg1DPtr blank1D(new unsigned char[imgDims[0] * imgDims[2] * imgDims[2]]);
	for (size_t i = 0; i < imgDims[0] * imgDims[2] * imgDims[2]; ++i) blank1D.get()[i] = 0;

	for (map<string, myImg1DPtr>::iterator it = this->slicePtrs.begin(); it != this->slicePtrs.end(); ++it)
	{
		if (it->first.length() >= 5)
		{
			if (!it->first.substr(0, 5).compare("blank"))
			{
				char blankNumChar = *(it->first.end() - 1);
				int blankNum = atoi(&blankNumChar);
				int newBlankNum = blankNum + 1;
				string newBlankNumString = "blank" + to_string(newBlankNum);

				this->slicePtrs.insert({ newBlankNumString, blank1D });
				return;
			}
		}
	}

	this->slicePtrs.insert({ "blank1", blank1D });
}

void integratedDataStructures::registeredImg::getHistMap_no0()
{
	if (slicePtrs.empty())
	{
		cerr << "No existing images. Do nothing and return." << endl;
		return;
	}

	int sliceDims[3];
	sliceDims[0] = this->dims[0];
	sliceDims[1] = this->dims[1];
	sliceDims[2] = 1;

	for (map<string, myImg1DPtr>::iterator it = this->slicePtrs.begin(); it != this->slicePtrs.end(); ++it)
	{
		map<int, size_t> currSliceHistMap = ImgProcessor::histQuickList(it->second.get(), sliceDims);
		for (map<int, size_t>::iterator cummIt = currSliceHistMap.begin(); cummIt != currSliceHistMap.end(); ++cummIt)
		{
			if (!this->histMap.insert({ cummIt->first, cummIt->second }).second)
				this->histMap[cummIt->first] = this->histMap[cummIt->first] = cummIt->second;
		}
	}
}

void integratedDataStructures::registeredImg::getHistMap_no0_log10()
{
	if (slicePtrs.empty())
	{
		cerr << "No existing images. Do nothing and return." << endl;
		return;
	}

	if (!this->histMap.empty())
	{
		for (map<int, size_t>::iterator it = this->histMap.begin(); it != this->histMap.end(); ++it)
		{
			double countLog10 = log10(float(it->second));
			this->histMap_log10.insert({ it->first, countLog10 });
		}
	}
	else
	{
		this->getHistMap_no0();
		this->getHistMap_no0_log10();
	}
}


/**
 * @file
 * @copyright This code is licensed under the 3-clause BSD license.\n
 *            Copyright ETH Zurich, Laboratory of Physical Chemistry, Reiher Group.\n
 *            See LICENSE.txt for details.
 */

#include "CrossValidation.h"
#include "Regression/RegressionModel.h"
#include <boost/accumulators/accumulators.hpp>
#include <boost/accumulators/statistics/mean.hpp>
#include <boost/accumulators/statistics/stats.hpp>
#include <boost/accumulators/statistics/variance.hpp>
#include <boost/bind.hpp>
#include <boost/ref.hpp>
#include <random>

using namespace boost::accumulators;

namespace Scine {
namespace Utils {
namespace MachineLearning {

CrossValidation::CrossValidation(const RegressionModel& model, int k) : model_(model), k_(k) {
  if (k_ <= 1) {
    throw std::runtime_error("The value for k cannot be less than or equal to one.");
  }
}

// Returns MAE and standard deviation as a pair
std::pair<double, double> CrossValidation::evaluateRegressionModel(const Eigen::MatrixXd& featureValues,
                                                                   const Eigen::MatrixXd& targetValues) {
  if (targetValues.rows() != featureValues.rows()) {
    throw std::runtime_error("The number of data points do not match between the feature and target matrices.");
  }

  numDataPoints_ = static_cast<int>(targetValues.rows());
  if (numDataPoints_ % k_ != 0) {
    throw std::runtime_error("The number of data points has to be divisible by k. Set a different value for k.");
  }

  // Shuffle the data randomly
  shuffleData(featureValues, targetValues);

  // Set some important variables
  numDataPointsPerSubset_ = numDataPoints_ / k_;
  numFeatures_ = static_cast<int>(featureValues.cols());
  numTargets_ = static_cast<int>(targetValues.cols());

  std::vector<double> absoluteErrors(k_);

#pragma omp parallel
  {
    std::unique_ptr<RegressionModel> localModel = model_.clone();
#pragma omp for schedule(dynamic)
    for (int i = 0; i < k_; ++i) {
      performIteration(i, absoluteErrors, *localModel);
    }
  }

  // Calculate MAE and std. dev. and return the result
  return calculateStatistics(absoluteErrors);
}

void CrossValidation::shuffleData(const Eigen::MatrixXd& featureValues, const Eigen::MatrixXd& targetValues) {
  Eigen::PermutationMatrix<Eigen::Dynamic, Eigen::Dynamic> permutationMatrix(numDataPoints_);
  permutationMatrix.setIdentity();
  std::mt19937 engine(randomSeed_);
  std::shuffle(permutationMatrix.indices().data(),
               permutationMatrix.indices().data() + permutationMatrix.indices().size(), engine);
  shuffledFeatureValues_ = permutationMatrix * featureValues;
  shuffledTargetValues_ = permutationMatrix * targetValues;
}

void CrossValidation::performIteration(int index, std::vector<double>& absoluteErrors, RegressionModel& model) {
  // Create training data set
  // For features:
  Eigen::MatrixXd trainingDataFeatures = transferDataForTraining(shuffledFeatureValues_, index);
  // For targets:
  Eigen::MatrixXd trainingDataTargets = transferDataForTraining(shuffledTargetValues_, index);

  model.trainModel(trainingDataFeatures, trainingDataTargets);

  double errorSum = 0.0;
  // Iterate over the rows that are in the test set
  for (int rowIndex = index * numDataPointsPerSubset_; rowIndex < ((index + 1) * numDataPointsPerSubset_); ++rowIndex) {
    Eigen::VectorXd result = model.predict(shuffledFeatureValues_.row(rowIndex));
    // Iterate over all targets
    for (int targetIndex = 0; targetIndex < numTargets_; ++targetIndex) {
      errorSum += std::abs(result(targetIndex) - shuffledTargetValues_(rowIndex, targetIndex));
    }
  }
  absoluteErrors[index] = errorSum / (numDataPointsPerSubset_ * numTargets_);
}

std::pair<double, double> CrossValidation::calculateStatistics(const std::vector<double>& absoluteErrors) {
  accumulator_set<double, stats<tag::mean, tag::variance(lazy)>> acc;
  std::for_each(absoluteErrors.begin(), absoluteErrors.end(), boost::bind<void>(boost::ref(acc), _1));
  return {mean(acc), sqrt(variance(acc))};
}

void CrossValidation::setNumberOfSubsets(int k) {
  k_ = k;
}

void CrossValidation::setRandomSeed(int seed) {
  randomSeed_ = seed;
}

Eigen::MatrixXd CrossValidation::transferDataForTraining(const Eigen::MatrixXd& originalData, int index) const {
  Eigen::MatrixXd trainingData(shuffledFeatureValues_.rows() - numDataPointsPerSubset_, originalData.cols());
  trainingData.topRows(index * numDataPointsPerSubset_) = originalData.topRows(index * numDataPointsPerSubset_);
  trainingData.bottomRows(trainingData.rows() - index * numDataPointsPerSubset_) =
      originalData.bottomRows(trainingData.rows() - index * numDataPointsPerSubset_);
  return trainingData;
}

} // namespace MachineLearning
} // namespace Utils
} // namespace Scine


#include "RBGL.hpp"
#include <boost/pending/disjoint_sets.hpp>
#include <boost/graph/incremental_components.hpp>

extern "C"
{
	using namespace boost;
	using namespace std;

        typedef graph_traits<Graph_ud>::vertex_descriptor Vertex;
        typedef graph_traits<Graph_ud>::vertices_size_type size_type;
	typedef Vertex* Parent;
	typedef size_type* Rank;

	static vector<size_type> rrank(1);
	static vector<Vertex> parent(1);
	static disjoint_sets<Rank, Parent> ds( &rrank[0], &parent[0]);
	static bool initialized = false;

	typedef component_index<unsigned int> Components;

	typedef enum {
		E_IC_INIT_INCREMENTAL_COMPONENT,
		E_IC_INCREMENTAL_COMPONENT
	} E_IC_METHOD;

    static SEXP BGL_incr_comp_internal(SEXP num_verts_in,
                SEXP num_edges_in, SEXP R_edges_in, E_IC_METHOD method)
    {
	using namespace boost;
        int NV = INTEGER(num_verts_in)[0] ;

        Graph_ud g(num_verts_in, num_edges_in, R_edges_in);

	if ( !initialized || method == E_IC_INIT_INCREMENTAL_COMPONENT )
	{
	    rrank.clear();   rrank.resize(NV, 0);
	    parent.clear(); parent.resize(NV, 0);

  	    disjoint_sets<Rank, Parent>  ds1(&rrank[0], &parent[0]);
  	    ds = ds1;
	    initialize_incremental_components(g, ds);

	    initialized = true;
	}

	if ( method == E_IC_INCREMENTAL_COMPONENT )
	{
	    incremental_components(g, ds);
	}

	Components components(&parent[0], &parent[0] + parent.size());

	unsigned int NC = components.size();

        SEXP anslst, conn, outvec;
        PROTECT(anslst = allocVector(VECSXP,NC+1));
	PROTECT(conn = NEW_INTEGER(1));

	INTEGER(conn)[0] = NC;
	SET_VECTOR_ELT(anslst,0,conn);

	int l;
	Components::size_type k;
	Components::value_type::iterator j;

        for (k = 0; k < NC; k++ )
	{
		// count how many vertices in this component: no size()
		l = 0;
		for (j = components[k].begin(); j != components[k].end(); j++ )
			l++;
		PROTECT(outvec = allocMatrix(INTSXP, 1, l));

		l = 0;
		for (j = components[k].begin(); j != components[k].end(); j++ )
			INTEGER(outvec)[l++] = *j;

		SET_VECTOR_ELT(anslst,k+1,outvec);
	}

        UNPROTECT(NC+2);
        return(anslst);
    }

    SEXP BGL_init_incremental_components(SEXP num_verts_in,
                SEXP num_edges_in, SEXP R_edges_in)
    {
        SEXP ans = BGL_incr_comp_internal(num_verts_in,
                num_edges_in, R_edges_in,
		E_IC_INIT_INCREMENTAL_COMPONENT);
	return(ans);
    }

    SEXP BGL_incremental_components(SEXP num_verts_in,
             SEXP num_edges_in, SEXP R_edges_in)
    {
        SEXP ans = BGL_incr_comp_internal(num_verts_in,
                num_edges_in, R_edges_in,
		E_IC_INCREMENTAL_COMPONENT);
	return(ans);
    }

    SEXP BGL_same_component(SEXP num_verts_in,
		    SEXP num_edges_in, SEXP R_edges_in,
		    SEXP vert_1, SEXP vert_2)
    {
        if ( !initialized )
           error("graph is not prepared to handle incremental components.");

        Graph_ud g(num_verts_in, num_edges_in, R_edges_in);

        int NV = INTEGER(num_verts_in)[0];
	int v1 = INTEGER(vert_1)[0];
	int v2 = INTEGER(vert_2)[0];

	bool r = FALSE;

	if ( 0 <= v1 && v1 < NV && 0 <= v2 && v2 < NV )
	    r = same_component(vertex(v1, g), vertex(v2, g), ds);

	SEXP conn;
	PROTECT(conn = NEW_LOGICAL(1));
	LOGICAL(conn)[0] = r;
	UNPROTECT(1);
	return(conn);
    }

}


#pragma once

#include <string>

#include <Eigen/Dense>

namespace Tools {
	namespace Converter {
		template <typename T>
		std::string matrix_to_string(Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic> &matrix);

		template <typename T>
		std::string matrix_to_string(T &vector);
	}
}


#ifndef STAN_MATH_PRIM_SCAL_PROB_INV_CHI_SQUARE_RNG_HPP
#define STAN_MATH_PRIM_SCAL_PROB_INV_CHI_SQUARE_RNG_HPP

#include <stan/math/prim/scal/err/check_consistent_sizes.hpp>
#include <stan/math/prim/scal/err/check_nonnegative.hpp>
#include <stan/math/prim/scal/err/check_not_nan.hpp>
#include <stan/math/prim/scal/err/check_positive_finite.hpp>
#include <stan/math/prim/scal/fun/constants.hpp>
#include <stan/math/prim/scal/fun/multiply_log.hpp>
#include <stan/math/prim/scal/fun/value_of.hpp>
#include <stan/math/prim/scal/fun/gamma_q.hpp>
#include <stan/math/prim/scal/fun/digamma.hpp>
#include <stan/math/prim/scal/meta/length.hpp>
#include <stan/math/prim/scal/meta/VectorBuilder.hpp>
#include <stan/math/prim/scal/meta/return_type.hpp>
#include <stan/math/prim/scal/fun/grad_reg_inc_gamma.hpp>
#include <stan/math/prim/scal/meta/include_summand.hpp>
#include <boost/random/chi_squared_distribution.hpp>
#include <boost/random/variate_generator.hpp>

namespace stan {
  namespace math {

    /**
     * Return a pseudorandom Inverse chi squared variate with the nu degrees of
     * freedom using the specified random number generator.
     *
     * @tparam RNG class of random number generator
     * @param nu positive degrees of freedom parameter
     * @param rng random number generator
     * @return inverse chi squared random variate
     * @throw std::domain_error if nu is nonpositive
     */
    template <class RNG>
    inline double
    inv_chi_square_rng(double nu,
                       RNG& rng) {
      using boost::variate_generator;
      using boost::random::chi_squared_distribution;

      static const char* function("inv_chi_square_rng");

      check_positive_finite(function, "Degrees of freedom parameter", nu);

      variate_generator<RNG&, chi_squared_distribution<> >
        chi_square_rng(rng, chi_squared_distribution<>(nu));
      return 1 / chi_square_rng();
    }

  }
}
#endif