All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.sandia.cognition.math.MathUtil Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * File:                MathUtil.java
 * Authors:             Justin Basilico, Kevin Dixon, Zachary Benz
 * Company:             Sandia National Laboratories
 * Project:             Cognitive Foundry
 *
 * Copyright November 13, 2007, Sandia Corporation.  Under the terms of Contract
 * DE-AC04-94AL85000, there is a non-exclusive license for use of this work by
 * or on behalf of the U.S. Government. Export of this program may require a
 * license from the United States Government. See CopyrightHistory.txt for
 * complete details.
 *
 */

package gov.sandia.cognition.math;

import gov.sandia.cognition.annotation.CodeReview;
import gov.sandia.cognition.annotation.PublicationReference;
import gov.sandia.cognition.annotation.PublicationReferences;
import gov.sandia.cognition.annotation.PublicationType;
import gov.sandia.cognition.math.matrix.Vector;

/**
 * The {@code MathUtil} class implements mathematical utility functions.
 *
 * @author Justin Basilico, Kevin Dixon, Zachary Benz
 * @since  2.0
 */
@CodeReview(
    reviewer="Kevin R. Dixon",
    date="2008-02-26",
    changesNeeded=false,
    comments={
        "Minor changes, log2 uses callback to main log() method.",
        "Otherwise, looks fine."
    }
)
public class MathUtil
{
    /**
     * Returns the log of the given base of the given value,
     * y=log_b(x) such that x=b^y
     * @param x The value.
     * @param base The base for the logarithm
     * @return The log of x using the given base.
     */
    public static double log(
        final double x,
        final double base)
    {
        return Math.log(x) / Math.log(base);
    }
    
    /**
     * Returns the base-2 logarithm of the given value. It is computed as
     * log(x) / log(2).
     *
     * @param  x The value.
     * @return The base-2 logarithm.
     */
    public static double log2(
        final double x)
    {
        return Math.log(x) / LogMath.LOG_2;
    }

    /**
     * Computes the logarithm of the Gamma function.
     * @param input
     * Input to evaluate the Natural Logarithm of the Gamma Function
     * @return
     * Natural Logarithm of the Gamma Function about the input
     */
    @PublicationReferences(
        references={
            @PublicationReference(
                author="Wikipedia",
                title="Gamma Function",
                type=PublicationType.WebPage,
                year=2010,
                url="http://en.wikipedia.org/wiki/Gamma_function"
            )
            ,
            @PublicationReference(
                author="jdhedden",
                title="Bug in 2nd edition version of gammln()",
                type=PublicationType.WebPage,
                year=2005,
                url="http://www.numerical-recipes.com/forum/showthread.php?t=606"
            )
        }
    )
    public static double logGammaFunction(
        final double input )
    {

        if (input <= 0.0)
        {
            throw new IllegalArgumentException( "Input must be > 0.0" );
        }

        double xx = input;

        double tmp, ser;

        tmp = xx + 4.5;
        tmp -= (xx - 0.5) * Math.log( tmp );

        ser = 1.000000000190015
            + (76.18009172947146 / xx)
            - (86.50532032941677 / (xx + 1.0))
            + (24.01409824083091 / (xx + 2.0))
            - (1.231739572450155 / (xx + 3.0))
            + (0.1208650973866179e-2 / (xx + 4.0))
            - (0.5395239384953e-5 / (xx + 5.0));

        return (Math.log( 2.5066282746310005 * ser ) - tmp);
    }

    /**
     * Computes the Lower incomplete gamma function.
     * Note that this has the reverse parameters order from octave.
     * @param a
     * Degrees of Freedom
     * @param x
     * Input value
     * @return
     * Value of the IncompleteGammaFunction(a,x)
     */
    @PublicationReferences(
        references={
            @PublicationReference(
                author="Wikipedia",
                title="Incomplete gamma function",
                type=PublicationType.WebPage,
                year=2010,
                url="http://en.wikipedia.org/wiki/Incomplete_gamma_function"
            )
            ,
            @PublicationReference(
                author={
                    "William H. Press",
                    "Saul A. Teukolsky",
                    "William T. Vetterling",
                    "Brian P. Flannery"
                },
                title="Numerical Recipes in C, Second Edition",
                type=PublicationType.Book,
                year=1992,
                pages=218,
                notes="Function gammap",
                url="http://www.nrbook.com/a/bookcpdf.php"
            )
        }
    )
    public static double lowerIncompleteGammaFunction(
        final double a,
        final double x )
    {

        if (a <= 0.0)
        {
            throw new IllegalArgumentException( "a must be > 0.0" );
        }

        double gammp;
        if (x <= 0.0)
        {
            gammp = 0.0;
        }
        else if (x > 1e10)
        {
            gammp = 1.0;
        }
        else if (x < (a + 1.0))
        {
            gammp = incompleteGammaSeriesExpansion( a, x );
        }
        else
        {
            gammp = incompleteGammaContinuedFraction( a, x );
        }

        return gammp;

    }

