#include "densities.h"

// ============================================================
// Zero-inflated Negative Binomial
// ============================================================

// Constructor and Destructor ---------------------------------
ZiNB::ZiNB()
{
	this->lxfactorials = NULL;
}

ZiNB::ZiNB(int* observations, int T, double size, double prob, double w)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	this->obs = observations;
	this->T = T;
	this->prob = prob;
	this->size = size;
	this->w = w;
	this->lxfactorials = NULL;
	if (this->obs != NULL)
	{
		this->max_obs = intMax(observations, T);
		this->lxfactorials = (double*) Calloc(max_obs+1, double);
		this->lxfactorials[0] = 0.0;	// Not necessary, already 0 because of Calloc
		this->lxfactorials[1] = 0.0;
		for (int j=2; j<=max_obs; j++)
		{
			this->lxfactorials[j] = this->lxfactorials[j-1] + log(j);
		}
	}
}

ZiNB::~ZiNB()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	if (this->lxfactorials != NULL)
	{
		Free(this->lxfactorials);
	}
}

// Methods ----------------------------------------------------
void ZiNB::calc_logdensities(double* logdens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR,lGammaRplusX,lxfactorial;
	lGammaR=lgamma(this->size);
	// Select strategy for computing gammas
	if (this->max_obs <= this->T)
	{
		//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
		std::vector<double> logdens_per_read(this->max_obs+1);
		logdens_per_read[0] = log( this->w + (1-this->w) * exp( lgamma(this->size + 0) - lGammaR - lxfactorials[0] + this->size * logp + 0 * log1minusp ) );
		for (int j=1; j<=this->max_obs; j++)
		{
			logdens_per_read[j] = log(1-this->w) + lgamma(this->size + j) - lGammaR - lxfactorials[j] + this->size * logp + j * log1minusp;
		}
		for (int t=0; t<this->T; t++)
		{
			logdens[t] = logdens_per_read[(int) this->obs[t]];
			if (std::isnan(logdens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "logdens["<<t<<"] = "<< logdens[t];
				throw nan_detected;
			}
		}
	}
	else
	{
		//FILE_LOG(logDEBUG2) << "Computing gammas in " << __func__ << " for every t, because max(O)>T";
		for (int t=0; t<this->T; t++)
		{
			lGammaRplusX = lgamma(this->size + this->obs[t]);
			lxfactorial = this->lxfactorials[(int) this->obs[t]];
			if (obs[t] == 0)
			{
				logdens[t] = log( this->w + (1-this->w) * exp( lGammaRplusX - lGammaR - lxfactorial + this->size * logp + this->obs[t] * log1minusp ) );
			}
			else
			{
				logdens[t] = log(1-this->w) + lGammaRplusX - lGammaR - lxfactorial + this->size * logp + this->obs[t] * log1minusp;
			}
			if (std::isnan(logdens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "logdens["<<t<<"] = "<< logdens[t];
				throw nan_detected;
			}
		}
	}
}

void ZiNB::calc_densities(double* dens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR,lGammaRplusX,lxfactorial;
	lGammaR = lgamma(this->size);
	// Select strategy for computing gammas
	if (this->max_obs <= this->T)
	{
		//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
		std::vector<double> dens_per_read(this->max_obs+1);
		dens_per_read[0] = this->w + (1-this->w) * exp( lgamma(this->size + 0) - lGammaR - lxfactorials[0] + this->size * logp + 0 * log1minusp );
		for (int j=1; j<=this->max_obs; j++)
		{
			dens_per_read[j] = (1-this->w) * exp( lgamma(this->size + j) - lGammaR - lxfactorials[j] + this->size * logp + j * log1minusp );
		}
		for (int t=0; t<this->T; t++)
		{
			dens[t] = dens_per_read[(int) this->obs[t]];
			//FILE_LOG(logDEBUG4) << "dens["<<t<<"] = " << dens[t];
			if (std::isnan(dens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "dens["<<t<<"] = "<< dens[t];
				throw nan_detected;
			}
		}
	}
	else
	{
		//FILE_LOG(logDEBUG2) << "Computing gammas in " << __func__ << " for every t, because max(O)>T";
		for (int t=0; t<this->T; t++)
		{
			lGammaRplusX = lgamma(this->size + this->obs[t]);
			lxfactorial = this->lxfactorials[(int) this->obs[t]];
			if (obs[t] == 0)
			{
				dens[t] = this->w + (1-this->w) * exp( lGammaRplusX - lGammaR - lxfactorial + this->size * logp + this->obs[t] * log1minusp );
			}
			else
			{
				dens[t] = (1-this->w) * exp( lGammaRplusX - lGammaR - lxfactorial + this->size * logp + this->obs[t] * log1minusp );
			}
			if (std::isnan(dens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "dens["<<t<<"] = "<< dens[t];
				throw nan_detected;
			}
		}
	}
}

void ZiNB::calc_CDFs(double* CDF)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR;
	lGammaR=lgamma(this->size);
	std::vector<double> precomputed_CDF(this->max_obs+1);
	double dens;

	//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
	// Calculate for j=0
	precomputed_CDF[0] = this->w + (1-this->w) * exp( lgamma(this->size) - lGammaR - this->lxfactorials[0] + this->size * logp );
	// Calculate for j>0
	for (int j=1; j<=this->max_obs; j++)
	{
		dens = (1-this->w) * exp( lgamma(this->size + j) - lGammaR - this->lxfactorials[j] + this->size * logp + j * log1minusp );
		if (std::isnan(dens))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "dens = "<< dens;
			throw nan_detected;
		}
		precomputed_CDF[j] = precomputed_CDF[j-1] + dens;
		if (precomputed_CDF[j] >= 1)
		{
			//FILE_LOG(logDEBUG4) << "CDF >= 1 for obs[t] = "<<j<< ", shifting to value of obs[t] = "<<j-1;
			precomputed_CDF[j] = precomputed_CDF[j-1]; 
		}
	}
	for (int t=0; t<this->T; t++)
	{
		CDF[t] = precomputed_CDF[(int)obs[t]];
		if (std::isnan(CDF[t]))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "CDF["<<t<<"] = "<< CDF[t];
			throw nan_detected;
		}
	}
}

void ZiNB::calc_logCDFs(double* logCDF)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR;
	lGammaR=lgamma(this->size);
	std::vector<double> precomputed_logCDF(this->max_obs+1);
	double logdens;

	//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
	// Calculate for j=0
	precomputed_logCDF[0] = log( this->w + (1-this->w) * exp( lgamma(this->size) - lGammaR - this->lxfactorials[0] + this->size * logp ) );
	// Calculate for j>0
	for (int j=1; j<=this->max_obs; j++)
	{
		logdens = log(1-this->w) + lgamma(this->size + j) - lGammaR - this->lxfactorials[j] + this->size * logp + j * log1minusp;
		if (std::isnan(logdens))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "logdens = "<< logdens;
			throw nan_detected;
		}
		precomputed_logCDF[j] = log( exp(precomputed_logCDF[j-1]) + exp(logdens) );
		if (precomputed_logCDF[j] >= 0)
		{
			//FILE_LOG(logDEBUG4) << "logCDF >= 0 for obs[t] = "<<j<< ", shifting to value of obs[t] = "<<j-1;
			precomputed_logCDF[j] = precomputed_logCDF[j-1]; 
		}
	}
	for (int t=0; t<this->T; t++)
	{
		logCDF[t] = precomputed_logCDF[(int)obs[t]];
		if (std::isnan(logCDF[t]))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "logCDF["<<t<<"] = "<< logCDF[t];
			throw nan_detected;
		}
	}
}

