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

gov.sandia.cognition.learning.algorithm.minimization.FunctionMinimizerNelderMead Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * File:                FunctionMinimizerNelderMead.java
 * Authors:             Kevin R. Dixon
 * Company:             Sandia National Laboratories
 * Project:             Cognitive Foundry
 *
 * Copyright November 9, 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.learning.algorithm.minimization;

import gov.sandia.cognition.annotation.PublicationReference;
import gov.sandia.cognition.annotation.PublicationReferences;
import gov.sandia.cognition.annotation.PublicationType;
import gov.sandia.cognition.evaluator.Evaluator;
import gov.sandia.cognition.learning.algorithm.minimization.line.DirectionalVectorToScalarFunction;
import gov.sandia.cognition.learning.data.DefaultInputOutputPair;
import gov.sandia.cognition.learning.data.InputOutputPair;
import gov.sandia.cognition.math.RingAccumulator;
import gov.sandia.cognition.math.matrix.Vector;
import java.util.ArrayList;

/**
 * Implementation of the Downhill Simplex minimization algorithm, also known as
 * the Nelder-Mead method.  It finds the minimum of a nonlinear function 
 * without using derivative information.  In my experience, this method
 * "gives up" easily and the simplex breaks down and gets stuck on complicated
 * nonlinear manifolds.  I would recommend using Powell's method instead.
 *
 * @author Kevin R. Dixon
 * @since  2.0
 *
 */
