/*
 * UncminOptimizer.java
 *
 * Created on May 28, 2002, 11:21 AM
 */

package hep.aida.ref.optimizer.uncmin;
import hep.aida.ref.optimizer.*;
import optimization.*;
import hep.aida.*;
import hep.aida.ref.*;
import hep.aida.ref.function.*;
import hep.aida.ext.*;

/**
 *
 * @author  turri
 */
public class UncminOptimizer extends hep.aida.ref.optimizer.AbstractOptimizer {
    
    int[] varIndex;
    
    /** Creates a new instance of UncminOptimizer */
    public UncminOptimizer() {
        result = new OptimizerResult();
        configuration = new UncminOptimizerConfiguration();
        domainConstraint = null;
    }
    
    /**
     * Perform the optimization.
     * @return <code>true</code> if the optimization was
     *        successfull, <code>false</code> otherwise.
     *
     */
    public void optimize() {
        
        if ( function == null ) throw new IllegalArgumentException("Cannot optimize!! The function was not set correctely!");
        
        String[] variableNames = function.variableNames();
        varIndex = new int[variableNames.length];
        
        if ( variableNames == null || variableNames.length == 0 ) throw new IllegalArgumentException("Cannot optimize!! There are no variables in this function!");
        
        int dimension = 0;
        
        for ( int i = 0; i<variableNames.length; i++ ) {
            String varName = variableNames[i];
            IVariableSettings varSet = variableSettings(varName);
            if ( ! varSet.isFixed() ) {
                varIndex[dimension] = i;
                dimension++;
            }
        }
        
        if ( dimension == 0 ) throw new IllegalArgumentException("There are no free variables!!");
        
        UncminFunc uncminFunc = new UncminFunc( function, dimension, this );
        
        String method = configuration.method();
        int strategy = configuration.strategy();
        
        // Input to the optimizer
        double[] xIn  = new double[dimension+1];
        double[] stepSize = new double[dimension+1];
        double[] fscale = new double[2];
        int[] methodIndex = new int[2];
        int[] strategyIndex = new int[2];
        int[] performChecks = new int[2];
        int[] ndigit = new int[2];
        int[] maxIter = new int[2];
        int[] useGradient = new int[2];
        int[] useHessian = new int[2];
        double[] trustRegionRadius = new double[2];
        double gradientTol[] = new double[2];
        double stepTol[] = new double[2];
        double stepMax[] = new double[2];
        
        
        // Optimizer's output
        double[] xOut = new double[dimension+1];
        double[] fOut = new double[2];
        double[] gradientOut = new double[dimension+1];
        double[][] hessianOut = new double[dimension+1][dimension+1];
        double[] hessianDiagOut = new double[dimension+1];
        int[] terminationCode = new int[2];
        
        // What are these fields for?
        fscale[1] = 1;
        ndigit[1] = -1;
        trustRegionRadius[1] = -1.0;
        stepMax[1] = 0.0;
        
        // Set the method
        method.toUpperCase();
        if ( method.startsWith(UncminOptimizerConfiguration.LINE_SEARCH) ) methodIndex[1] = 1;
        else if ( method.startsWith(UncminOptimizerConfiguration.DOUBLE_DOGLEG) ) methodIndex[1] = 2;
        else if ( method.startsWith(UncminOptimizerConfiguration.MORE_HEBDON) ) methodIndex[1] = 3;
        
        // Set the strategy
        switch ( strategy ) {
            case UncminOptimizerConfiguration.LOW_CALL_STRATEGY:
                strategyIndex[1] = 1;
                performChecks[1] = 0;
                break;
            case UncminOptimizerConfiguration.HIGH_CALL_STRATEGY:
                strategyIndex[1] = 0;
                performChecks[1] = 0;
                break;
            case UncminOptimizerConfiguration.LOW_CALL_STRATEGY_CHECK:
                strategyIndex[1] = 1;
                performChecks[1] = 1;
                break;
            case UncminOptimizerConfiguration.HIGH_CALL_STRATEGY_CHECK:
                strategyIndex[1] = 0;
                performChecks[1] = 1;
                break;
        }
        
        // Set maxIterations
        maxIter[1] = configuration.maxIterations();
        
        // Set gradient and Hessian calculation
        if ( configuration.useFunctionGradient() ) useGradient[1] = 1;
        if ( configuration.useFunctionHessian() ) useHessian[1] = 1;
        
        // Set Tolerance
        double tolerance = configuration.tolerance();
        gradientTol[1] = tolerance;
        stepTol[1] = tolerance;
        
        for ( int i = 0; i<dimension; i++ ) {
            String varName = variableNames[ varIndex(i) ];
            IVariableSettings varSet = variableSettings(varName);
            xIn[i+1] = varSet.value();
            stepSize[i+1] = varSet.stepSize();
        }
        
        Uncmin_f77.optif9_f77(dimension,xIn,uncminFunc,stepSize,fscale,
        methodIndex,strategyIndex,performChecks,ndigit,maxIter,useGradient,useHessian,
        trustRegionRadius,gradientTol,stepMax,stepTol,xOut,fOut,gradientOut,terminationCode,hessianOut,
        hessianDiagOut);
        
        switch( terminationCode[1] ) {
            case 0:
                result.setOptimizationStatus( IOptimizerResult.CONVERGED );
                break;
            case 1:
                result.setOptimizationStatus( IOptimizerResult.CONVERGED_SMALL_GRADIENT );
                break;
            case 2:
                result.setOptimizationStatus( IOptimizerResult.CONVERGED_SMALL_STEP_SIZE );
                break;
            case 3:
                result.setOptimizationStatus( IOptimizerResult.NOT_CONVERGED );
                break;
            case 4:
                result.setOptimizationStatus( IOptimizerResult.REACHED_MAX_ITER );
                break;
            case 5:
                result.setOptimizationStatus( IOptimizerResult.TOO_MANY_LARGE_STEPS );
                break;
        }
        
        
        
        double[] pars = new double[variableNames.length];
        int outCount = 0;
        for ( int i = 0; i<variableNames.length; i++ ) {
            String varName = variableNames[i];
            IVariableSettings varSet = variableSettings(varName);
            if ( ! varSet.isFixed() ) {
                double val = xOut[outCount+1];
                double err = Math.sqrt(2./hessianDiagOut[outCount+1]);
                varSet.setValue(val);
                varSet.setStepSize(err);
                outCount++;
            }
            pars[i] = varSet.value();
        }
        
        result.setParameters(pars);
        
        double[][] covMatrix = new double[dimension][dimension];

        for ( int i = 0; i<dimension; i++ ) {
            for ( int j = i; j<dimension; j++ ) {
                    covMatrix[i][j] = Math.pow(1/(hessianOut[i+1][j+1]/2),2);
                    covMatrix[j][i] = Math.pow(1/(hessianOut[i+1][j+1]/2),2);
            }
        }
        
        result.setCovarianceMatrix( covMatrix );
    }
    
    private int varIndex( int index ) {
        return varIndex[index];
    }
    
    private class UncminFunc implements Uncmin_methods {
        
        IFunction function;
        
        UncminOptimizer optimizer;
        int dimension;
        double[] vars;
        
        UncminFunc( IFunction function, int dimension, UncminOptimizer optimizer ) {
            this.dimension = dimension;
            this.function = function;
            this.optimizer = optimizer;
            String[] variableNames = function.variableNames();
            vars = new double[variableNames.length];
            for( int i = 0; i < variableNames.length; i++ )
                vars[i] = optimizer.variableSettings( variableNames[i] ).value();
        }
        
        public double f_to_minimize(double x[]) {
            for ( int i = 0; i<dimension; i++ )
                vars[optimizer.varIndex(i)] = x[i+1];
            return function.value(vars);
        }
        
        public void gradient(double x[], double g[]) {
            g = function.gradient( x );
        }
        public void hessian(double x[], double h[][]) {
            throw new UnsupportedOperationException("Cannot calculate function's Hessian. Please report this problem");
        }
        
        private int indexOf( int index ) {
            return varIndex[ index ];
        }
    }
}