void ZiNB::copy(Density* other)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	ZiNB* o = (ZiNB*)other;
	this->prob = o->prob;
	this->size = o->size;
	this->obs = o->obs;
	this->w = o->w;
}

double ZiNB::getLogDensityAt(int x)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR,lGammaRplusX,lxfactorial;
	double logdens;
	// Calculate variance
	double mean = 0, variance = 0;
	for(int t=0; t<this->T; t++)
	{
		mean += obs[t];
	}
	mean = mean / this->T;
	for(int t=0; t<this->T; t++)
	{
		variance += pow(obs[t] - mean, 2);
	}
	variance = variance / this->T;
	// Calculate logdensity
	lGammaR = lgamma(this->size);
	lGammaRplusX = lgamma(this->size + x);
	lxfactorial = this->lxfactorials[x];
	if (x == 0)
	{
		logdens = log( this->w + (1-this->w) * exp( lGammaRplusX - lGammaR - lxfactorial + this->size * logp + x * log1minusp ) );
	}
	else
	{
		logdens = log(1-this->w) + lGammaRplusX - lGammaR - lxfactorial + this->size * logp + x * log1minusp;
	}
	if (std::isnan(logdens))
	{
		//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
		//FILE_LOG(logERROR) << "logdens = "<< logdens;
		throw nan_detected;
	}
	
	return(logdens);
}