@PublicationReferences(
    references={
        @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={411,412},
            notes="Section 10.5",
            url="http://www.nrbook.com/a/bookcpdf.php"
        ),
        @PublicationReference(
            author="Wikipedia",
            title="Nelder-Mead method",
            type=PublicationType.WebPage,
            year=2008,
            url="http://en.wikipedia.org/wiki/Nelder-Mead_method"
        )
    }
)
public class FunctionMinimizerNelderMead 
    extends AbstractAnytimeFunctionMinimizer>
{

    /**
     * Default tolerance, {@value}
     */
    public static final double DEFAULT_TOLERANCE = 1e-3;

    /**
     * Default max iterations, {@value}
     */
    public static final int DEFAULT_MAX_ITERATIONS = 4000;    
    
    /** 
     * Creates a new instance of FunctionMinimizerNelderMead 
     */
    public FunctionMinimizerNelderMead()
    {
        super( null, DEFAULT_TOLERANCE, DEFAULT_MAX_ITERATIONS );
    }
    
    /**
     * InputOutputPairs that define the simplex
     */
    private ArrayList> simplex;

    /**
     * A running sum of the inputs of the simplex, used for reflection
     */
    private Vector simplexInputSum;
    
    /**
     * Small number to keep me from dividing by zero
     */
    private static final double TINY = 1e-10;    
    
    @Override
    protected boolean initializeAlgorithm()
    {
        Evaluator f = this.data;
        
        Vector x0 = this.getInitialGuess().clone();
        Double y0 = f.evaluate( x0 );
        InputOutputPair p0 = 
            new DefaultInputOutputPair( x0, y0 );

        // Create the simplex and keep a running average of its x-axis midpoint
        this.simplex = initializeSimplex( p0, 1.0 );        
        this.simplexInputSum = this.computeSimplexInputSum();
        
        this.result = null;
        
        this.lineFunction = new DirectionalVectorToScalarFunction(
            this.data, null, null );
        
        return true;
    }
    
    /**
     * Initializes the simplex about the initial point
     * @param initialPoint
     * Initial point about which to initialize the simplex
     * @param offsetValue
     * Value to use to spread out the vertices of the simplex
     * @return
     * Simplex
     */
    public ArrayList> initializeSimplex(
        InputOutputPair initialPoint,
        double offsetValue )
    {
        Vector x0 = initialPoint.getInput();
        int M = x0.getDimensionality();
        
        // Create the simplex and keep a running average of its x-axis midpoint
        ArrayList> localSimplex =
            new ArrayList>( M+1 );
        
        // Add the initial point to the simplex
        localSimplex.add( DefaultInputOutputPair.create(
            initialPoint.getInput(),
            initialPoint.getOutput()) );

        // Add the remaining M points to the simplex
        for( int i = 0; i < M; i++ )
        {
            Vector xi = x0.clone();
            xi.setElement( i, x0.getElement(i) + offsetValue );
            Double yi = this.data.evaluate( xi );
            localSimplex.add( new DefaultInputOutputPair( xi, yi ) );
        }
        
        return localSimplex;
        
    }
    
    @Override
    protected boolean step()
    {
        
        // Find the highest and lowest vertex of the simplex
        int indexLow = 0;
        DefaultInputOutputPair pLow = this.simplex.get(indexLow);
        DefaultInputOutputPair pHigh = this.simplex.get(0);
        DefaultInputOutputPair pSecondHighest = null;
        for( int i = 1; i < this.simplex.size(); i++ )
        {
            DefaultInputOutputPair pi = this.simplex.get( i );
            if( pHigh.getOutput() < pi.getOutput() )
            {
                pSecondHighest = pHigh;
                pHigh = pi;
            }
            else if( (pSecondHighest == null)
                || (pSecondHighest.getOutput() < pi.getOutput()) )
            {
                pSecondHighest = pi;
            }
            
            if( pLow.getOutput() > pi.getOutput() )
            {
                pLow = pi;
                indexLow = i;
            }
        }
                
        double fhigh = pHigh.getOutput();
        double flow = pLow.getOutput();
        
        // This is the termination criterion...
        // If the simplex has contracted sufficiently
        double rtol = 2.0 * Math.abs(fhigh - flow) /
            (Math.abs(fhigh) + Math.abs(flow) + TINY);
        if( rtol <= this.getTolerance() )
        {
            return false;
        }

        // Set the result as the lowest point in the simplex
        this.result = pLow;
        
        // Begin a new iteration. First extrapolate by a factor -1 through
        // the face of the simplex across from the high point, i.e., reflex the
        // simplex from the high point
        DefaultInputOutputPair pnew = this.pointTry( pHigh, -1.0 );
        
        // Gives a result better than the best point,
        // so try an additional extrapolation by a factor of 2
        if( pnew.getOutput() <= pLow.getOutput() )
        {
            pnew = this.pointTry( pHigh, 2.0 );

            // If the new point is still better than the lowest, then assign
            // it as the result
            if( pnew.getOutput() <= pLow.getOutput() )
            {
                this.result = pnew;
            }
        }
        
        // The reflected point is worse than the second-highest,
        // so look for an intermediate lower point, that is, 
        // we'll perform a one-dimensional contraction
        else if( pnew.getOutput() >= pSecondHighest.getOutput() )
        {
            double yHighSave = pHigh.getOutput();
            pnew = this.pointTry( pHigh, 0.5 );

            // Can't seem to get rid of that high point.
            // Better contract around the lowest (best) point.
            if( pnew.getOutput() > yHighSave )
            {
                Vector xlow = pLow.getInput();
                for( int i = 0; i < this.simplex.size(); i++ )
                {
                    DefaultInputOutputPair pi = this.simplex.get(i);
                    if( i != indexLow )
                    {
                        Vector xi = pi.getInput();
                        xi.plusEquals( xlow );
                        xi.scaleEquals( 0.5 );
                        Double yi = this.data.evaluate( xi );
                        
                        // I don't actually have to setInput here, since the
                        // scaleEquals() methods (etc) will automagically
                        // show up inside "pi", but I know I'll forget this
                        // when I look at the code in the future.
                        // So here it is.
                        pi.setInput( xi );
                        pi.setOutput( yi );
                    }
                    
                }
                
                // It is more efficient to recompute the simplexInputSum from
                // scratch here, instead of tracking the change in individual
                // point coordinates
                this.simplexInputSum = this.computeSimplexInputSum();
            }
            
        }
        
        return true;
    }

    @Override
    protected void cleanupAlgorithm()
    {
    }
    
    /**
     * Computes the sum of input values in the simplex
     * @return
     * Sum of input values in the simplex
     */
    protected Vector computeSimplexInputSum()
    {
        RingAccumulator simplexInputAccumulator =
            new RingAccumulator();
        for( InputOutputPair pi : this.simplex )
        {
            simplexInputAccumulator.accumulate( pi.getInput() );
        }
                
        return simplexInputAccumulator.getSum();
    }
    
    /**
     * Function that defines the simplex growth direction
     */
    private DirectionalVectorToScalarFunction lineFunction;

    /**
     * Function that attempts to find a better point using the given
     * scale factor
     * @param pold
     * Old point to improve upon
     * @param scaleFactor
     * Scale factor to reflect/stretch/contract the about the simplex
     * @return
     * New point in the simplex
     */
    private DefaultInputOutputPair pointTry(
        DefaultInputOutputPair pold,
        double scaleFactor )
    {
        
        int M = this.initialGuess.getDimensionality();

        Vector xold = pold.getInput();
        
        // This is the midpoint of the remaining points in the simplex,
        // as if xold didn't exist
        Vector xmid = this.simplexInputSum.minus( xold );
        xmid.scaleEquals( 1.0 / M );
        
        // Here is the direction of the operation
        Vector direction = xold.minus( xmid );
        this.lineFunction.setDirection( direction );
        this.lineFunction.setVectorOffset( xmid );
        
        // Now, compute the reflected, contracted, expanded, etc. point.
        // Oddly enough, line search seems to slow things down quite a bit
        // instead of just using a brain-dead computation here.
        // Don't know why, but it hurts.
        // So, dear person reading this in the future, please feel free
        // to use line minimization here but, as you'll soon find out,
        // future person, it really does hurt.
        Vector xnew = this.lineFunction.computeVector( scaleFactor );
        double ynew = this.lineFunction.evaluate( scaleFactor );
        DefaultInputOutputPair pnew =
            new DefaultInputOutputPair( xnew, ynew );
        
        // If the new point is better than the original point,
        // then replace the original point with the new point.
        if( ynew < pold.getOutput() )
        {
            this.simplexInputSum.minusEquals( pold.getInput() );
            this.simplexInputSum.plusEquals( xnew );
            pold.setInput( xnew );
            pold.setOutput( ynew );
        }
        
        return pnew;

    }    

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy