001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.apache.commons.math.optimization;
019    
020    import java.util.Arrays;
021    import java.util.Comparator;
022    
023    import org.apache.commons.math.MathRuntimeException;
024    import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
025    import org.apache.commons.math.FunctionEvaluationException;
026    import org.apache.commons.math.exception.util.LocalizedFormats;
027    import org.apache.commons.math.random.RandomVectorGenerator;
028    
029    /**
030     * Special implementation of the {@link DifferentiableMultivariateVectorialOptimizer} interface adding
031     * multi-start features to an existing optimizer.
032     * <p>
033     * This class wraps a classical optimizer to use it several times in
034     * turn with different starting points in order to avoid being trapped
035     * into a local extremum when looking for a global one.
036     * </p>
037     * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 f??vr. 2011) $
038     * @since 2.0
039     */
040    public class MultiStartDifferentiableMultivariateVectorialOptimizer
041        implements DifferentiableMultivariateVectorialOptimizer {
042    
043        /** Serializable version identifier. */
044        private static final long serialVersionUID = 9206382258980561530L;
045    
046        /** Underlying classical optimizer. */
047        private final DifferentiableMultivariateVectorialOptimizer optimizer;
048    
049        /** Maximal number of iterations allowed. */
050        private int maxIterations;
051    
052        /** Number of iterations already performed for all starts. */
053        private int totalIterations;
054    
055        /** Maximal number of evaluations allowed. */
056        private int maxEvaluations;
057    
058        /** Number of evaluations already performed for all starts. */
059        private int totalEvaluations;
060    
061        /** Number of jacobian evaluations already performed for all starts. */
062        private int totalJacobianEvaluations;
063    
064        /** Number of starts to go. */
065        private int starts;
066    
067        /** Random generator for multi-start. */
068        private RandomVectorGenerator generator;
069    
070        /** Found optima. */
071        private VectorialPointValuePair[] optima;
072    
073        /**
074         * Create a multi-start optimizer from a single-start optimizer
075         * @param optimizer single-start optimizer to wrap
076         * @param starts number of starts to perform (including the
077         * first one), multi-start is disabled if value is less than or
078         * equal to 1
079         * @param generator random vector generator to use for restarts
080         */
081        public MultiStartDifferentiableMultivariateVectorialOptimizer(
082                    final DifferentiableMultivariateVectorialOptimizer optimizer,
083                    final int starts,
084                    final RandomVectorGenerator generator) {
085            this.optimizer                = optimizer;
086            this.totalIterations          = 0;
087            this.totalEvaluations         = 0;
088            this.totalJacobianEvaluations = 0;
089            this.starts                   = starts;
090            this.generator                = generator;
091            this.optima                   = null;
092            setMaxIterations(Integer.MAX_VALUE);
093            setMaxEvaluations(Integer.MAX_VALUE);
094        }
095    
096        /** Get all the optima found during the last call to {@link
097         * #optimize(DifferentiableMultivariateVectorialFunction,
098         * double[], double[], double[]) optimize}.
099         * <p>The optimizer stores all the optima found during a set of
100         * restarts. The {@link #optimize(DifferentiableMultivariateVectorialFunction,
101         * double[], double[], double[]) optimize} method returns the
102         * best point only. This method returns all the points found at the
103         * end of each starts, including the best one already returned by the {@link
104         * #optimize(DifferentiableMultivariateVectorialFunction, double[],
105         * double[], double[]) optimize} method.
106         * </p>
107         * <p>
108         * The returned array as one element for each start as specified
109         * in the constructor. It is ordered with the results from the
110         * runs that did converge first, sorted from best to worst
111         * objective value (i.e in ascending order if minimizing and in
112         * descending order if maximizing), followed by and null elements
113         * corresponding to the runs that did not converge. This means all
114         * elements will be null if the {@link #optimize(DifferentiableMultivariateVectorialFunction,
115         * double[], double[], double[]) optimize} method did throw a {@link
116         * org.apache.commons.math.ConvergenceException ConvergenceException}).
117         * This also means that if the first element is non null, it is the best
118         * point found across all starts.</p>
119         * @return array containing the optima
120         * @exception IllegalStateException if {@link #optimize(DifferentiableMultivariateVectorialFunction,
121         * double[], double[], double[]) optimize} has not been called
122         */
123        public VectorialPointValuePair[] getOptima() throws IllegalStateException {
124            if (optima == null) {
125                throw MathRuntimeException.createIllegalStateException(LocalizedFormats.NO_OPTIMUM_COMPUTED_YET);
126            }
127            return optima.clone();
128        }
129    
130        /** {@inheritDoc} */
131        public void setMaxIterations(int maxIterations) {
132            this.maxIterations = maxIterations;
133        }
134    
135        /** {@inheritDoc} */
136        public int getMaxIterations() {
137            return maxIterations;
138        }
139    
140        /** {@inheritDoc} */
141        public int getIterations() {
142            return totalIterations;
143        }
144    
145        /** {@inheritDoc} */
146        public void setMaxEvaluations(int maxEvaluations) {
147            this.maxEvaluations = maxEvaluations;
148        }
149    
150        /** {@inheritDoc} */
151        public int getMaxEvaluations() {
152            return maxEvaluations;
153        }
154    
155        /** {@inheritDoc} */
156        public int getEvaluations() {
157            return totalEvaluations;
158        }
159    
160        /** {@inheritDoc} */
161        public int getJacobianEvaluations() {
162            return totalJacobianEvaluations;
163        }
164    
165        /** {@inheritDoc} */
166        public void setConvergenceChecker(VectorialConvergenceChecker checker) {
167            optimizer.setConvergenceChecker(checker);
168        }
169    
170        /** {@inheritDoc} */
171        public VectorialConvergenceChecker getConvergenceChecker() {
172            return optimizer.getConvergenceChecker();
173        }
174    
175        /** {@inheritDoc} */
176        public VectorialPointValuePair optimize(final DifferentiableMultivariateVectorialFunction f,
177                                                final double[] target, final double[] weights,
178                                                final double[] startPoint)
179            throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
180    
181            optima                   = new VectorialPointValuePair[starts];
182            totalIterations          = 0;
183            totalEvaluations         = 0;
184            totalJacobianEvaluations = 0;
185    
186            // multi-start loop
187            for (int i = 0; i < starts; ++i) {
188    
189                try {
190                    optimizer.setMaxIterations(maxIterations - totalIterations);
191                    optimizer.setMaxEvaluations(maxEvaluations - totalEvaluations);
192                    optima[i] = optimizer.optimize(f, target, weights,
193                                                   (i == 0) ? startPoint : generator.nextVector());
194                } catch (FunctionEvaluationException fee) {
195                    optima[i] = null;
196                } catch (OptimizationException oe) {
197                    optima[i] = null;
198                }
199    
200                totalIterations          += optimizer.getIterations();
201                totalEvaluations         += optimizer.getEvaluations();
202                totalJacobianEvaluations += optimizer.getJacobianEvaluations();
203    
204            }
205    
206            // sort the optima from best to worst, followed by null elements
207            Arrays.sort(optima, new Comparator<VectorialPointValuePair>() {
208                public int compare(final VectorialPointValuePair o1, final VectorialPointValuePair o2) {
209                    if (o1 == null) {
210                        return (o2 == null) ? 0 : +1;
211                    } else if (o2 == null) {
212                        return -1;
213                    }
214                    return Double.compare(weightedResidual(o1), weightedResidual(o2));
215                }
216                private double weightedResidual(final VectorialPointValuePair pv) {
217                    final double[] value = pv.getValueRef();
218                    double sum = 0;
219                    for (int i = 0; i < value.length; ++i) {
220                        final double ri = value[i] - target[i];
221                        sum += weights[i] * ri * ri;
222                    }
223                    return sum;
224                }
225            });
226    
227            if (optima[0] == null) {
228                throw new OptimizationException(
229                        LocalizedFormats.NO_CONVERGENCE_WITH_ANY_START_POINT,
230                        starts);
231            }
232    
233            // return the found point given the best objective function value
234            return optima[0];
235    
236        }
237    
238    }