View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.math.estimation;
19  
20  import java.util.Arrays;
21  
22  import org.apache.commons.math.linear.InvalidMatrixException;
23  import org.apache.commons.math.linear.RealMatrixImpl;
24  
25  /**
26   * Base class for implementing estimators.
27   * <p>This base class handles the boilerplates methods associated to thresholds
28   * settings, jacobian and error estimation.</p>
29   * @version $Revision: 627987 $ $Date: 2008-02-15 03:01:26 -0700 (Fri, 15 Feb 2008) $
30   * @since 1.2
31   *
32   */
33  public abstract class AbstractEstimator implements Estimator {
34  
35      /**
36       * Build an abstract estimator for least squares problems.
37       */
38      protected AbstractEstimator() {
39      }
40  
41      /**
42       * Set the maximal number of cost evaluations allowed.
43       * 
44       * @param maxCostEval maximal number of cost evaluations allowed
45       * @see #estimate
46       */
47      public final void setMaxCostEval(int maxCostEval) {
48          this.maxCostEval = maxCostEval;
49      }
50  
51      /**
52       * Get the number of cost evaluations.
53       * 
54       * @return number of cost evaluations
55       * */
56      public final int getCostEvaluations() {
57          return costEvaluations;
58      }
59  
60      /** 
61       * Get the number of jacobian evaluations.
62       * 
63       * @return number of jacobian evaluations
64       * */
65      public final int getJacobianEvaluations() {
66          return jacobianEvaluations;
67      }
68  
69      /** 
70       * Update the jacobian matrix.
71       */
72      protected void updateJacobian() {
73          incrementJacobianEvaluationsCounter();
74          Arrays.fill(jacobian, 0);
75          for (int i = 0, index = 0; i < rows; i++) {
76              WeightedMeasurement wm = measurements[i];
77              double factor = -Math.sqrt(wm.getWeight());
78              for (int j = 0; j < cols; ++j) {
79                  jacobian[index++] = factor * wm.getPartial(parameters[j]);
80              }
81          }
82      }
83  
84      /**
85       * Increment the jacobian evaluations counter.
86       */
87      protected final void incrementJacobianEvaluationsCounter() {
88        ++jacobianEvaluations;
89      }
90  
91      /** 
92       * Update the residuals array and cost function value.
93       * @exception EstimationException if the number of cost evaluations
94       * exceeds the maximum allowed
95       */
96      protected void updateResidualsAndCost()
97      throws EstimationException {
98  
99          if (++costEvaluations > maxCostEval) {
100             throw new EstimationException("maximal number of evaluations exceeded ({0})",
101                                           new Object[] { new Integer(maxCostEval) });
102         }
103 
104         cost = 0;
105         for (int i = 0, index = 0; i < rows; i++, index += cols) {
106             WeightedMeasurement wm = measurements[i];
107             double residual = wm.getResidual();
108             residuals[i] = Math.sqrt(wm.getWeight()) * residual;
109             cost += wm.getWeight() * residual * residual;
110         }
111         cost = Math.sqrt(cost);
112 
113     }
114 
115     /** 
116      * Get the Root Mean Square value.
117      * Get the Root Mean Square value, i.e. the root of the arithmetic
118      * mean of the square of all weighted residuals. This is related to the
119      * criterion that is minimized by the estimator as follows: if
120      * <em>c</em> if the criterion, and <em>n</em> is the number of
121      * measurements, then the RMS is <em>sqrt (c/n)</em>.
122      * 
123      * @param problem estimation problem
124      * @return RMS value
125      */
126     public double getRMS(EstimationProblem problem) {
127         WeightedMeasurement[] wm = problem.getMeasurements();
128         double criterion = 0;
129         for (int i = 0; i < wm.length; ++i) {
130             double residual = wm[i].getResidual();
131             criterion += wm[i].getWeight() * residual * residual;
132         }
133         return Math.sqrt(criterion / wm.length);
134     }
135 
136     /**
137      * Get the Chi-Square value.
138      * @param problem estimation problem
139      * @return chi-square value
140      */
141     public double getChiSquare(EstimationProblem problem) {
142         WeightedMeasurement[] wm = problem.getMeasurements();
143         double chiSquare = 0;
144         for (int i = 0; i < wm.length; ++i) {
145             double residual = wm[i].getResidual();
146             chiSquare += residual * residual / wm[i].getWeight();
147         }
148         return chiSquare;
149     }
150 
151     /**
152      * Get the covariance matrix of estimated parameters.
153      * @param problem estimation problem
154      * @return covariance matrix
155      * @exception EstimationException if the covariance matrix
156      * cannot be computed (singular problem)
157      */
158     public double[][] getCovariances(EstimationProblem problem)
159       throws EstimationException {
160  
161         // set up the jacobian
162         updateJacobian();
163 
164         // compute transpose(J).J, avoiding building big intermediate matrices
165         final int rows = problem.getMeasurements().length;
166         final int cols = problem.getAllParameters().length;
167         final int max  = cols * rows;
168         double[][] jTj = new double[cols][cols];
169         for (int i = 0; i < cols; ++i) {
170             for (int j = i; j < cols; ++j) {
171                 double sum = 0;
172                 for (int k = 0; k < max; k += cols) {
173                     sum += jacobian[k + i] * jacobian[k + j];
174                 }
175                 jTj[i][j] = sum;
176                 jTj[j][i] = sum;
177             }
178         }
179 
180         try {
181             // compute the covariances matrix
182             return new RealMatrixImpl(jTj).inverse().getData();
183         } catch (InvalidMatrixException ime) {
184             throw new EstimationException("unable to compute covariances: singular problem",
185                                           new Object[0]);
186         }
187 
188     }
189 
190     /**
191      * Guess the errors in estimated parameters.
192      * <p>Guessing is covariance-based, it only gives rough order of magnitude.</p>
193      * @param problem estimation problem
194      * @return errors in estimated parameters
195      * @exception EstimationException if the covariances matrix cannot be computed
196      * or the number of degrees of freedom is not positive (number of measurements
197      * lesser or equal to number of parameters)
198      */
199     public double[] guessParametersErrors(EstimationProblem problem)
200       throws EstimationException {
201         int m = problem.getMeasurements().length;
202         int p = problem.getAllParameters().length;
203         if (m <= p) {
204             throw new EstimationException("no degrees of freedom ({0} measurements, {1} parameters)",
205                                           new Object[] { new Integer(m), new Integer(p)});
206         }
207         double[] errors = new double[problem.getAllParameters().length];
208         final double c = Math.sqrt(getChiSquare(problem) / (m - p));
209         double[][] covar = getCovariances(problem);
210         for (int i = 0; i < errors.length; ++i) {
211             errors[i] = Math.sqrt(covar[i][i]) * c;
212         }
213         return errors;
214     }
215 
216     /**
217      * Initialization of the common parts of the estimation.
218      * <p>This method <em>must</em> be called at the start
219      * of the {@link #estimate(EstimationProblem) estimate}
220      * method.</p>
221      * @param problem estimation problem to solve
222      */
223     protected void initializeEstimate(EstimationProblem problem) {
224 
225         // reset counters
226         costEvaluations     = 0;
227         jacobianEvaluations = 0;
228 
229         // retrieve the equations and the parameters
230         measurements = problem.getMeasurements();
231         parameters   = problem.getUnboundParameters();
232 
233         // arrays shared with the other private methods
234         rows      = measurements.length;
235         cols      = parameters.length;
236         jacobian  = new double[rows * cols];
237         residuals = new double[rows];
238 
239         cost = Double.POSITIVE_INFINITY;
240 
241     }
242 
243     /** 
244      * Solve an estimation problem.
245      *
246      * <p>The method should set the parameters of the problem to several
247      * trial values until it reaches convergence. If this method returns
248      * normally (i.e. without throwing an exception), then the best
249      * estimate of the parameters can be retrieved from the problem
250      * itself, through the {@link EstimationProblem#getAllParameters
251      * EstimationProblem.getAllParameters} method.</p>
252      *
253      * @param problem estimation problem to solve
254      * @exception EstimationException if the problem cannot be solved
255      *
256      */
257     public abstract void estimate(EstimationProblem problem)
258     throws EstimationException;
259 
260     /** Array of measurements. */
261     protected WeightedMeasurement[] measurements;
262 
263     /** Array of parameters. */
264     protected EstimatedParameter[] parameters;
265 
266     /** 
267      * Jacobian matrix.
268      * <p>This matrix is in canonical form just after the calls to
269      * {@link #updateJacobian()}, but may be modified by the solver
270      * in the derived class (the {@link LevenbergMarquardtEstimator
271      * Levenberg-Marquardt estimator} does this).</p>
272      */
273     protected double[] jacobian;
274 
275     /** Number of columns of the jacobian matrix. */
276     protected int cols;
277 
278     /** Number of rows of the jacobian matrix. */
279     protected int rows;
280 
281     /** Residuals array.
282      * <p>This array is in canonical form just after the calls to
283      * {@link #updateJacobian()}, but may be modified by the solver
284      * in the derived class (the {@link LevenbergMarquardtEstimator
285      * Levenberg-Marquardt estimator} does this).</p>
286      */
287     protected double[] residuals;
288 
289     /** Cost value (square root of the sum of the residuals). */
290     protected double cost;
291 
292     /** Maximal allowed number of cost evaluations. */
293     private int maxCostEval;
294 
295     /** Number of cost evaluations. */
296     private int costEvaluations;
297 
298     /** Number of jacobian evaluations. */
299     private int jacobianEvaluations;
300 
301 }