/***************************************************************************
* Math.C                                                                   *
*                                                                          *
* Some basic math functions.                                               *
*                                                                          *
*   HISTORY                                                                *
*      Name    Date        Description                                     *
*                                                                          *
*      arvo    06/21/93    Initial coding.                                 *
*                                                                          *
*--------------------------------------------------------------------------*
* Copyright (C) 1999, James Arvo                                           *
*                                                                          *
* This program is free software; you can redistribute it and/or modify it  *
* under the terms of the GNU General Public License as published by the    *
* Free Software Foundation.  See http://www.fsf.org/copyleft/gpl.html      *
*                                                                          *
* This program is distributed in the hope that it will be useful, but      *
* WITHOUT EXPRESS OR IMPLIED WARRANTY of merchantability or fitness for    *
* any particular purpose.  See the GNU General Public License for more     *
* details.                                                                 *
*                                                                          *
***************************************************************************/
#include <math.h>
#include <stdlib.h>
#include <iostream>
#include <assert.h>
#include "ArvoMath.h"
#include "form.h"

namespace ArvoMath {
	static const float  Epsilon = 1.0E-5;
	static const double LogTwo  = log( 2.0 );

#define BinCoeffMax 500

	double RelErr( double x, double y )
	{
		double z = x - y;
		if( x < 0.0 ) x = -x;
		if( y < 0.0 ) y = -y;
		return z / ( x > y ? x : y );
	}

	/***************************************************************************
	*  A R C   Q U A D                                                         *
	*                                                                          *
	* Returns the theta / ( 2*PI ) where the input variables x and y are       *
	* such that  x == COS( theta ) and  y == SIN( theta ).                     *
	*                                                                          *
	***************************************************************************/
	float ArcQuad( float x, float y )
	{
		if( Abs( x ) > Epsilon )
		{
			float temp = OverTwoPi * atan( Abs( y ) / Abs( x ) );
			if( x < 0.0 ) temp = 0.5 - temp;
			if( y < 0.0 ) temp = 1.0 - temp;
			return( temp );
		}
		else if( y >  Epsilon ) return( 0.25 );
		else if( y < -Epsilon ) return( 0.75 );
		else return( 0.0 ); 
	}

	/***************************************************************************
	*  A R C   T A N                                                           *
	*                                                                          *
	* Returns the angle theta such that x = COS( theta ) & y = SIN( theta ).   *
	*                                                                          *
	***************************************************************************/
	float ArcTan( float x, float y )
	{
		if( Abs( x ) > Epsilon )
		{
			float temp = atan( Abs( y ) / Abs( x ) );
			if( x < 0.0 ) temp = Pi    - temp;
			if( y < 0.0 ) temp = TwoPi - temp;
			return( temp );
		}
		else if( y >  Epsilon ) return(     PiOverTwo );
		else if( y < -Epsilon ) return( 3 * PiOverTwo );
		else return( 0.0 ); 
	}

	/***************************************************************************
	*  M A C H I N E   E P S I L O N                                           *
	*                                                                          *
	* Returns the machine epsilon.                                             *
	*                                                                          *
	***************************************************************************/
	float MachineEpsilon()
	{
		float x = 1.0;
		float y;
		float z = 1.0 + x;
		while( z > 1.0 )
		{
			y = x;
			x /= 2.0;
			z = (float)( 1.0 + (float)x );  // Avoid double precision!
		}
		return (float)y;
	}