    /**
     * Computes the series expansion approximation to the incomplete
     * gamma function.
     * Note that this has the reverse parameters order from octave.
     * @param a
     * Degrees of Freedom
     * @param x
     * Input value
     * @return
     * Value of the IncompleteGammaFunction(a,x)
     */
    @PublicationReference(
        author={
            "William H. Press",
            "Saul A. Teukolsky",
            "William T. Vetterling",
            "Brian P. Flannery"
        },
        title="Numerical Recipes in C, Second Edition",
        type=PublicationType.Book,
        year=1992,
        pages={218,219},
        url="http://www.nrbook.com/a/bookcpdf.php",
        notes="Function gser()"
    )
    protected static double incompleteGammaSeriesExpansion(
        final double a,
        final double x )
    {
        final int MAX_ITERATIONS = 1000;
        final double EPS = 3e-7;
        double gamser = 0.0;

        if (x <= 0.0)
        {
            if (x < 0.0)
            {
                throw new IllegalArgumentException( "x must be >= 0.0" );
            }
            gamser = 0.0;
        }
        else
        {
            double gln = logGammaFunction( a );
            double del, sum;
            double ap = a;
            del = sum = 1.0 / a;
            int n;
            for (n = 1; n <= MAX_ITERATIONS; n++)
            {
                ap++;
                del *= x / ap;
                sum += del;
                if (Math.abs( del ) < (Math.abs( sum ) * EPS))
                {
                    gamser = sum * Math.exp( -x + a * Math.log( x ) - gln );
                    break;
                }
            }

            if (n > MAX_ITERATIONS)
            {
                throw new OperationNotConvergedException(
                    "a too large, MAX_ITERATIONS too small in seriesExpansion()" );
            }

        }

        return gamser;

    }

    /**
     * Returns the incomplete Gamma function using the continued
     * fraction expansion evaluation using Lentz's method
     * @param a
     * Degrees of Freedom
     * @param x
     * Input value
     * @return
     * Value of the IncompleteGammaFunction(a,x)
     */
    @PublicationReference(
        author={
            "William H. Press",
            "Saul A. Teukolsky",
            "William T. Vetterling",
            "Brian P. Flannery"
        },
        title="Numerical Recipes in C, Second Edition",
        type=PublicationType.Book,
        year=1992,
        pages={216,219},
        url="http://www.nrbook.com/a/bookcpdf.php"
    )
    public static double incompleteGammaContinuedFraction(
        final double a,
        final double x )
    {

        LentzMethod lentz = new LentzMethod();

        lentz.initializeAlgorithm(0.0);
        lentz.iterate(1.0, (x+1-a) );
        while( lentz.getKeepGoing() )
        {
            int i = lentz.getIteration();
            double aterm = -i * (i-a);
            double bterm = x + (2*i+1)-a;
            lentz.iterate(aterm, bterm);
        }

        double gln = logGammaFunction( a );
        double gcf = lentz.getResult();
        double gamma = 1.0 - Math.exp( -x + a * Math.log( x ) - gln ) * gcf;
        return gamma;
    }

    /**
     * Returns the binomial coefficient for "N choose k".  In other words, this
     * is the number of different ways of choosing k objects from a total of
     * N different ones, where order doesn't matter and without replacement.
     * @param N Total number of objects in the bag
     * @param k Total number of objects to choose, must be less than or equal
     * to N
     * @return Binomial coefficient for N choose k
     */
    @PublicationReference(
        author="Wikipedia",
        title="Binomial coefficient",
        type=PublicationType.WebPage,
        year=2010,
        url="http://en.wikipedia.org/wiki/Binomial_coefficient"
    )
    public static int binomialCoefficient(
        final int N,
        final int k )
    {
        return (int) Math.round( Math.exp( logBinomialCoefficient(N, k) ) );
    }