// Getter and Setter ------------------------------------------
double ZiNB::get_mean()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return (1-this->w)*this->size*(1-this->prob)/this->prob;
}

double ZiNB::get_variance()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return (1-this->w)*this->size*(1-this->prob)/this->prob/this->prob; //TODO: Is this correct?
}

DensityName ZiNB::get_name()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(ZERO_INFLATED_NEGATIVE_BINOMIAL);
}

double ZiNB::get_size()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
  return(this->size);
}

double ZiNB::get_prob()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
  return(this->prob);
}

double ZiNB::get_w()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(this->w);
}


// ============================================================
// Negative Binomial density
// ============================================================

// Constructor and Destructor ---------------------------------
NegativeBinomial::NegativeBinomial()
{
	this->lxfactorials = NULL;
}

NegativeBinomial::NegativeBinomial(int* observations, int T, double size, double prob)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	this->obs = observations;
	this->T = T;
	this->size = size;
	this->prob = prob;
	this->lxfactorials = NULL;
	// Precompute the lxfactorials that are used in computing the densities
	if (this->obs != NULL)
	{
		this->max_obs = intMax(observations, T);
		this->lxfactorials = (double*) Calloc(max_obs+1, double);
		this->lxfactorials[0] = 0.0;	// Not necessary, already 0 because of Calloc
		this->lxfactorials[1] = 0.0;
		for (int j=2; j<=max_obs; j++)
		{
			this->lxfactorials[j] = this->lxfactorials[j-1] + log(j);
		}
	}
}

NegativeBinomial::~NegativeBinomial()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	if (this->lxfactorials != NULL)
	{
		Free(this->lxfactorials);
	}
}