	/***************************************************************************
	*  L O G   G A M M A                                                       *
	*                                                                          *
	*  Computes the natural log of the gamma function using the Lanczos        *
	*  approximation formula.  Gamma is defined by                             *
	*                                                                          *
	*                                 ( z - 1 )   -t                           *
	*         gamma( z ) = Integral[ t           e    dt ]                     *
	*                                                                          *
	*                                                                          *
	*  where the integral ranges from 0 to infinity.  The gamma function       *
	*  satisfies                                                               *
	*                    gamma( n + 1 ) = n!                                   *
	*                                                                          *
	*  This algorithm has been adapted from "Numerical Recipes", p. 157.       *
	*                                                                          *
	***************************************************************************/
	double LogGamma( double x )
	{
		static const double 
			coeff0 =  7.61800917300E+1,
			coeff1 = -8.65053203300E+1,
			coeff2 =  2.40140982200E+1,
			coeff3 = -1.23173951600E+0,
			coeff4 =  1.20858003000E-3,
			coeff5 = -5.36382000000E-6,
			stp    =  2.50662827465E+0,
			half   =  5.00000000000E-1,
			fourpf =  4.50000000000E+0,
			one    =  1.00000000000E+0,
			two    =  2.00000000000E+0, 
			three  =  3.00000000000E+0,
			four   =  4.00000000000E+0, 
			five   =  5.00000000000E+0;
		double r = coeff0 / ( x        ) + coeff1 / ( x + one   ) +
			coeff2 / ( x + two  ) + coeff3 / ( x + three ) +
			coeff4 / ( x + four ) + coeff5 / ( x + five  ) ;
		double s = x + fourpf;
		double t = ( x - half ) * log( s ) - s;
		return t + log( stp * ( r + one ) );
	}

	/***************************************************************************
	*  L O G   F A C T                                                         *
	*                                                                          *
	*  Returns the natural logarithm of n factorial.  For efficiency, some     *
	*  of the values are cached, so they need be computed only once.           *
	*                                                                          *
	***************************************************************************/
	double LogFact( int n )
	{
		static const int Cache_Size = 100;
		static double c[ Cache_Size ] = { 0.0 }; // Cache some of the values.
		if( n <= 1 ) return 0.0;
		if( n < Cache_Size )
		{
			if( c[n] == 0.0 ) c[n] = LogGamma((double)(n+1));
			return c[n];
		}
		return LogGamma((double)(n+1)); // gamma(n+1) == n!
	}

	/***************************************************************************
	*  M U L T I N O M I A L    C O E F F                                      *
	*                                                                          *
	*  Returns the multinomial coefficient ( n; X1 X2 ... Xk ) which is        *
	*  defined to be n! / ( X1! X2! ... Xk! ).  This is done by computing      *
	*  exp( log(n!) - log(X1!) - log(X2!) - ... - log(Xk!) ).  The value of    *
	*  n is obtained by summing the Xi's.                                      *
	*                                                                          *
	***************************************************************************/
	double MultinomialCoeff( int k, int X[] )
	{
		int i;
		// Find n by summing the coefficients.

		int  n = X[0];
		for( i = 1; i < k; i++ ) n += X[i];

		// Compute log(n!) then subtract log(X!) for each X.

		double LogCoeff = LogFact( n );
		for( i = 0; i < k; i++ ) LogCoeff -= LogFact( X[i] );

		// Round the exponential of the result to the nearest integer.

		return floor( exp( LogCoeff ) + 0.5 );
	}


	double MultinomialCoeff( int i, int j, int k )
	{
		int    n = i + j + k;
		double x = LogFact( n ) - LogFact( i ) - LogFact( j ) - LogFact( k );
		return floor( exp( x ) + 0.5 );
	}

	/***************************************************************************
	*  B I N O M I A L    C O E F F S                                          *
	*                                                                          *
	*  Generate all n+1 binomial coefficents for a given n.  This is done by   *
	*  computing the n'th row of Pascal's triange, starting from the top.      *
	*  No additional storage is required.                                      *
	*                                                                          *
	***************************************************************************/
	void BinomialCoeffs( int n, long *coeff )
	{
		coeff[0] = 1;
		for( int i = 1; i <= n; i++ )
		{
			long a = coeff[0];
			long b = coeff[1];
			for( int j = 1; j < i; j++ )  // Make next row of Pascal's triangle.
			{
				coeff[j] = a + b; // Overwrite the old row.
				a = b;
				b = coeff[j+1];
			}
			coeff[i] = 1;  // The last entry in any row is always 1.
		}
	}