    /**
     * Computes the natural logarithm of the binomial coefficient.
     * @param N Total number of objects in the bag
     * @param k Total number of objects to choose, must be less than or equal
     * to N
     * @return Natural logarithm of the binomial coefficient for N choose k
     */
    public static double logBinomialCoefficient(
        final int N,
        final int k )
    {
        return logFactorial( N ) - logFactorial( k ) - logFactorial( N - k );
    }

    /**
     * Returns the natural logarithm of n factorial log(n!) =
     * log(n*(n-1)*...*3*2*1)
     * @param n
     * Parameter for choose for n factorial
     * @return
     * n factorial
     */
    public static double logFactorial(
        final int n )
    {
        if (n < 0)
        {
            throw new IllegalArgumentException( "Factorial must be >= 0" );
        }
        // Less than 1 is defined to be 1, taking its logarithm yields 0.0
        else if (n <= 1)
        {
            return 0.0;
        }
        else
        {
            return logGammaFunction( n + 1.0 );
        }
    }

    /**
     * Compute the natural logarithm of the Beta Function.
     * @param a
     * First parameter to the Beta function
     * @param b
     * Second parameter to the Beta function
     * @return
     * Natural logarithm of the Beta Function.
     */
    @PublicationReference(
        author="Wikipedia",
        title="Beta function",
        type=PublicationType.WebPage,
        year=2010,
        url="http://en.wikipedia.org/wiki/Beta_function"
    )
    public static double logBetaFunction(
        final double a,
        final double b )
    {
        double ga = logGammaFunction( a );
        double gb = logGammaFunction( b );
        double gab = logGammaFunction( a + b );
        return ga + gb - gab;
    }

    /**
     * Computes the regularized incomplete Beta function.
     * @param a
     * Parameter a to the Beta function
     * @param b
     * Parameter b to the Beta function
     * @param x
     * Parameter x to for the integral from 0 to x
     * @return
     * Incomplete beta function for I_x(a,b)
     */
    @PublicationReferences(
        references={
            @PublicationReference(
                author="Wikipedia",
                title="Beta function, Incomplete Beta function",
                type=PublicationType.WebPage,
                year=2010,
                url="http://en.wikipedia.org/wiki/Beta_function#Incomplete_beta_function"
            )
            ,
            @PublicationReference(
                author={
                    "William H. Press",
                    "Saul A. Teukolsky",
                    "William T. Vetterling",
                    "Brian P. Flannery"
                },
                title="Numerical Recipes in C, Second Edition",
                type=PublicationType.Book,
                year=1992,
                pages=227,
                notes="Function betai",
                url="http://www.nrbook.com/a/bookcpdf.php"
            )
        }
    )
    public static double regularizedIncompleteBetaFunction(
        final double a,
        final double b,
        final double x )
    {

        double bt;

        if ((x < 0.0) || (x > 1.0))
        {
            throw new IllegalArgumentException( "0 <= x <= 1" );
        }

        if ((x == 0.0) || (x == 1.0))
        {
            bt = 0.0;
        }
        else
        {
            bt = Math.exp(
                a*Math.log( x ) +
                b*Math.log( 1.0-x ) -
                logBetaFunction(a, b) );
        }

        if (x < ((a + 1.0) / (a + b + 2.0)))
        {
            return bt * incompleteBetaContinuedFraction( a, b, x ) / a;
        }
        else
        {
            return 1.0 - bt*incompleteBetaContinuedFraction( b, a, 1.0 - x )/b;
        }

    }