// Methods ----------------------------------------------------
void NegativeBinomial::calc_logdensities(double* logdens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR,lGammaRplusX,lxfactorial;
	lGammaR = lgamma(this->size);
	// Select strategy for computing gammas
	if (this->max_obs <= this->T)
	{
		//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
		std::vector<double> logdens_per_read(this->max_obs+1);
		for (int j=0; j<=this->max_obs; j++)
		{
			logdens_per_read[j] = lgamma(this->size + j) - lGammaR - lxfactorials[j] + this->size * logp + j * log1minusp;
		}
		for (int t=0; t<this->T; t++)
		{
			logdens[t] = logdens_per_read[(int) this->obs[t]];
			//FILE_LOG(logDEBUG4) << "logdens["<<t<<"] = " << logdens[t];
			if (std::isnan(logdens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "logdens["<<t<<"] = "<< logdens[t];
				throw nan_detected;
			}
		}
	}
	else
	{
		//FILE_LOG(logDEBUG2) << "Computing gammas in " << __func__ << " for every t, because max(O)>T";
		for (int t=0; t<this->T; t++)
		{
			lGammaRplusX = lgamma(this->size + this->obs[t]);
			lxfactorial = this->lxfactorials[(int) this->obs[t]];
			logdens[t] = lGammaRplusX - lGammaR - lxfactorial + this->size * logp + this->obs[t] * log1minusp;
			//FILE_LOG(logDEBUG4) << "logdens["<<t<<"] = " << logdens[t];
			if (std::isnan(logdens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "logdens["<<t<<"] = "<< logdens[t];
				throw nan_detected;
			}
		}
	}
} 

void NegativeBinomial::calc_densities(double* dens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR,lGammaRplusX,lxfactorial;
	lGammaR=lgamma(this->size);
	// Select strategy for computing gammas
	if (this->max_obs <= this->T)
	{
		//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
		std::vector<double> dens_per_read(this->max_obs+1);
		for (int j=0; j<=this->max_obs; j++)
		{
			dens_per_read[j] = exp( lgamma(this->size + j) - lGammaR - lxfactorials[j] + this->size * logp + j * log1minusp );
		}
		for (int t=0; t<this->T; t++)
		{
			dens[t] = dens_per_read[(int) this->obs[t]];
			//FILE_LOG(logDEBUG4) << "dens["<<t<<"] = " << dens[t];
			if (std::isnan(dens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "dens["<<t<<"] = "<< dens[t];
				throw nan_detected;
			}
		}
	}
	else
	{
		//FILE_LOG(logDEBUG2) << "Computing gammas in " << __func__ << " for every t, because max(O)>T";
		for (int t=0; t<this->T; t++)
		{
			lGammaRplusX = lgamma(this->size + this->obs[t]);
			lxfactorial = this->lxfactorials[(int) this->obs[t]];
			dens[t] = exp( lGammaRplusX - lGammaR - lxfactorial + this->size * logp + this->obs[t] * log1minusp );
			//FILE_LOG(logDEBUG4) << "dens["<<t<<"] = " << dens[t];
			if (std::isnan(dens[t]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "dens["<<t<<"] = "<< dens[t];
				throw nan_detected;
			}
		}
	}
} 

void NegativeBinomial::calc_CDFs(double* CDF)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR;
	lGammaR=lgamma(this->size);
	std::vector<double> precomputed_CDF(this->max_obs+1);
	double dens;

	//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
	// Calculate for j=0
	precomputed_CDF[0] = exp( lgamma(this->size) - lGammaR - this->lxfactorials[0] + this->size * logp );
	// Calculate for j>0
	for (int j=1; j<=this->max_obs; j++)
	{
		dens = exp( lgamma(this->size + j) - lGammaR - this->lxfactorials[j] + this->size * logp + j * log1minusp );
		if (std::isnan(dens))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "dens = "<< dens;
			throw nan_detected;
		}
		precomputed_CDF[j] = precomputed_CDF[j-1] + dens;
		if (precomputed_CDF[j] >= 1)
		{
			//FILE_LOG(logDEBUG4) << "CDF >= 1 for obs[t] = "<<j<< ", shifting to value of obs[t] = "<<j-1;
			precomputed_CDF[j] = precomputed_CDF[j-1]; 
		}
	}
	for (int t=0; t<this->T; t++)
	{
		CDF[t] = precomputed_CDF[(int)obs[t]];
		if (std::isnan(CDF[t]))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "CDF["<<t<<"] = "<< CDF[t];
			throw nan_detected;
		}
	}
}

void NegativeBinomial::calc_logCDFs(double* logCDF)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR;
	lGammaR=lgamma(this->size);
	std::vector<double> precomputed_logCDF(this->max_obs+1);
	double logdens;

	//FILE_LOG(logDEBUG3) << "Precomputing gammas in " << __func__ << " for every obs[t], because max(O)<=T";
	// Calculate for j=0
	precomputed_logCDF[0] = lgamma(this->size) - lGammaR - this->lxfactorials[0] + this->size * logp;
	// Calculate for j>0
	for (int j=1; j<=this->max_obs; j++)
	{
		logdens = lgamma(this->size + j) - lGammaR - this->lxfactorials[j] + this->size * logp + j * log1minusp;
		if (std::isnan(logdens))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "logdens = "<< logdens;
			throw nan_detected;
		}
		precomputed_logCDF[j] = log( exp(precomputed_logCDF[j-1]) + exp(logdens) );
		if (precomputed_logCDF[j] >= 0)
		{
			//FILE_LOG(logDEBUG4) << "logCDF >= 0 for obs[t] = "<<j<< ", shifting to value of obs[t] = "<<j-1;
			precomputed_logCDF[j] = precomputed_logCDF[j-1]; 
		}
	}
	for (int t=0; t<this->T; t++)
	{
		logCDF[t] = precomputed_logCDF[(int)obs[t]];
		if (std::isnan(logCDF[t]))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "logCDF["<<t<<"] = "<< logCDF[t];
			throw nan_detected;
		}
	}
}