	void BinomialCoeffs( int n, double *coeff )
	{
		coeff[0] = 1.0;
		for( int i = 1; i <= n; i++ )
		{
			double a = coeff[0];
			double b = coeff[1];
			for( int j = 1; j < i; j++ )  // Make next row of Pascal's triangle.
			{
				coeff[j] = a + b; // Overwrite the old row.
				a = b;
				b = coeff[j+1];
			}
			coeff[i] = 1.0;  // The last entry in any row is always 1.
		}
	}

	const double *BinomialCoeffs( int n )
	{
		static double *coeff[ BinCoeffMax + 1 ] = { 0 };
		if( n > BinCoeffMax || n < 0 ) 
		{
			std::cerr << form( "%d is outside of (0,%d) in BinomialCoeffs", n, BinCoeffMax );
			return NULL;
		}
		if( coeff[n] == NULL ) // Fill in this entry.
		{
			double *c = new double[ n + 1 ];
			if( c == NULL )
			{
				std::cerr << form( "Could not allocate for BinomialCoeffs(%d)", n );
				return NULL;
			}
			BinomialCoeffs( n, c );
			coeff[n] = c;
		}
		return coeff[n];
	}

	/***************************************************************************
	*  B I N O M I A L    C O E F F                                            *
	*                                                                          *
	*  Compute a given binomial coefficient.  Several rows of Pascal's         *
	*  triangle are stored for efficiently computing the small coefficients.   *
	*  Higher-order terms are computed using LogFact.                          *
	*                                                                          *
	***************************************************************************/
	double BinomialCoeff( int n, int k )
	{
		double b;
		int    p = n - k;
		if( k <= 1 || p <= 1 )  // Check for errors and special cases.
		{
			if( k == 0 || p == 0 ) return 1;
			if( k == 1 || p == 1 ) return n;
			std::cerr << form( "BinomialCoeff(%d,%d) is undefined", n, k );
			return 0;
		}
		static const int  // Store part of Pascal's triange for small coeffs.
			n0[] = { 1 },
			n1[] = { 1, 1 },
			n2[] = { 1, 2, 1 },
			n3[] = { 1, 3, 3, 1 },
			n4[] = { 1, 4, 6, 4, 1 },
			n5[] = { 1, 5, 10, 10, 5, 1 },
			n6[] = { 1, 6, 15, 20, 15, 6, 1 },
			n7[] = { 1, 7, 21, 35, 35, 21, 7, 1 },
			n8[] = { 1, 8, 28, 56, 70, 56, 28, 8, 1 },
			n9[] = { 1, 9, 36, 84, 126, 126, 84, 36, 9, 1 };
		switch( n )
		{
		case 0 : b = n0[k]; break;
		case 1 : b = n1[k]; break;
		case 2 : b = n2[k]; break;
		case 3 : b = n3[k]; break;
		case 4 : b = n4[k]; break;
		case 5 : b = n5[k]; break;
		case 6 : b = n6[k]; break;
		case 7 : b = n7[k]; break;
		case 8 : b = n8[k]; break;
		case 9 : b = n9[k]; break;
		default:
			{
				double x = LogFact( n ) - LogFact( p ) - LogFact( k );
				b = floor( exp( x ) + 0.5 );
			}
		}
		return b;
	}


	/***************************************************************************
	*  L O G   D O U B L E   F A C T   (Log of double factorial)               *
	*                                                                          *
	*  Return log( n!! ) where the double factorial is defined by              *
	*                                                                          *
	*      (2 n + 1)!! = 1 * 3 * 5 * ... * (2n + 1)    (Odd integers)          *
	*                                                                          *
	*      (2 n)!!     = 2 * 4 * 6 * ... * 2n          (Even integers)         *
	*                                                                          *
	*  and is related to the single factorial via                              *
	*                                                                          *
	*      (2 n + 1)!! = (2 n + 1)! / ( 2^n n! )       (Odd integers)          *
	*                                                                          *
	*      (2 n)!!     = 2^n n!                        (Even integers)         *
	*                                                                          *
	***************************************************************************/
	double LogDoubleFact( int n )   // log( n!! )
	{
		int    k = n / 2;
		double f = LogFact( k ) + k * LogTwo;
		if( Odd(n) ) f = LogFact( n ) - f;
		return f;
	}
};
