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 }