void NegativeBinomial::update(double* weights)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	//FILE_LOG(logDEBUG1) << "size = "<<this->size << ", prob = "<<this->prob;
	double eps = 1e-4;
	double kmax = 20;
	double numerator, denominator, size0, DigammaSize, TrigammaSize;
	double F, dFdSize, FdivM;
	double logp = log(this->prob);
	// Update prob (p)
	numerator=denominator=0.0;
// 	clock_t time, dtime;
// 	time = clock();
	for (int t=0; t<this->T; t++)
	{
		numerator += weights[t] * this->size;
		denominator += weights[t] * (this->size + this->obs[t]);
	}
	this->prob = numerator/denominator; // Update this->prob
// 	logp = log(this->prob); // Update of size is done with new prob
	
// 	dtime = clock() - time;
// 	//FILE_LOG(logDEBUG1) << "updateP(): "<<dtime<< " clicks";

	// Update of size with Newton Method
	size0 = this->size;
// 	time = clock();
	// Select strategy for computing digammas
	if (this->max_obs <= this->T)
	{
		//FILE_LOG(logDEBUG2) << "Precomputing digammas in " << __func__ << " for every obs[t], because max(O)<=T";
		std::vector<double> DigammaSizePlusX(this->max_obs+1);
		std::vector<double> TrigammaSizePlusX(this->max_obs+1);
		for (int k=0; k<kmax; k++)
		{
			F=dFdSize=0.0;
			DigammaSize = digamma(size0); // boost::math::digamma<>(size0);
			TrigammaSize = trigamma(size0); // boost::math::digamma<>(size0);
			// Precompute the digammas by iterating over all possible values of the observation vector
			for (int j=0; j<=this->max_obs; j++)
			{
				DigammaSizePlusX[j] = digamma(size0+j);
				TrigammaSizePlusX[j] = trigamma(size0+j);
			}
			for(int t=0; t<this->T; t++)
			{
				if(this->obs[t]==0)
				{
					F += weights[t] * logp;
					//dFdSize+=0;
				}
				if(this->obs[t]!=0)
				{
					F += weights[t] * (logp - DigammaSize + DigammaSizePlusX[(int)obs[t]]);
					dFdSize += weights[t] * (-TrigammaSize + TrigammaSizePlusX[(int)obs[t]]);
				}
			}
			FdivM = F/dFdSize;
// Rprintf("k = %d, F = %g, dFdSize = %g, FdivM = %g, size0 = %g\n", k, F, dFdSize, FdivM, size0);
			if (FdivM < size0)
			{
				size0 = size0-FdivM;
			}
			else if (FdivM >= size0)
			{
				size0 = size0/2.0;
			}
			if(fabs(F)<eps)
			{
				break;
			}
		}
	}
	else
	{
		//FILE_LOG(logDEBUG2) << "Computing digammas in " << __func__ << " for every t, because max(O)>T";
		double DigammaSizePlusX, TrigammaSizePlusX;
		for (int k=0; k<kmax; k++)
		{
			F = dFdSize = 0.0;
			DigammaSize = digamma(size0); // boost::math::digamma<>(size0);
			TrigammaSize = trigamma(size0); // boost::math::digamma<>(size0);
			for(int t=0; t<this->T; t++)
			{
				DigammaSizePlusX = digamma(size0+this->obs[t]); //boost::math::digamma<>(size0+this->obs[ti]);
				TrigammaSizePlusX = trigamma(size0+this->obs[t]);
				if(this->obs[t]==0)
				{
					F += weights[t] * logp;
					//dFdSize+=0;
				}
				if(this->obs[t]!=0)
				{
					F += weights[t] * (logp - DigammaSize + DigammaSizePlusX);
					dFdSize += weights[t] * (-TrigammaSize + TrigammaSizePlusX);
				}
			}
			FdivM = F/dFdSize;
			if (FdivM < size0)
			{
				size0 = size0-FdivM;
			}
			else if (FdivM >= size0)
			{
				size0 = size0/2.0;
			}
			if(fabs(F)<eps)
			{
				break;
			}
		}
	}
	this->size = size0;
	//FILE_LOG(logDEBUG1) << "size = "<<this->size << ", prob = "<<this->prob;

