#include <itkImage.h>
#include <itkImageFileReader.h>
#include <itkImageFileWriter.h>
#include <itkImageRegionIteratorWithIndex.h>
#include <itkImageLinearIteratorWithIndex.h>
#include <itkImageRegionIterator.h>
#include <itkDiffusionTensor3D.h>

#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>
#include <boost/numeric/ublas/vector.hpp>

#include <boost/math/special_functions/ellint_rf.hpp>
#include <boost/math/special_functions/ellint_rd.hpp>

#include "tkdCmdParser.h"

#include <algorithm> // for sort
#include <cmath> // for pow and sqrt
#include <valarray> // for atan
#include <iostream>
#include <iterator>
#include <vector>

#include <vnl/vnl_vector.h>
#include <vnl/vnl_matrix.h>
#include <vnl/algo/vnl_symmetric_eigensystem.h>

#include "vnl_vector_to_std_vector.h"
#include "std_vector_to_vnl_vector.h"

namespace dki
{
	/**
	 * Calculate diffusion kurtosis maps from given input tensors.
	 *
	 * Modification to Tabesh et al: If eigenvalues are negative set them to 0!
	 *
	 */
	class DkiMaps
	{
	public:

		struct parameters
		{
			std::string inputFileName;
			std::string maskFileName;
			std::string outputFileName;
		};

		typedef double PixelType;

		typedef itk::Image< PixelType, 4 > ImageType;
		typedef itk::Image< PixelType, 3 > OutputImageType;
		typedef itk::ImageFileReader< OutputImageType > MaskReaderType;
		typedef itk::ImageFileReader< ImageType > ReaderType;
		typedef itk::ImageLinearConstIteratorWithIndex< ImageType > ConstIterator4DType;
		typedef itk::ImageLinearIteratorWithIndex< ImageType > Iterator4DType;
		typedef itk::ImageRegionIteratorWithIndex< OutputImageType > Iterator3DType;

		typedef itk::ImageRegionConstIteratorWithIndex< OutputImageType > ConstIterator3DType;
		typedef itk::ImageFileWriter< OutputImageType > WriterType;

		typedef vnl_vector< PixelType > VectorType;
		typedef vnl_matrix< PixelType > MatrixType;

		typedef std::pair< PixelType, VectorType > EigenType;
		typedef std::vector< EigenType > EigenContainerType;

		/**
		 * Constructor.
		 */
		DkiMaps( const parameters& args )
		{
			SetInput( args.inputFileName );
			InitMask( args.maskFileName );
			AllocateOutput();
			InitGlobalIndices();
			CalculateMaps();
			Write( args.outputFileName );
		}

	protected:

		ImageType::Pointer m_Input;
		OutputImageType::Pointer m_Mask;

		// dti maps

		OutputImageType::Pointer m_FA;
		OutputImageType::Pointer m_Trace;
		OutputImageType::Pointer m_Lradial;
		OutputImageType::Pointer m_Laxial;

		// dki maps

		OutputImageType::Pointer m_MK;
		OutputImageType::Pointer m_Kradial;
		OutputImageType::Pointer m_Kaxial;

		// indices
		VectorType m_kvec_to_table;
		MatrixType m_indices_w0000;
		MatrixType m_indices_w1111;
		MatrixType m_indices_w2222;
		MatrixType m_indices_w1122;
		MatrixType m_indices_w0022;
		MatrixType m_indices_w0011;

		/**
		 * Init indices for Wtilda rotation.
		 */
		void InitGlobalIndices()
		{
			MatrixType index_table = GetIndexTable();
			MatrixType kvec_indices = GetKvecIndices();
			m_kvec_to_table = GetKvec2Table( GetRowSort( index_table ), kvec_indices );

			m_indices_w0000 = GetIndicesW( 0, 0, 0, 0, index_table );
			m_indices_w1111 = GetIndicesW( 1, 1, 1, 1, index_table );
			m_indices_w2222 = GetIndicesW( 2, 2, 2, 2, index_table );
			m_indices_w1122 = GetIndicesW( 1, 1, 2, 2, index_table );
			m_indices_w0022 = GetIndicesW( 0, 0, 2, 2, index_table );
			m_indices_w0011 = GetIndicesW( 0, 0, 1, 1, index_table );
		}

		/**
		 * DKI maps.
		 */
		void CalculateMaps()
		{
			ConstIterator4DType it( m_Input, m_Input->GetLargestPossibleRegion() );
			ConstIterator3DType mit( m_Mask, m_Mask->GetLargestPossibleRegion() );

			Iterator3DType itFA( m_FA, m_FA->GetLargestPossibleRegion() );
			Iterator3DType itTrace( m_Trace, m_Trace->GetLargestPossibleRegion() );
			Iterator3DType itLaxial( m_Laxial, m_Laxial->GetLargestPossibleRegion() );
			Iterator3DType itLradial( m_Lradial, m_Lradial->GetLargestPossibleRegion() );

			Iterator3DType itMK( m_MK, m_MK->GetLargestPossibleRegion() );
			Iterator3DType itKaxial( m_Kaxial, m_Kaxial->GetLargestPossibleRegion() );
			Iterator3DType itKradial( m_Kradial, m_Kradial->GetLargestPossibleRegion() );

			it.SetDirection( 3 );
			it.GoToBegin();

			mit.GoToBegin();

			itFA.GoToBegin();
			itTrace.GoToBegin();
			itLaxial.GoToBegin();
			itLradial.GoToBegin();

			itMK.GoToBegin();
			itKaxial.GoToBegin();
			itKradial.GoToBegin();

			if ( m_Input->GetLargestPossibleRegion().GetSize()[3] < 21 )
			{
				std::cerr << "*** ERROR ***: input does not contain less than 21 tensor elements!" << std::endl;
				exit( EXIT_FAILURE );
			}

			unsigned int sliceIndex = 0;

			// for each voxel

			while ( !it.IsAtEnd(), !mit.IsAtEnd() )
			{
				if ( mit.Get() != 0 )
				{

					// slice index
					if ( sliceIndex != it.GetIndex()[2] )
					{
						std::cout << "Processing slice: " << sliceIndex << std::endl;
						sliceIndex = it.GetIndex()[2];
					}

					VectorType b( 6 );
					VectorType kvec( 15 );

					while ( !it.IsAtEndOfLine() )
					{
						unsigned int i = ( it.GetIndex() )[3];

						if ( i < 6 )
							b( i ) = it.Get();
						else
							kvec( i - 6 ) = it.Get();

						++it;
					}

					MatrixType DT = GetDTITensor( b );

					itFA.Set( GetFA( DT ) );
					itTrace.Set( GetTrace( DT ) );

					EigenContainerType eig = GetEigenSystem( DT );

					// set negative eigenvalues to 0

					for ( unsigned int i = 0; i < eig.size(); i++ )
						if ( eig.at( i ).first < 0 )
							eig.at( i ).first = 0;

					itLaxial.Set( eig.at( 0 ).first );
					itLradial.Set( ( eig.at( 1 ).first + eig.at( 2 ).first ) / 2. );

					try
					{
						VectorType kvec_scaled = kvec * ( 1. / std::pow( GetTrace( DT ), 2 ) );
						VectorType k = GetKurtosisValues( kvec_scaled, eig ); // TODO
						itMK.Set( k( 0 ) );
						itKaxial.Set( k( 1 ) );
						itKradial.Set( k( 2 ) );
					} catch ( boost::math::evaluation_error e )
					{
						// convergence error, leave kurtosis output zero?
					}
				}

				// go to next voxel

				it.NextLine();
				++mit;
				++itFA;
				++itTrace;
				++itLaxial;
				++itLradial;

				++itMK;
				++itKaxial;
				++itKradial;
			}
		}

		// %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

		/**
		 * Return eigenvalues and eigenvector sorted from l1 > l2 > l3.
		 */
		EigenContainerType GetEigenSystem( const MatrixType& DT )
		{
			vnl_symmetric_eigensystem< PixelType > eig( DT );

			EigenContainerType container( 3 );
			container.at( 0 ) = EigenType( eig.get_eigenvalue( 2 ), eig.get_eigenvector( 2 ) );
			container.at( 1 ) = EigenType( eig.get_eigenvalue( 1 ), eig.get_eigenvector( 1 ) );
			container.at( 2 ) = EigenType( eig.get_eigenvalue( 0 ), eig.get_eigenvector( 0 ) );
			return container;
		}
		/**
		 * Return sum eigenvalues.
		 */
		PixelType GetTrace( const MatrixType& DT )
		{
			return DT( 0, 0 ) + DT( 1, 1 ) + DT( 2, 2 );
		}

		/**
		 * Return fractional anisotropy (FA).
		 */
		PixelType GetFA( const MatrixType& DT )
		{
			PixelType isp = inner_product( DT, DT );
			if ( isp > 0.0 )
			{
				PixelType trace = GetTrace( DT );
				PixelType anisotropy = 3.0 * isp - trace * trace;
				PixelType fractionalAnisotropy = std::sqrt( anisotropy / ( 2.0 * isp ) );
				return fractionalAnisotropy;
			}
			return 0.0;
		}

		/**
		 * Index table W sort.
		 */
		MatrixType GetIndexTable()
		{
			MatrixType table( 81, 4 );

			unsigned int lin_idx = 0;

			for ( unsigned int i = 0; i < 3; i++ )
				for ( unsigned int j = 0; j < 3; j++ )
					for ( unsigned int k = 0; k < 3; k++ )
						for ( unsigned int l = 0; l < 3; l++ )
						{
							VectorType row( 4 );
							row( 0 ) = i;
							row( 1 ) = j;
							row( 2 ) = k;
							row( 3 ) = l;
							table.set_row( lin_idx, row );
							lin_idx++;
						}

			return table;
		}

		/**
		 * Sort rows in matrix ('ascend' way).
		 */
		MatrixType GetRowSort( const MatrixType& M )
		{
			MatrixType out( M );

			for ( unsigned int r = 0; r < out.rows(); r++ )
			{
				std::vector< PixelType > v = vnl_vector_to_std_vector( out.get_row( r ) );
				std::sort( v.begin(), v.end() );
				out.set_row( r, std_vector_to_vnl_vector( v ) );
			}

			return out;
		}

		/**
		 * Return matching indices.
		 */
		VectorType GetKvec2Table( const MatrixType& table, const MatrixType& kvec )
		{
			VectorType indices( table.rows(), 0 );

			for ( unsigned int i = 0; i < table.rows(); i++ )
			{
				for ( unsigned int j = 0; j < kvec.rows(); j++ )
				{
					if ( kvec.get_row( j ) == table.get_row( i ) )
					{
						indices( i ) = j;
					}
				}
			}
			return indices;
		}

		/**
		 * Kvec:
		 *
		 * k1111 k2222 k3333
		 * k1112 k1113 k1222
		 * k2223 k1333 k2333
		 * k1122 k1133 k2233
		 * k1123 k1223 k1233
		 */
		MatrixType GetKvecIndices()
		{
			MatrixType t( 15, 4 );

			PixelType data[60] =
			{ 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 0, 0, 0, 1, 0, 0, 0, 2, 0, 1, 1, 1, 1, 1, 1, 2, 0, 2, 2, 2, 1, 2, 2, 2, 0, 0, 1, 1, 0, 0,
					2, 2, 1, 1, 2, 2, 0, 0, 1, 2, 0, 1, 1, 2, 0, 1, 2, 2 };

			t.set( data );

			return t;
		}

		/**
		 * Matlab sub2ind.
		 */
		unsigned int sub2ind( unsigned int nrow, unsigned int x, unsigned int y )
		{

			return ( x + y * nrow );
		}

		/**
		 * Return W_ijkl in terms of indices.
		 */
		MatrixType GetIndicesW( unsigned int i, unsigned int j, unsigned int k, unsigned int l, const MatrixType indices )
		{
			MatrixType W( 81, 4 );

			for ( unsigned int r = 0; r < W.rows(); r++ )
			{
				W( r, 0 ) = sub2ind( 3, indices( r, 0 ), i );
				W( r, 1 ) = sub2ind( 3, indices( r, 1 ), j );
				W( r, 2 ) = sub2ind( 3, indices( r, 2 ), k );
				W( r, 3 ) = sub2ind( 3, indices( r, 3 ), l );
			}

			return W;
		}

		/**
		 * Resort kvec.
		 */
		VectorType GetKvecExtended( const VectorType& kvec, const VectorType& indices )
		{
			VectorType out( indices.size(), 0 );

			for ( unsigned int i = 0; i < out.size(); i++ )
			{
				out( i ) = kvec( indices( i ) );
			}
			return out;
		}

		/**
		 * Get column-wise vector of sorted eigen vectors.
		 */
		VectorType EigenVectorsToStretchedFormat( const VectorType& e1, const VectorType& e2, const VectorType& e3 )
		{
			VectorType s( 9 );
			s( 0 ) = e1( 0 );
			s( 1 ) = e2( 0 );
			s( 2 ) = e3( 0 );

			s( 3 ) = e1( 1 );
			s( 4 ) = e2( 1 );
			s( 5 ) = e3( 1 );

			s( 6 ) = e1( 2 );
			s( 7 ) = e2( 2 );
			s( 8 ) = e3( 2 );

			return s;
		}

		/**
		 * Return W~, rotated in DT coordinates.
		 */
		PixelType GetRotatedW( const MatrixType& indices, const EigenContainerType& eig, const VectorType& kvec )
		{
			VectorType V = EigenVectorsToStretchedFormat( eig.at( 0 ).second, eig.at( 1 ).second, eig.at( 2 ).second );

			VectorType col1( indices.rows() );
			VectorType col2( indices.rows() );
			VectorType col3( indices.rows() );
			VectorType col4( indices.rows() );

			for ( unsigned int i = 0; i < col1.size(); i++ )
			{
				col1( i ) = V( indices( i, 0 ) );
				col2( i ) = V( indices( i, 1 ) );
				col3( i ) = V( indices( i, 2 ) );
				col4( i ) = V( indices( i, 3 ) );
			}

			VectorType tmp = element_product( element_product( col1, col2 ), element_product( col3, col4 ) );

			return dot_product( tmp, kvec );
		}

		/**
		 * Return MK, Kaxial and Kradial.
		 *
		 * Given eig
		 * Given kvec (15 kurtosis parameters)
		 */
		VectorType GetKurtosisValues( const VectorType kvec, const EigenContainerType& eig )
		{
			VectorType kvec_extended = GetKvecExtended( kvec, m_kvec_to_table );

			PixelType Wtilda_0000 = GetRotatedW( m_indices_w0000, eig, kvec_extended );
			PixelType Wtilda_0011 = GetRotatedW( m_indices_w0011, eig, kvec_extended );
			PixelType Wtilda_0022 = GetRotatedW( m_indices_w0022, eig, kvec_extended );
			PixelType Wtilda_1111 = GetRotatedW( m_indices_w1111, eig, kvec_extended );
			PixelType Wtilda_1122 = GetRotatedW( m_indices_w1122, eig, kvec_extended );
			PixelType Wtilda_2222 = GetRotatedW( m_indices_w2222, eig, kvec_extended );

			PixelType l1 = eig.at( 0 ).first;
			PixelType l2 = eig.at( 1 ).first;
			PixelType l3 = eig.at( 2 ).first;

			PixelType MK = F1( l1, l2, l3 ) * Wtilda_0000 + F1( l2, l1, l3 ) * Wtilda_1111 + F1( l3, l2, l1 ) * Wtilda_2222 + F2( l1, l2,
					l3 ) * Wtilda_1122 + F2( l2, l1, l3 ) * Wtilda_0022 + F2( l3, l2, l1 ) * Wtilda_0011;

			PixelType Kaxial = ( std::pow( l1 + l2 + l3, 2 ) / 9 * std::pow( l1, 2 ) ) * Wtilda_0000;

			PixelType Kradial = G1( l1, l2, l3 ) * Wtilda_1111 + G1( l1, l3, l2 ) * Wtilda_2222 + G2( l1, l2, l3 ) * Wtilda_1122;

			VectorType k( 3 );
			k( 0 ) = MK;
			k( 1 ) = Kaxial;
			k( 2 ) = Kradial;

			return k;
		}

		/**
		 * Eq (33) Tabesh et al.
		 */
		PixelType G1( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( l2 != l3 )
			{
				PixelType a = std::pow( l1 + l2 + l3, 2 ) / 18 * l1 * std::pow( l2 - l3, 2 );
				PixelType b = 2 * l2 + ( std::pow( l3, 2 ) - 3 * l2 * l3 ) / std::sqrt( l2 * l3 );

				return a * ( 2 * l2 + b );
			}

			return std::pow( l1 + 2 * l2, 2 ) / std::pow( 24. * l2, 2 );
		}

		/**
		 * Eq (34) Tabesh et al.
		 */
		PixelType G2( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( l2 != l3 )
			{
				PixelType a = std::pow( l1 + l2 + l3, 2 ) / 3 * std::pow( l2 - l3, 2 );
				PixelType b = l2 + l3 / std::sqrt( l2 + l3 );

				return a * ( b - 2 );
			}

			return 6 * std::pow( l1 + 2 * l2, 2 ) / std::pow( 72 * l2, 2 );
		}

		/**
		 * Eq (27) Tabesh et al.
		 */
		PixelType F1( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( ( l1 == l2 ) && ( l2 == l3 ) )
			{
				return 0.2;
			} else if ( l1 == l2 )
			{
				return 0.5 * F2( l3, l1, l1 );
			} else if ( l1 == l3 )
			{
				return 0.5 * F2( l2, l1, l1 );
			}

			PixelType a = std::pow( l1 + l2 + l3, 2 ) / 18 * ( l1 - l2 ) * ( l1 - l3 );
			PixelType b = std::sqrt( l2 * l3 ) / l1;
			PixelType c = boost::math::ellint_rf( l1 / l2, l1 / l3, 1. );
			PixelType d = ( 3 * std::pow( l1, 2 ) - l1 * l2 - l1 * l3 - l2 * l3 ) / 3 * l1 * std::sqrt( l2 * l3 );
			PixelType e = boost::math::ellint_rd( l1 / l2, l1 / l3, 1. );

			return a * ( b * c + d * e - 1 );
		}

		/**
		 * Eq (28) Tabesh et al.
		 */
		PixelType F2( PixelType l1, PixelType l2, PixelType l3 )
		{
			if ( l2 != l3 )
			{
				PixelType a = std::pow( l1 + l2 + l3, 2 ) / ( 3 * std::pow( l2 - l3, 2 ) );
				PixelType b = ( l2 + l3 ) / std::sqrt( l2 * l3 );
				PixelType c = boost::math::ellint_rf( l1 / l2, l1 / l3, 1. );
				PixelType d = ( 2 * l1 - l2 + l3 ) / ( 3 * std::sqrt( l2 * l3 ) );
				PixelType e = boost::math::ellint_rd( l1 / l2, l1 / l3, 1. );

				return a * ( b * c + d * e - 2 );
			} else if ( l1 != l3 )
			{
				PixelType a = std::pow( l1 + 2 * l3, 2 ) / std::pow( 144 * l3, 2 ) * std::pow( l1 - l3, 2 );
				PixelType b = l3 * ( l1 + 2 * l3 );
				PixelType c = 1 - ( l1 / l3 );
				PixelType d = l1 * ( l1 - 4 * l3 );

				c = std::sqrt( std::abs< PixelType >( c ) );
				d = d * ( 1. / c ) * std::atan( c );

				return 6 * a * ( b + d );
			}

			return 6. / 15.;
		}

		/**
		 * Return 2-rank DTI tensor.
		 *
		 * layout =>
		 * 		| 0  1  2  |
		 *	    | 1  3  4  |
		 *      | 2  4  5  |
		 */
		MatrixType GetDTITensor( const VectorType& x )
		{
			MatrixType DT( 3, 3 );

			/*
			 m_A_D( i, 0 ) =     Gx * Gx;
			 m_A_D( i, 1 ) =     Gy * Gy;
			 m_A_D( i, 2 ) = 2 * Gx * Gy;
			 m_A_D( i, 3 ) =     Gz * Gz;
			 m_A_D( i, 4 ) = 2 * Gy * Gz;
			 m_A_D( i, 5 ) = 2 * Gx * Gz;
			 */

			DT( 0, 0 ) = x( 0 ); // x * x
			DT( 0, 1 ) = x( 2 );
			DT( 0, 2 ) = x( 5 );

			DT( 1, 0 ) = x( 2 );
			DT( 1, 1 ) = x( 1 ); // y * y
			DT( 1, 2 ) = x( 4 );

			DT( 2, 0 ) = x( 5 );
			DT( 2, 1 ) = x( 4 );
			DT( 2, 2 ) = x( 3 ); // z * z

			return DT;
		}

		/**
		 * Set input image.
		 */
		void SetInput( const std::string& inputFileName )
		{
			if ( !inputFileName.empty() )
			{
				ReaderType::Pointer reader = ReaderType::New();
				reader->SetFileName( inputFileName );
				reader->Update();
				m_Input = reader->GetOutput();
			} else
			{
				std::cerr << "Could not read input: " << inputFileName << "!" << std::endl;
				exit( EXIT_FAILURE );
			}
		}

		/**
			 * Init mask if file given, else create empty mask from input file.
			 */
			void InitMask( const std::string& maskFileName )
			{
				if ( !maskFileName.empty() )
				{
					MaskReaderType::Pointer reader = MaskReaderType::New();
					reader->SetFileName( maskFileName );
					reader->Update();
					m_Mask = reader->GetOutput();
				} else
				{
					OutputImageType::Pointer output = OutputImageType::New();
					OutputImageType::RegionType region3D;
					OutputImageType::IndexType index3D;
					OutputImageType::SizeType size3D;
					OutputImageType::SpacingType spacing3D;
					OutputImageType::PointType origin3D;

					ImageType::RegionType region = m_Input->GetLargestPossibleRegion();
					ImageType::SizeType size = region.GetSize();
					ImageType::IndexType index = region.GetIndex();
					ImageType::SpacingType spacing = m_Input->GetSpacing();
					ImageType::PointType origin = m_Input->GetOrigin();

					size3D[0] = size[0];
					size3D[1] = size[1];
					size3D[2] = size[2];
					index3D[0] = index[0];
					index3D[1] = index[1];
					index3D[2] = index[2];
					origin3D[0] = origin[0];
					origin3D[1] = origin[1];
					origin3D[2] = origin[2];
					spacing3D[0] = spacing[0];
					spacing3D[1] = spacing[1];
					spacing3D[2] = spacing[2];

					region3D.SetSize( size3D );
					region3D.SetIndex( index3D );

					// set
					output->SetRegions( region3D );
					output->SetSpacing( spacing3D );
					output->SetOrigin( origin3D );
					output->Allocate();
					output->FillBuffer( 1 );

					m_Mask = output;
				}
			}

		/**
		 * Allocate all output images to 0.
		 */
		void AllocateOutput()
		{
			OutputImageType::RegionType region3D;
			OutputImageType::IndexType index3D;
			OutputImageType::SizeType size3D;
			OutputImageType::SpacingType spacing3D;
			OutputImageType::PointType origin3D;

			ImageType::RegionType region = m_Input->GetLargestPossibleRegion();
			ImageType::SizeType size = region.GetSize();
			ImageType::IndexType index = region.GetIndex();
			ImageType::SpacingType spacing = m_Input->GetSpacing();
			ImageType::PointType origin = m_Input->GetOrigin();

			size3D[0] = size[0];
			size3D[1] = size[1];
			size3D[2] = size[2];
			index3D[0] = index[0];
			index3D[1] = index[1];
			index3D[2] = index[2];
			origin3D[0] = origin[0];
			origin3D[1] = origin[1];
			origin3D[2] = origin[2];
			spacing3D[0] = spacing[0];
			spacing3D[1] = spacing[1];
			spacing3D[2] = spacing[2];

			region3D.SetSize( size3D );
			region3D.SetIndex( index3D );

			// create
			m_FA = OutputImageType::New();
			m_Trace = OutputImageType::New();
			m_Laxial = OutputImageType::New();
			m_Lradial = OutputImageType::New();

			m_MK = OutputImageType::New();
			m_Kaxial = OutputImageType::New();
			m_Kradial = OutputImageType::New();

			// set
			m_FA->SetRegions( region3D );
			m_FA->SetSpacing( spacing3D );
			m_FA->SetOrigin( origin3D );
			m_FA->Allocate();
			m_FA->FillBuffer( 0 );

			m_Trace->SetRegions( region3D );
			m_Trace->SetSpacing( spacing3D );
			m_Trace->SetOrigin( origin3D );
			m_Trace->Allocate();
			m_Trace->FillBuffer( 0 );

			m_Laxial->SetRegions( region3D );
			m_Laxial->SetSpacing( spacing3D );
			m_Laxial->SetOrigin( origin3D );
			m_Laxial->Allocate();
			m_Laxial->FillBuffer( 0 );

			m_Lradial->SetRegions( region3D );
			m_Lradial->SetSpacing( spacing3D );
			m_Lradial->SetOrigin( origin3D );
			m_Lradial->Allocate();
			m_Lradial->FillBuffer( 0 );

			m_MK->SetRegions( region3D );
			m_MK->SetSpacing( spacing3D );
			m_MK->SetOrigin( origin3D );
			m_MK->Allocate();
			m_MK->FillBuffer( 0 );

			m_Kaxial->SetRegions( region3D );
			m_Kaxial->SetSpacing( spacing3D );
			m_Kaxial->SetOrigin( origin3D );
			m_Kaxial->Allocate();
			m_Kaxial->FillBuffer( 0 );

			m_Kradial->SetRegions( region3D );
			m_Kradial->SetSpacing( spacing3D );
			m_Kradial->SetOrigin( origin3D );
			m_Kradial->Allocate();
			m_Kradial->FillBuffer( 0 );
		}