    /**
     * Evaluates the continued fraction of the incomplete beta function.
     * Based on the math from NRC's 6.4 "Incomplete Beta Function"
     * @param a
     * Parameter a to the beta continued fraction
     * @param b
     * Parameter b to the beta continued fraction
     * @param x
     * Parameter x to the beta continued fraction
     * @return
     * Incomplete beta function continued fraction
     */
    @PublicationReference(
        author={
            "William H. Press",
            "Saul A. Teukolsky",
            "William T. Vetterling",
            "Brian P. Flannery"
        },
        title="Numerical Recipes in C, Second Edition",
        type=PublicationType.Book,
        year=1992,
        pages=227,
        notes="Incomplete Beta Function continued fraction terms for Lentz's method",
        url="http://www.nrbook.com/a/bookcpdf.php"

    )
    protected static double incompleteBetaContinuedFraction(
        final double a,
        final double b,
        final double x )
    {

        double apb = a+b;
        LentzMethod lentz = new LentzMethod();
        lentz.initializeAlgorithm( 0.0 );
        lentz.iterate(1.0, 1.0);
        while( lentz.getKeepGoing() )
        {
            int m = lentz.getIteration() / 2;
            double ap2m = a + 2*m;

            double aterm;

            if( (lentz.getIteration() % 2) != 0 )
            {
                // Odd iteration cycle
                double num = -(a+m) * (apb+m) * x;
                double den = ap2m * (ap2m + 1);
                aterm = num / den;
            }
            else
            {
                // Even iteration cycle
                double num = m * (b-m) * x;
                double den = (ap2m - 1) * ap2m;
                aterm = num / den;
            }

            lentz.iterate(aterm, 1.0);

        }

        if( lentz.isResultValid() )
        {
            return lentz.getResult();
        }
        else
        {
            throw new OperationNotConvergedException(
                "Lentz's Method failed in Beta continuous fraction: "
                + String.format( "a = %f, b = %f, x = %f", a, b, x));
        }

    }

    /**
     * Evaluates the natural logarithm of the multinomial beta function
     * for the given input vector.
     * @param input
     * Input vector to consider.
     * @return
     * Natural logarithm of the Multinomial beta function evaluated at the
     * given input.
     */
    @PublicationReference(
        author="Wikipedia",
        title="Dirichlet distribution",
        type=PublicationType.WebPage,
        year=2009,
        url="http://en.wikipedia.org/wiki/Dirichlet_distribution",
        notes="Multinomial Beta Function found in the \"Probability density function\" section."
    )
    static public double logMultinomialBetaFunction(
        final Vector input)
    {
        double logsum = 0.0;
        double inputSum = 0.0;
        for( int i = 0; i < input.getDimensionality(); i++ )
        {
            double ai = input.getElement(i);
            inputSum += ai;
            logsum += logGammaFunction(ai);
        }
        logsum -= logGammaFunction(inputSum);
        return logsum;
    }
      
    /**
     * Safely checks for underflow/overflow before adding two integers.  If an
     * underflow or overflow would occur as a result of the addition, an
     * {@code ArithmeticException} is thrown.
     * 
     * @param a
     *      The first integer to add
     * @param b
     *      The second integer to add
     * @return
     *      The sum of integers a and b
     * @throws ArithmeticException 
     *      If an underflow or overflow will occur upon adding a and b
     */
    @PublicationReference(
        author={
            "Tov Are",
            "Paul van Keep",
            "Mike Cowlishaw",
            "Pierre Baillargeon",
            "Bill Wilkinson",
            "Patricia Shanahan",
            "Joseph Bowbeer",
            "Charles Thomas",
            "Joel Crisp",
            "Eric Nagler",
            "Daniel Leuck",
            "William Brogden",
            "Yves Bossu",
            "Chad Loder"
        },
        title="Java Gotchas",
        type=PublicationType.WebPage,
        year=2011,
        url="http://202.38.93.17/bookcd/285/1.iso/faq/gloss/gotchas.html#OVERFLOW",
        notes="")
    static public int checkedAdd(
        final int a,
        final int b)
        throws ArithmeticException
    {
        if ((a > 0) && (b > Integer.MAX_VALUE - a))
        {
            throw new ArithmeticException("Integer Overflow: " +
                a + " + " + b + " > Integer.MAX_VALUE");
        }
        else if ((a < 0) && (b < Integer.MIN_VALUE - a))
        {
            throw new ArithmeticException("Integer Underflow: " +
                a + " + " + b + " < Integer.MIN_VALUE");            
        }
        else
        {
            return a + b;
        }
    }
    