// 	dtime = clock() - time;
// 	//FILE_LOG(logDEBUG1) << "updateR(): "<<dtime<< " clicks";

}

void NegativeBinomial::copy(Density* other)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	NegativeBinomial* o = (NegativeBinomial*)other;
	this->prob = o->prob;
	this->size = o->size;
	this->obs = o->obs;
}

double NegativeBinomial::getLogDensityAt(int x)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logp = log(this->prob);
	double log1minusp = log(1-this->prob);
	double lGammaR,lGammaRplusX,lxfactorial;
	double logdens;
	// Calculate variance
	double mean = 0, variance = 0;
	for(int t=0; t<this->T; t++)
	{
		mean += obs[t];
	}
	mean = mean / this->T;
	for(int t=0; t<this->T; t++)
	{
		variance += pow(obs[t] - mean, 2);
	}
	variance = variance / this->T;
	// Calculate logdensity
	lGammaR = lgamma(this->size);
	lGammaRplusX = lgamma(this->size + x);
	lxfactorial = this->lxfactorials[x];
	logdens = lGammaRplusX - lGammaR - lxfactorial + this->size * logp + x * log1minusp;
	if (std::isnan(logdens))
	{
		//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
		//FILE_LOG(logERROR) << "logdens = "<< logdens;
		throw nan_detected;
	}
	
	return(logdens);
}

// Getter and Setter ------------------------------------------
double NegativeBinomial::get_mean()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return this->size*(1-this->prob)/this->prob;
}

double NegativeBinomial::get_variance()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return this->size*(1-this->prob)/this->prob/this->prob;
}

DensityName NegativeBinomial::get_name()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(NEGATIVE_BINOMIAL);
}

double NegativeBinomial::get_size()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(this->size);
}

double NegativeBinomial::get_prob()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(this->prob);
}


// ============================================================
// Zero Inflation density
// ============================================================

// Constructor and Destructor ---------------------------------
ZeroInflation::ZeroInflation() {}

ZeroInflation::ZeroInflation(int* observations, int T)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	this->obs = observations;
	this->T = T;
}

ZeroInflation::~ZeroInflation()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
}

// Methods ----------------------------------------------------
void ZeroInflation::calc_logdensities(double* logdens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	for (int t=0; t<this->T; t++)
	{
		if(obs[t]==0)
		{
			logdens[t] = 0.0;
		};
		if(obs[t]>0)
		{
			logdens[t] = -INFINITY;
		}
		//FILE_LOG(logDEBUG4) << "logdens["<<t<<"] = " << logdens[t];
	}
}

void ZeroInflation::calc_densities(double* dens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	for (int t=0; t<this->T; t++)
	{
		if(obs[t]==0)
		{
			dens[t] = 1.0;
		}
		if(obs[t]>0)
		{
			dens[t] = 0.0;
		}
		//FILE_LOG(logDEBUG4) << "dens["<<t<<"] = " << dens[t];
	}
}

void ZeroInflation::copy(Density*) {}

void ZeroInflation::update(double*)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
}

double ZeroInflation::getLogDensityAt(int x)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double logdens;
	// Calculate logdensity
	if (x == 0)
	{
		logdens = 0;
	}
	else
	{
		logdens = -INFINITY;
	}
	
	return(logdens);
}

// Getter and Setter ------------------------------------------
double ZeroInflation::get_mean()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return 0;
}

double ZeroInflation::get_variance()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return 0;
}

DensityName ZeroInflation::get_name()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(ZERO_INFLATION);
}

	
// ============================================================
// Multivariate Copula Approximation
// ============================================================

// Constructor and Destructor ---------------------------------
MVCopulaApproximation::MVCopulaApproximation(int** multiobservations, int T, std::vector<Density*> marginals, double* cor_matrix_inv, double cor_matrix_determinant)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	this->multi_obs = multiobservations;
	this->T = T;
	// these are the marginal distributions (we need their CDF function)
	this->marginals = marginals;
	this->Nmod = this->marginals.size();
	this->cor_matrix_inv = cor_matrix_inv;
	this->cor_matrix_determinant = cor_matrix_determinant;
}

MVCopulaApproximation::~MVCopulaApproximation()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	for (int imod=0; imod<this->Nmod; imod++)
	{
		delete this->marginals[imod];
	}
}

// Methods ----------------------------------------------------
void MVCopulaApproximation::calc_logdensities(double* logdens)
{
// Rprintf("new state\n");
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	// Calculate logdensities for marginals
	double** marginals_logdensities = CallocDoubleMatrix(this->Nmod, this->T);
	double** marginals_CDFs = CallocDoubleMatrix(this->Nmod, this->T);
	for (int imod=0; imod<this->Nmod; imod++)
	{
		//FILE_LOG(logDEBUG2) << __func__ << ": calculating marginals for imod = " << imod;
		this->marginals[imod]->calc_logdensities(marginals_logdensities[imod]);
		this->marginals[imod]->calc_CDFs(marginals_CDFs[imod]);
	}
	// Calculate multivariate Copula approximation
	//FILE_LOG(logDEBUG2) << __func__ << ": calculate Copula approximation";
	double sum, uniform, exponent, exponentTemp;
	double* z = (double*) Calloc(this->Nmod, double);
	for (int t=0; t<this->T; t++)
	{
		sum = 0.0;
		for (int imod=0; imod<this->Nmod; imod++)
		{
			sum += marginals_logdensities[imod][t];
			uniform = marginals_CDFs[imod][t];
			z[imod] = qnorm(uniform, 0, 1, 1, 0);
			if (std::isnan(z[imod]))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "uniform = "<< uniform;
				//FILE_LOG(logERROR) << "z[imod] = "<< z[imod];
				throw nan_detected;
			}
// if (t==0)
// {
// 	Rprintf("\nmarginal_logdensities[imod=%d][%d] = %g\n", imod, t, marginals_logdensities[imod][t]);
// 	Rprintf("sum = %g\n", sum);
// 	Rprintf("uniform = %g\n", uniform);
// 	Rprintf("z[imod=%d] = %g\n", imod, z[imod]);
// }
		}
		exponent = 0.0;
		for (int imod=0; imod<this->Nmod; imod++)
		{
			exponentTemp = 0.0;
			for(int jmod=0; jmod<Nmod; jmod++)
			{
				if (std::isinf(z[jmod]))
				{
					exponentTemp = INFINITY;
					break;
				}
				if (imod==jmod)
				{
					exponentTemp += z[jmod] * (this->cor_matrix_inv[imod * Nmod + jmod] - 1);
				}
				else
				{
					exponentTemp += z[jmod] * this->cor_matrix_inv[imod * Nmod + jmod];
				}
				if (std::isnan(exponentTemp))
				{
					//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
					//FILE_LOG(logERROR) << "exponentTemp = "<< exponentTemp;
					//FILE_LOG(logERROR) << "cor_matrix_inv = "<< cor_matrix_inv[imod * Nmod + jmod];
					//FILE_LOG(logERROR) << "z["<<jmod<<"] = "<< z[jmod];
					throw nan_detected;
				}
			}
			if (std::isinf(exponentTemp))
			{
				exponent = INFINITY;
				break;
			}
			else
			{
				exponent += exponentTemp * z[imod];
			}
			if (std::isnan(exponent))
			{
				//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
				//FILE_LOG(logERROR) << "exponentTemp = "<< exponentTemp;
				//FILE_LOG(logERROR) << "z["<<imod<<"] = "<< z[imod];
				//FILE_LOG(logERROR) << "exponent = "<< exponent;
				throw nan_detected;
			}
		}
// 		logdens[t] = log(1/sqrt(this->cor_matrix_determinant)) - 0.5 * exponent + sum;
		logdens[t] = -0.5 * log(this->cor_matrix_determinant) - 0.5 * exponent + sum;
		if (std::isnan(logdens[t]))
		{
			//FILE_LOG(logERROR) << __PRETTY_FUNCTION__;
			//FILE_LOG(logERROR) << "cor_matrix_determinant = " << this->cor_matrix_determinant;
			//FILE_LOG(logERROR) << "sum = " << sum;
			//FILE_LOG(logERROR) << "exponentTemp = " << exponentTemp;
			//FILE_LOG(logERROR) << "exponent = " << exponent;
			//FILE_LOG(logERROR) << "logdens["<<t<<"] = " << logdens[t];
			throw nan_detected;
		}		
// if (t==0)
// {
// 	Rprintf("\nlogdens[%d] = %g\n", t, logdens[t]);
// 	Rprintf("-0.5*exponent = %g\n", -0.5*exponent);
// 	Rprintf("sum = %g\n", sum);
// 	Rprintf("cor_matrix_determinant = %g\n", this->cor_matrix_determinant);
// 	Rprintf("-0.5*log(cor_matrix_determinant) = %g\n", -0.5*log(this->cor_matrix_determinant));
// }
	}

	// Clean up
	FreeDoubleMatrix(marginals_logdensities, this->Nmod);
	FreeDoubleMatrix(marginals_CDFs, this->Nmod);
	Free(z);
}