		/**
		 * Write output images.
		 */
		void Write( const std::string& outputFileName )
		{
			// Writer
			WriterType::Pointer writer = WriterType::New();

			// FA
			writer->SetFileName( ( outputFileName + std::string( "_FA.nii.gz" ) ).c_str() );
			writer->SetInput( m_FA );
			writer->Update();

			// Trace
			writer->SetFileName( ( outputFileName + std::string( "_Trace.nii.gz" ) ).c_str() );
			writer->SetInput( m_Trace );
			writer->Update();

			// L axial
			writer->SetFileName( ( outputFileName + std::string( "_Laxial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Laxial );
			writer->Update();

			// L radial
			writer->SetFileName( ( outputFileName + std::string( "_Lradial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Lradial );
			writer->Update();

			// MK
			writer->SetFileName( ( outputFileName + std::string( "_MK.nii.gz" ) ).c_str() );
			writer->SetInput( m_MK );
			writer->Update();

			// K axial
			writer->SetFileName( ( outputFileName + std::string( "_Kaxial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Kaxial );
			writer->Update();

			// K radial
			writer->SetFileName( ( outputFileName + std::string( "_Kradial.nii.gz" ) ).c_str() );
			writer->SetInput( m_Kradial );
			writer->Update();
		}

	};

} // end namespace dki

/**
 * Create dki maps.
 */
int main( int argc, char ** argv )
{
	tkd::CmdParser p( argv[0], "Create diffusion kurtosis maps." );

	dki::DkiMaps::parameters args;

	p.AddArgument( args.inputFileName, "input" ) ->AddAlias( "i" ) ->SetDescription( "Input 4D image with 21 tensor elements" ) ->SetRequired(
			true );

	p.AddArgument( args.outputFileName, "output" ) ->AddAlias( "o" ) ->SetDescription( "Output filename base" ) ->SetRequired( true );

	p.AddArgument( args.maskFileName, "mask" ) ->AddAlias( "m" ) ->SetDescription( "Mask 3D image" );

	if ( !p.Parse( argc, argv ) )
	{
		p.PrintUsage( std::cout );
		return EXIT_FAILURE;
	}

	dki::DkiMaps maps( args );

	return EXIT_SUCCESS;
}


#include <frovedis.hpp>
#include <frovedis/matrix/jds_crs_hybrid.hpp>
#include <frovedis/matrix/sparse_svd.hpp>

#include <boost/program_options.hpp>

using namespace boost;
using namespace frovedis;
using namespace std;

/*
  input: m x n
  u: m x k (<-m)
  s: k(<-m) x k(<-n)
  vt: k(<-n) x n / v: n x k
 */
void do_sparse_svd(const string& input_matrix, const string& u_file,
                   const string& s_file, const string& v_file, int k,
                   bool binary) {
  time_spent t(DEBUG);
  crs_matrix<double> matrix;
  if(binary) {
    matrix = make_crs_matrix_loadbinary<double>(input_matrix);
  } else {
    matrix = make_crs_matrix_load<double>(input_matrix);
  }
  t.show("load time: ");
  colmajor_matrix<double> u, v;
  diag_matrix_local<double> s;
  time_spent t2(DEBUG), t3(DEBUG);
#if defined(_SX) || defined(__ve__)
  sparse_svd<jds_crs_hybrid<double>, jds_crs_hybrid_local<double>>
    (std::move(matrix), u, s, v, k);
  t2.show("sparse_svd: ");
#else
  sparse_svd<double>(matrix, u, s, v, k);
  t2.show("sparse_svd: ");
#endif
  t3.show("total time w/o I/O: ");
  if(binary) {
    u.to_rowmajor().savebinary(u_file);
    s.savebinary(s_file);
    v.to_rowmajor().savebinary(v_file);
  } else {
    u.to_rowmajor().save(u_file);
    s.save(s_file);
    v.to_rowmajor().save(v_file);
  }
  t2.show("save time: ");
}

int main(int argc, char* argv[]) {
  use_frovedis use(argc, argv);

  using namespace boost::program_options;

  options_description opt("option");
  opt.add_options()
    ("help,h", "print help")
    ("input,i", value<string>(), "input sparse data matrix")
    ("u,u", value<string>(), "left singular vectors to save")
    ("s,s", value<string>(), "singular values to save")
    ("v,v", value<string>(), "right singular vectors to save")
    ("k,k", value<int>(), "number of singular values to compute")
    ("verbose", "set loglevel DEBUG")
    ("verbose2", "set loglevel TRACE")
    ("binary,b", "use binary input/output");

  variables_map argmap;
  store(command_line_parser(argc,argv).options(opt).allow_unregistered().
        run(), argmap);
  notify(argmap);

  string input, s, u, v;
  int k;
  bool binary = false;

  if(argmap.count("help")){
    cerr << opt << endl;
    exit(1);
  }

  if(argmap.count("input")){
    input = argmap["input"].as<string>();
  } else {
    cerr << "input is not specified" << endl;
    cerr << opt << endl;
    exit(1);
  }

  if(argmap.count("s")){
    s = argmap["s"].as<string>();
  } else {
    cerr << "file to store singular value is not specified" << endl;
    cerr << opt << endl;
    exit(1);
  }

  if(argmap.count("v")){
    v = argmap["v"].as<string>();
  } else {
    cerr << "file to store left singular vector is not specified" << endl;
    cerr << opt << endl;
    exit(1);
  }

  if(argmap.count("u")){
    u = argmap["u"].as<string>();
  } else {
    cerr << "file to store right singular vector is not specified" << endl;
    cerr << opt << endl;
    exit(1);
  }

  if(argmap.count("k")){
    k = argmap["k"].as<int>();
  } else {
    cerr << "number of singular value to compute is not specified" << endl;
    cerr << opt << endl;
    exit(1);
  }

  if(argmap.count("binary")){
    binary = true;
  }

  if(argmap.count("verbose")){
    set_loglevel(DEBUG);
  }

  if(argmap.count("verbose2")){
    set_loglevel(TRACE);
  }

  do_sparse_svd(input, u, s, v, k, binary);
}


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_CONSTANT_FACT_9_HPP_INCLUDED
#define BOOST_SIMD_CONSTANT_FACT_9_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{
  /*!
    @ingroup group-constant

    Generate 9! that is 362880,

    @par Header <boost/simd/constant/fact_9.hpp>

    @par Semantic:

    @code
    T r = Fact_9<T>();
    @endcode

    is similar to:

    @code
    T r = T(362880);
    @endcode

    @return The Fact_9 constant for the proper type
  **/
  template<typename T> T Fact_9();

  namespace functional
  {
    /*!
      @ingroup group-callable-constant
      Generate the  constant fact_9.

      @return The Fact_9 constant for the proper type
    **/
    const boost::dispatch::functor<tag::fact_9_> fact_9 = {};
  }
} }
#endif

#include <boost/simd/constant/scalar/fact_9.hpp>
#include <boost/simd/constant/simd/fact_9.hpp>

#endif


//==================================================================================================
/*!

  Copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#include <boost/simd/function/scalar/abs.hpp>
#include <boost/simd/constant/inf.hpp>
#include <boost/simd/constant/minf.hpp>
#include <boost/simd/constant/mone.hpp>
#include <boost/simd/constant/nan.hpp>
#include <boost/simd/constant/one.hpp>
#include <boost/simd/constant/zero.hpp>

#include <boost/simd/function/std.hpp>
#include <boost/simd/function/saturated.hpp>
#include <scalar_test.hpp>

STF_CASE_TPL( "Check abs behavior with floating", STF_IEEE_TYPES )
{
  namespace bs = boost::simd;
  using bs::abs;
  using r_t = decltype(abs(T()));
  STF_TYPE_IS(r_t, T);

#ifndef BOOST_SIMD_NO_INVALIDS
  STF_EQUAL(abs(bs::Inf<T>()),  bs::Inf<r_t>());
  STF_EQUAL(abs(bs::Minf<T>()), bs::Inf<r_t>());
  STF_IEEE_EQUAL(abs(bs::Nan<T>()),  bs::Nan<r_t>());
#endif
  STF_EQUAL(abs(bs::Zero<T>()), bs::Zero<r_t>());
  STF_EQUAL(abs(bs::One<T>()),  bs::One<r_t>());
  STF_EQUAL(abs(bs::Mone<T>()), bs::One<r_t>());
}


///////////////////////////////////////////////////////////////
//  Copyright 2021 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt

//
// Compare arithmetic results using fixed_int to GMP results.
//

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

#include <boost/multiprecision/gmp.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include "timer.hpp"
#include "test.hpp"

#ifdef _MSC_VER
#pragma warning(disable : 4127) //  Conditional expression is constant
#endif

#if !defined(TEST1) && !defined(TEST2) && !defined(TEST3) && !defined(TEST4) && !defined(TEST5) && !defined(TEST6)
#define TEST1
#define TEST2
#define TEST3
#define TEST4
#define TEST5
#define TEST6
#endif

template <class T>
T generate_random(unsigned bits_wanted)
{
   static boost::random::mt19937               gen;
   typedef boost::random::mt19937::result_type random_type;

   T        max_val;
   unsigned digits;
   if (std::numeric_limits<T>::is_bounded && (bits_wanted == (unsigned)std::numeric_limits<T>::digits))
   {
      max_val = (std::numeric_limits<T>::max)();
      digits  = std::numeric_limits<T>::digits;
   }
   else
   {
      max_val = T(1) << bits_wanted;
      digits  = bits_wanted;
   }

   unsigned bits_per_r_val = std::numeric_limits<random_type>::digits - 1;
   while ((random_type(1) << bits_per_r_val) > (gen.max)())
      --bits_per_r_val;

   unsigned terms_needed = digits / bits_per_r_val + 1;

   T val = 0;
   for (unsigned i = 0; i < terms_needed; ++i)
   {
      val *= (gen.max)();
      val += gen();
   }
   val %= max_val;
   return val;
}

template <class Number>
struct tester
{
   typedef Number                                         test_type;
   typedef typename test_type::backend_type::checked_type checked;

   unsigned     last_error_count;
   timer tim;

   boost::multiprecision::mpz_int a, b, c, d;
   int                            si;
   unsigned                       ui;
   boost::multiprecision::double_limb_type large_ui;
   test_type                      a1, b1, c1, d1;

   void t1()
   {
      //
      // Arithmetic, non-mixed:
      //
      boost::multiprecision::mpq_rational x(a, b), y(c, d), z;
      boost::multiprecision::cpp_rational x1(a1, b1), y1(c1, d1), z1;

      BOOST_CHECK_EQUAL(x.str(), x1.str());
      BOOST_CHECK_EQUAL(y.str(), y1.str());

      // positive x, y:
      z = x + y;
      z1 = x1 + y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x - y;
      z1 = x1 - y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x * y;
      z1 = x1 * y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x / y;
      z1 = x1 / y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // negative y:
      y = -y;
      y1 = -y1;
      BOOST_CHECK(y < 0);
      z  = x + y;
      z1 = x1 + y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - y;
      z1 = x1 - y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * y;
      z1 = x1 * y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / y;
      z1 = x1 / y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // negative x:
      x.swap(y);
      x1.swap(y1);
      BOOST_CHECK(x < 0);
      z  = x + y;
      z1 = x1 + y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - y;
      z1 = x1 - y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * y;
      z1 = x1 * y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / y;
      z1 = x1 / y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // negative x, y:
      y = -y;
      y1 = -y1;
      BOOST_CHECK(x < 0);
      BOOST_CHECK(y < 0);
      z  = x + y;
      z1 = x1 + y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - y;
      z1 = x1 - y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * y;
      z1 = x1 * y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / y;
      z1 = x1 / y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // Inplace, negative x, y:
      BOOST_CHECK(x < 0);
      BOOST_CHECK(y < 0);
      z = x;
      z += y;
      z1 = x1;
      z1 += y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z -= y;
      z1 = x1;
      z1 -= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= y;
      z1 = x1;
      z1 *= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= z;
      z1 = x1;
      z1 *= z1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z /= y;
      z1 = x1;
      z1 /= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // Inplace, negative x:
      y  = -y;
      y1 = -y1;
      BOOST_CHECK(x < 0);
      z = x;
      z += y;
      z1 = x1;
      z1 += y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z -= y;
      z1 = x1;
      z1 -= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= y;
      z1 = x1;
      z1 *= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= z;
      z1 = x1;
      z1 *= z1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z /= y;
      z1 = x1;
      z1 /= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // Inplace, negative y:
      x.swap(y);
      x1.swap(y1);
      BOOST_CHECK(y < 0);
      z = x;
      z += y;
      z1 = x1;
      z1 += y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z -= y;
      z1 = x1;
      z1 -= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= y;
      z1 = x1;
      z1 *= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= z;
      z1 = x1;
      z1 *= z1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z /= y;
      z1 = x1;
      z1 /= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // Inplace, positive x, y:
      y = -y;
      y1 = -y1;
      BOOST_CHECK(x > 0);
      BOOST_CHECK(y > 0);
      z = x;
      z += y;
      z1 = x1;
      z1 += y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z -= y;
      z1 = x1;
      z1 -= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= y;
      z1 = x1;
      z1 *= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z *= z;
      z1 = x1;
      z1 *= z1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z /= y;
      z1 = x1;
      z1 /= y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      BOOST_CHECK_EQUAL((x == y), (x1 == y1));
      BOOST_CHECK_EQUAL((x != y), (x1 != y1));
      BOOST_CHECK_EQUAL((x <= y), (x1 <= y1));
      BOOST_CHECK_EQUAL((x >= y), (x1 >= y1));
      BOOST_CHECK_EQUAL((x < y), (x1 < y1));
      BOOST_CHECK_EQUAL((x > y), (x1 > y1));

      z = x;
      z1 = x1;
      BOOST_CHECK_EQUAL((x == z), (x1 == z1));
      BOOST_CHECK_EQUAL((x != z), (x1 != z1));
      BOOST_CHECK_EQUAL((x <= z), (x1 <= z1));
      BOOST_CHECK_EQUAL((x >= z), (x1 >= z1));
      BOOST_CHECK_EQUAL((x < z), (x1 < z1));
      BOOST_CHECK_EQUAL((x > z), (x1 > z1));
   }

   void t2()
   {
      //
      // Mixed with signed integer:
      //
      boost::multiprecision::mpq_rational x(a, b), y(c, d), z;
      boost::multiprecision::cpp_rational x1(a1, b1), y1(c1, d1), z1;

      BOOST_CHECK_EQUAL(x.str(), x1.str());
      BOOST_CHECK_EQUAL(y.str(), y1.str());

      // Both positive:
      z = x + si;
      z1 = x1 + si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x - si;
      z1 = x1 - si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x * si;
      z1 = x1 * si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x / si;
      z1 = x1 / si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si + x;
      z1 = si + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si - x;
      z1 = si - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si * x;
      z1 = si * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si / x;
      z1 = si / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      x = -x;
      x1 = -x1;
      z  = x + si;
      z1 = x1 + si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - si;
      z1 = x1 - si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * si;
      z1 = x1 * si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / si;
      z1 = x1 / si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si + x;
      z1 = si + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si - x;
      z1 = si - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si * x;
      z1 = si * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si / x;
      z1 = si / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x and si both negative:
      z  = x + si;
      z1 = x1 + si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - si;
      z1 = x1 - si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * si;
      z1 = x1 * si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / si;
      z1 = x1 / si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si + x;
      z1 = si + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si - x;
      z1 = si - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si * x;
      z1 = si * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si / x;
      z1 = si / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // si negative:
      x = -x;
      x1 = -x1;
      z  = x + si;
      z1 = x1 + si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - si;
      z1 = x1 - si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * si;
      z1 = x1 * si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / si;
      z1 = x1 / si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si + x;
      z1 = si + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si - x;
      z1 = si - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si * x;
      z1 = si * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = si / x;
      z1 = si / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      si = -si;
      // Inplace:
      z = x;
      z1 = x1;
      z += si;
      z1 += si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = x;
      z1 = x1;
      z -= si;
      z1 -= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= si;
      z1 *= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= si;
      z1 /= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // si negative:
      si = -si;
      z  = x;
      z1 = x1;
      z += si;
      z1 += si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= si;
      z1 -= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= si;
      z1 *= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= si;
      z1 /= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // Both negative:
      x  = -x;
      x1 = -x1;
      z  = x;
      z1 = x1;
      z += si;
      z1 += si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= si;
      z1 -= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= si;
      z1 *= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= si;
      z1 /= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      si = -si;
      z  = x;
      z1 = x1;
      z += si;
      z1 += si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= si;
      z1 -= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= si;
      z1 *= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= si;
      z1 /= si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      BOOST_CHECK_EQUAL((x == si), (x1 == si));
      BOOST_CHECK_EQUAL((x != si), (x1 != si));
      BOOST_CHECK_EQUAL((x <= si), (x1 <= si));
      BOOST_CHECK_EQUAL((x >= si), (x1 >= si));
      BOOST_CHECK_EQUAL((x < si), (x1 < si));
      BOOST_CHECK_EQUAL((x > si), (x1 > si));

      z = si;
      z1 = si;
      BOOST_CHECK_EQUAL((x == si), (x1 == si));
      BOOST_CHECK_EQUAL((x != si), (x1 != si));
      BOOST_CHECK_EQUAL((x <= si), (x1 <= si));
      BOOST_CHECK_EQUAL((x >= si), (x1 >= si));
      BOOST_CHECK_EQUAL((x < si), (x1 < si));
      BOOST_CHECK_EQUAL((x > si), (x1 > si));
   }

   void t3()
   {
      //
      // Mixed with unsigned integer:
      //
      boost::multiprecision::mpq_rational x(a, b), y(c, d), z;
      boost::multiprecision::cpp_rational x1(a1, b1), y1(c1, d1), z1;

      BOOST_CHECK_EQUAL(x.str(), x1.str());
      BOOST_CHECK_EQUAL(y.str(), y1.str());

      // Both positive:
      z  = x + ui;
      z1 = x1 + ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - ui;
      z1 = x1 - ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * ui;
      z1 = x1 * ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / ui;
      z1 = x1 / ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui + x;
      z1 = ui + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui - x;
      z1 = ui - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui * x;
      z1 = ui * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui / x;
      z1 = ui / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      x  = -x;
      x1 = -x1;
      z  = x + ui;
      z1 = x1 + ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - ui;
      z1 = x1 - ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * ui;
      z1 = x1 * ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / ui;
      z1 = x1 / ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui + x;
      z1 = ui + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui - x;
      z1 = ui - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui * x;
      z1 = ui * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = ui / x;
      z1 = ui / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      x = -x;
      x1 = -x1;
      // Inplace:
      z  = x;
      z1 = x1;
      z += ui;
      z1 += ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= ui;
      z1 -= ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= ui;
      z1 *= ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= ui;
      z1 /= ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      x  = -x;
      x1 = -x1;
      z  = x;
      z1 = x1;
      z += ui;
      z1 += ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= ui;
      z1 -= ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= ui;
      z1 *= ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= ui;
      z1 /= ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      x = -x;
      x1 = -x1;

      BOOST_CHECK_EQUAL((x == ui), (x1 == ui));
      BOOST_CHECK_EQUAL((x != ui), (x1 != ui));
      BOOST_CHECK_EQUAL((x <= ui), (x1 <= ui));
      BOOST_CHECK_EQUAL((x >= ui), (x1 >= ui));
      BOOST_CHECK_EQUAL((x < ui), (x1 < ui));
      BOOST_CHECK_EQUAL((x > ui), (x1 > ui));

      z  = ui;
      z1 = ui;
      BOOST_CHECK_EQUAL((x == ui), (x1 == ui));
      BOOST_CHECK_EQUAL((x != ui), (x1 != ui));
      BOOST_CHECK_EQUAL((x <= ui), (x1 <= ui));
      BOOST_CHECK_EQUAL((x >= ui), (x1 >= ui));
      BOOST_CHECK_EQUAL((x < ui), (x1 < ui));
      BOOST_CHECK_EQUAL((x > ui), (x1 > ui));
   }

   void t4()
   {
      //
      // Mixed with unsigned long integer:
      //
      boost::multiprecision::mpq_rational x(a, b), y(c, d), z;
      boost::multiprecision::cpp_rational x1(a1, b1), y1(c1, d1), z1;

      BOOST_CHECK_EQUAL(x.str(), x1.str());
      BOOST_CHECK_EQUAL(y.str(), y1.str());

      // Both positive:
      z  = x + large_ui;
      z1 = x1 + large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - large_ui;
      z1 = x1 - large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * large_ui;
      z1 = x1 * large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / large_ui;
      z1 = x1 / large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui + x;
      z1 = large_ui + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui - x;
      z1 = large_ui - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui * x;
      z1 = large_ui * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui / x;
      z1 = large_ui / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      x  = -x;
      x1 = -x1;
      z  = x + large_ui;
      z1 = x1 + large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - large_ui;
      z1 = x1 - large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * large_ui;
      z1 = x1 * large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / large_ui;
      z1 = x1 / large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui + x;
      z1 = large_ui + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui - x;
      z1 = large_ui - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui * x;
      z1 = large_ui * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = large_ui / x;
      z1 = large_ui / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      x  = -x;
      x1 = -x1;
      // Inplace:
      z  = x;
      z1 = x1;
      z += large_ui;
      z1 += large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= large_ui;
      z1 -= large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= large_ui;
      z1 *= large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= large_ui;
      z1 /= large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      x  = -x;
      x1 = -x1;
      z  = x;
      z1 = x1;
      z += large_ui;
      z1 += large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= large_ui;
      z1 -= large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= large_ui;
      z1 *= large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= large_ui;
      z1 /= large_ui;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      x  = -x;
      x1 = -x1;

      BOOST_CHECK_EQUAL((x == large_ui), (x1 == large_ui));
      BOOST_CHECK_EQUAL((x != large_ui), (x1 != large_ui));
      BOOST_CHECK_EQUAL((x <= large_ui), (x1 <= large_ui));
      BOOST_CHECK_EQUAL((x >= large_ui), (x1 >= large_ui));
      BOOST_CHECK_EQUAL((x < large_ui), (x1 < large_ui));
      BOOST_CHECK_EQUAL((x > large_ui), (x1 > large_ui));

      z  = large_ui;
      z1 = large_ui;
      BOOST_CHECK_EQUAL((x == large_ui), (x1 == large_ui));
      BOOST_CHECK_EQUAL((x != large_ui), (x1 != large_ui));
      BOOST_CHECK_EQUAL((x <= large_ui), (x1 <= large_ui));
      BOOST_CHECK_EQUAL((x >= large_ui), (x1 >= large_ui));
      BOOST_CHECK_EQUAL((x < large_ui), (x1 < large_ui));
      BOOST_CHECK_EQUAL((x > large_ui), (x1 > large_ui));
   }

   void t5()
   {
      //
      // Special cases:
      //
      boost::multiprecision::mpq_rational x(a, b), y(c, d), z;
      boost::multiprecision::cpp_rational x1(a1, b1), y1(c1, d1), z1;

      BOOST_CHECK_EQUAL(x.str(), x1.str());
      BOOST_CHECK_EQUAL(y.str(), y1.str());

      BOOST_CHECK_EQUAL(x1 * 0, 0);
      BOOST_CHECK_EQUAL(x1 * 1, x1);
      BOOST_CHECK_EQUAL(x1 * -1, -x1);

      z = x * y;
      z1 = x1;
      x1 = x1 * y1;
      BOOST_CHECK_EQUAL(z.str(), x1.str());
      x1 = z1;

      x1 = y1 * x1;
      BOOST_CHECK_EQUAL(z.str(), x1.str());
      x1 = z1;

      z = x * si;
      x1 = x1 * si;
      BOOST_CHECK_EQUAL(z.str(), x1.str());
      x1 = z1;

      x1 = si * x1;
      BOOST_CHECK_EQUAL(z.str(), x1.str());
      x1 = z1;

      z1 = x1;
      z1 *= 0;
      BOOST_CHECK_EQUAL(z1, 0);
      z1 = x1;
      z1 *= 1;
      BOOST_CHECK_EQUAL(z1, x1);
      z1 = x1;
      z1 *= -1;
      BOOST_CHECK_EQUAL(z1, -x1);

      z1 = x1 / x1;
      BOOST_CHECK_EQUAL(z1, 1);
      z1 = x1 / -x1;
      BOOST_CHECK_EQUAL(z1, -1);
      z = x / y;
      z1 = x1;
      z1 = z1 / y1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z1 = y1;
      z1 = x1 / z1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      z = x / si;
      z1 = x1;
      z1 = z1 / si;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z = si / x;
      z1 = x1;
      z1 = si / z1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      BOOST_CHECK_THROW(z1 = x1 / 0, std::overflow_error);
      z1= x1;
      BOOST_CHECK_THROW(z1 /= 0, std::overflow_error);
      z1 = x1;
      z1 /= 1;
      BOOST_CHECK_EQUAL(z1, x1);
      z1 /= -1;
      BOOST_CHECK_EQUAL(z1, -x1);
      z1 = x1 / 1;
      BOOST_CHECK_EQUAL(z1, x1);
      z1 = x1 / -1;
      BOOST_CHECK_EQUAL(z1, -x1);

      z1 = 0;
      BOOST_CHECK_EQUAL(z1 * si, 0);
      BOOST_CHECK_EQUAL(z1 / si, 0);
      BOOST_CHECK_EQUAL(z1 + si, si);
      BOOST_CHECK_EQUAL(z1 - si, -si);
      z1 *= si;
      BOOST_CHECK_EQUAL(z1, 0);
      z1 /= si;
      BOOST_CHECK_EQUAL(z1, 0);
      z1 += si;
      BOOST_CHECK_EQUAL(z1, si);
      z1 = 0;
      z1 -= si;
      BOOST_CHECK_EQUAL(z1, -si);

      z1 = si;
      z1 /= si;
      BOOST_CHECK_EQUAL(z1, 1);
      z1 = si;
      z1 /= -si;
      BOOST_CHECK_EQUAL(z1, -1);
      z1 = -si;
      z1 /= si;
      BOOST_CHECK_EQUAL(z1, -1);
      z1 = -si;
      z1 /= -si;
      BOOST_CHECK_EQUAL(z1, 1);

      x1 = si;
      z1 = si / x1;
      BOOST_CHECK_EQUAL(z1, 1);
      x1 = si;
      z1 = x1 / si;
      BOOST_CHECK_EQUAL(z1, 1);
      x1 = -si;
      z1 = si / x1;
      BOOST_CHECK_EQUAL(z1, -1);
      x1 = -si;
      z1 = x1 / si;
      BOOST_CHECK_EQUAL(z1, -1);
      x1 = -si;
      z1 = -si / x1;
      BOOST_CHECK_EQUAL(z1, 1);
      x1 = -si;
      z1 = x1 / -si;
      BOOST_CHECK_EQUAL(z1, 1);
      x1 = si;
      z1 = -si / x1;
      BOOST_CHECK_EQUAL(z1, -1);
      x1 = si;
      z1 = x1 / -si;
      BOOST_CHECK_EQUAL(z1, -1);
   }

   void t6()
   {
      //
      // Mixed with signed integer:
      //
      boost::multiprecision::mpq_rational x(a, b), y(c, d), z;
      boost::multiprecision::cpp_rational x1(a1, b1), y1(c1, d1), z1;

      boost::multiprecision::mpz_int bi = generate_random<boost::multiprecision::mpz_int>(1000);
      boost::multiprecision::cpp_int bi1(bi.str());

      BOOST_CHECK_EQUAL(x.str(), x1.str());
      BOOST_CHECK_EQUAL(y.str(), y1.str());
      BOOST_CHECK_EQUAL(bi.str(), bi1.str());

      // Both positive:
      z  = x + bi;
      z1 = x1 + bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - bi;
      z1 = x1 - bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * bi;
      z1 = x1 * bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / bi;
      z1 = x1 / bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi + x;
      z1 = bi1 + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi - x;
      z1 = bi1 - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi * x;
      z1 = bi1 * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi / x;
      z1 = bi1 / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      x  = -x;
      x1 = -x1;
      z  = x + bi;
      z1 = x1 + bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - bi;
      z1 = x1 - bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * bi;
      z1 = x1 * bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / bi;
      z1 = x1 / bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi + x;
      z1 = bi1 + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi - x;
      z1 = bi1 - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi * x;
      z1 = bi1 * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi / x;
      z1 = bi1 / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x and bi both negative:
      z  = x + bi;
      z1 = x1 + bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - bi;
      z1 = x1 - bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * bi;
      z1 = x1 * bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / bi;
      z1 = x1 / bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi + x;
      z1 = bi1 + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi - x;
      z1 = bi1 - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi * x;
      z1 = bi1 * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi / x;
      z1 = bi1 / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // bi negative:
      x  = -x;
      x1 = -x1;
      z  = x + bi;
      z1 = x1 + bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x - bi;
      z1 = x1 - bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x * bi;
      z1 = x1 * bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x / bi;
      z1 = x1 / bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi + x;
      z1 = bi1 + x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi - x;
      z1 = bi1 - x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi * x;
      z1 = bi1 * x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = bi / x;
      z1 = bi1 / x1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      bi = -bi;
      bi1 = -bi1;
      // Inplace:
      z  = x;
      z1 = x1;
      z += bi;
      z1 += bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= bi;
      z1 -= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= bi;
      z1 *= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= bi;
      z1 /= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // bi negative:
      bi = -bi;
      bi1 = -bi1;
      z  = x;
      z1 = x1;
      z += bi;
      z1 += bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= bi;
      z1 -= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= bi;
      z1 *= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= bi;
      z1 /= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // Both negative:
      x  = -x;
      x1 = -x1;
      z  = x;
      z1 = x1;
      z += bi;
      z1 += bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= bi;
      z1 -= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= bi;
      z1 *= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= bi;
      z1 /= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      // x negative:
      bi = -bi;
      bi1 = -bi1;
      z  = x;
      z1 = x1;
      z += bi;
      z1 += bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z -= bi;
      z1 -= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z *= bi;
      z1 *= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());
      z  = x;
      z1 = x1;
      z /= bi;
      z1 /= bi1;
      BOOST_CHECK_EQUAL(z.str(), z1.str());

      BOOST_CHECK_EQUAL((x == bi), (x1 == bi1));
      BOOST_CHECK_EQUAL((x != bi), (x1 != bi1));
      BOOST_CHECK_EQUAL((x <= bi), (x1 <= bi1));
      BOOST_CHECK_EQUAL((x >= bi), (x1 >= bi1));
      BOOST_CHECK_EQUAL((x < bi), (x1 < bi1));
      BOOST_CHECK_EQUAL((x > bi), (x1 > bi1));

      z  = bi;
      z1 = bi1;
      BOOST_CHECK_EQUAL((x == bi), (x1 == bi1));
      BOOST_CHECK_EQUAL((x != bi), (x1 != bi1));
      BOOST_CHECK_EQUAL((x <= bi), (x1 <= bi1));
      BOOST_CHECK_EQUAL((x >= bi), (x1 >= bi1));
      BOOST_CHECK_EQUAL((x < bi), (x1 < bi1));
      BOOST_CHECK_EQUAL((x > bi), (x1 > bi1));
   }

   void test()
   {
      using namespace boost::multiprecision;

      last_error_count = 0;

      BOOST_CHECK_EQUAL(Number(), 0);

      for (int i = 0; i < 10000; ++i)
      {
         a = generate_random<mpz_int>(1000);
         b = generate_random<mpz_int>(1000);
         c = generate_random<mpz_int>(1000);
         d = generate_random<mpz_int>(1000);

         si       = generate_random<int>(std::numeric_limits<int>::digits - 2);
         ui       = generate_random<unsigned>(std::numeric_limits<unsigned>::digits - 2);
         large_ui = generate_random<boost::multiprecision::double_limb_type>(std::numeric_limits<boost::multiprecision::double_limb_type>::digits - 2);

         a1 = static_cast<test_type>(a.str());
         b1 = static_cast<test_type>(b.str());
         c1 = static_cast<test_type>(c.str());
         d1 = static_cast<test_type>(d.str());

         t1();
         t2();
         t3();
         t4();
         t5();
         t6();

         if (last_error_count != (unsigned)boost::detail::test_errors())
         {
            last_error_count = boost::detail::test_errors();
            std::cout << std::hex << std::showbase;

            std::cout << "a    = " << a << std::endl;
            std::cout << "a1   = " << a1 << std::endl;
            std::cout << "b    = " << b << std::endl;
            std::cout << "b1   = " << b1 << std::endl;
            std::cout << "c    = " << c << std::endl;
            std::cout << "c1   = " << c1 << std::endl;
            std::cout << "d    = " << d << std::endl;
            std::cout << "d1   = " << d1 << std::endl;
            std::cout << "a + b   = " << a + b << std::endl;
            std::cout << "a1 + b1 = " << a1 + b1 << std::endl;
            std::cout << std::dec;
            std::cout << "a - b   = " << a - b << std::endl;
            std::cout << "a1 - b1 = " << a1 - b1 << std::endl;
            std::cout << "-a + b   = " << mpz_int(-a) + b << std::endl;
            std::cout << "-a1 + b1 = " << test_type(-a1) + b1 << std::endl;
            std::cout << "-a - b   = " << mpz_int(-a) - b << std::endl;
            std::cout << "-a1 - b1 = " << test_type(-a1) - b1 << std::endl;
            std::cout << "c*d    = " << c * d << std::endl;
            std::cout << "c1*d1  = " << c1 * d1 << std::endl;
            std::cout << "b*c    = " << b * c << std::endl;
            std::cout << "b1*c1  = " << b1 * c1 << std::endl;
            std::cout << "a/b    = " << a / b << std::endl;
            std::cout << "a1/b1  = " << a1 / b1 << std::endl;
            std::cout << "a/d    = " << a / d << std::endl;
            std::cout << "a1/d1  = " << a1 / d1 << std::endl;
            std::cout << "a%b    = " << a % b << std::endl;
            std::cout << "a1%b1  = " << a1 % b1 << std::endl;
            std::cout << "a%d    = " << a % d << std::endl;
            std::cout << "a1%d1  = " << a1 % d1 << std::endl;
         }

         //
         // Check to see if test is taking too long.
         // Tests run on the compiler farm time out after 300 seconds,
         // so don't get too close to that:
         //
#ifndef CI_SUPPRESS_KNOWN_ISSUES
         if (tim.elapsed() > 200)
#else
         if (tim.elapsed() > 25)
#endif
         {
            std::cout << "Timeout reached, aborting tests now....\n";
            break;
         }
      }
   }
};

int main()
{
   using namespace boost::multiprecision;

   tester<cpp_int> t1;
   t1.test();
   return boost::report_errors();
}


//
// Created by mathis on 28/02/2020.
//

#ifndef DAFT_ENGINE_TYPES_HPP
#define DAFT_ENGINE_TYPES_HPP

#include <Eigen/Core>
#include <Eigen/Geometry>
#include <Eigen/Sparse>

namespace Utils {
using VectorN  = Eigen::Matrix<float, Eigen::Dynamic, 1>;
using VectorNf = Eigen::VectorXf;
using VectorNd = Eigen::VectorXd;

using Vector4  = Eigen::Matrix<float, 4, 1>;
using Vector4f = Eigen::Vector4f;
using Vector4d = Eigen::Vector4d;

using Vector3  = Eigen::Matrix<float, 3, 1>;
using Vector3f = Eigen::Vector3f;
using Vector3d = Eigen::Vector3d;

using Vector2  = Eigen::Matrix<float, 2, 1>;
using Vector2f = Eigen::Vector2f;
using Vector2d = Eigen::Vector2d;

using VectorNi = Eigen::VectorXi;
using Vector2i = Eigen::Vector2i;
using Vector3i = Eigen::Vector3i;
using Vector4i = Eigen::Vector4i;

using VectorNui = Eigen::Matrix<uint, Eigen::Dynamic, 1>;
using Vector1ui = Eigen::Matrix<uint, 1, 1>;
using Vector2ui = Eigen::Matrix<uint, 2, 1>;
using Vector3ui = Eigen::Matrix<uint, 3, 1>;
using Vector4ui = Eigen::Matrix<uint, 4, 1>;

using Ray  = Eigen::ParametrizedLine<float, 3>;
using Rayf = Eigen::ParametrizedLine<float, 3>;
using Rayd = Eigen::ParametrizedLine<double, 3>;

using MatrixN = Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic>;
using Matrix4 = Eigen::Matrix<float, 4, 4>;
using Matrix3 = Eigen::Matrix<float, 3, 3>;
using Matrix2 = Eigen::Matrix<float, 2, 2>;

using MatrixNf = Eigen::MatrixXf;
using Matrix4f = Eigen::Matrix4f;
using Matrix3f = Eigen::Matrix3f;
using Matrix2f = Eigen::Matrix2f;

using MatrixNd = Eigen::MatrixXd;
using Matrix4d = Eigen::Matrix4d;
using Matrix3d = Eigen::Matrix3d;
using Matrix2d = Eigen::Matrix2d;

using MatrixNui = Eigen::Matrix<uint, Eigen::Dynamic, Eigen::Dynamic>;

using Diagonal = Eigen::DiagonalMatrix<float, Eigen::Dynamic>;
using Sparse   = Eigen::SparseMatrix<float>;

using Quaternion  = Eigen::Quaternion<float>;
using Quaternionf = Eigen::Quaternionf;
using Quaterniond = Eigen::Quaterniond;

using Transform  = Eigen::Transform<float, 3, Eigen::Affine>;
using Transformf = Eigen::Affine3f;
using Transformd = Eigen::Affine3d;

using Aabb  = Eigen::AlignedBox<float, 3>;
using Aabbf = Eigen::AlignedBox3f;
using Aabbd = Eigen::AlignedBox3d;

using AngleAxis  = Eigen::AngleAxis<float>;
using AngleAxisf = Eigen::AngleAxisf;
using AngleAxisd = Eigen::AngleAxisd;

using Translation  = Eigen::Translation<float, 3>;
using Translationf = Eigen::Translation3f;
using Translationd = Eigen::Translation3d;
} // namespace Utils

#endif // DAFT_ENGINE_TYPES_HPP


/* Copyright © 2019 Apple Inc. All rights reserved.
 *
 * Use of this source code is governed by a BSD-3-clause license that can
 * be found in the LICENSE.txt file or at
 * https://opensource.org/licenses/BSD-3-Clause
 */

#ifndef TURI_PARAMETER_SAMPLER_H_
#define TURI_PARAMETER_SAMPLER_H_

#include <Eigen/Core>
#include <Eigen/Dense>

#include <algorithm>
#include <cmath>
#include <limits>
#include <random>
#include <vector>

namespace turi {
namespace one_shot_object_detection {

/* A ParameterSampler class to randomly generate different samples of parameters
 * that can later be used to compute the transformation matrix necessary to
 * create image projections.
 */
class ParameterSampler {
 public:
  ParameterSampler(size_t starter_width, size_t starter_height, size_t dx,
                   size_t dy);

  /* Getters for all the parameters:
   * theta: rotation around the x axis.
   * phi: rotation around the y axis.
   * gamma: rotation around the z axis.
   * dz: distance of the object from the camera.
   * focal: focal length of the camera used.
   * transform: The transformation matrix built from the above parameters.
   * warped_corners: The four corners of the object in the warped image.
   */
  double get_theta();
  double get_phi();
  double get_gamma();
  std::vector<double> get_theta_means();
  std::vector<double> get_phi_means();
  std::vector<double> get_gamma_means();
  double get_theta_stdev();
  double get_phi_stdev();
  double get_gamma_stdev();
  size_t get_dz();
  double get_focal();
  Eigen::Matrix<float, 3, 3> get_transform();
  std::vector<Eigen::Vector3f> get_warped_corners();

  /* Setter for warped_corners, built after applying the transformation
   * matrix on the corners of the starter image.
   * Order of warped_corners is top_left, top_right, bottom_left, bottom_right
   */
  void set_warped_corners(const std::vector<Eigen::Vector3f> &warped_corners);

  /* Function to sample all the parameters needed to build a transform, and
   * then also build the transform.
   */
  void sample(size_t background_width, size_t background_height, size_t seed,
              size_t row_number);

 private:
  size_t starter_width_;
  size_t starter_height_;
  size_t max_depth_ = 13000;
  double angle_stdev_ = 20.0;
  double focal_stdev_ = 40.0;
  std::vector<double> theta_means_ = {0.0};
  std::vector<double> phi_means_ = {0.0};
  std::vector<double> gamma_means_ = {0.0};
  double theta_;
  double phi_;
  double gamma_;
  size_t dx_;
  size_t dy_;
  size_t dz_;
  double focal_;
  Eigen::Matrix<float, 3, 3> transform_;
  std::vector<Eigen::Vector3f> warped_corners_;
};

}  // namespace one_shot_object_detection
}  // namespace turi

#endif  // TURI_PARAMETER_SAMPLER_H_


#include <array>
#include <vector>
#include "found_path.hpp"
#include "mesh_components.hpp"
#include <Eigen/Dense>

using namespace Eigen;
using namespace std;

FoundPath::FoundPath(unsigned long int _planeId, Vector3d _pt0, Vector3d _pt1, double _lowerBound, double _upperBound){
    planeId=_planeId;
    pt0=_pt0;
    pt1=_pt1;
    lowerBound=_lowerBound;
    upperBound=_upperBound;
}

unsigned long int FoundPath::PlaneId(){
    return planeId;
}

double FoundPath::UpperBound(){
    return upperBound;
}

double FoundPath::LowerBound(){
    return lowerBound;
}

array<Vector3d, 2> FoundPath::Points(){
    return {pt0, pt1};
}


#include <iostream>

#include <El.hpp>
#include <boost/mpi.hpp>
#include <boost/format.hpp>
#include <skylark.hpp>

#ifndef SKYLARK_AVOID_BOOST_PO

#include <boost/program_options.hpp>
namespace bpo = boost::program_options;

int parse_program_options(int argc, char* argv[],
    int &seed, int &s, int &mind, std::string &infile, std::string &outfile,
    double &sigma) {

    bpo::options_description desc("Options");
    desc.add_options()
        ("help,h", "produce a help message")
        ("input",
            bpo::value<std::string>(&infile),
            "Input file.")
        ("output",
            bpo::value<std::string>(&outfile),
            "Output file.")
        ("seed,s",
            bpo::value<int>(&seed),
            "Seed for random number generation. THIS DETERMINES THE TRANSFORM!.")
        ("sigma,x",
            bpo::value<double>(&sigma),
            "Kernel bandwidth.")
        ("mind,d",
            bpo::value<int>(&mind)->default_value(0),
            "Kernel bandwidth.")
        ("numfeatures,f",
            bpo::value<int>(&s),
            "Number of random features.");

    bpo::positional_options_description positional;
    positional.add("input", 1);
    positional.add("output", 2);

    bpo::variables_map vm;
    try {
        bpo::store(bpo::command_line_parser(argc, argv)
            .options(desc).positional(positional).run(), vm);

        if (vm.count("help")) {
            std::cout << "Usage: " << argv[0]
                      << " [options] input-file-name output-file-name"
                      << std::endl;
            std::cout << desc;
            return 0;
        }

        if (!vm.count("input") || !vm.count("output")) {
            std::cout << "Input and output files are required." << std::endl;
            return -1;
        }

        if (!vm.count("seed")) {
            std::cout << "Seed is required." << std::endl;
            return -1;
        }

        if (!vm.count("sigma")) {
            std::cout << "Sigma is required." << std::endl;
            return -1;
        }

        if (!vm.count("numfeatures")) {
            std::cout << "Number of features is required." << std::endl;
            return -1;
        }

        bpo::notify(vm);
    } catch(bpo::error& e) {
        std::cerr << e.what() << std::endl;
        std::cerr << desc << std::endl;
        return -1;
    }

    return 1000;
}

#else

// TODO

#endif

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

    El::Initialize(argc, argv);

    int seed, s, mind;
    std::string infile, outfile;
    double sigma;

    int flag = parse_program_options(argc, argv, seed, s, mind,
        infile, outfile, sigma);
    if (flag != 1000)
        return flag;

    skylark::base::context_t context(seed);

    boost::mpi::communicator world;
    int rank = world.rank();

    typedef El::DistMatrix<double, El::STAR, El::VR> data_matrix_t;
    typedef El::DistMatrix<El::Int, El::STAR, El::VR> label_matrix_t;

    data_matrix_t X;
    label_matrix_t L;

    boost::mpi::timer timer;

    // Load A and Y
    if (rank == 0) {
        std::cout << "Reading the matrix... ";
        std::cout.flush();
        timer.restart();
    }

    skylark::utility::io::ReadLIBSVM(infile, X, L,
        skylark::base::COLUMNS, mind);

    if (rank == 0)
        std::cout <<"took " << boost::format("%.2e") % timer.elapsed()
                  << " sec\n";

    // Compute kernel matrix
    if (rank == 0) {
        std::cout << "Computing the transformed matrix... ";
        std::cout.flush();
        timer.restart();
    }

    data_matrix_t Z(s, X.Width());
    skylark::ml::gaussian_t k(X.Height(), sigma);
    auto S = k.create_rft<data_matrix_t, data_matrix_t>(s,
        skylark::ml::regular_feature_transform_tag(), context);
    S->apply(X, Z, skylark::sketch::columnwise_tag());

    if (rank == 0)
        std::cout << "took " << boost::format("%.2e") % timer.elapsed()
                  << " sec\n";

    // Write output
    if (rank == 0) {
        std::cout << "Writing the matrix... ";
        std::cout.flush();
        timer.restart();
    }

    skylark::utility::io::WriteLIBSVM(outfile, Z, L, skylark::base::COLUMNS);

    if (rank == 0)
        std::cout <<"took " << boost::format("%.2e") % timer.elapsed()
                  << " sec\n";

    El::Finalize();
    return 0;
}


/* Copyright (c) 2016, Stefan.Eilemann@epfl.ch
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice, this
 *   list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 * - Neither the name of Eyescale Software GmbH nor the names of its
 *   contributors may be used to endorse or promote products derived from this
 *   software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <vmmlib/matrix.hpp>
#include <vmmlib/types.hpp>

#define BOOST_TEST_MODULE matrix
#include <boost/test/unit_test.hpp>

BOOST_AUTO_TEST_CASE(base)
{
    vmml::Matrix4d matrix;
    for (size_t i = 0; i < 16; ++i)
        if ((i % 5) == 0)
            BOOST_CHECK_EQUAL(matrix.array[i], 1.);
        else
            BOOST_CHECK_EQUAL(matrix.array[i], 0.);

    double data[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
    const vmml::Matrix4d matrix2(data, data + 16);
    matrix = matrix2;
    for (size_t i = 0; i < 16; ++i)
    {
        BOOST_CHECK_EQUAL(matrix2.array[i], i + 1.);
        BOOST_CHECK_EQUAL(matrix.array[i], matrix2.array[i]);
    }
}

BOOST_AUTO_TEST_CASE(construction)
{
    vmml::Matrix4f m1(vmml::Quaternionf(), vmml::Vector3f(1.f, 2.f, 3.f));
    const float data[] = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1.f, 2.f, 3.f, 1};
    BOOST_CHECK_EQUAL(m1, vmml::Matrix4f(data, data + 16));
}

BOOST_AUTO_TEST_CASE(lookat)
{
    const vmml::Vector3f eye(1, 2, 3);
    const vmml::Vector3f lookAt(eye + vmml::Vector3f(0, 1, 0));
    const vmml::Vector3f up(0, 0, 1);

    const vmml::Matrix4f m1(eye, lookAt, up);

    vmml::Vector3f newEye, newLookAt, newUp;
    m1.getLookAt(newEye, newLookAt, newUp);
    BOOST_CHECK_EQUAL(eye, newEye);
    BOOST_CHECK_EQUAL(lookAt, newLookAt);
    BOOST_CHECK_EQUAL(up, newUp);
}

BOOST_AUTO_TEST_CASE(nonInvertible)
{
    vmml::Matrix2f m2;
    m2(0, 0) = 0.f;
    m2(1, 1) = 0.f;
    const vmml::Matrix2f i2 = m2.inverse();
    BOOST_CHECK(std::isnan(i2(0, 1)));

    vmml::Matrix3f m3;
    m3(0, 0) = 0.f;
    m3(1, 1) = 0.f;
    m3(2, 2) = 0.f;
    const vmml::Matrix3f i3 = m3.inverse();
    BOOST_CHECK(std::isnan(i3(0, 1)));

    vmml::Matrix4f m4;
    m4(0, 0) = 0.f;
    m4(1, 1) = 0.f;
    m4(2, 2) = 0.f;
    m4(3, 3) = 0.f;
    const vmml::Matrix4f i4 = m4.inverse();
    BOOST_CHECK(std::isnan(i4(0, 1)));
}

BOOST_AUTO_TEST_CASE(invertible)
{
    vmml::Matrix4f m4;
    m4(0, 0) = 0.0007f;
    m4(1, 1) = 0.0007f;
    m4(2, 2) = 0.0007f;
    m4(3, 3) = 1.0f;
    m4(0, 3) = -0.0242f;
    m4(1, 3) = -0.5168f;
    m4(2, 3) = -0.0332f;
    const vmml::Matrix4f i4 = m4.inverse();
    BOOST_CHECK(!std::isnan(i4(0, 1)));
}

// Verify code by instantiating some templates:
template class vmml::Matrix<2, 2, float>;
template class vmml::Matrix<3, 3, float>;
template class vmml::Matrix<4, 4, float>;
template class vmml::Matrix<2, 2, double>;
template class vmml::Matrix<3, 3, short>;
template class vmml::Matrix<3, 4, int>;
template class vmml::Matrix<4, 4, long>;


#include <iostream>
#include <string>
//#include <intrin.h>
//#include <boost/multiprecision/cpp_int.hpp>
#include "../permcomb/concurrent_comb.h"
#include "../common/timer.h"

void test_find_comb(uint32_t fullset, uint32_t subset);
void unit_test();
void unit_test_threaded();
void unit_test_threaded_predicate();
void unit_test_threaded_shard();
void unit_test_comb_by_idx();
void usage_of_comb_by_idx();
void usage_of_next_comb();
void usage_of_next_comb_with_state();
void usage_of_comb_state_by_idx();
void benchmark_comb();

template<typename T>
bool compare_vec(T& results1, T& results2)
{
	if (results1.size() != results2.size())
		return false;

	for (size_t i = 0; i<results1.size(); ++i)
	{
		if (results1[i] != results2[i])
			return false;
	}
	return true;
}

template<typename T1, typename T2>
bool compare_vec2(T1& results1, T2& results2)
{
	if (results1.size() != results2.size())
		return false;

	for (size_t i = 0; i < results1.size(); ++i)
	{
		if (*results1[i] != results2[i])
		{
			return false;
		}
	}
	return true;
}

template<typename T>
void display(T& results)
{
	for (size_t i = 0; i<results.size(); ++i)
	{
		std::cout << results[i] << ", ";
	}
	std::cout << std::endl;
}

template<typename int_type>
bool test_threaded_comb_predicate(int_type thread_cnt, uint32_t fullset_size, uint32_t subset_size)
{
	std::cout << "test_threaded_comb_predicate(" << thread_cnt << ", " << fullset_size << ", " << subset_size << ") starting" << std::endl;

	std::vector<uint32_t> fullset(fullset_size);
	std::iota(fullset.begin(), fullset.end(), 0);

	std::vector<std::vector< std::vector<uint32_t> > > vecvecvec((size_t)thread_cnt);

	concurrent_comb::compute_all_comb(thread_cnt, subset_size, fullset,
		[&vecvecvec](const int thread_index,
			const size_t fullset_cnt,
			const std::vector<uint32_t>& cont) -> bool
	{
		vecvecvec[(size_t)thread_index].push_back(cont);
		return true;
	},
		[](const int thread_index,
			const size_t fullset_cnt,
			const std::vector<uint32_t>& cont,
            const std::string& error) -> void
	{
		std::cerr << error;
	},
		[](uint32_t a, uint32_t b) { return a == b; });

	std::vector<uint32_t> subset(subset_size);
	std::iota(subset.begin(), subset.end(), 0);
	std::vector< std::vector<uint32_t> > vecvec;
	do
	{
		vecvec.push_back(std::vector<uint32_t>(subset.begin(), subset.end()));
	} while (stdcomb::next_combination(fullset.begin(), fullset.end(), subset.begin(), subset.end(), [](uint32_t a, uint32_t b) { return a == b; }));

	// compare results
	size_t cnt = 0;
	bool error = false;
	for (size_t i = 0; i < vecvecvec.size(); ++i)
	{
		for (size_t j = 0; j < vecvecvec[i].size(); ++j, ++cnt)
		{
			if (!compare_vec(vecvec[cnt], vecvecvec[i][j]))
			{
				error = true;

				std::cout << "Perm at " << cnt << " is not the same!" << std::endl;

				display(vecvec[cnt]);
				display(vecvecvec[i][j]);

				return false;
			}
		}
	}

	std::cout << "test_threaded_comb_predicate(" << thread_cnt << ", " << fullset_size << ", " << subset_size <<
		") finished with" << ((error) ? " errors" : " no errors") << std::endl;

	return true;
}

template<typename int_type>
bool test_threaded_comb(int_type thread_cnt, uint32_t fullset_size, uint32_t subset_size)
{
	std::cout << "test_threaded_comb(" << thread_cnt << ", " << fullset_size << ", " << subset_size << ") starting" << std::endl;

	std::vector<uint32_t> fullset(fullset_size);
	std::iota(fullset.begin(), fullset.end(), 0);

	std::vector<std::vector< std::vector<uint32_t> > > vecvecvec((size_t)thread_cnt);

	concurrent_comb::compute_all_comb(thread_cnt, subset_size, fullset,
		[&vecvecvec](const int thread_index,
			const size_t fullset_cnt,
			const std::vector<uint32_t>& cont) -> bool
		{
			vecvecvec[(size_t)thread_index].push_back(cont);
			return true;
		},
        [](const int thread_index,
			const size_t fullset_cnt,
			const std::vector<uint32_t>& cont,
            const std::string& error) -> void
        {
            std::cerr << error;
        });

	std::vector<uint32_t> subset(subset_size);
	std::iota(subset.begin(), subset.end(), 0);
	std::vector< std::vector<uint32_t> > vecvec;
	do
	{
		vecvec.push_back(std::vector<uint32_t>(subset.begin(), subset.end()));
	} while (stdcomb::next_combination(fullset.begin(), fullset.end(), subset.begin(), subset.end()));

	// compare results
	size_t cnt = 0;
	bool error = false;
	for (size_t i = 0; i < vecvecvec.size(); ++i)
	{
		for (size_t j = 0; j < vecvecvec[i].size(); ++j, ++cnt)
		{
			if (!compare_vec(vecvec[cnt], vecvecvec[i][j]))
			{
				error = true;

				std::cout << "Perm at " << cnt << " is not the same!" << std::endl;

				display(vecvec[cnt]);
				display(vecvecvec[i][j]);

				return false;
			}
		}
	}

	std::cout << "test_threaded_comb(" << thread_cnt << ", " << fullset_size << ", " << subset_size <<
		") finished with" << ((error) ? " errors" : " no errors") << std::endl;

	return true;
}

template<typename int_type>
bool test_threaded_comb_shard(int_type thread_cnt, uint32_t fullset_size, uint32_t subset_size)
{
	std::cout << "test_threaded_comb_shard(" << thread_cnt << ", " << fullset_size << ", " << subset_size << ") starting" << std::endl;

	std::vector<uint32_t> fullset(fullset_size);
	std::iota(fullset.begin(), fullset.end(), 0);

	std::vector<std::vector< std::vector<uint32_t> > > vecvecvec((size_t)thread_cnt);

	int_type cpu_cnt = 4;
	std::vector<std::vector<std::vector< std::vector<uint32_t> > > > vecvecvecvec;
	for (int_type i = 0; i < cpu_cnt; ++i)
		vecvecvecvec.push_back(vecvecvec);

	for (int_type i = 0; i < cpu_cnt; ++i)
	{
		int_type cpu_index = i;
		int cpu_index_n = static_cast<int>(cpu_index);

		concurrent_comb::compute_all_comb_shard(cpu_index, cpu_cnt, thread_cnt, subset_size, fullset,
			[&vecvecvecvec, cpu_index_n](const int thread_index,
				const size_t fullset_cnt,
				const std::vector<uint32_t>& cont) -> bool
		{
			vecvecvecvec[cpu_index_n][(size_t)thread_index].push_back(cont);
			return true;
		},
			[](const int thread_index,
				const size_t fullset_cnt,
				const std::vector<uint32_t>& cont,
                const std::string& error) -> void
		{
            std::cerr << error;
		});
	}
	std::vector<uint32_t> subset(subset_size);
	std::iota(subset.begin(), subset.end(), 0);
	std::vector< std::vector<uint32_t> > vecvec;
	do
	{
		vecvec.push_back(std::vector<uint32_t>(subset.begin(), subset.end()));
	} while (stdcomb::next_combination(fullset.begin(), fullset.end(), subset.begin(), subset.end()));

	// compare results
	size_t cnt = 0;
	bool error = false;
	for (size_t k = 0; k < vecvecvecvec.size(); ++k)
	{
		for (size_t i = 0; i < vecvecvecvec[k].size(); ++i)
		{
			for (size_t j = 0; j < vecvecvecvec[k][i].size(); ++j, ++cnt)
			{
				if (!compare_vec(vecvec[cnt], vecvecvecvec[k][i][j]))
				{
					error = true;

					std::cout << "Comb at " << cnt << " is not the same!" << std::endl;

					display(vecvec[cnt]);
					display(vecvecvecvec[k][i][j]);

					return false;
				}
			}
		}
	}
	std::cout << "test_threaded_comb_shard(" << thread_cnt << ", " << fullset_size << ", " << subset_size <<
		") finished with" << ((error) ? " errors" : " no errors") << std::endl;

	return true;
}

// return false to stop processing
template<typename container_type>
struct empty_callback_t
{
	bool operator()(const int thread_index, const size_t fullset_size, const container_type& cont)
	{
		return true;
	}
};

template<typename container_type>
struct error_callback_t
{
	void operator()(const int thread_index, const size_t fullset_size, const container_type& cont, const std::string& error)
	{
		std::cerr << error << std::endl;
	}
};

//typedef boost::multiprecision::cpp_int int_type;
//typedef boost::multiprecision::int128_t int_type;
typedef int64_t int_type;

int main(int argc, char* argv[])
{
	//benchmark_comb();

	//unit_test();

	//unit_test_threaded();

	//unit_test_threaded_predicate();

	//unit_test_threaded_shard();

	//unit_test_comb_by_idx();

	//usage_of_next_comb();

	//usage_of_next_comb_with_state();

	//usage_of_comb_by_idx();

	usage_of_comb_state_by_idx();

	return 0;
}

void benchmark_comb()
{
	std::vector<int> fullset_vec(20);
	std::iota(fullset_vec.begin(), fullset_vec.end(), 0);
	uint32_t subset = 10;

	timer stopwatch;
	{
		std::vector<int> fullset_vec2(fullset_vec.begin(), fullset_vec.end());
		std::vector<int> subset_vec(fullset_vec.begin(), fullset_vec.begin()+ subset);

		stopwatch.start("next_combination");
		while (stdcomb::next_combination(fullset_vec2.begin(), fullset_vec2.end(), subset_vec.begin(), subset_vec.end()))
		{

		}
		stopwatch.stop();
	}

	int_type thread_cnt = 1;

	typedef empty_callback_t<decltype(fullset_vec)> callback_t;
	typedef error_callback_t<decltype(fullset_vec)> err_callback_t;

	stopwatch.start("1 thread(s)");
	thread_cnt = 1;
	concurrent_comb::compute_all_comb(thread_cnt, subset, fullset_vec, callback_t(), err_callback_t());
	stopwatch.stop();

	stopwatch.start("2 thread(s)");
	thread_cnt = 2;
	concurrent_comb::compute_all_comb(thread_cnt, subset, fullset_vec, callback_t(), err_callback_t());
	stopwatch.stop();

	stopwatch.start("3 thread(s)");
	thread_cnt = 3;
	concurrent_comb::compute_all_comb(thread_cnt, subset, fullset_vec, callback_t(), err_callback_t());
	stopwatch.stop();

	stopwatch.start("4 thread(s)");
	thread_cnt = 4;
	concurrent_comb::compute_all_comb(thread_cnt, subset, fullset_vec, callback_t(), err_callback_t());
	stopwatch.stop();
}

void test_find_comb(uint32_t fullset, uint32_t subset)
{
	std::cout << "test_find_comb(" << fullset << "," << subset << ") starting" << std::endl;
	std::vector<uint32_t> fullset_vec(fullset);
	std::vector<uint32_t> results1(subset);
	std::vector<uint32_t> results2(subset);

	std::iota(fullset_vec.begin(), fullset_vec.end(), 0);
	std::iota(results1.begin(), results1.end(), 0);
	std::iota(results2.begin(), results2.end(), 0);

	uint32_t nTotal = 0;
	if (!concurrent_comb::compute_total_comb(fullset, subset, nTotal))
	{
		std::cerr << "compute_total_comb() returns false" << std::endl;
		return;
	}

	bool error = false;
	for(uint32_t j=0; j<nTotal; ++j )
	{
		if(concurrent_comb::find_comb(fullset, subset, j, results1))
		{
			if(compare_vec(results1, results2)==false)
			{
				error = true;
				std::cout << "Perm at " << j << " is not the same!" << std::endl;
				display(results1);
				display(results2);
			}
			else
			{
				//display(results1);
			}
		}
		stdcomb::next_combination(fullset_vec.begin(), fullset_vec.end(), results2.begin(), results2.end());
	}
	std::cout << "test_find_comb(" << fullset << "," << subset << ") finished with" << ((error) ? " errors" : " no errors") << std::endl;
}

void unit_test()
{
	test_find_comb(3,2);
	test_find_comb(4,2);
	test_find_comb(5,3);
	test_find_comb(6,3);
	test_find_comb(7,4);
	test_find_comb(8,4);
	test_find_comb(9,5);
	test_find_comb(10,5);
	test_find_comb(11,6);
	test_find_comb(12,6);
	test_find_comb(13,7);
	test_find_comb(14,7);
	test_find_comb(15,8);
	test_find_comb(16,8);
	test_find_comb(17,9);
	test_find_comb(18,9);
}

void unit_test_threaded()
{
	int_type thread_cnt = 4;
	test_threaded_comb(thread_cnt, 5, 3);
	test_threaded_comb(thread_cnt, 6, 3);
	test_threaded_comb(thread_cnt, 7, 4);
	test_threaded_comb(thread_cnt, 8, 4);
	test_threaded_comb(thread_cnt, 9, 5);
	test_threaded_comb(thread_cnt, 10, 5);
	thread_cnt = 10;
	test_threaded_comb(thread_cnt, 2, 1);
}

void unit_test_threaded_predicate()
{
	int_type thread_cnt = 4;
	test_threaded_comb_predicate(thread_cnt, 5, 3);
	test_threaded_comb_predicate(thread_cnt, 6, 3);
	test_threaded_comb_predicate(thread_cnt, 7, 4);
	test_threaded_comb_predicate(thread_cnt, 8, 4);
	test_threaded_comb_predicate(thread_cnt, 9, 5);
	test_threaded_comb_predicate(thread_cnt, 10, 5);
}

void unit_test_threaded_shard()
{
	int_type thread_cnt = 2;
	test_threaded_comb_shard(thread_cnt, 5, 3);
	test_threaded_comb_shard(thread_cnt, 6, 3);
	test_threaded_comb_shard(thread_cnt, 7, 4);
	test_threaded_comb_shard(thread_cnt, 8, 4);
	test_threaded_comb_shard(thread_cnt, 9, 5);
	test_threaded_comb_shard(thread_cnt, 10, 5);
	//thread_cnt = 10;
	//test_threaded_comb_shard(thread_cnt, 2, 1); // should fail
}

void unit_test_comb_by_idx()
{
	uint64_t index_to_find = 0;
	std::string original_text = "123456";
	std::string std_combined = "123";
	while (true)
	{
		std::string combined = concurrent_comb::find_comb_by_idx(std_combined.size(), index_to_find, original_text);
		if (combined.size() == 0)
		{
			std::cout << "Ended at index: " << index_to_find << std::endl;
			break;
		}
		if (compare_vec(combined, std_combined) == false)
		{
			std::cout << "string not equal at index: " << index_to_find << std::endl;
			break;
		}
		++index_to_find;
		stdcomb::next_combination(original_text.begin(), original_text.end(), std_combined.begin(), std_combined.end());
	}

}

void usage_of_next_comb()
{
	std::string original_text = "1234567890ABCDEFGHIJKLMNO";
	std::string std_combined = "12345678";
	uint64_t total = 0;
	if(concurrent_comb::compute_total_comb(original_text.size(), std_combined.size(), total))
	{
		timer stopwatch;
		stopwatch.start("next_comb");

		//std::cout << std_combined << std::endl;
		for (uint64_t i = 1; i < total; ++i)
		{
			stdcomb::next_combination(original_text.begin(), original_text.end(), std_combined.begin(), std_combined.end());
			//std::cout << std_combined << std::endl;
		}
		stopwatch.stop();
	}
	std::cout << std_combined << std::endl;
}

void usage_of_next_comb_with_state()
{
	//std::string original_text = "1234567890ABCDEFGHIJKLMNO";
	//std::string std_combined = "12345678";

	std::string original_text = "123456";
	std::string std_combined = "123";

	uint64_t total = 0;
	std::vector<std::string::iterator> state;
	std::string::iterator it = original_text.begin();
	for (size_t j = 0; j < std_combined.size(); ++j, ++it)
	{
		state.push_back(it);
	}
	if (concurrent_comb::compute_total_comb(original_text.size(), std_combined.size(), total))
	{
		timer stopwatch;
		stopwatch.start("next_comb_with_state");

		//std::cout << std_combined << std::endl;
		for (uint64_t i = 1; i < total; ++i)
		{
			stdcomb::next_combination_with_state(original_text.begin(), original_text.end(), state.begin(), state.end());
			//std::cout << std_combined << std::endl;
			for (auto s : state)
			{
				std::cout << *s;
			}
			std::cout << std::endl;
		}
		stopwatch.stop();

	}
/*
	// Checking if all_results is correct
	std_combined = "123";
	for (size_t i = 0; i < all_results.size(); ++i)
	{
		//std::cout << all_results[i] << std::endl;
		if (compare_vec2(all_results[i], std_combined) == false)
		{
			std::cout << "string not equal at index: " << i << std::endl;
			break;
		}
		stdcomb::next_combination(original_text.begin(), original_text.end(), std_combined.begin(), std_combined.end());
	}

	for (size_t j = 0; j < std_combined.size(); ++j, ++it)
	{
		std_combined[j] = *state[j];
	}
	std::cout << std_combined << std::endl;
	*/
	/* output
	123
	124
	125
	126
	134
	135
	136
	145
	146
	156
	234
	235
	236
	245
	246
	256
	345
	346
	356
	456
	*/
}

void usage_of_comb_by_idx()
{
	uint64_t index_to_find = 0;
	std::string original_text = "123456";
	std::string std_combined = "123";
	std::vector<std::string> all_results;
	size_t repeat_times = 5;
	for (size_t i = 0; i < 4; ++i)
	{
		std::string combined = concurrent_comb::find_comb_by_idx(std_combined.size(), index_to_find, original_text);

		std::thread th([original_text, combined, &all_results, repeat_times] () mutable {

			// do your work on the combined result, instead of pushing to vector
			all_results.push_back(combined);
			for (size_t j = 1; j < repeat_times; ++j)
			{
				stdcomb::next_combination(original_text.begin(), original_text.end(), combined.begin(), combined.end());
				// do your work on the combined result, instead of pushing to vector
				all_results.push_back(combined);
			}
		});
		th.join();

		index_to_find += repeat_times;
	}

	// Checking if all_results is correct
	std_combined = "123";
	for (size_t i = 0; i < all_results.size(); ++i)
	{
		std::cout << all_results[i] << std::endl;
		if (compare_vec(all_results[i], std_combined) == false)
		{
			std::cout << "string not equal at index: " << i << std::endl;
			break;
		}
		stdcomb::next_combination(original_text.begin(), original_text.end(), std_combined.begin(), std_combined.end());
	}

}

void usage_of_comb_state_by_idx()
{
	uint64_t index_to_find = 0;
	std::string original_text = "123456";
	std::string std_combined = "123";
	std::vector< std::vector<std::string::iterator> > all_results;
	size_t repeat_times = 5;
	for (size_t i = 0; i < 4; ++i)
	{
		std::vector<std::string::iterator> combined = concurrent_comb::find_comb_state_by_idx(std_combined.size(), index_to_find, original_text);

		std::thread th([&original_text, combined, &all_results, repeat_times]() mutable {

			// do your work on the combined result, instead of pushing to vector
			all_results.push_back(combined);
			for (size_t j = 1; j < repeat_times; ++j)
			{
				stdcomb::next_combination_with_state(original_text.begin(), original_text.end(), combined.begin(), combined.end());
				// do your work on the combined result, instead of pushing to vector
				all_results.push_back(combined);
			}
		});
		th.join();

		index_to_find += repeat_times;
	}

	// Checking if all_results is correct
	std_combined = "123";
	for (size_t i = 0; i < all_results.size(); ++i)
	{
		//std::cout << all_results[i] << std::endl;
		for (auto s : all_results[i])
		{
			std::cout << *s;
		}
		std::cout << std::endl;
		if (compare_vec2(all_results[i], std_combined) == false)
		{
			std::cout << "string not equal at index: " << i << std::endl;
			break;
		}
		stdcomb::next_combination(original_text.begin(), original_text.end(), std_combined.begin(), std_combined.end());
	}

}


//==================================================================================================
/*!
  @file

    @copyright 2016 NumScale SAS

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_FUNCTION_CEIL_HPP_INCLUDED
#define BOOST_SIMD_FUNCTION_CEIL_HPP_INCLUDED

#if defined(DOXYGEN_ONLY)
namespace boost { namespace simd
{

  /*!
    @ingroup group-arithmetic
    This function object computes the smallest integral representable value of
    its parameter type which is greater or equal to it.

    @par Header <boost/simd/function/ceil.hpp>

    @par Notes

     - @c ceil is also used as parameter to pass to @ref div or @ref rem

    @par Decorators

    - std_ for floating entries call std::ceil

    @see  floor, round, nearbyint, trunc, iceil

    @par Example:

      @snippet ceil.cpp ceil

    @par Possible output:

      @snippet ceil.txt ceil

  **/
  Value ceil(Value const& x);
} }
#endif

#include <boost/simd/function/scalar/ceil.hpp>
#include <boost/simd/function/simd/ceil.hpp>

#endif


#pragma once

#include "emu_core/memory.hpp"
#include <boost/random/uniform_int_distribution.hpp>
#include <cstdint>
#include <iostream>
#include <list>
#include <queue>
#include <random>

namespace emu::module::random {

class Mt19937Device : public Memory16 {
public:
    using IntType = uint32_t;
    using CrReg = uint8_t;
    using OutReg = uint8_t;

    enum class Register : Memory16::Address_t {
        kSeed0,
        kSeed1,
        kSeed2,
        kSeed3,
        kEntropy,
        kCR0,
    };

    static constexpr IntType kDefaultSeed = 0xDEADBEEF;
    static constexpr Memory16::Address_t kDeviceMemorySize =
        sizeof(IntType) + sizeof(OutReg) + sizeof(CrReg);

    Mt19937Device(IntType default_seed = kDefaultSeed,
                  std::ostream *verbose_output = nullptr);

    [[nodiscard]] uint8_t Load(Address_t address) const override;
    void Store(Address_t address, uint8_t value) override;

    [[nodiscard]] std::optional<uint8_t> DebugRead(Address_t address) const override;

private:
    mutable boost::random::uniform_int_distribution<uint8_t> byte_distribution{0, 0xff};
    mutable std::mt19937 mt;
    IntType current_seed;
    uint8_t control_reg = 0;
    std::ostream *const verbose_output;

    [[noreturn]] void Error(const std::string &msg) const {
        if (verbose_output != nullptr) {
            (*verbose_output) << msg << "\n";
        }
        throw std::runtime_error(msg);
    }
};

} // namespace emu::module::random