    /**
     * Safely checks for overflow before multiplying two integers.  
     * If an overflow would occur as a result of the
     * multiplication, an {@code ArithmeticException} is thrown.
     * 
     * @param a
     *      The first integer to multiply
     * @param b
     *      The second integer to multiply
     * @return
     *      The result of multiplying the integers a and b
     * @throws ArithmeticException 
     *      If an overflow will occur upon multiplying a and b
     */
    @PublicationReference(
        author={
            "Tov Are",
            "Paul van Keep",
            "Mike Cowlishaw",
            "Pierre Baillargeon",
            "Bill Wilkinson",
            "Patricia Shanahan",
            "Joseph Bowbeer",
            "Charles Thomas",
            "Joel Crisp",
            "Eric Nagler",
            "Daniel Leuck",
            "William Brogden",
            "Yves Bossu",
            "Chad Loder"
        },
        title="Java Gotchas",
        type=PublicationType.WebPage,
        year=2011,
        url="http://202.38.93.17/bookcd/285/1.iso/faq/gloss/gotchas.html#OVERFLOW",
        notes="")
    static public int checkedMultiply(
        final int a,
        final int b)
        throws ArithmeticException
    {
        final long result = (long)a * (long)b;
        final int desiredHighBits = - ((int)( result >>> 31 ) & 1);
        final int actualHighBits = (int)( result >>> 32 );
        if (desiredHighBits == actualHighBits)
        {
            return(int)result;
        }
        else if (result > 0)
        {
            throw new ArithmeticException("Integer Overflow: " +
                a + " * " + b + " > Integer.MAX_VALUE");
        }
        else
        {
            throw new ArithmeticException("Integer Underflow: " +
                a + " * " + b + " < Integer.MIN_VALUE");
        }
    }
    
    /**
     * Computes log(1 + x). For small values, this is closer to the actual value
     * than actually calling log(1 + x). This is an alias for Math.log1p(x).
     * 
     * @param   x
     *      The value.
     * @return 
     *      The result of log(1 + x).
     */
    public static double log1Plus(
        final double x)
    {
        return Math.log1p(x);
    }
    
    /**
     * Computes exp(x - 1). For small values, this is closer to the actual value
     * than actually calling exp(x - 1). This is an alias for Math.expm1(x).
     * 
     * @param   x
     *      The value.
     * @return 
     *      The result of exp(x - 1).
     */
    public static double expMinus1Plus(
        final double x)
    {
        return Math.expm1(x);
    }
    
    /**
     * Computes log(1 - exp(x)). For small values, this is closer to the actual 
     * value than actually calling log(1 - exp(x)).
     * 
     * @param   x
     *      The value.
     * @return 
     *      The result of log(1 - exp(x)).
     */
    @PublicationReference(
        title="Accurately Computing log(1 − exp(−|a|)): Assessed by the Rmpfr package",
        author="Martin Machler",
        year=2012,
        type=PublicationType.WebPage,
        url="http://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf")
    public static double log1MinusExp(
        final double x)
    {
        if (x < 0.0 && x >= -LogMath.LOG_2)
        {
            // For small x, use the -(exp(x) - 1) with higher precision.
            return Math.log(-MathUtil.expMinus1Plus(x));
        }
        else
        {
            // For large x, use log(1 - exp(x)) with higher precision.
            return Math.log1p(-Math.exp(x));
        }
    }
    
    /**
     * Computes log(1 + exp(x)). For small values, this is closer to the actual 
     * value than actually calling log(1 + exp(x)).
     * 
     * @param   x
     *      The value.
     * @return 
     *      The result of log(1 + exp(x)).
     */
    @PublicationReference(
        title="Accurately Computing log(1 − exp(−|a|)): Assessed by the Rmpfr package",
        author="Martin Machler",
        year=2012,
        type=PublicationType.WebPage,
        url="http://cran.r-project.org/web/packages/Rmpfr/vignettes/log1mexp-note.pdf")
    public static double log1PlusExp(
        final double x)
    {
        if (x <= 18.0)
        {
            // At this scale we can just perform the exponetiation.
            return Math.log1p(Math.exp(x));
        }
        else if (x <= 33.3)
        {
            // Intermediate scale.
            return x + Math.exp(-x);
        }
        else
        {
            // At this scale exp(x) is sufficiently large that the +1
            // term goes away so log(1 + exp(x)) ~= log(exp(x)) = x.
            return x;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy