summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java')
-rw-r--r--src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java374
1 files changed, 374 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java b/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java
new file mode 100644
index 0000000..6271f53
--- /dev/null
+++ b/src/main/java/org/apache/commons/math/optimization/general/AbstractLeastSquaresOptimizer.java
@@ -0,0 +1,374 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.commons.math.optimization.general;
+
+import org.apache.commons.math.FunctionEvaluationException;
+import org.apache.commons.math.MaxEvaluationsExceededException;
+import org.apache.commons.math.MaxIterationsExceededException;
+import org.apache.commons.math.analysis.DifferentiableMultivariateVectorialFunction;
+import org.apache.commons.math.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math.exception.util.LocalizedFormats;
+import org.apache.commons.math.linear.InvalidMatrixException;
+import org.apache.commons.math.linear.LUDecompositionImpl;
+import org.apache.commons.math.linear.MatrixUtils;
+import org.apache.commons.math.linear.RealMatrix;
+import org.apache.commons.math.optimization.OptimizationException;
+import org.apache.commons.math.optimization.SimpleVectorialValueChecker;
+import org.apache.commons.math.optimization.VectorialConvergenceChecker;
+import org.apache.commons.math.optimization.DifferentiableMultivariateVectorialOptimizer;
+import org.apache.commons.math.optimization.VectorialPointValuePair;
+import org.apache.commons.math.util.FastMath;
+
+/**
+ * Base class for implementing least squares optimizers.
+ * <p>This base class handles the boilerplate methods associated to thresholds
+ * settings, jacobian and error estimation.</p>
+ * @version $Revision: 1073158 $ $Date: 2011-02-21 22:46:52 +0100 (lun. 21 févr. 2011) $
+ * @since 1.2
+ *
+ */
+public abstract class AbstractLeastSquaresOptimizer implements DifferentiableMultivariateVectorialOptimizer {
+
+ /** Default maximal number of iterations allowed. */
+ public static final int DEFAULT_MAX_ITERATIONS = 100;
+
+ /** Convergence checker. */
+ protected VectorialConvergenceChecker checker;
+
+ /**
+ * Jacobian matrix.
+ * <p>This matrix is in canonical form just after the calls to
+ * {@link #updateJacobian()}, but may be modified by the solver
+ * in the derived class (the {@link LevenbergMarquardtOptimizer
+ * Levenberg-Marquardt optimizer} does this).</p>
+ */
+ protected double[][] jacobian;
+
+ /** Number of columns of the jacobian matrix. */
+ protected int cols;
+
+ /** Number of rows of the jacobian matrix. */
+ protected int rows;
+
+ /**
+ * Target value for the objective functions at optimum.
+ * @since 2.1
+ */
+ protected double[] targetValues;
+
+ /**
+ * Weight for the least squares cost computation.
+ * @since 2.1
+ */
+ protected double[] residualsWeights;
+
+ /** Current point. */
+ protected double[] point;
+
+ /** Current objective function value. */
+ protected double[] objective;
+
+ /** Current residuals. */
+ protected double[] residuals;
+
+ /** Weighted Jacobian */
+ protected double[][] wjacobian;
+
+ /** Weighted residuals */
+ protected double[] wresiduals;
+
+ /** Cost value (square root of the sum of the residuals). */
+ protected double cost;
+
+ /** Maximal number of iterations allowed. */
+ private int maxIterations;
+
+ /** Number of iterations already performed. */
+ private int iterations;
+
+ /** Maximal number of evaluations allowed. */
+ private int maxEvaluations;
+
+ /** Number of evaluations already performed. */
+ private int objectiveEvaluations;
+
+ /** Number of jacobian evaluations. */
+ private int jacobianEvaluations;
+
+ /** Objective function. */
+ private DifferentiableMultivariateVectorialFunction function;
+
+ /** Objective function derivatives. */
+ private MultivariateMatrixFunction jF;
+
+ /** Simple constructor with default settings.
+ * <p>The convergence check is set to a {@link SimpleVectorialValueChecker}
+ * and the maximal number of evaluation is set to its default value.</p>
+ */
+ protected AbstractLeastSquaresOptimizer() {
+ setConvergenceChecker(new SimpleVectorialValueChecker());
+ setMaxIterations(DEFAULT_MAX_ITERATIONS);
+ setMaxEvaluations(Integer.MAX_VALUE);
+ }
+
+ /** {@inheritDoc} */
+ public void setMaxIterations(int maxIterations) {
+ this.maxIterations = maxIterations;
+ }
+
+ /** {@inheritDoc} */
+ public int getMaxIterations() {
+ return maxIterations;
+ }
+
+ /** {@inheritDoc} */
+ public int getIterations() {
+ return iterations;
+ }
+
+ /** {@inheritDoc} */
+ public void setMaxEvaluations(int maxEvaluations) {
+ this.maxEvaluations = maxEvaluations;
+ }
+
+ /** {@inheritDoc} */
+ public int getMaxEvaluations() {
+ return maxEvaluations;
+ }
+
+ /** {@inheritDoc} */
+ public int getEvaluations() {
+ return objectiveEvaluations;
+ }
+
+ /** {@inheritDoc} */
+ public int getJacobianEvaluations() {
+ return jacobianEvaluations;
+ }
+
+ /** {@inheritDoc} */
+ public void setConvergenceChecker(VectorialConvergenceChecker convergenceChecker) {
+ this.checker = convergenceChecker;
+ }
+
+ /** {@inheritDoc} */
+ public VectorialConvergenceChecker getConvergenceChecker() {
+ return checker;
+ }
+
+ /** Increment the iterations counter by 1.
+ * @exception OptimizationException if the maximal number
+ * of iterations is exceeded
+ */
+ protected void incrementIterationsCounter()
+ throws OptimizationException {
+ if (++iterations > maxIterations) {
+ throw new OptimizationException(new MaxIterationsExceededException(maxIterations));
+ }
+ }
+
+ /**
+ * Update the jacobian matrix.
+ * @exception FunctionEvaluationException if the function jacobian
+ * cannot be evaluated or its dimension doesn't match problem dimension
+ */
+ protected void updateJacobian() throws FunctionEvaluationException {
+ ++jacobianEvaluations;
+ jacobian = jF.value(point);
+ if (jacobian.length != rows) {
+ throw new FunctionEvaluationException(point, LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+ jacobian.length, rows);
+ }
+ for (int i = 0; i < rows; i++) {
+ final double[] ji = jacobian[i];
+ double wi = FastMath.sqrt(residualsWeights[i]);
+ for (int j = 0; j < cols; ++j) {
+ ji[j] *= -1.0;
+ wjacobian[i][j] = ji[j]*wi;
+ }
+ }
+ }
+
+ /**
+ * Update the residuals array and cost function value.
+ * @exception FunctionEvaluationException if the function cannot be evaluated
+ * or its dimension doesn't match problem dimension or maximal number of
+ * of evaluations is exceeded
+ */
+ protected void updateResidualsAndCost()
+ throws FunctionEvaluationException {
+
+ if (++objectiveEvaluations > maxEvaluations) {
+ throw new FunctionEvaluationException(new MaxEvaluationsExceededException(maxEvaluations),
+ point);
+ }
+ objective = function.value(point);
+ if (objective.length != rows) {
+ throw new FunctionEvaluationException(point, LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+ objective.length, rows);
+ }
+ cost = 0;
+ int index = 0;
+ for (int i = 0; i < rows; i++) {
+ final double residual = targetValues[i] - objective[i];
+ residuals[i] = residual;
+ wresiduals[i]= residual*FastMath.sqrt(residualsWeights[i]);
+ cost += residualsWeights[i] * residual * residual;
+ index += cols;
+ }
+ cost = FastMath.sqrt(cost);
+
+ }
+
+ /**
+ * Get the Root Mean Square value.
+ * Get the Root Mean Square value, i.e. the root of the arithmetic
+ * mean of the square of all weighted residuals. This is related to the
+ * criterion that is minimized by the optimizer as follows: if
+ * <em>c</em> if the criterion, and <em>n</em> is the number of
+ * measurements, then the RMS is <em>sqrt (c/n)</em>.
+ *
+ * @return RMS value
+ */
+ public double getRMS() {
+ return FastMath.sqrt(getChiSquare() / rows);
+ }
+
+ /**
+ * Get a Chi-Square-like value assuming the N residuals follow N
+ * distinct normal distributions centered on 0 and whose variances are
+ * the reciprocal of the weights.
+ * @return chi-square value
+ */
+ public double getChiSquare() {
+ return cost*cost;
+ }
+
+ /**
+ * Get the covariance matrix of optimized parameters.
+ * @return covariance matrix
+ * @exception FunctionEvaluationException if the function jacobian cannot
+ * be evaluated
+ * @exception OptimizationException if the covariance matrix
+ * cannot be computed (singular problem)
+ */
+ public double[][] getCovariances()
+ throws FunctionEvaluationException, OptimizationException {
+
+ // set up the jacobian
+ updateJacobian();
+
+ // compute transpose(J).J, avoiding building big intermediate matrices
+ double[][] jTj = new double[cols][cols];
+ for (int i = 0; i < cols; ++i) {
+ for (int j = i; j < cols; ++j) {
+ double sum = 0;
+ for (int k = 0; k < rows; ++k) {
+ sum += wjacobian[k][i] * wjacobian[k][j];
+ }
+ jTj[i][j] = sum;
+ jTj[j][i] = sum;
+ }
+ }
+
+ try {
+ // compute the covariance matrix
+ RealMatrix inverse =
+ new LUDecompositionImpl(MatrixUtils.createRealMatrix(jTj)).getSolver().getInverse();
+ return inverse.getData();
+ } catch (InvalidMatrixException ime) {
+ throw new OptimizationException(LocalizedFormats.UNABLE_TO_COMPUTE_COVARIANCE_SINGULAR_PROBLEM);
+ }
+
+ }
+
+ /**
+ * Guess the errors in optimized parameters.
+ * <p>Guessing is covariance-based, it only gives rough order of magnitude.</p>
+ * @return errors in optimized parameters
+ * @exception FunctionEvaluationException if the function jacobian cannot b evaluated
+ * @exception OptimizationException if the covariances matrix cannot be computed
+ * or the number of degrees of freedom is not positive (number of measurements
+ * lesser or equal to number of parameters)
+ */
+ public double[] guessParametersErrors()
+ throws FunctionEvaluationException, OptimizationException {
+ if (rows <= cols) {
+ throw new OptimizationException(
+ LocalizedFormats.NO_DEGREES_OF_FREEDOM,
+ rows, cols);
+ }
+ double[] errors = new double[cols];
+ final double c = FastMath.sqrt(getChiSquare() / (rows - cols));
+ double[][] covar = getCovariances();
+ for (int i = 0; i < errors.length; ++i) {
+ errors[i] = FastMath.sqrt(covar[i][i]) * c;
+ }
+ return errors;
+ }
+
+ /** {@inheritDoc} */
+ public VectorialPointValuePair optimize(final DifferentiableMultivariateVectorialFunction f,
+ final double[] target, final double[] weights,
+ final double[] startPoint)
+ throws FunctionEvaluationException, OptimizationException, IllegalArgumentException {
+
+ if (target.length != weights.length) {
+ throw new OptimizationException(LocalizedFormats.DIMENSIONS_MISMATCH_SIMPLE,
+ target.length, weights.length);
+ }
+
+ // reset counters
+ iterations = 0;
+ objectiveEvaluations = 0;
+ jacobianEvaluations = 0;
+
+ // store least squares problem characteristics
+ function = f;
+ jF = f.jacobian();
+ targetValues = target.clone();
+ residualsWeights = weights.clone();
+ this.point = startPoint.clone();
+ this.residuals = new double[target.length];
+
+ // arrays shared with the other private methods
+ rows = target.length;
+ cols = point.length;
+ jacobian = new double[rows][cols];
+
+ wjacobian = new double[rows][cols];
+ wresiduals = new double[rows];
+
+ cost = Double.POSITIVE_INFINITY;
+
+ return doOptimize();
+
+ }
+
+ /** Perform the bulk of optimization algorithm.
+ * @return the point/value pair giving the optimal value for objective function
+ * @exception FunctionEvaluationException if the objective function throws one during
+ * the search
+ * @exception OptimizationException if the algorithm failed to converge
+ * @exception IllegalArgumentException if the start point dimension is wrong
+ */
+ protected abstract VectorialPointValuePair doOptimize()
+ throws FunctionEvaluationException, OptimizationException, IllegalArgumentException;
+
+
+}