#include <boost\math\special_functions.hpp>

#include "common.h"

#include "cusolverOperations.h"

namespace matCUDA
{
	template< typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::ls( Array<TElement> *A, Array<TElement> *x, Array<TElement> *C )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		TElement *d_A, *Workspace, *d_C;
		int INFOh = 2;

		CUDA_CALL( cudaMalloc(&d_A, A->getDim(0) * A->getDim(1) * sizeof(TElement)) );
		CUDA_CALL( cudaMalloc(&d_C, C->getDim(0) * C->getDim(1) * sizeof(TElement)) );

		CUDA_CALL( cudaMemcpy(d_A, A->data(), A->getDim(0) * A->getDim(1) * sizeof(TElement), cudaMemcpyHostToDevice) );
		CUDA_CALL( cudaMemcpy(d_C, C->data(), C->getDim(0) * C->getDim(1) * sizeof(TElement), cudaMemcpyHostToDevice) );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgetrf_bufferSize(&handle, A->getDim(0), A->getDim(1), d_A, A->getDim(0), &Lwork) );

		CUDA_CALL( cudaMalloc( &Workspace, Lwork * sizeof(TElement) ) );

		int *devIpiv, *devInfo;
		size_t size_pivot = std::min(C->getDim(0),C->getDim(1));

		CUDA_CALL( cudaMalloc( &devIpiv, size_pivot * sizeof(int) ) );
		CUDA_CALL( cudaMalloc( &devInfo, sizeof(int) ) );

		/////***** performance test *****/////
		//CUDA_CALL( cudaDeviceSynchronize() );
		//tic();
		//for( int i = 0; i < 10; i++ ) {

		CUSOLVER_CALL( cusolverDnTgetrf( &handle, A->getDim(0), A->getDim(1), d_A, A->getDim(0), Workspace, devIpiv, devInfo ) );
		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Factorization Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		CUSOLVER_CALL( cusolverDnTgetrs( &handle, CUBLAS_OP_N, std::min(A->getDim(0),A->getDim(1)), C->getDim(1), d_A, A->getDim(0), devIpiv, d_C, C->getDim(0), devInfo ) );

		//}
		//CUDA_CALL( cudaDeviceSynchronize() );
		//toc();
		////***** end of performance test *****/////

		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		INFOh = 2;
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Inversion Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		CUDA_CALL( cudaMemcpy( C->data(), d_C, C->getNElements()*sizeof( TElement ), cudaMemcpyDeviceToHost ) );
		for( int i = 0; i< x->GetDescriptor().GetDim( 0 ); i++ ) {
			for( int j = 0; j < x->GetDescriptor().GetDim( 1 ); j++ )
				(*x)( i, j ) = (*C)( i, j );
		}

		// free memory
		CUDA_CALL( cudaFree( d_A ) );
		CUDA_CALL( cudaFree( d_C ) );
		CUDA_CALL( cudaFree( Workspace ) );
		CUDA_CALL( cudaFree( devIpiv ) );
		CUDA_CALL( cudaFree( devInfo ) );

		// Destroy the handle
		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::ls( Array<int> *A, Array<int> *x, Array<int> *C );
	template cusolverStatus_t cusolverOperations<float>::ls( Array<float> *A, Array<float> *x, Array<float> *C );
	template cusolverStatus_t cusolverOperations<double>::ls( Array<double> *A, Array<double> *x, Array<double> *C );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::ls( Array<ComplexFloat> *A, Array<ComplexFloat> *x, Array<ComplexFloat> *C );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::ls( Array<ComplexDouble> *A, Array<ComplexDouble> *x, Array<ComplexDouble> *C );

	template< typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::invert( Array<TElement> *result, Array<TElement> *data )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		size_t M = data->getDim(0);
		size_t N = data->getDim(1);
		size_t minMN = std::min(M,N);

		TElement *d_A, *Workspace, *d_B;
		int INFOh = 2;

		CUDA_CALL( cudaMalloc(&d_A, M * N * sizeof(TElement)) );
		CUDA_CALL( cudaMalloc(&d_B, M * N * sizeof(TElement)) );

		CUDA_CALL( cudaMemcpy(d_A, data->data(), M * N * sizeof(TElement), cudaMemcpyHostToDevice) );
		cuda_eye<TElement>( d_B, minMN );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgetrf_bufferSize(&handle, M, N, d_A, M, &Lwork) );

		CUDA_CALL( cudaMalloc( &Workspace, Lwork * sizeof(TElement) ) );

		int *devIpiv, *devInfo;
		size_t size_pivot = std::min(data->getDim(0),data->getDim(1));

		CUDA_CALL( cudaMalloc( &devIpiv, size_pivot * sizeof(int) ) );
		CUDA_CALL( cudaMalloc( &devInfo, sizeof(int) ) );

		/////***** performance test *****/////
		//CUDA_CALL( cudaDeviceSynchronize() );
		//tic();
		//for( int i = 0; i < 10; i++ ) {

		CUSOLVER_CALL( cusolverDnTgetrf( &handle, M, N, d_A, M, Workspace, devIpiv, devInfo ) );
		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Factorization Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		CUSOLVER_CALL( cusolverDnTgetrs( &handle, CUBLAS_OP_N, data->getDim(0), data->getDim(1), d_A, data->getDim(0), devIpiv, d_B, data->getDim(0), devInfo ) );

		//}
		//CUDA_CALL( cudaDeviceSynchronize() );
		//toc();
		////***** end of performance test *****/////

		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		INFOh = 2;
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Inversion Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		CUDA_CALL( cudaMemcpy( result->data(), d_B, result->getNElements()*sizeof( TElement ), cudaMemcpyDeviceToHost ) );

		// free memory
		CUDA_CALL( cudaFree( d_A ) );
		CUDA_CALL( cudaFree( d_B ) );
		CUDA_CALL( cudaFree( Workspace ) );
		CUDA_CALL( cudaFree( devIpiv ) );
		CUDA_CALL( cudaFree( devInfo ) );

		// Destroy the handle
		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::invert( Array<int> *result, Array<int> *data );
	template cusolverStatus_t cusolverOperations<float>::invert( Array<float> *result, Array<float> *data );
	template cusolverStatus_t cusolverOperations<double>::invert( Array<double> *result, Array<double> *data );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::invert( Array<ComplexFloat> *result, Array<ComplexFloat> *data );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::invert( Array<ComplexDouble> *result, Array<ComplexDouble> *data );

	template< typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::invert_zerocopy( Array<TElement> *result, Array<TElement> *data )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		size_t M = data->getDim(0);
		size_t N = data->getDim(1);
		size_t minMN = std::min(M,N);

		TElement *d_A, *Workspace, *d_B;

		CUDA_CALL( cudaMalloc(&d_A, M * N * sizeof(TElement)) );

		// pass host pointer to device
		CUDA_CALL( cudaHostGetDevicePointer( &d_B, result->data(), 0 ) );

		CUDA_CALL( cudaMemcpy(d_A, data->data(), M * N * sizeof(TElement), cudaMemcpyHostToDevice) );
		cuda_eye<TElement>( d_B, minMN );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgetrf_bufferSize(&handle, M, N, d_A, M, &Lwork) );

		CUDA_CALL( cudaMalloc( &Workspace, Lwork * sizeof(TElement) ) );

		int *devIpiv, *devInfo;
		size_t size_pivot = std::min(data->getDim(0),data->getDim(1));

		CUDA_CALL( cudaMalloc( &devIpiv, size_pivot * sizeof(int) ) );
		CUDA_CALL( cudaMalloc( &devInfo, sizeof(int) ) );

		CUSOLVER_CALL( cusolverDnTgetrf( &handle, M, N, d_A, M, Workspace, devIpiv, devInfo ) );
		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		int INFOh = 2;
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Factorization Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		CUSOLVER_CALL( cusolverDnTgetrs( &handle, CUBLAS_OP_N, data->getDim(0), data->getDim(1), d_A, data->getDim(0), devIpiv, d_B, data->getDim(0), devInfo ) );
		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		INFOh = 2;
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Inversion Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		// free memory
		CUDA_CALL( cudaFree( d_A ) );
		CUDA_CALL( cudaFree( Workspace ) );
		CUDA_CALL( cudaFree( devIpiv ) );
		CUDA_CALL( cudaFree( devInfo ) );

		// Destroy the handle
		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::invert_zerocopy( Array<int> *result, Array<int> *data );
	template cusolverStatus_t cusolverOperations<float>::invert_zerocopy( Array<float> *result, Array<float> *data );
	template cusolverStatus_t cusolverOperations<double>::invert_zerocopy( Array<double> *result, Array<double> *data );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::invert_zerocopy( Array<ComplexFloat> *result, Array<ComplexFloat> *data );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::invert_zerocopy( Array<ComplexDouble> *result, Array<ComplexDouble> *data );

	template< typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::lu( Array<TElement> *A, Array<TElement> *lu, Array<TElement> *Pivot )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		size_t M = A->getDim(0);
		size_t N = A->getDim(1);
		size_t minMN = std::min(M,N);

		TElement *d_A, *Workspace;

		CUDA_CALL( cudaMalloc(&d_A, M * N * sizeof(TElement)) );
		CUDA_CALL( cudaMemcpy(d_A, A->data(), M * N * sizeof(TElement), cudaMemcpyHostToDevice) );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgetrf_bufferSize(&handle, M, N, d_A, M, &Lwork) );

		CUDA_CALL( cudaMalloc( &Workspace, Lwork * sizeof(TElement) ) );

		int *devIpiv, *devInfo;
		size_t size_pivot = std::min(A->getDim(0),A->getDim(1));

		CUDA_CALL( cudaMalloc( &devIpiv, size_pivot * sizeof(int) ) );
		CUDA_CALL( cudaMalloc( &devInfo, sizeof(int) ) );

		CUSOLVER_CALL( cusolverDnTgetrf( &handle, M, N, d_A, M, Workspace, devIpiv, devInfo ) );
		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		int INFOh = 2;
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Factorization Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		Array<int> pivotVector( size_pivot );
		CUDA_CALL( cudaMemcpy( lu->data(), d_A, lu->getNElements()*sizeof( TElement ), cudaMemcpyDeviceToHost ) );
		CUDA_CALL( cudaMemcpy( pivotVector.data(), devIpiv, size_pivot*sizeof( int ), cudaMemcpyDeviceToHost ) );

		from_permutation_vector_to_permutation_matrix( Pivot, &pivotVector );

		// free memory
		CUDA_CALL( cudaFree( d_A ) );
		CUDA_CALL( cudaFree( Workspace ) );
		CUDA_CALL( cudaFree( devIpiv ) );
		CUDA_CALL( cudaFree( devInfo ) );

		// Destroy the handle
		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::lu( Array<int> *A, Array<int> *lu, Array<int> *Pivot );
	template cusolverStatus_t cusolverOperations<float>::lu( Array<float> *A, Array<float> *lu, Array<float> *Pivot );
	template cusolverStatus_t cusolverOperations<double>::lu( Array<double> *A, Array<double> *lu, Array<double> *Pivot );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::lu( Array<ComplexFloat> *A, Array<ComplexFloat> *lu, Array<ComplexFloat> *Pivot );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::lu( Array<ComplexDouble> *A, Array<ComplexDouble> *lu, Array<ComplexDouble> *Pivot );

	template< typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::lu( Array<TElement> *A, Array<TElement> *lu )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		size_t M = A->getDim(0);
		size_t N = A->getDim(1);
		size_t minMN = std::min(M,N);

		TElement *d_A, *Workspace;

		CUDA_CALL( cudaMalloc(&d_A, M * N * sizeof(TElement)) );
		CUDA_CALL( cudaMemcpy(d_A, A->data(), M * N * sizeof(TElement), cudaMemcpyHostToDevice) );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgetrf_bufferSize(&handle, M, N, d_A, M, &Lwork) );

		CUDA_CALL( cudaMalloc( &Workspace, Lwork * sizeof(TElement) ) );

		int *devIpiv, *devInfo;
		size_t size_pivot = std::min(A->getDim(0),A->getDim(1));

		CUDA_CALL( cudaMalloc( &devIpiv, size_pivot * sizeof(int) ) );
		CUDA_CALL( cudaMalloc( &devInfo, sizeof(int) ) );

		/////***** performance test *****/////
		//CUDA_CALL( cudaDeviceSynchronize() );
		//tic();
		//for( int i = 0; i < 10; i++ ) {

		CUSOLVER_CALL( cusolverDnTgetrf( &handle, M, N, d_A, M, Workspace, devIpiv, devInfo ) );

		//}
		//CUDA_CALL( cudaDeviceSynchronize() );
		//toc();
		////***** end of performance test *****/////

		CUDA_CALL( cudaDeviceSynchronize() );

		// copy from GPU
		int INFOh = 2;
		CUDA_CALL( cudaMemcpy( &INFOh, devInfo, sizeof( int ), cudaMemcpyDeviceToHost ) );

		if( INFOh > 0 )
		{
			printf("Factorization Failed: Matrix is singular\n");
			return CUSOLVER_STATUS_EXECUTION_FAILED;
		}

		CUDA_CALL( cudaMemcpy( lu->data(), d_A, lu->getNElements()*sizeof( TElement ), cudaMemcpyDeviceToHost ) );

		// free memory
		CUDA_CALL( cudaFree( d_A ) );
		CUDA_CALL( cudaFree( Workspace ) );
		CUDA_CALL( cudaFree( devIpiv ) );
		CUDA_CALL( cudaFree( devInfo ) );

		// Destroy the handle
		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::lu( Array<int> *A, Array<int> *lu );
	template cusolverStatus_t cusolverOperations<float>::lu( Array<float> *A, Array<float> *lu );
	template cusolverStatus_t cusolverOperations<double>::lu( Array<double> *A, Array<double> *lu );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::lu( Array<ComplexFloat> *A, Array<ComplexFloat> *lu );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::lu( Array<ComplexDouble> *A, Array<ComplexDouble> *lu );

	template <typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::qr( Array<TElement> *A, Array<TElement> *Q, Array<TElement> *R )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		int M = A->getDim(0);
		int N = A->getDim(1);
		int minMN = std::min(M,N);

		TElement *d_A, *h_A, *TAU, *Workspace, *d_Q;
		h_A = A->data();
		CUDA_CALL( cudaMalloc(&d_A, M * N * sizeof(TElement)) );
		CUDA_CALL( cudaMemcpy(d_A, h_A, M * N * sizeof(TElement), cudaMemcpyHostToDevice) );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgeqrf_bufferSize(&handle, M, N, d_A, M, &Lwork) );
		CUDA_CALL( cudaMalloc(&TAU, minMN * sizeof(TElement)) );
		CUDA_CALL(cudaMalloc(&Workspace, Lwork * sizeof(TElement)));

		int *devInfo;
		CUDA_CALL( cudaMalloc(&devInfo, sizeof(int)) );
		CUDA_CALL( cudaMemset( (void*)devInfo, 0, 1 ) );

		CUSOLVER_CALL( cusolverDnTgeqrf(&handle, M, N, d_A, M, TAU, Workspace, Lwork, devInfo) );

		int devInfo_h = 0;
		CUDA_CALL( cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost) );
		if (devInfo_h != 0)
			return CUSOLVER_STATUS_INTERNAL_ERROR;

		// CALL CUDA FUNCTION
		CUDA_CALL( cudaMemcpy( R->data(), d_A, R->getNElements()*sizeof( TElement ), cudaMemcpyDeviceToHost ) );
		for(int j = 0; j < M; j++)
			for(int i = j + 1; i < N; i++)
				(*R)(i,j) = 0;

		// --- Initializing the output Q matrix (Of course, this step could be done by a kernel function directly on the device)
		//*Q = eye<TElement> ( std::min(Q->getDim(0),Q->getDim(1)) );
		CUDA_CALL( cudaMalloc(&d_Q, M*M*sizeof(TElement)) );
		cuda_eye<TElement>( d_Q, std::min(Q->getDim(0),Q->getDim(1)) );

		// --- CUDA qr execution
		CUSOLVER_CALL( cusolverDnTormqr(&handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_N, M, N, std::min(M, N), d_A, M, TAU, d_Q, M, Workspace, Lwork, devInfo) );

		// --- At this point, d_Q contains the elements of Q. Showing this.
		CUDA_CALL( cudaMemcpy(Q->data(), d_Q, M*M*sizeof(TElement), cudaMemcpyDeviceToHost) );
		CUDA_CALL( cudaDeviceSynchronize() );

		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::qr( Array<int> *A, Array<int> *Q, Array<int> *R );
	template cusolverStatus_t cusolverOperations<float>::qr( Array<float> *A, Array<float> *Q, Array<float> *R );
	template cusolverStatus_t cusolverOperations<double>::qr( Array<double> *A, Array<double> *Q, Array<double> *R );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::qr( Array<ComplexFloat> *A, Array<ComplexFloat> *Q, Array<ComplexFloat> *R );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::qr( Array<ComplexDouble> *A, Array<ComplexDouble> *Q, Array<ComplexDouble> *R );

	template<> cusolverStatus_t cusolverOperations<ComplexFloat>::dpss( Array<ComplexFloat> *eigenvector, index_t N, double NW, index_t degree )
	{
		return CUSOLVER_STATUS_NOT_INITIALIZED;
	}

	template<> cusolverStatus_t cusolverOperations<ComplexDouble>::dpss( Array<ComplexDouble> *eigenvector, index_t N, double NW, index_t degree )
	{
		return CUSOLVER_STATUS_NOT_INITIALIZED;
	}

	template <typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::dpss( Array<TElement> *eigenvector, index_t N, double NW, index_t degree )
	{
		// define matrix T (NxN)
		TElement** T = new TElement*[ N ];
		for(int i = 0; i < N; ++i)
			T[ i ] = new TElement[ N ];

		// fill in T as function of ( N, W )
		// T is a tridiagonal matrix, i. e., it has diagonal, subdiagonal and superdiagonal
		// the others elements are 0
		for (int i = 0; i < N; i++) {
			for (int j = 0; j < N; j++) {
				if( j == i - 1 ) // subdiagonal
					T[ i ][ j ] = ( (TElement)N - i )*i/2;
				else if( j == i ) // diagonal
					T[ i ][ j ] = pow( (TElement)(N-1)/2 - i, 2 )*boost::math::cos_pi( 2*NW/(TElement)N/boost::math::constants::pi<TElement>() );
				else if( j == i + 1 ) // superdiagonal
					T[ i ][ j ] = ( i + 1 )*( (TElement)N - 1 - i )/2*( j == i + 1 );
				else // others elements
					T[ i ][ j ] = 0;
			}
		}

		// declarations needed
		cusolverStatus_t statCusolver = CUSOLVER_STATUS_SUCCESS;
		cusolverSpHandle_t handleCusolver = NULL;
		cusparseHandle_t handleCusparse = NULL;
		cusparseMatDescr_t descrA = NULL;
		int *h_cooRowIndex = NULL, *h_cooColIndex = NULL;
		TElement *h_cooVal = NULL;
		int *d_cooRowIndex = NULL, *d_cooColIndex = NULL, *d_csrRowPtr = NULL;
		TElement *d_cooVal = NULL;
		int nnz;
		TElement *h_eigenvector0 = NULL, *d_eigenvector0 = NULL, *d_eigenvector = NULL;
		int maxite = 1e6; // number of maximum iteration
		TElement tol = 1; // tolerance
		TElement mu, *d_mu;
		TElement max_lambda;

		// define interval of eigenvalues of T
		// interval is [-max_lambda,max_lambda]
		max_lambda = ( N - 1 )*( N + 2 ) + N*( N + 1 )/8 + 0.25;

		// amount of nonzero elements of T
		nnz = 3*N - 2;

		// allocate host memory
		h_cooRowIndex = new int[ nnz*sizeof( int ) ];
		h_cooColIndex = new int[ nnz*sizeof( int ) ];
		h_cooVal = new TElement[ nnz*sizeof( TElement ) ];
		h_eigenvector0 = new TElement[ N*sizeof( TElement ) ];

		// fill in vectors that describe T as a sparse matrix
		int counter = 0;
		for (int i = 0; i < N; i++ ) {
			for( int j = 0; j < N; j++ ) {
				if( T[ i ][ j ] != 0 ) {
					h_cooRowIndex[counter] = i;
					h_cooColIndex[counter] = j;
					h_cooVal[counter++] = T[ i ][ j ];
				}
			}
		}

		// fill in initial eigenvector guess
		for( int i = 0; i < N; i++ )
			h_eigenvector0[ i ] =  1/( abs( i - N/2 ) + 1 );

		// allocate device memory
		CUDA_CALL( cudaMalloc((void**)&d_cooRowIndex,nnz*sizeof( int )) );
		CUDA_CALL( cudaMalloc((void**)&d_cooColIndex,nnz*sizeof( int )) );
		CUDA_CALL( cudaMalloc((void**)&d_cooVal, nnz*sizeof( TElement )) );
		CUDA_CALL( cudaMalloc((void**)&d_csrRowPtr, (N+1)*sizeof( int )) );
		CUDA_CALL( cudaMalloc((void**)&d_eigenvector0, N*sizeof( TElement )) );
		CUDA_CALL( cudaMalloc((void**)&d_eigenvector, N*sizeof( TElement )) );
		CUDA_CALL( cudaMalloc( &d_mu, sizeof( TElement ) ) );
		CUDA_CALL( cudaMemset( d_mu, -max_lambda, sizeof( TElement ) ) );

		// copy data to device
		CUDA_CALL( cudaMemcpy( d_cooRowIndex, h_cooRowIndex, (size_t)(nnz*sizeof( int )), cudaMemcpyHostToDevice ) );
		CUDA_CALL( cudaMemcpy( d_cooColIndex, h_cooColIndex, (size_t)(nnz*sizeof( int )), cudaMemcpyHostToDevice ) );
		CUDA_CALL( cudaMemcpy( d_cooVal, h_cooVal, (size_t)(nnz*sizeof( TElement )), cudaMemcpyHostToDevice ) );
		CUDA_CALL( cudaMemcpy( d_eigenvector0, h_eigenvector0, (size_t)(N*sizeof( TElement )), cudaMemcpyHostToDevice ) );
		CUDA_CALL( cudaMemcpy( &mu, d_mu, sizeof( TElement ), cudaMemcpyDeviceToHost ) );

		// initialize cusparse and cusolver
		CUSOLVER_CALL( cusolverSpCreate( &handleCusolver ) );
		CUSPARSE_CALL( cusparseCreate( &handleCusparse ) );

		// create and define cusparse matrix descriptor
		CUSPARSE_CALL( cusparseCreateMatDescr(&descrA) );
		CUSPARSE_CALL( cusparseSetMatType(descrA, CUSPARSE_MATRIX_TYPE_GENERAL ) );
		CUSPARSE_CALL( cusparseSetMatIndexBase(descrA, CUSPARSE_INDEX_BASE_ZERO ) );

		// transform from coordinates (COO) values to compressed row pointers (CSR) values
		CUSPARSE_CALL( cusparseXcoo2csr( handleCusparse, d_cooRowIndex, nnz, N, d_csrRowPtr, CUSPARSE_INDEX_BASE_ZERO ) );

		// call cusolverSp<type>csreigvsi
		CUSOLVER_CALL( cusolverSpTcsreigvsi( &handleCusolver, N, nnz, &descrA, d_cooVal, d_csrRowPtr, d_cooColIndex, max_lambda, d_eigenvector0, maxite, tol, d_mu, d_eigenvector ) );

		cudaDeviceSynchronize();
		CUDA_CALL( cudaGetLastError() );

		// copy from device to host
		CUDA_CALL( cudaMemcpy( &mu, d_mu, (size_t)sizeof( TElement ), cudaMemcpyDeviceToHost ) );
		CUDA_CALL( cudaMemcpy( eigenvector->data(), d_eigenvector, (size_t)(N*sizeof( TElement )), cudaMemcpyDeviceToHost ) );

		// destroy and free stuff
		CUSPARSE_CALL( cusparseDestroyMatDescr( descrA ) );
		CUSPARSE_CALL( cusparseDestroy( handleCusparse ) );
		CUSOLVER_CALL( cusolverSpDestroy( handleCusolver ) );
		CUDA_CALL( cudaFree( d_cooRowIndex ) );
		CUDA_CALL( cudaFree( d_cooColIndex ) );
		CUDA_CALL( cudaFree( d_cooVal ) );
		CUDA_CALL( cudaFree( d_csrRowPtr ) );
		CUDA_CALL( cudaFree( d_eigenvector0 ) );
		CUDA_CALL( cudaFree( d_eigenvector ) );
		CUDA_CALL( cudaFree( d_mu ) );
		delete[] h_eigenvector0;
		delete[] h_cooRowIndex;
		delete[] h_cooColIndex;
		delete[] h_cooVal;

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<float>::dpss( Array<float> *eigenvector, index_t N, double NW, index_t degree );
	template cusolverStatus_t cusolverOperations<double>::dpss( Array<double> *eigenvector, index_t N, double NW, index_t degree );

	template <typename TElement>
	cusolverStatus_t cusolverOperations<TElement>::qr_zerocopy( Array<TElement> *A, Array<TElement> *Q, Array<TElement> *R )
	{
		cusolverDnHandle_t handle;
		CUSOLVER_CALL( cusolverDnCreate(&handle) );

		int M = A->getDim(0);
		int N = A->getDim(1);
		int minMN = std::min(M,N);

		TElement *d_A, *h_A, *TAU, *Workspace, *d_Q, *d_R;
		h_A = A->data();
		CUDA_CALL( cudaMalloc(&d_A, M * N * sizeof(TElement)) );
		CUDA_CALL( cudaMemcpy(d_A, h_A, M * N * sizeof(TElement), cudaMemcpyHostToDevice) );

		int Lwork = 0;
		CUSOLVER_CALL( cusolverDnTgeqrf_bufferSize(&handle, M, N, d_A, M, &Lwork) );
		CUDA_CALL( cudaMalloc(&TAU, minMN * sizeof(TElement)) );
		CUDA_CALL(cudaMalloc(&Workspace, Lwork * sizeof(TElement)));

		int *devInfo;
		CUDA_CALL( cudaMalloc(&devInfo, sizeof(int)) );
		CUDA_CALL( cudaMemset( (void*)devInfo, 0, 1 ) );

		CUSOLVER_CALL( cusolverDnTgeqrf(&handle, M, N, d_A, M, TAU, Workspace, Lwork, devInfo) );

		int devInfo_h = 0;
		CUDA_CALL( cudaMemcpy(&devInfo_h, devInfo, sizeof(int), cudaMemcpyDeviceToHost) );
		if (devInfo_h != 0)
			return CUSOLVER_STATUS_INTERNAL_ERROR;

		// CALL CUDA FUNCTION
		CUDA_CALL( cudaMemcpy( R->data(), d_A, R->getNElements()*sizeof( TElement ), cudaMemcpyDeviceToHost ) );

		// pass host pointer to device
		CUDA_CALL( cudaHostGetDevicePointer( &d_R, R->data(), 0 ) );
		CUDA_CALL( cudaHostGetDevicePointer( &d_Q, Q->data(), 0 ) );

		zeros_under_diag<TElement>( d_R, std::min(R->getDim(0),R->getDim(1)) );
		//for(int j = 0; j < M; j++)
		//	for(int i = j + 1; i < N; i++)
		//		(*R)(i,j) = 0;

		// --- Initializing the output Q matrix (Of course, this step could be done by a kernel function directly on the device)
		//*Q = eye<TElement> ( std::min(Q->getDim(0),Q->getDim(1)) );
		cuda_eye<TElement>( d_Q, std::min(Q->getDim(0),Q->getDim(1)) );
		CUDA_CALL( cudaDeviceSynchronize() );

		// --- CUDA qr_zerocopy execution
		CUSOLVER_CALL( cusolverDnTormqr(&handle, CUBLAS_SIDE_LEFT, CUBLAS_OP_N, M, N, std::min(M, N), d_A, M, TAU, d_Q, M, Workspace, Lwork, devInfo) );
		CUDA_CALL( cudaDeviceSynchronize() );

		CUSOLVER_CALL( cusolverDnDestroy(handle) );

		return CUSOLVER_STATUS_SUCCESS;
	}

	template cusolverStatus_t cusolverOperations<int>::qr_zerocopy( Array<int> *A, Array<int> *Q, Array<int> *R );
	template cusolverStatus_t cusolverOperations<float>::qr_zerocopy( Array<float> *A, Array<float> *Q, Array<float> *R );
	template cusolverStatus_t cusolverOperations<double>::qr_zerocopy( Array<double> *A, Array<double> *Q, Array<double> *R );
	template cusolverStatus_t cusolverOperations<ComplexFloat>::qr_zerocopy( Array<ComplexFloat> *A, Array<ComplexFloat> *Q, Array<ComplexFloat> *R );
	template cusolverStatus_t cusolverOperations<ComplexDouble>::qr_zerocopy( Array<ComplexDouble> *A, Array<ComplexDouble> *Q, Array<ComplexDouble> *R );// transform permutation vector into permutation matrix
	// TODO (or redo) - too slow!!!

	template <typename TElement>
	void cusolverOperations<TElement>::from_permutation_vector_to_permutation_matrix( Array<TElement> *pivotMatrix, Array<int> *pivotVector )
	{
		//pivotVector->print();
		//*pivotMatrix = eye<TElement>(pivotVector->getDim(0));
		//index_t idx1, idx2;
		//for( int i = 0; i < pivotVector->GetDescriptor().GetDim(0); i++ ) {
		//	if( i + 1 == (*pivotVector)(i) )
		//		continue;
		//	else
		//	{
		//		idx1 = i;
		//		idx2 = (*pivotVector)(i)-1;
		//		(*pivotMatrix)( idx1, idx1 ) = 0;
		//		(*pivotMatrix)( idx2, idx2 ) = 0;
		//		(*pivotMatrix)( idx1, idx2 ) = 1;
		//		(*pivotMatrix)( idx2, idx1 ) = 1;
		//	}
		//	pivotMatrix->print();
		//}
		//pivotMatrix->print();
		//
		//*pivotMatrix = eye<TElement>(pivotVector->getDim(0));

		//pivotVector->print();
		//eye<double>(pivotVector->getDim(0)).print();
		Array<TElement> pivotAux = eye<TElement>(pivotVector->GetDescriptor().GetDim(0));
		index_t idx1, idx2;
		for( int i = 0; i < pivotVector->GetDescriptor().GetDim(0); i++ ) {
			idx1 = i;
			idx2 = (*pivotVector)(i)-1;
			pivotAux( idx1, idx1 ) = 0;
			pivotAux( idx2, idx2 ) = 0;
			pivotAux( idx1, idx2 ) = 1;
			pivotAux( idx2, idx1 ) = 1;

			(*pivotMatrix) = pivotAux*(*pivotMatrix);
			pivotAux = eye<TElement>(pivotVector->GetDescriptor().GetDim(0));
			//pivotMatrix->print();
		}
		//pivotMatrix->print();
	}

	cusolverStatus_t cusolverOperations<float>::cusolverSpTcsreigvsi( cusolverSpHandle_t *handle, int m, int nnz, cusparseMatDescr_t *descrA, const float *csrValA, const int *csrRowPtrA, const int *csrColIndA, float mu0, const float *x0, int maxite, float tol, float *mu, float *x )
	{
		return cusolverSpScsreigvsi( *handle, m ,nnz, *descrA, csrValA, csrRowPtrA, csrColIndA, mu0, x0, maxite, tol, mu, x );;
	}

	cusolverStatus_t cusolverOperations<double>::cusolverSpTcsreigvsi( cusolverSpHandle_t *handle, int m, int nnz, cusparseMatDescr_t *descrA, const double *csrValA, const int *csrRowPtrA, const int *csrColIndA, double mu0, const double *x0, int maxite, double tol, double *mu, double *x )
	{
		return cusolverSpDcsreigvsi( *handle, m ,nnz, *descrA, csrValA, csrRowPtrA, csrColIndA, mu0, x0, maxite, tol, mu, x );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverSpTcsreigvsi( cusolverSpHandle_t *handle, int m, int nnz, cusparseMatDescr_t *descrA, const ComplexFloat *csrValA, const int *csrRowPtrA, const int *csrColIndA, ComplexFloat mu0, const ComplexFloat *x0, int maxite, ComplexFloat tol, ComplexFloat *mu, ComplexFloat *x )
	{
		cuFloatComplex mu02 = make_cuFloatComplex( mu0.real(), mu0.imag() );
		return cusolverSpCcsreigvsi( *handle, m ,nnz, *descrA, (const cuFloatComplex*)csrValA, csrRowPtrA, csrColIndA, mu02, (const cuFloatComplex*)x0, maxite, tol.real(), (cuFloatComplex*)mu, (cuFloatComplex*)x );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverSpTcsreigvsi( cusolverSpHandle_t *handle, int m, int nnz, cusparseMatDescr_t *descrA, const ComplexDouble *csrValA, const int *csrRowPtrA, const int *csrColIndA, ComplexDouble mu0, const ComplexDouble *x0, int maxite, ComplexDouble tol, ComplexDouble *mu, ComplexDouble *x )
	{
		cuDoubleComplex mu02 = make_cuDoubleComplex( mu0.real(), mu0.imag() );
		return cusolverSpZcsreigvsi( *handle, m ,nnz, *descrA, (const cuDoubleComplex*)csrValA, csrRowPtrA, csrColIndA, mu02, (const cuDoubleComplex*)x0, maxite, tol.real(), (cuDoubleComplex*)mu, (cuDoubleComplex*)x );
	}

	cusolverStatus_t cusolverOperations<int>::cusolverDnTgeqrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, int *A, int lda, int *Lwork )
	{
		return CUSOLVER_STATUS_SUCCESS;
	}

	cusolverStatus_t cusolverOperations<float>::cusolverDnTgeqrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, float *A, int lda, int *Lwork )
	{
		return cusolverDnSgeqrf_bufferSize( *handle, m, n, A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<double>::cusolverDnTgeqrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, double *A, int lda, int *Lwork )
	{
		return cusolverDnDgeqrf_bufferSize( *handle, m, n, A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverDnTgeqrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, ComplexFloat *A, int lda, int *Lwork )
	{
		return cusolverDnCgeqrf_bufferSize( *handle, m, n, (cuFloatComplex*)A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverDnTgeqrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, ComplexDouble *A, int lda, int *Lwork )
	{
		return cusolverDnZgeqrf_bufferSize( *handle, m, n, (cuDoubleComplex*)A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<int>::cusolverDnTgeqrf( cusolverDnHandle_t *handle, int m, int n, int *A, int lda, int *TAU, int *Workspace, int Lwork, int *devInfo )
	{
		return CUSOLVER_STATUS_SUCCESS;
	}

	cusolverStatus_t cusolverOperations<float>::cusolverDnTgeqrf( cusolverDnHandle_t *handle, int m, int n, float *A, int lda, float *TAU, float *Workspace, int Lwork, int *devInfo )
	{
		return cusolverDnSgeqrf( *handle, m, n, A, lda, TAU, Workspace, Lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<double>::cusolverDnTgeqrf( cusolverDnHandle_t *handle, int m, int n, double *A, int lda, double *TAU, double *Workspace, int Lwork, int *devInfo )
	{
		return cusolverDnDgeqrf( *handle, m, n, A, lda, TAU, Workspace, Lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverDnTgeqrf( cusolverDnHandle_t *handle, int m, int n, ComplexFloat *A, int lda, ComplexFloat *TAU, ComplexFloat *Workspace, int Lwork, int *devInfo )
	{
		return cusolverDnCgeqrf( *handle, m, n, (cuFloatComplex*)A, lda, (cuFloatComplex*)TAU, (cuFloatComplex*)Workspace, Lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverDnTgeqrf( cusolverDnHandle_t *handle, int m, int n, ComplexDouble *A, int lda, ComplexDouble *TAU, ComplexDouble *Workspace, int Lwork, int *devInfo )
	{
		return cusolverDnZgeqrf( *handle, m, n, (cuDoubleComplex*)A, lda, (cuDoubleComplex*)TAU, (cuDoubleComplex*)Workspace, Lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<int>::cusolverDnTormqr( cusolverDnHandle_t *handle, cublasSideMode_t side, cublasOperation_t trans, int m, int n, int k, const int *A, int lda, const int *tau, int *C, int ldc, int *work, int lwork, int *devInfo )
	{
		return CUSOLVER_STATUS_SUCCESS;
	}

	cusolverStatus_t cusolverOperations<float>::cusolverDnTormqr( cusolverDnHandle_t *handle, cublasSideMode_t side, cublasOperation_t trans, int m, int n, int k, const float *A, int lda, const float *tau, float *C, int ldc, float *work, int lwork, int *devInfo )
	{
		return cusolverDnSormqr( *handle, side, trans, m, n, k, A, lda, tau, C, ldc, work, lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<double>::cusolverDnTormqr( cusolverDnHandle_t *handle, cublasSideMode_t side, cublasOperation_t trans, int m, int n, int k, const double *A, int lda, const double *tau, double *C, int ldc, double *work, int lwork, int *devInfo )
	{
		return cusolverDnDormqr( *handle, side, trans, m, n, k, A, lda, tau, C, ldc, work, lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverDnTormqr( cusolverDnHandle_t *handle, cublasSideMode_t side, cublasOperation_t trans, int m, int n, int k, const ComplexFloat *A, int lda, const ComplexFloat *tau, ComplexFloat *C, int ldc, ComplexFloat *work, int lwork, int *devInfo )
	{
		return cusolverDnCunmqr( *handle, side, trans, m, n, k, (const cuFloatComplex*)A, lda, (const cuFloatComplex*)tau, (cuFloatComplex*)C, ldc, (cuFloatComplex*)work, lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverDnTormqr( cusolverDnHandle_t *handle, cublasSideMode_t side, cublasOperation_t trans, int m, int n, int k, const ComplexDouble *A, int lda, const ComplexDouble *tau, ComplexDouble *C, int ldc, ComplexDouble *work, int lwork, int *devInfo )
	{
		return cusolverDnZunmqr( *handle, side, trans, m, n, k, (const cuDoubleComplex*)A, lda, (const cuDoubleComplex*)tau, (cuDoubleComplex*)C, ldc, (cuDoubleComplex*)work, lwork, devInfo );
	}

	cusolverStatus_t cusolverOperations<int>::cusolverDnTgetrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, int *A, int lda, int *Lwork )
	{
		return CUSOLVER_STATUS_SUCCESS;
	}

	cusolverStatus_t cusolverOperations<float>::cusolverDnTgetrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, float *A, int lda, int *Lwork )
	{
		return cusolverDnSgetrf_bufferSize( *handle, m, n, A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<double>::cusolverDnTgetrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, double *A, int lda, int *Lwork )
	{
		return cusolverDnDgetrf_bufferSize( *handle, m, n, A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverDnTgetrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, ComplexFloat *A, int lda, int *Lwork )
	{
		return cusolverDnCgetrf_bufferSize( *handle, m, n, (cuFloatComplex*)A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverDnTgetrf_bufferSize( cusolverDnHandle_t *handle, int m, int n, ComplexDouble *A, int lda, int *Lwork )
	{
		return cusolverDnZgetrf_bufferSize( *handle, m, n, (cuDoubleComplex*)A, lda, Lwork );
	}

	cusolverStatus_t cusolverOperations<int>::cusolverDnTgetrf( cusolverDnHandle_t *handle, int m, int n, int *A, int lda, int *Workspace, int *devIpiv, int *devInfo )
	{
		return CUSOLVER_STATUS_SUCCESS;
	}

	cusolverStatus_t cusolverOperations<float>::cusolverDnTgetrf( cusolverDnHandle_t *handle, int m, int n, float *A, int lda, float *Workspace, int *devIpiv, int *devInfo )
	{
		return cusolverDnSgetrf( *handle, m, n, A, lda, Workspace, devIpiv, devInfo );
	}

	cusolverStatus_t cusolverOperations<double>::cusolverDnTgetrf( cusolverDnHandle_t *handle, int m, int n, double *A, int lda, double *Workspace, int *devIpiv, int *devInfo )
	{
		return cusolverDnDgetrf( *handle, m, n, A, lda, Workspace, devIpiv, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverDnTgetrf( cusolverDnHandle_t *handle, int m, int n, ComplexFloat *A, int lda, ComplexFloat *Workspace, int *devIpiv, int *devInfo )
	{
		return cusolverDnCgetrf( *handle, m, n, (cuFloatComplex*)A, lda, (cuFloatComplex*)Workspace, devIpiv, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverDnTgetrf( cusolverDnHandle_t *handle, int m, int n, ComplexDouble *A, int lda, ComplexDouble *Workspace, int *devIpiv, int *devInfo )
	{
		return cusolverDnZgetrf( *handle, m, n, (cuDoubleComplex*)A, lda, (cuDoubleComplex*)Workspace, devIpiv, devInfo );
	}

	cusolverStatus_t cusolverOperations<int>::cusolverDnTgetrs( cusolverDnHandle_t *handle, cublasOperation_t trans, int n, int nrhs, const int *A, int lda, const int *devIpiv, int *B, int ldb, int *devInfo )
	{
		return CUSOLVER_STATUS_SUCCESS;
	}

	cusolverStatus_t cusolverOperations<float>::cusolverDnTgetrs( cusolverDnHandle_t *handle, cublasOperation_t trans, int n, int nrhs, const float *A, int lda, const int *devIpiv, float *B, int ldb, int *devInfo )
	{
		return cusolverDnSgetrs( *handle, trans, n, nrhs, A, lda, devIpiv, B, ldb, devInfo );
	}

	cusolverStatus_t cusolverOperations<double>::cusolverDnTgetrs( cusolverDnHandle_t *handle, cublasOperation_t trans, int n, int nrhs, const double *A, int lda, const int *devIpiv, double *B, int ldb, int *devInfo )
	{
		return cusolverDnDgetrs( *handle, trans, n, nrhs, A, lda, devIpiv, B, ldb, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexFloat>::cusolverDnTgetrs( cusolverDnHandle_t *handle, cublasOperation_t trans, int n, int nrhs, const ComplexFloat *A, int lda, const int *devIpiv, ComplexFloat *B, int ldb, int *devInfo )
	{
		return cusolverDnCgetrs( *handle, trans, n, nrhs, (const cuFloatComplex*)A, lda, devIpiv, (cuFloatComplex*)B, ldb, devInfo );
	}

	cusolverStatus_t cusolverOperations<ComplexDouble>::cusolverDnTgetrs( cusolverDnHandle_t *handle, cublasOperation_t trans, int n, int nrhs, const ComplexDouble *A, int lda, const int *devIpiv, ComplexDouble *B, int ldb, int *devInfo )
	{
		return cusolverDnZgetrs( *handle, trans, n, nrhs, (const cuDoubleComplex*)A, lda, devIpiv, (cuDoubleComplex*)B, ldb, devInfo );
	}
}


/*
 *
 * Copyright (c) Toon Knapen & Kresimir Fresl 2003
 *
 * Distributed under 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)
 *
 * KF acknowledges the support of the Faculty of Civil Engineering,
 * University of Zagreb, Croatia.
 *
 */

#ifndef BOOST_NUMERIC_BINDINGS_LAPACK_GESV_HPP
#define BOOST_NUMERIC_BINDINGS_LAPACK_GESV_HPP

#include <boost/numeric/bindings/traits/type_traits.hpp>
#include <boost/numeric/bindings/traits/traits.hpp>
#include <boost/numeric/bindings/lapack/lapack.h>
#include <boost/numeric/bindings/traits/detail/array.hpp>
#include <boost/numeric/bindings/lapack/workspace.hpp>
#include <boost/numeric/bindings/lapack/ilaenv.hpp>

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
#  include <boost/static_assert.hpp>
#  include <boost/type_traits/is_same.hpp>
#endif

#include <cassert>

namespace boost { namespace numeric { namespace bindings {

  namespace lapack {

    ///////////////////////////////////////////////////////////////////
    //
    // general system of linear equations A * X = B
    //
    ///////////////////////////////////////////////////////////////////

    /*
     * gesv() computes the solution to a system of linear equations
     * A * X = B, where A is an N-by-N matrix and X and B are N-by-NRHS
     * matrices.
     *
     * The LU decomposition with partial pivoting and row interchanges
     * is used to factor A as A = P * L * U, where P is a permutation
     * matrix, L is unit lower triangular, and U is upper triangular.
     * The factored form of A is then used to solve the system of
     * equations A * X = B.
     */

    namespace detail {

      inline
      void gesv (integer_t const n, integer_t const nrhs,
                 float* a, integer_t const lda, integer_t* ipiv,
                 float* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_SGESV (&n, &nrhs, a, &lda, ipiv, b, &ldb, info);
      }

      inline
      void gesv (integer_t const n, integer_t const nrhs,
                 double* a, integer_t const lda, integer_t* ipiv,
                 double* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_DGESV (&n, &nrhs, a, &lda, ipiv, b, &ldb, info);
      }

      inline
      void gesv (integer_t const n, integer_t const nrhs,
                 traits::complex_f* a, integer_t const lda, integer_t* ipiv,
                 traits::complex_f* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_CGESV (&n, &nrhs,
                      traits::complex_ptr (a), &lda, ipiv,
                      traits::complex_ptr (b), &ldb, info);
      }

      inline
      void gesv (integer_t const n, integer_t const nrhs,
                 traits::complex_d* a, integer_t const lda, integer_t* ipiv,
                 traits::complex_d* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_ZGESV (&n, &nrhs,
                      traits::complex_ptr (a), &lda, ipiv,
                      traits::complex_ptr (b), &ldb, info);
      }

    } //namespace detail

    template <typename MatrA, typename MatrB, typename IVec>
    int gesv (MatrA& a, IVec& ipiv, MatrB& b) {

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::matrix_structure,
        traits::general_t
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrB>::matrix_structure,
        traits::general_t
      >::value));
#endif

      integer_t const n = traits::matrix_size1 (a);
      assert (n == traits::matrix_size2 (a));
      assert (n == traits::matrix_size1 (b));
      assert (n == traits::vector_size (ipiv));

      integer_t info;
      detail::gesv (n, traits::matrix_size2 (b),
                    traits::matrix_storage (a),
                    traits::leading_dimension (a),
                    traits::vector_storage (ipiv),
                    traits::matrix_storage (b),
                    traits::leading_dimension (b),
                    &info);
      return info;
    }

    template <typename MatrA, typename MatrB>
    int gesv (MatrA& a, MatrB& b) {
      // with 'internal' pivot vector

      // gesv() errors:
      //   if (info == 0), successful
      //   if (info < 0), the -info argument had an illegal value
      //   -- we will use -101 if allocation fails
      //   if (info > 0), U(i-1,i-1) is exactly zero
      integer_t info = -101;
      traits::detail::array<integer_t> ipiv (traits::matrix_size1 (a));
      if (ipiv.valid())
        info = gesv (a, ipiv, b);
      return info;
    }

    /*
     * getrf() computes an LU factorization of a general M-by-N matrix A
     * using partial pivoting with row interchanges. The factorization
     * has the form A = P * L * U, where P is a permutation matrix,
     * L is lower triangular with unit diagonal elements (lower
     * trapezoidal if M > N), and U is upper triangular (upper
     * trapezoidal if M < N).
     */

    namespace detail {

      inline
      void getrf (integer_t const n, integer_t const m,
                  float* a, integer_t const lda, integer_t* ipiv, integer_t* info)
      {
        LAPACK_SGETRF (&n, &m, a, &lda, ipiv, info);
      }

      inline
      void getrf (integer_t const n, integer_t const m,
                  double* a, integer_t const lda, integer_t* ipiv, integer_t* info)
      {
        LAPACK_DGETRF (&n, &m, a, &lda, ipiv, info);
      }

      inline
      void getrf (integer_t const n, integer_t const m,
                  traits::complex_f* a, integer_t const
                  lda, integer_t* ipiv, integer_t* info)
      {
        LAPACK_CGETRF (&n, &m, traits::complex_ptr (a), &lda, ipiv, info);
      }

      inline
      void getrf (integer_t const n, integer_t const m,
                  traits::complex_d* a, integer_t const lda,
                  integer_t* ipiv, integer_t* info)
      {
        LAPACK_ZGETRF (&n, &m, traits::complex_ptr (a), &lda, ipiv, info);
      }

    } //namespace detail

    template <typename MatrA, typename IVec>
    int getrf (MatrA& a, IVec& ipiv) {

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::matrix_structure,
        traits::general_t
      >::value));
#endif

      integer_t const n = traits::matrix_size1 (a);
      integer_t const m = traits::matrix_size2 (a);
      assert (traits::vector_size (ipiv) == (m < n ? m : n));

      integer_t info;
      detail::getrf (n, m,
                     traits::matrix_storage (a),
                     traits::leading_dimension (a),
                     traits::vector_storage (ipiv),
                     &info);
      return info;
    }

    /*
     * getrs() solves a system of linear equations A * X = B
     * or A^T * X = B with a general N-by-N matrix A using
     * the LU factorization computed by getrf().
     */

    namespace detail {

      inline
      void getrs (char const trans, integer_t const n, integer_t const nrhs,
                  float const* a, integer_t const lda, integer_t const* ipiv,
                  float* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_SGETRS (&trans, &n, &nrhs, a, &lda, ipiv, b, &ldb, info);
      }

      inline
      void getrs (char const trans, integer_t const n, integer_t const nrhs,
                  double const* a, integer_t const lda, integer_t const* ipiv,
                  double* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_DGETRS (&trans, &n, &nrhs, a, &lda, ipiv, b, &ldb, info);
      }

      inline
      void getrs (char const trans, integer_t const n, integer_t const nrhs,
                  traits::complex_f const* a, integer_t const lda,
                  integer_t const* ipiv,
                  traits::complex_f* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_CGETRS (&trans, &n, &nrhs,
                       traits::complex_ptr (a), &lda, ipiv,
                       traits::complex_ptr (b), &ldb, info);
      }

      inline
      void getrs (char const trans, integer_t const n, integer_t const nrhs,
                  traits::complex_d const* a, integer_t const lda,
                  integer_t const* ipiv,
                  traits::complex_d* b, integer_t const ldb, integer_t* info)
      {
        LAPACK_ZGETRS (&trans, &n, &nrhs,
                       traits::complex_ptr (a), &lda, ipiv,
                       traits::complex_ptr (b), &ldb, info);
      }

    } // namespace detail

    template <typename MatrA, typename MatrB, typename IVec>
    int getrs (char const trans, MatrA const& a, IVec const& ipiv, MatrB& b)
    {
      assert (trans == 'N' || trans == 'T' || trans == 'C');

#ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrA>::matrix_structure,
        traits::general_t
      >::value));
      BOOST_STATIC_ASSERT((boost::is_same<
        typename traits::matrix_traits<MatrB>::matrix_structure,
        traits::general_t
      >::value));
#endif

      integer_t const n = traits::matrix_size1 (a);
      assert (n == traits::matrix_size2 (a));
      assert (n == traits::matrix_size1 (b));
      assert (n == traits::vector_size (ipiv));

      integer_t info;
      detail::getrs (trans, n, traits::matrix_size2 (b),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
                     traits::matrix_storage (a),
#else
                     traits::matrix_storage_const (a),
#endif
                     traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
                     traits::vector_storage (ipiv),
#else
                     traits::vector_storage_const (ipiv),
#endif
                     traits::matrix_storage (b),
                     traits::leading_dimension (b),
                     &info);
      return info;
    }

    template <typename MatrA, typename MatrB, typename IVec>
    inline
    int getrs (MatrA const& a, IVec const& ipiv, MatrB& b) {
      char const no_transpose = 'N';
      return getrs (no_transpose, a, ipiv, b);
    }

    /*
     * getri() computes the inverse of a matrix using
     * the LU factorization computed by getrf().
     */

    namespace detail {

      inline
      void getri (integer_t const n, float* a, integer_t const lda, integer_t const* ipiv,
                  float* work, integer_t const lwork, integer_t* info)
      {
        LAPACK_SGETRI (&n, a, &lda, ipiv, work, &lwork, info);
      }

      inline
      void getri (integer_t const n, double* a, integer_t const lda, integer_t const* ipiv,
                  double* work, integer_t const lwork, integer_t* info)
      {
        LAPACK_DGETRI (&n, a, &lda, ipiv, work, &lwork, info);
      }

      inline
      void getri (integer_t const n, traits::complex_f* a, integer_t const lda,
          integer_t const* ipiv, traits::complex_f* work, integer_t const lwork,
          integer_t* info)
      {
        LAPACK_CGETRI (&n, traits::complex_ptr (a), &lda, ipiv,
            traits::complex_ptr (work), &lwork, info);
      }

      inline
      void getri (integer_t const n, traits::complex_d* a, integer_t const lda,
          integer_t const* ipiv, traits::complex_d* work, integer_t const lwork,
          integer_t* info)
      {
        LAPACK_ZGETRI (&n, traits::complex_ptr (a), &lda, ipiv,
            traits::complex_ptr (work), &lwork, info);
      }

      template <typename MatrA, typename IVec, typename Work>
      int getri (MatrA& a, IVec const& ipiv, Work& work)
      {
        #ifndef BOOST_NUMERIC_BINDINGS_NO_STRUCTURE_CHECK
        BOOST_STATIC_ASSERT((boost::is_same<
              typename traits::matrix_traits<MatrA>::matrix_structure,
              traits::general_t
              >::value));
        #endif

        integer_t const n = traits::matrix_size1 (a);
        assert (n > 0);
        assert (n <= traits::leading_dimension (a));
        assert (n == traits::matrix_size2 (a));
        assert (n == traits::vector_size (ipiv));
        assert (n <= traits::vector_size (work)); //Minimal workspace size

        integer_t info;
        //double* dummy = traits::matrix_storage (a);
        detail::getri (n, traits::matrix_storage (a),
            traits::leading_dimension (a),
#ifndef BOOST_NO_FUNCTION_TEMPLATE_ORDERING
            traits::vector_storage (ipiv),
#else
            traits::vector_storage_const (ipiv),
#endif
            traits::vector_storage (work),
            traits::vector_size (work),
            &info);
        return info;
      }

      inline
      integer_t getri_block(float)
      {
        return lapack::ilaenv(1, "sgetri", "");
      }

      inline
      integer_t getri_block(double)
      {
        return lapack::ilaenv(1, "dgetri", "");
      }

      inline
      integer_t getri_block(traits::complex_f)
      {
        return lapack::ilaenv(1, "cgetri", "");
      }

      inline
      integer_t getri_block(traits::complex_d)
      {
        return lapack::ilaenv(1, "zgetri", "");
      }

    } // namespace detail

    template <typename MatrA, typename IVec>
    int getri(MatrA& a, IVec& ipiv, minimal_workspace)
    {
      typedef typename MatrA::value_type value_type;

      std::ptrdiff_t n = traits::matrix_size1(a);
      traits::detail::array<value_type> work(std::max<std::ptrdiff_t>(1,n));

      return detail::getri(a, ipiv, work);

    }

    // optimal workspace allocation
    template <typename MatrA, typename IVec>
    int getri(MatrA& a, IVec& ipiv, optimal_workspace)
    {
      typedef typename MatrA::value_type value_type;

      std::ptrdiff_t n = traits::matrix_size1(a);
      std::ptrdiff_t nb = detail::getri_block(value_type());
      traits::detail::array<value_type> work(std::max<std::ptrdiff_t>(1,n*nb));

      return detail::getri(a, ipiv, work);
    }

    template <typename MatrA, typename IVec>
    inline
    int getri(MatrA& a, IVec& ipiv)
    {
      return getri(a, ipiv, optimal_workspace());
    }

    template <typename MatrA, typename IVec, typename Work>
    inline
    int getri(MatrA& a, IVec& ipiv, Work& work)
    {
      return detail::getri(a, ipiv, work);
    }

  } // namespace lapack

}}} // namespace boost::numeric::bindings

#endif


/*
 *  carpack.hpp
 *  carpack
 *
 *  Created by Dr. Brandon Kelly on 12/19/12.
 *
 *  Header file containing the class definitions and function protoypes
 *  for CARMCMC.
 *
 */

#ifndef __CARPACK_HDEF__
#define __CARPACK_HDEF__

#include <boost/math/special_functions/binomial.hpp>
#include <stdexcept>
#include <string>
#include <memory>
#include <random.hpp>
#include <proposals.hpp>
#include <samplers.hpp>
#include <steps.hpp>
#include <parameters.hpp>
#include "kfilter.hpp"

/*
 First-order continuous time autoregressive process (CAR(1)) class. Note that this is the same
 as an Ornstein-Uhlenbeck process. A CAR(1) process, Y(t), is defined as

 dY(t) = -omega * (Y(t) - mu) * dt + sigma * dW(t),

 where tau = 1 / omega is the "characteristic time scale" of the process, mu is the mean
 of the process, sigma is the amplitude of the driving noise, and the driving noise dW(t)
 is the derivative of the Weiner process (i.e., a white noise process). The variance of the
 process is Var(Y(t)) = sigma^2 / (2 * omega).

 The data member of this class include the time series values (y), the 1-sigma uncertainties
 on y (yerr), and the time values (time). Note that it is assumed that the CAR(1) process is
 Gaussian, and that the uncertainties on y are normally distributed with mean zero. The member
 functions of this class include methods to calculate the Kalman filter and the logarithms of the
 posterior probability distribution. The parameters of the CAR(1) process are held in
 the value_ private member, where value_ = (mu, log(omega), sigma) and tau = 1 / omega.

 The prior on theta is assumed to be uniform on theta, subject to the an upper bound
 on Var(Y(t)) and omega. The default value of the upper bound on Var(Y(t)) was chosen
 to be 6.9 (i.e., three orders of magnitude when Y(t) is the logarithm of some quantity),
 but this may be overriden through the use of the SetPrior method. The upper bound on
 omega is fixed to be 1 / min(dt), where dt is the vector of time steps.
*/

template <class OmegaType>
class CARMA_Base : public Parameter<arma::vec> {
public:
    // Constructors
    CARMA_Base() {}
    CARMA_Base(bool track, std::string name, std::vector<double> time, std::vector<double> y, std::vector<double> yerr,
               double temperature=1.0) : Parameter<arma::vec>(track, name, temperature)
    {
        // default is to do Bayesian inference
        ignore_prior_ = false;

        // Set the degrees of freedom for the prior on the measurement error scaling parameter
        measerr_dof_ = 50;

        // convert input data to armadillo vectors
        y_  = arma::conv_to<arma::vec>::from(y);
        time_ = arma::conv_to<arma::vec>::from(time);
        yerr_ = arma::conv_to<arma::vec>::from(yerr);

        // default prior bounds on the standard deviation of the time series
        SetPrior(10.0 * sqrt(arma::var(y_)));
    }

    virtual arma::vec StartingValue() = 0;

    std::string StringValue()
    {
        std::stringstream ss;

        ss << log_posterior_;
        for (int i=0; i<value_.n_elem; i++) {
            ss << " " << value_(i);
        }

        std::string theta_str = ss.str();
        return theta_str;
    }

    void Save(arma::vec& new_value)
    {
        // new carma value ---> value_
        value_ = new_value;

        // Update the log-posterior using this new value of theta.
        //
        // IMPORTANT: This assumes that the Kalman filter was calculated
        // using the value of new_value.
        //
        log_posterior_ = 0.0;
        double mu = new_value(2);
        for (int i=0; i<time_.n_elem; i++) {
            double ycent = y_(i) - pKFilter_->mean(i) - mu;
            log_posterior_ += -0.5 * log(pKFilter_->var(i)) - 0.5 * ycent * ycent / pKFilter_->var(i);
        }
        log_posterior_ += LogPrior(new_value);

    }

    // extract the lorentzian parameters from the CARMA parameter vector
    virtual OmegaType ExtractAR(arma::vec theta) = 0;
    // extract the moving-average parameters from the CARMA parameter vector
    virtual arma::vec ExtractMA(arma::vec theta) = 0;
    // extract the variance in the driving noise from the CARMA parameter vector
    virtual double ExtractSigsqr(arma::vec theta) = 0;

    // compute the log-prior of the CARMA parameters
    virtual double LogPrior(arma::vec theta)
    {
        double measerr_scale = theta(1);

        double logprior = -0.5 * measerr_dof_ / measerr_scale -
        (1.0 + measerr_dof_ / 2.0) * log(measerr_scale);

        return logprior;
    }

    virtual void PrintOmega(OmegaType omega) {};

    // compute the log-posterior
    double LogDensity(arma::vec theta)
    {
        // Prior bounds satisfied?
        bool prior_satisfied = CheckPriorBounds(theta);
        if (!prior_satisfied) {
            double logpost = -1.0 * arma::datum::inf;
            return logpost;
        }

        OmegaType omega = ExtractAR(theta);
        arma::vec ma_coefs = ExtractMA(theta);
        double sigsqr = ExtractSigsqr(theta);
        double measerr_scale = theta(1);
        double mu = theta(2);

        // Run the Kalman filter
        pKFilter_->SetSigsqr(sigsqr);
        pKFilter_->SetOmega(omega);
        pKFilter_->SetMA(ma_coefs);
        arma::vec proposed_yerr = sqrt(measerr_scale) * yerr_;
        pKFilter_->SetTimeSeriesErr(proposed_yerr);
        arma::vec ycent = y_ - mu;
        pKFilter_->SetTimeSeries(ycent);
        try {
            pKFilter_->Filter();
        } catch (std::runtime_error& e) {
            std::cout << "Caught a runtime error when trying to run the Kalman Filter: " << e.what() << std::endl;
            std::cout << "Rejecting this proposal..." << std::endl;
            PrintOmega(omega);
            bool prior_satisfied = CheckPriorBounds(theta);
            std::cout << "Prior satisfied: " << prior_satisfied << std::endl;
            double logpost = -1.0 * arma::datum::inf;
            return logpost;
        }

        // calculate the log-likelihood
        double logpost = 0.0;
        for (int i=0; i<time_.n_elem; i++) {
            double ycent = y_(i) - pKFilter_->mean(i) - mu;
            logpost += -0.5 * log(pKFilter_->var(i)) - 0.5 * ycent * ycent / pKFilter_->var(i);
        }

        logpost += LogPrior(theta);

        return logpost;
    }

    bool virtual CheckPriorBounds(arma::vec theta)
    {
        if (ignore_prior_) {return true;}

        double ysigma = theta(0);
        double measerr_scale = theta(1);
        bool prior_satisfied = true;
        if ( (ysigma > max_stdev_) || (ysigma < 0) ||
            (measerr_scale < 0.5) || (measerr_scale > 2.0) )
        {
            prior_satisfied = false;
        }
        return prior_satisfied;
    }

    // Setters and Getters
    arma::vec GetTime() { return time_; }
    arma::vec GetTimeSeries() { return y_; }
    arma::vec GetTimeSeriesErr() { return yerr_; }
    arma::vec GetKalmanMean() { return value_(2) + pKFilter_->mean; }
    arma::vec GetKalmanVar() { return pKFilter_->var; }
    std::shared_ptr<KalmanFilter<OmegaType> > GetKalmanPtr() { return pKFilter_; }

    virtual void SetPrior(double max_stdev) // set the bounds on the uniform prior
    {
        max_stdev_ = max_stdev;
        arma::vec dt = time_(arma::span(1,time_.n_elem-1)) - time_(arma::span(0,time_.n_elem-2));
        max_freq_ = 1.0 / dt.min();
        min_freq_ = 1.0 / (time_.max() - time_.min());
    }

    // Return a copy of the MCMC samples
    std::vector<std::vector<double> > getSamples() {
        int nx = samples_.size();
        int ny = samples_[0].n_elem;
        std::vector<std::vector<double> > samples(nx,std::vector<double>(ny));
        for (int i = 0; i < nx; i++) {
            samples[i] = arma::conv_to<std::vector<double> >::from(samples_[i]);
        }
        return samples;
    }

    // grab the log-prior and log-posterior for a std::vector input
    double getLogPrior(std::vector<double> theta)
    {
        arma::vec armaVec = arma::conv_to<arma::vec>::from(theta);
        return LogPrior(armaVec);
    }
    double getLogDensity(std::vector<double> theta)
    {
        arma::vec armaVec = arma::conv_to<arma::vec>::from(theta);
        return LogDensity(armaVec);
    }

    // set flag for maximum-likelihood estimation
    void SetMLE(bool ignore_prior) {ignore_prior_ = ignore_prior;}

protected:
    // time series data
    arma::vec time_;
    arma::vec y_;
    arma::vec yerr_;
    // pointer to Kalman Filter object. The Kalman filter is the workhorse behind the likelihood calculations.
    std::shared_ptr<KalmanFilter<OmegaType> > pKFilter_;
    // prior parameters
    double max_stdev_; // Maximum value of the standard deviation of the CAR(1) process
	double max_freq_; // Maximum value of omega = 1 / tau
	double min_freq_; // Minimum value of omega = 1 / tau
	int measerr_dof_; // Degrees of freedom for prior on measurement error scaling parameter
    bool ignore_prior_; // If true, then do maximum-likelihood estimation
};

// class for a CAR(1) process
class CAR1 : public CARMA_Base<double> {

public:
	// Constructors //
    CAR1() {}
	CAR1(bool track, std::string name, std::vector<double> time, std::vector<double> y, std::vector<double> yerr,
         double temperature=1.0) : CARMA_Base<double>(track, name, time, y, yerr, temperature)
    {
        pKFilter_ = std::make_shared<KalmanFilter1>(time_, y_, yerr_);
        // Set the size of the parameter vector theta=(mu,sigma,measerr_scale,log(omega))
        value_.set_size(4);
    }

    // extract the AR parameters from the parameter vector
    double ExtractAR(arma::vec theta) { return exp(theta(3)); }
    arma::vec ExtractMA(arma::vec theta) { return arma::zeros<arma::vec>(1); }

    // generate starting values of the CAR(1) parameters
	arma::vec StartingValue();
    arma::vec SetStartingValue(arma::vec init);

    // return the variance of a CAR(1) process
    double ExtractSigsqr(arma::vec theta) {
        return 2.0 * theta(0) * theta(0) * exp(theta(3));
    }

	// Set the bounds on the uniform prior.
    bool CheckPriorBounds(arma::vec theta);
};

/*
 Continuous time autoregressive process of order p.
*/

class CARp : public CARMA_Base<arma::cx_vec> {
public:
    // Constructor
    CARp() {}
    CARp(bool track, std::string name, std::vector<double> time, std::vector<double> y, std::vector<double> yerr, int p,
         double temperature=1.0): CARMA_Base<arma::cx_vec>(track, name, time, y, yerr, temperature), p_(p)
	{
        pKFilter_ = std::make_shared<KalmanFilterp>(time_, y_, yerr_);
		value_.set_size(p_+3);
        ma_coefs_ = arma::zeros(p);
        ma_coefs_(0) = 1.0;
        order_lorentzians_ = true;
        pKFilter_->SetMA(ma_coefs_);
	}

    // calculate the roots of the AR(p) polynomial from the CAR(p) process parameters
    arma::cx_vec ARRoots(arma::vec theta);

    // Return the starting value and set log_posterior_
	arma::vec StartingValue();
    arma::vec SetStartingValue(arma::vec init);
     // return the starting values for the AR and MA parameters
    arma::vec StartingAR();

    // extract the lorentzian parameters from the CARMA parameter vector
    arma::cx_vec ExtractAR(arma::vec theta) {
        return ARRoots(theta);
    }
    // extract the moving-average parameters from the CARMA parameter vector
    arma::vec ExtractMA(arma::vec theta) { return ma_coefs_; }

    double ExtractSigsqr(arma::vec theta) {
        arma::cx_vec ar_roots = ARRoots(theta);
        return theta(0) * theta(0) / Variance(ar_roots, ma_coefs_, 1.0);
    }

    // Calculate the variance of the CAR(p) process
    double Variance(arma::cx_vec alpha_roots, arma::vec ma_coefs, double sigma, double dt=0.0);

    // Set the bounds on the uniform prior.
    bool CheckPriorBounds(arma::vec theta);

    void PrintOmega(arma::cx_vec omega) {
        omega.print("AR Roots:");
    }

protected:
    int p_; // Order of the CAR(p) process
    bool order_lorentzians_; // force the lorentzian centroids to be in order?
private:
    arma::vec ma_coefs_;
};

/*
 Same as CARp class, but using the Belcher et al. (1994) parameterization for the moving average coefficients.
 */
class ZCAR : public CARp
{
public:
    // constructor //
    ZCAR() {}
    ZCAR(bool track, std::string name, std::vector<double> time, std::vector<double> y, std::vector<double> yerr, int p,
         double temperature=1.0) : CARp(track, name, time, y, yerr, p, temperature)
    {
        // set value of kappa
        arma::vec dt = time_(arma::span(1,time_.n_elem-1)) - time_(arma::span(0,time_.n_elem-2));
        kappa_ = 1.0 / dt.min();
        // set the moving average coefficients
        ma_coefs_ = arma::zeros(p_);
        ma_coefs_(0) = 1.0;
        for (int i=1; i<p_; i++) {
            ma_coefs_(i) = boost::math::binomial_coefficient<double>(p_-1, i) / pow(kappa_,i);
        }
        pKFilter_->SetMA(ma_coefs_);
    }
private:
    double kappa_; // minimum frequency resolved by the observation times
    arma::vec ma_coefs_;
};

/*
 Continuous time autoregressive moving average process of order (p,q)
*/

class CARMA : public CARp
{
public:
	// Constructor //
    CARMA() {}
	CARMA(bool track, std::string name, std::vector<double> time, std::vector<double> y, std::vector<double> yerr, int p, int q,
          double temperature=1.0) : CARp(track, name, time, y, yerr, p, temperature), q_(q)
    {
        BOOST_ASSERT_MSG(q < p, "Order of moving average polynomial must be less than order of autoregressive polynomial");
        value_.set_size(p_+q_+3);
    }

    // Return the starting value and set log_posterior_
	arma::vec StartingValue();
    arma::vec SetStartingValue(arma::vec init);

    // return the starting value for the MA coefficients
    arma::vec StartingMA();

    // extract the moving-average parameters from the CARMA parameter vector
    arma::vec ExtractMA(arma::vec theta);

    double ExtractSigsqr(arma::vec theta) {
        arma::cx_vec ar_roots = ARRoots(theta);
        arma::vec ma_coefs = ExtractMA(theta);
        return theta(0) * theta(0) / Variance(ar_roots, ma_coefs, 1.0);
    }

private:
    int q_; // order of moving average polynomial
};

/*
 CARMA(p,p-1) model using the z-transformed parameterization (Belcher et al. 1994). This is the same as the ZCAR model, except
 that kappa is a free parameter for the ZCARMA model.
 */

class ZCARMA : public CARp
{
public:
    ZCARMA() {}
    ZCARMA(bool track, std::string name, std::vector<double> time, std::vector<double> y, std::vector<double> yerr, int p,
           double temperature=1.0) : CARp(track, name, time, y, yerr, p, temperature)
    {
        value_.set_size(p_+4);
        // set default boundaries on kappa
        arma::vec dt = time_(arma::span(1,time_.n_elem-1)) - time_(arma::span(0,time_.n_elem-2));
        kappa_high_ = 1.0 / dt.min();
        // kappa_low_ = 0.9 / dt.min();
        // kappa_low_ = 1.0 / (time_.max() - time_.min());
        kappa_low_ = std::max(1.0 / (time_.max() - time_.min()), 1.0 / (10.0 * arma::median(dt)));
    }

    // Return the starting value and set log_posterior_
	arma::vec StartingValue();
    arma::vec SetStartingValue(arma::vec init);
     // Return the starting value for the kappa parameter
    double StartingKappa();

    // extract the moving-average parameters from the CARMA parameter vector
    arma::vec ExtractMA(arma::vec theta);

    double ExtractSigsqr(arma::vec theta) {
        arma::cx_vec ar_roots = ARRoots(theta);
        arma::vec ma_coefs = ExtractMA(theta);
        return theta(0) * theta(0) / Variance(ar_roots, ma_coefs, 1.0);
    }

    // Set bounds on kappa
    void SetKappaBounds(double kappa_low, double kappa_high) {
        kappa_low_ = kappa_low;
        kappa_high_ = kappa_high;
    }

    // compute the log-prior of the ZCARMA parameters
    double LogPrior(arma::vec theta)
    {
        // first compute prior for measurement error scaling parameter
        double measerr_scale = theta(1);
        double logprior = -0.5 * measerr_dof_ / measerr_scale -
        (1.0 + measerr_dof_ / 2.0) * log(measerr_scale);

        // now compute prior on x = logit(kappa_norm), assuming a uniform prior on kappa
        double logit_kappa = theta(p_+3);
        logprior += -logit_kappa - 2.0 * log(1.0 + exp(-logit_kappa));

        return logprior;
    }

private:
    double kappa_low_, kappa_high_; // prior bounds on the kappa parameter
};

/********************************
	FUNCTION PROTOTYPES
********************************/

double logit(double x);
double inv_logit(double x);

// Check if all of the roots are unique within some fractional tolerance
bool unique_roots(arma::cx_vec roots, double tolerance);

// Return the coefficients of a polynomial given its roots.
arma::vec polycoefs(arma::cx_vec roots);

#endif