void MVCopulaApproximation::calc_densities(double* dens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	this->calc_logdensities(dens);

	for (int t=0; t<this->T; t++)
	{
		dens[t] = exp( dens[t] );
	}
}

// Getter and Setter ------------------------------------------
DensityName MVCopulaApproximation::get_name()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(OTHER);
}

	
// ============================================================
// Multivariate Product of Bernoullis
// ============================================================

// Constructor and Destructor ---------------------------------
BernoulliProduct::BernoulliProduct(double** multiobservations, bool* binary_states, int T, int Nmod)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	this->multi_obs = multiobservations;
	this->binary_states = binary_states;
	this->T = T;
	this->Nmod = Nmod;
}

BernoulliProduct::~BernoulliProduct()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
}

// Methods ----------------------------------------------------
void BernoulliProduct::calc_logdensities(double* logdens)
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	double d, mult;
	double** tempPost = CallocDoubleMatrix(this->Nmod, this->T);

	for (int t=0; t<this->T; t++)
	{
		d = 1.0;
		for (int imod=0; imod<this->Nmod; imod++)
		{
			//if state[iN] is such that modification imod is unmodified, multiProb[t][imod] is the univariate posterior of being unmodified. 
			//if state[iN] is such that modification imod is modified, multiProb[t][imod] is the univariate posterior of being modified
			if (binary_states[imod])
			{
				mult = 1-this->multi_obs[imod][t];
			}
			else
			{
				mult = this->multi_obs[imod][t];
			}
			if(mult>=1) mult=0.9999999999999;
			if(mult<=0) mult=0.0000000000001;
			d=d*mult;
		}
		logdens[t] = log(d);
	}
	FreeDoubleMatrix(tempPost, this->Nmod);
}

// Getter and Setter ------------------------------------------
DensityName BernoulliProduct::get_name()
{
	//FILE_LOG(logDEBUG2) << __PRETTY_FUNCTION__;
	return(OTHER);
}