From acd569595ee919d1afed3884cf7584cc609ad9ef Mon Sep 17 00:00:00 2001
From: Gilles Sadowski
+ * This class solve a least-square problem by solving the normal equations
+ * of the linearized problem at each iteration. Either LU decomposition or
+ * QR decomposition can be used to solve the normal equations. LU decomposition
+ * is faster but QR decomposition is more robust for difficult problems.
+ * This implementation should work even for over-determined systems
+ * (i.e. systems having more point than equations). Over-determined systems
+ * are solved by ignoring the point which have the smallest impact according
+ * to their jacobian column norm. Only the rank of the matrix and some loop bounds
+ * are changed to implement this. The resolution engine is a simple translation of the MINPACK lmder routine with minor
+ * changes. The changes include the over-determined resolution, the use of
+ * inherited convergence checker and the Q.R. decomposition which has been
+ * rewritten following the algorithm described in the
+ * P. Lascaux and R. Theodor book Analyse numérique matricielle
+ * appliquée à l'art de l'ingénieur, Masson 1986. The authors of the original fortran version are:
+ *
+ * Note that this operation involves the inversion of the
+ * JTJ
matrix, where {@code J} is the
+ * Jacobian matrix.
+ * The {@code threshold} parameter is a way for the caller to specify
+ * that the result of this computation should be considered meaningless,
+ * and thus trigger an exception.
+ *
+ * @param params Model parameters.
+ * @param threshold Singularity threshold.
+ * @return the covariance matrix.
+ * @throws org.apache.commons.math3.linear.SingularMatrixException
+ * if the covariance matrix cannot be computed (singular problem).
+ */
+ public double[][] computeCovariances(double[] params,
+ double threshold) {
+ // Set up the Jacobian.
+ final RealMatrix j = computeWeightedJacobian(params);
+
+ // Compute transpose(J)J.
+ final RealMatrix jTj = j.transpose().multiply(j);
+
+ // Compute the covariances matrix.
+ final DecompositionSolver solver
+ = new QRDecomposition(jTj, threshold).getSolver();
+ return solver.getInverse().getData();
+ }
+
+ /**
+ * Computes an estimate of the standard deviation of the parameters. The
+ * returned values are the square root of the diagonal coefficients of the
+ * covariance matrix, {@code sd(a[i]) ~= sqrt(C[i][i])}, where {@code a[i]}
+ * is the optimized value of the {@code i}-th parameter, and {@code C} is
+ * the covariance matrix.
+ *
+ * @param params Model parameters.
+ * @param covarianceSingularityThreshold Singularity threshold (see
+ * {@link #computeCovariances(double[],double) computeCovariances}).
+ * @return an estimate of the standard deviation of the optimized parameters
+ * @throws org.apache.commons.math3.linear.SingularMatrixException
+ * if the covariance matrix cannot be computed.
+ */
+ public double[] computeSigma(double[] params,
+ double covarianceSingularityThreshold) {
+ final int nC = params.length;
+ final double[] sig = new double[nC];
+ final double[][] cov = computeCovariances(params, covarianceSingularityThreshold);
+ for (int i = 0; i < nC; ++i) {
+ sig[i] = FastMath.sqrt(cov[i][i]);
+ }
+ return sig;
+ }
+
+ /**
+ * Gets the weight matrix of the observations.
+ *
+ * @return the weight matrix.
+ */
+ public RealMatrix getWeight() {
+ return weight.copy();
+ }
+
+ /**
+ * Computes the normalized cost.
+ * It is the square-root of the sum of squared of the residuals, divided
+ * by the number of measurements.
+ *
+ * @param params Model function parameters.
+ * @return the cost.
+ */
+ public double computeRMS(double[] params) {
+ final double cost = computeCost(computeResiduals(getModel().value(params)));
+ return FastMath.sqrt(cost * cost / target.length);
+ }
+
+ /**
+ * Calling this method will raise an exception.
+ *
+ * @param optData Obsolete.
+ * @return nothing.
+ * @throws MathUnsupportedOperationException if called.
+ * @deprecated Do not use this method.
+ */
+ @Deprecated
+ @Override
+ public PointVectorValuePair optimize(OptimizationData... optData)
+ throws MathUnsupportedOperationException {
+ throw new MathUnsupportedOperationException();
+ }
+
+ /**
+ * Gets a reference to the corresponding field.
+ * Altering it could jeopardize the consistency of this class.
+ *
+ * @return the reference.
+ */
+ protected double[] getTargetInternal() {
+ return target;
+ }
+
+ /**
+ * Gets a reference to the corresponding field.
+ * Altering it could jeopardize the consistency of this class.
+ *
+ * @return the reference.
+ */
+ protected RealMatrix getWeightInternal() {
+ return weight;
+ }
+
+ /**
+ * Gets a reference to the corresponding field.
+ * Altering it could jeopardize the consistency of this class.
+ *
+ * @return the reference.
+ */
+ protected RealMatrix getWeightSquareRootInternal() {
+ return weightSqrt;
+ }
+
+ /**
+ * Computes the objective function value.
+ * This method must be called by subclasses to enforce the
+ * evaluation counter limit.
+ *
+ * @param params Point at which the objective function must be evaluated.
+ * @return the objective function value at the specified point.
+ * @throws org.apache.commons.math3.exception.TooManyEvaluationsException
+ * if the maximal number of evaluations (of the model vector function) is
+ * exceeded.
+ */
+ protected double[] computeObjectiveValue(double[] params) {
+ super.incrementEvaluationCount();
+ return model.value(params);
+ }
+
+ /**
+ * Computes the weighted Jacobian matrix.
+ *
+ * @param params Model parameters at which to compute the Jacobian.
+ * @return the weighted Jacobian: W1/2 J.
+ * @throws DimensionMismatchException if the Jacobian dimension does not
+ * match problem dimension.
+ */
+ protected RealMatrix computeWeightedJacobian(double[] params) {
+ return weightSqrt.multiply(MatrixUtils.createRealMatrix(computeJacobian(params)));
+ }
+
+ /**
+ * Computes the Jacobian matrix.
+ *
+ * @param params Point at which the Jacobian must be evaluated.
+ * @return the Jacobian at the specified point.
+ */
+ protected double[][] computeJacobian(final double[] params) {
+ return jacobian.value(params);
+ }
+
+ /**
+ * Computes the cost.
+ *
+ * @param residuals Residuals.
+ * @return the cost.
+ * @see #computeResiduals(double[])
+ */
+ protected double computeCost(double[] residuals) {
+ final ArrayRealVector r = new ArrayRealVector(residuals);
+ return FastMath.sqrt(r.dotProduct(weight.operate(r)));
+ }
+
+ /**
+ * Computes the residuals.
+ * The residual is the difference between the observed (target)
+ * values and the model (objective function) value.
+ * There is one residual for each element of the vector-valued
+ * function.
+ *
+ * @param objectiveValue Value of the the objective function. This is
+ * the value returned from a call to
+ * {@link #computeObjectiveValue(double[]) computeObjectiveValue}
+ * (whose array argument contains the model parameters).
+ * @return the residuals.
+ * @throws DimensionMismatchException if {@code params} has a wrong
+ * length.
+ */
+ protected double[] computeResiduals(double[] objectiveValue) {
+ if (objectiveValue.length != target.length) {
+ throw new DimensionMismatchException(target.length,
+ objectiveValue.length);
+ }
+
+ final double[] residuals = new double[target.length];
+ for (int i = 0; i < target.length; i++) {
+ residuals[i] = target[i] - objectiveValue[i];
+ }
+
+ return residuals;
+ }
+
+ /**
+ * Computes the square-root of the weight matrix.
+ *
+ * @param m Symmetric, positive-definite (weight) matrix.
+ * @return the square-root of the weight matrix.
+ */
+ private RealMatrix squareRoot(RealMatrix m) {
+ if (m instanceof DiagonalMatrix) {
+ final int dim = m.getRowDimension();
+ final RealMatrix sqrtM = new DiagonalMatrix(dim);
+ for (int i = 0; i < dim; i++) {
+ sqrtM.setEntry(i, i, FastMath.sqrt(m.getEntry(i, i)));
+ }
+ return sqrtM;
+ } else {
+ final EigenDecomposition dec = new EigenDecomposition(m);
+ return dec.getSquareRoot();
+ }
+ }
+}
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java
new file mode 100644
index 000000000..8c6988461
--- /dev/null
+++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/GaussNewtonOptimizer.java
@@ -0,0 +1,325 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.exception.DimensionMismatchException;
+import org.apache.commons.math3.exception.ConvergenceException;
+import org.apache.commons.math3.exception.NullArgumentException;
+import org.apache.commons.math3.exception.MathInternalError;
+import org.apache.commons.math3.exception.util.LocalizedFormats;
+import org.apache.commons.math3.linear.ArrayRealVector;
+import org.apache.commons.math3.linear.BlockRealMatrix;
+import org.apache.commons.math3.linear.DecompositionSolver;
+import org.apache.commons.math3.linear.LUDecomposition;
+import org.apache.commons.math3.linear.QRDecomposition;
+import org.apache.commons.math3.linear.RealMatrix;
+import org.apache.commons.math3.linear.SingularMatrixException;
+import org.apache.commons.math3.optim.ConvergenceChecker;
+import org.apache.commons.math3.optim.PointVectorValuePair;
+
+/**
+ * Gauss-Newton least-squares solver.
+ *
+ *
+ * The default for the algorithm is to solve the normal equations
+ * using LU decomposition.
+ *
+ * @return an instance of this class.
+ */
+ public static GaussNewtonOptimizer create() {
+ return new GaussNewtonOptimizer(null, null, null, null, null, null, null,
+ 0, 0, true);
+ }
+
+ /** {@inheritDoc} */
+ public GaussNewtonOptimizer withTarget(double[] target) {
+ return new GaussNewtonOptimizer(target,
+ getWeightInternal(),
+ getWeightSquareRootInternal(),
+ getModel(),
+ getJacobian(),
+ getConvergenceChecker(),
+ getStart(),
+ getMaxEvaluations(),
+ getMaxIterations(),
+ useLU);
+ }
+
+ /** {@inheritDoc} */
+ public GaussNewtonOptimizer withWeight(RealMatrix weight) {
+ return new GaussNewtonOptimizer(getTargetInternal(),
+ weight,
+ null,
+ getModel(),
+ getJacobian(),
+ getConvergenceChecker(),
+ getStart(),
+ getMaxEvaluations(),
+ getMaxIterations(),
+ useLU);
+ }
+
+ /** {@inheritDoc} */
+ public GaussNewtonOptimizer withModelAndJacobian(MultivariateVectorFunction model,
+ MultivariateMatrixFunction jacobian) {
+ return new GaussNewtonOptimizer(getTargetInternal(),
+ getWeightInternal(),
+ getWeightSquareRootInternal(),
+ model,
+ jacobian,
+ getConvergenceChecker(),
+ getStart(),
+ getMaxEvaluations(),
+ getMaxIterations(),
+ useLU);
+ }
+
+ /** {@inheritDoc} */
+ public GaussNewtonOptimizer withConvergenceChecker(ConvergenceChecker
+ *
+ * The redistribution policy for MINPACK is available here, for convenience, it
+ * is reproduced below.
+ * Minpack Copyright Notice (1999) University of Chicago. + * All rights reserved + * |
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
|
This implementation is a translation in Java of the MINPACK + * lmpar + * routine.
+ *This method sets the lmPar and lmDir attributes.
+ *The authors of the original fortran function are:
+ *Luc Maisonobe did the Java translation.
+ * + * @param qy Array containing qTy. + * @param delta Upper bound on the euclidean norm of diagR * lmDir. + * @param diag Diagonal matrix. + * @param internalData Data (modified in-place in this method). + * @param solvedCols Number of solved point. + * @param work1 work array + * @param work2 work array + * @param work3 work array + */ + private void determineLMParameter(double[] qy, double delta, double[] diag, + InternalData internalData, int solvedCols, + double[] work1, double[] work2, double[] work3) { + final double[][] weightedJacobian = internalData.weightedJacobian; + final int[] permutation = internalData.permutation; + final int rank = internalData.rank; + final double[] diagR = internalData.diagR; + + final int nC = weightedJacobian[0].length; + + // compute and store in x the gauss-newton direction, if the + // jacobian is rank-deficient, obtain a least squares solution + for (int j = 0; j < rank; ++j) { + lmDir[permutation[j]] = qy[j]; + } + for (int j = rank; j < nC; ++j) { + lmDir[permutation[j]] = 0; + } + for (int k = rank - 1; k >= 0; --k) { + int pk = permutation[k]; + double ypk = lmDir[pk] / diagR[pk]; + for (int i = 0; i < k; ++i) { + lmDir[permutation[i]] -= ypk * weightedJacobian[i][pk]; + } + lmDir[pk] = ypk; + } + + // evaluate the function at the origin, and test + // for acceptance of the Gauss-Newton direction + double dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work1[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double fp = dxNorm - delta; + if (fp <= 0.1 * delta) { + lmPar = 0; + return; + } + + // if the jacobian is not rank deficient, the Newton step provides + // a lower bound, parl, for the zero of the function, + // otherwise set this bound to zero + double sum2; + double parl = 0; + if (rank == solvedCols) { + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] *= diag[pj] / dxNorm; + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i < j; ++i) { + sum += weightedJacobian[i][pj] * work1[permutation[i]]; + } + double s = (work1[pj] - sum) / diagR[pj]; + work1[pj] = s; + sum2 += s * s; + } + parl = fp / (delta * sum2); + } + + // calculate an upper bound, paru, for the zero of the function + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double sum = 0; + for (int i = 0; i <= j; ++i) { + sum += weightedJacobian[i][pj] * qy[i]; + } + sum /= diag[pj]; + sum2 += sum * sum; + } + double gNorm = FastMath.sqrt(sum2); + double paru = gNorm / delta; + if (paru == 0) { + paru = Precision.SAFE_MIN / FastMath.min(delta, 0.1); + } + + // if the input par lies outside of the interval (parl,paru), + // set par to the closer endpoint + lmPar = FastMath.min(paru, FastMath.max(lmPar, parl)); + if (lmPar == 0) { + lmPar = gNorm / dxNorm; + } + + for (int countdown = 10; countdown >= 0; --countdown) { + + // evaluate the function at the current value of lmPar + if (lmPar == 0) { + lmPar = FastMath.max(Precision.SAFE_MIN, 0.001 * paru); + } + double sPar = FastMath.sqrt(lmPar); + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = sPar * diag[pj]; + } + determineLMDirection(qy, work1, work2, internalData, solvedCols, work3); + + dxNorm = 0; + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + double s = diag[pj] * lmDir[pj]; + work3[pj] = s; + dxNorm += s * s; + } + dxNorm = FastMath.sqrt(dxNorm); + double previousFP = fp; + fp = dxNorm - delta; + + // if the function is small enough, accept the current value + // of lmPar, also test for the exceptional cases where parl is zero + if (FastMath.abs(fp) <= 0.1 * delta || + (parl == 0 && + fp <= previousFP && + previousFP < 0)) { + return; + } + + // compute the Newton correction + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] = work3[pj] * diag[pj] / dxNorm; + } + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + work1[pj] /= work2[j]; + double tmp = work1[pj]; + for (int i = j + 1; i < solvedCols; ++i) { + work1[permutation[i]] -= weightedJacobian[i][pj] * tmp; + } + } + sum2 = 0; + for (int j = 0; j < solvedCols; ++j) { + double s = work1[permutation[j]]; + sum2 += s * s; + } + double correction = fp / (delta * sum2); + + // depending on the sign of the function, update parl or paru. + if (fp > 0) { + parl = FastMath.max(parl, lmPar); + } else if (fp < 0) { + paru = FastMath.min(paru, lmPar); + } + + // compute an improved estimate for lmPar + lmPar = FastMath.max(parl, lmPar + correction); + } + + return; + } + + /** + * Solve a*x = b and d*x = 0 in the least squares sense. + *This implementation is a translation in Java of the MINPACK + * qrsolv + * routine.
+ *This method sets the lmDir and lmDiag attributes.
+ *The authors of the original fortran function are:
+ *Luc Maisonobe did the Java translation.
+ * + * @param qy array containing qTy + * @param diag diagonal matrix + * @param lmDiag diagonal elements associated with lmDir + * @param internalData Data (modified in-place in this method). + * @param solvedCols Number of sloved point. + * @param work work array + */ + private void determineLMDirection(double[] qy, double[] diag, + double[] lmDiag, + InternalData internalData, + int solvedCols, + double[] work) { + final int[] permutation = internalData.permutation; + final double[][] weightedJacobian = internalData.weightedJacobian; + final double[] diagR = internalData.diagR; + + // copy R and Qty to preserve input and initialize s + // in particular, save the diagonal elements of R in lmDir + for (int j = 0; j < solvedCols; ++j) { + int pj = permutation[j]; + for (int i = j + 1; i < solvedCols; ++i) { + weightedJacobian[i][pj] = weightedJacobian[j][permutation[i]]; + } + lmDir[j] = diagR[pj]; + work[j] = qy[j]; + } + + // eliminate the diagonal matrix d using a Givens rotation + for (int j = 0; j < solvedCols; ++j) { + + // prepare the row of d to be eliminated, locating the + // diagonal element using p from the Q.R. factorization + int pj = permutation[j]; + double dpj = diag[pj]; + if (dpj != 0) { + Arrays.fill(lmDiag, j + 1, lmDiag.length, 0); + } + lmDiag[j] = dpj; + + // the transformations to eliminate the row of d + // modify only a single element of Qty + // beyond the first n, which is initially zero. + double qtbpj = 0; + for (int k = j; k < solvedCols; ++k) { + int pk = permutation[k]; + + // determine a Givens rotation which eliminates the + // appropriate element in the current row of d + if (lmDiag[k] != 0) { + + final double sin; + final double cos; + double rkk = weightedJacobian[k][pk]; + if (FastMath.abs(rkk) < FastMath.abs(lmDiag[k])) { + final double cotan = rkk / lmDiag[k]; + sin = 1.0 / FastMath.sqrt(1.0 + cotan * cotan); + cos = sin * cotan; + } else { + final double tan = lmDiag[k] / rkk; + cos = 1.0 / FastMath.sqrt(1.0 + tan * tan); + sin = cos * tan; + } + + // compute the modified diagonal element of R and + // the modified element of (Qty,0) + weightedJacobian[k][pk] = cos * rkk + sin * lmDiag[k]; + final double temp = cos * work[k] + sin * qtbpj; + qtbpj = -sin * work[k] + cos * qtbpj; + work[k] = temp; + + // accumulate the tranformation in the row of s + for (int i = k + 1; i < solvedCols; ++i) { + double rik = weightedJacobian[i][pk]; + final double temp2 = cos * rik + sin * lmDiag[i]; + lmDiag[i] = -sin * rik + cos * lmDiag[i]; + weightedJacobian[i][pk] = temp2; + } + } + } + + // store the diagonal element of s and restore + // the corresponding diagonal element of R + lmDiag[j] = weightedJacobian[j][permutation[j]]; + weightedJacobian[j][permutation[j]] = lmDir[j]; + } + + // solve the triangular system for z, if the system is + // singular, then obtain a least squares solution + int nSing = solvedCols; + for (int j = 0; j < solvedCols; ++j) { + if ((lmDiag[j] == 0) && (nSing == solvedCols)) { + nSing = j; + } + if (nSing < solvedCols) { + work[j] = 0; + } + } + if (nSing > 0) { + for (int j = nSing - 1; j >= 0; --j) { + int pj = permutation[j]; + double sum = 0; + for (int i = j + 1; i < nSing; ++i) { + sum += weightedJacobian[i][pj] * work[i]; + } + work[j] = (work[j] - sum) / lmDiag[j]; + } + } + + // permute the components of z back to components of lmDir + for (int j = 0; j < lmDir.length; ++j) { + lmDir[permutation[j]] = work[j]; + } + } + + /** + * Decompose a matrix A as A.P = Q.R using Householder transforms. + *As suggested in the P. Lascaux and R. Theodor book + * Analyse numérique matricielle appliquée à + * l'art de l'ingénieur (Masson, 1986), instead of representing + * the Householder transforms with uk unit vectors such that: + *
+ * Hk = I - 2uk.ukt + *+ * we use k non-unit vectors such that: + *
+ * Hk = I - betakvk.vkt + *+ * where vk = ak - alphak ek. + * The betak coefficients are provided upon exit as recomputing + * them from the vk vectors would be costly. + *
This decomposition handles rank deficient cases since the tranformations + * are performed in non-increasing columns norms order thanks to columns + * pivoting. The diagonal elements of the R matrix are therefore also in + * non-increasing absolute values order.
+ * + * @param jacobian Weighted Jacobian matrix at the current point. + * @param solvedCols Number of solved point. + * @return data used in other methods of this class. + * @throws ConvergenceException if the decomposition cannot be performed. + */ + private InternalData qrDecomposition(RealMatrix jacobian, + int solvedCols) throws ConvergenceException { + // Code in this class assumes that the weighted Jacobian is -(W^(1/2) J), + // hence the multiplication by -1. + final double[][] weightedJacobian = jacobian.scalarMultiply(-1).getData(); + + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + final int[] permutation = new int[nC]; + final double[] diagR = new double[nC]; + final double[] jacNorm = new double[nC]; + final double[] beta = new double[nC]; + + // initializations + for (int k = 0; k < nC; ++k) { + permutation[k] = k; + double norm2 = 0; + for (int i = 0; i < nR; ++i) { + double akk = weightedJacobian[i][k]; + norm2 += akk * akk; + } + jacNorm[k] = FastMath.sqrt(norm2); + } + + // transform the matrix column after column + for (int k = 0; k < nC; ++k) { + + // select the column with the greatest norm on active components + int nextColumn = -1; + double ak2 = Double.NEGATIVE_INFINITY; + for (int i = k; i < nC; ++i) { + double norm2 = 0; + for (int j = k; j < nR; ++j) { + double aki = weightedJacobian[j][permutation[i]]; + norm2 += aki * aki; + } + if (Double.isInfinite(norm2) || Double.isNaN(norm2)) { + throw new ConvergenceException(LocalizedFormats.UNABLE_TO_PERFORM_QR_DECOMPOSITION_ON_JACOBIAN, + nR, nC); + } + if (norm2 > ak2) { + nextColumn = i; + ak2 = norm2; + } + } + if (ak2 <= qrRankingThreshold) { + return new InternalData(weightedJacobian, permutation, k, diagR, jacNorm, beta); + } + int pk = permutation[nextColumn]; + permutation[nextColumn] = permutation[k]; + permutation[k] = pk; + + // choose alpha such that Hk.u = alpha ek + double akk = weightedJacobian[k][pk]; + double alpha = (akk > 0) ? -FastMath.sqrt(ak2) : FastMath.sqrt(ak2); + double betak = 1.0 / (ak2 - akk * alpha); + beta[pk] = betak; + + // transform the current column + diagR[pk] = alpha; + weightedJacobian[k][pk] -= alpha; + + // transform the remaining columns + for (int dk = nC - 1 - k; dk > 0; --dk) { + double gamma = 0; + for (int j = k; j < nR; ++j) { + gamma += weightedJacobian[j][pk] * weightedJacobian[j][permutation[k + dk]]; + } + gamma *= betak; + for (int j = k; j < nR; ++j) { + weightedJacobian[j][permutation[k + dk]] -= gamma * weightedJacobian[j][pk]; + } + } + } + + return new InternalData(weightedJacobian, permutation, solvedCols, diagR, jacNorm, beta); + } + + /** + * Compute the product Qt.y for some Q.R. decomposition. + * + * @param y vector to multiply (will be overwritten with the result) + * @param internalData Data. + */ + private void qTy(double[] y, + InternalData internalData) { + final double[][] weightedJacobian = internalData.weightedJacobian; + final int[] permutation = internalData.permutation; + final double[] beta = internalData.beta; + + final int nR = weightedJacobian.length; + final int nC = weightedJacobian[0].length; + + for (int k = 0; k < nC; ++k) { + int pk = permutation[k]; + double gamma = 0; + for (int i = k; i < nR; ++i) { + gamma += weightedJacobian[i][pk] * y[i]; + } + gamma *= beta[pk]; + for (int i = k; i < nR; ++i) { + y[i] -= gamma * weightedJacobian[i][pk]; + } + } + } +} diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/WithConvergenceChecker.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/WithConvergenceChecker.java new file mode 100644 index 000000000..1b523c5b7 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/WithConvergenceChecker.java @@ -0,0 +1,39 @@ +/* + * 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.math3.fitting.leastsquares; + +import org.apache.commons.math3.optim.ConvergenceChecker; +import org.apache.commons.math3.optim.PointVectorValuePair; + +/** + * Interface for "fluent-API" that advertizes a capability of the optimizer. + * + * @param
+ * mvn test -Dtest=AbstractLeastSquaresOptimizerTestValidation
+ *
+ * or by running
+ *
+ * mvn test -Dtest=AbstractLeastSquaresOptimizerTestValidation -DargLine="-DmcRuns=1234 -server"
+ *
+ */
+public class AbstractLeastSquaresOptimizerTestValidation {
+ /** Number of runs. */
+ private static final int MONTE_CARLO_RUNS = Integer.parseInt(System.getProperty("mcRuns",
+ "100"));
+
+ /**
+ * Using a Monte-Carlo procedure, this test checks the error estimations
+ * as provided by the square-root of the diagonal elements of the
+ * covariance matrix.
+ * Some of the unit tests are re-implementations of the MINPACK file17 and file22 test files.
+ * The redistribution policy for MINPACK is available here/
+ *
+ * @version $Id$
+ */
+public class GaussNewtonOptimizerTest
+ extends AbstractLeastSquaresOptimizerAbstractTest Some of the unit tests are re-implementations of the MINPACK file17 and file22 test files.
+ * The redistribution policy for MINPACK is available here.
+ *
+ * @version $Id$
+ */
+public class LevenbergMarquardtOptimizerTest
+ extends AbstractLeastSquaresOptimizerAbstractTest Some of the unit tests are re-implementations of the MINPACK file17 and file22 test files.
+ * The redistribution policy for MINPACK is available here, for
+ * convenience, it is reproduced below.
+ * Parses the specified text lines, and extracts the indices of the first
+ * and last lines of the data defined by the specified {@code key}. This key
+ * must be one of
+ *
+ * In the NIST data files, the line indices are separated by the keywords
+ * {@code "lines"} and {@code "to"}.
+ *
+ *
+
+ * @author Argonne National Laboratory. MINPACK project. March 1980 (original fortran minpack tests)
+ * @author Burton S. Garbow (original fortran minpack tests)
+ * @author Kenneth E. Hillstrom (original fortran minpack tests)
+ * @author Jorge J. More (original fortran minpack tests)
+ * @author Luc Maisonobe (non-minpack tests and minpack tests Java translation)
+ */
+public class MinpackTest {
+
+ @Test
+ public void testMinpackLinearFullRank() {
+ minpackTest(new LinearFullRankFunction(10, 5, 1.0,
+ 5.0, 2.23606797749979), false);
+ minpackTest(new LinearFullRankFunction(50, 5, 1.0,
+ 8.06225774829855, 6.70820393249937), false);
+ }
+
+ @Test
+ public void testMinpackLinearRank1() {
+ minpackTest(new LinearRank1Function(10, 5, 1.0,
+ 291.521868819476, 1.4638501094228), false);
+ minpackTest(new LinearRank1Function(50, 5, 1.0,
+ 3101.60039334535, 3.48263016573496), false);
+ }
+
+ @Test
+ public void testMinpackLinearRank1ZeroColsAndRows() {
+ minpackTest(new LinearRank1ZeroColsAndRowsFunction(10, 5, 1.0), false);
+ minpackTest(new LinearRank1ZeroColsAndRowsFunction(50, 5, 1.0), false);
+ }
+
+ @Test
+ public void testMinpackRosenbrok() {
+ minpackTest(new RosenbrockFunction(new double[] { -1.2, 1.0 },
+ FastMath.sqrt(24.2)), false);
+ minpackTest(new RosenbrockFunction(new double[] { -12.0, 10.0 },
+ FastMath.sqrt(1795769.0)), false);
+ minpackTest(new RosenbrockFunction(new double[] { -120.0, 100.0 },
+ 11.0 * FastMath.sqrt(169000121.0)), false);
+ }
+
+ @Test
+ public void testMinpackHelicalValley() {
+ minpackTest(new HelicalValleyFunction(new double[] { -1.0, 0.0, 0.0 },
+ 50.0), false);
+ minpackTest(new HelicalValleyFunction(new double[] { -10.0, 0.0, 0.0 },
+ 102.95630140987), false);
+ minpackTest(new HelicalValleyFunction(new double[] { -100.0, 0.0, 0.0},
+ 991.261822123701), false);
+ }
+
+ @Test
+ public void testMinpackPowellSingular() {
+ minpackTest(new PowellSingularFunction(new double[] { 3.0, -1.0, 0.0, 1.0 },
+ 14.6628782986152), false);
+ minpackTest(new PowellSingularFunction(new double[] { 30.0, -10.0, 0.0, 10.0 },
+ 1270.9838708654), false);
+ minpackTest(new PowellSingularFunction(new double[] { 300.0, -100.0, 0.0, 100.0 },
+ 126887.903284750), false);
+ }
+
+ @Test
+ public void testMinpackFreudensteinRoth() {
+ minpackTest(new FreudensteinRothFunction(new double[] { 0.5, -2.0 },
+ 20.0124960961895, 6.99887517584575,
+ new double[] {
+ 11.4124844654993,
+ -0.896827913731509
+ }), false);
+ minpackTest(new FreudensteinRothFunction(new double[] { 5.0, -20.0 },
+ 12432.833948863, 6.9988751744895,
+ new double[] {
+ 11.41300466147456,
+ -0.896796038685959
+ }), false);
+ minpackTest(new FreudensteinRothFunction(new double[] { 50.0, -200.0 },
+ 11426454.595762, 6.99887517242903,
+ new double[] {
+ 11.412781785788564,
+ -0.8968051074920405
+ }), false);
+ }
+
+ @Test
+ public void testMinpackBard() {
+ minpackTest(new BardFunction(1.0, 6.45613629515967, 0.0906359603390466,
+ new double[] {
+ 0.0824105765758334,
+ 1.1330366534715,
+ 2.34369463894115
+ }), false);
+ minpackTest(new BardFunction(10.0, 36.1418531596785, 4.17476870138539,
+ new double[] {
+ 0.840666673818329,
+ -158848033.259565,
+ -164378671.653535
+ }), false);
+ minpackTest(new BardFunction(100.0, 384.114678637399, 4.17476870135969,
+ new double[] {
+ 0.840666673867645,
+ -158946167.205518,
+ -164464906.857771
+ }), false);
+ }
+
+ @Test
+ public void testMinpackKowalikOsborne() {
+ minpackTest(new KowalikOsborneFunction(new double[] { 0.25, 0.39, 0.415, 0.39 },
+ 0.0728915102882945,
+ 0.017535837721129,
+ new double[] {
+ 0.192807810476249,
+ 0.191262653354071,
+ 0.123052801046931,
+ 0.136053221150517
+ }), false);
+ minpackTest(new KowalikOsborneFunction(new double[] { 2.5, 3.9, 4.15, 3.9 },
+ 2.97937007555202,
+ 0.032052192917937,
+ new double[] {
+ 728675.473768287,
+ -14.0758803129393,
+ -32977797.7841797,
+ -20571594.1977912
+ }), false);
+ minpackTest(new KowalikOsborneFunction(new double[] { 25.0, 39.0, 41.5, 39.0 },
+ 29.9590617016037,
+ 0.0175364017658228,
+ new double[] {
+ 0.192948328597594,
+ 0.188053165007911,
+ 0.122430604321144,
+ 0.134575665392506
+ }), false);
+ }
+
+ @Test
+ public void testMinpackMeyer() {
+ minpackTest(new MeyerFunction(new double[] { 0.02, 4000.0, 250.0 },
+ 41153.4665543031, 9.37794514651874,
+ new double[] {
+ 0.00560963647102661,
+ 6181.34634628659,
+ 345.223634624144
+ }), false);
+ minpackTest(new MeyerFunction(new double[] { 0.2, 40000.0, 2500.0 },
+ 4168216.89130846, 792.917871779501,
+ new double[] {
+ 1.42367074157994e-11,
+ 33695.7133432541,
+ 901.268527953801
+ }), true);
+ }
+
+ @Test
+ public void testMinpackWatson() {
+ minpackTest(new WatsonFunction(6, 0.0,
+ 5.47722557505166, 0.0478295939097601,
+ new double[] {
+ -0.0157249615083782, 1.01243488232965,
+ -0.232991722387673, 1.26043101102818,
+ -1.51373031394421, 0.99299727291842
+ }), false);
+ minpackTest(new WatsonFunction(6, 10.0,
+ 6433.12578950026, 0.0478295939096951,
+ new double[] {
+ -0.0157251901386677, 1.01243485860105,
+ -0.232991545843829, 1.26042932089163,
+ -1.51372776706575, 0.99299573426328
+ }), false);
+ minpackTest(new WatsonFunction(6, 100.0,
+ 674256.040605213, 0.047829593911544,
+ new double[] {
+ -0.0157247019712586, 1.01243490925658,
+ -0.232991922761641, 1.26043292929555,
+ -1.51373320452707, 0.99299901922322
+ }), false);
+ minpackTest(new WatsonFunction(9, 0.0,
+ 5.47722557505166, 0.00118311459212420,
+ new double[] {
+ -0.153070644166722e-4, 0.999789703934597,
+ 0.0147639634910978, 0.146342330145992,
+ 1.00082109454817, -2.61773112070507,
+ 4.10440313943354, -3.14361226236241,
+ 1.05262640378759
+ }), false);
+ minpackTest(new WatsonFunction(9, 10.0,
+ 12088.127069307, 0.00118311459212513,
+ new double[] {
+ -0.153071334849279e-4, 0.999789703941234,
+ 0.0147639629786217, 0.146342334818836,
+ 1.00082107321386, -2.61773107084722,
+ 4.10440307655564, -3.14361222178686,
+ 1.05262639322589
+ }), false);
+ minpackTest(new WatsonFunction(9, 100.0,
+ 1269109.29043834, 0.00118311459212384,
+ new double[] {
+ -0.153069523352176e-4, 0.999789703958371,
+ 0.0147639625185392, 0.146342341096326,
+ 1.00082104729164, -2.61773101573645,
+ 4.10440301427286, -3.14361218602503,
+ 1.05262638516774
+ }), false);
+ minpackTest(new WatsonFunction(12, 0.0,
+ 5.47722557505166, 0.217310402535861e-4,
+ new double[] {
+ -0.660266001396382e-8, 1.00000164411833,
+ -0.000563932146980154, 0.347820540050756,
+ -0.156731500244233, 1.05281515825593,
+ -3.24727109519451, 7.2884347837505,
+ -10.271848098614, 9.07411353715783,
+ -4.54137541918194, 1.01201187975044
+ }), false);
+ minpackTest(new WatsonFunction(12, 10.0,
+ 19220.7589790951, 0.217310402518509e-4,
+ new double[] {
+ -0.663710223017410e-8, 1.00000164411787,
+ -0.000563932208347327, 0.347820540486998,
+ -0.156731503955652, 1.05281517654573,
+ -3.2472711515214, 7.28843489430665,
+ -10.2718482369638, 9.07411364383733,
+ -4.54137546533666, 1.01201188830857
+ }), false);
+ minpackTest(new WatsonFunction(12, 100.0,
+ 2018918.04462367, 0.217310402539845e-4,
+ new double[] {
+ -0.663806046485249e-8, 1.00000164411786,
+ -0.000563932210324959, 0.347820540503588,
+ -0.156731504091375, 1.05281517718031,
+ -3.24727115337025, 7.28843489775302,
+ -10.2718482410813, 9.07411364688464,
+ -4.54137546660822, 1.0120118885369
+ }), false);
+ }
+
+ @Test
+ public void testMinpackBox3Dimensional() {
+ minpackTest(new Box3DimensionalFunction(10, new double[] { 0.0, 10.0, 20.0 },
+ 32.1115837449572), false);
+ }
+
+ @Test
+ public void testMinpackJennrichSampson() {
+ minpackTest(new JennrichSampsonFunction(10, new double[] { 0.3, 0.4 },
+ 64.5856498144943, 11.1517793413499,
+ new double[] {
+// 0.2578330049, 0.257829976764542
+ 0.2578199266368004, 0.25782997676455244
+ }), false);
+ }
+
+ @Test
+ public void testMinpackBrownDennis() {
+ minpackTest(new BrownDennisFunction(20,
+ new double[] { 25.0, 5.0, -5.0, -1.0 },
+ 2815.43839161816, 292.954288244866,
+ new double[] {
+ -11.59125141003, 13.2024883984741,
+ -0.403574643314272, 0.236736269844604
+ }), false);
+ minpackTest(new BrownDennisFunction(20,
+ new double[] { 250.0, 50.0, -50.0, -10.0 },
+ 555073.354173069, 292.954270581415,
+ new double[] {
+ -11.5959274272203, 13.2041866926242,
+ -0.403417362841545, 0.236771143410386
+ }), false);
+ minpackTest(new BrownDennisFunction(20,
+ new double[] { 2500.0, 500.0, -500.0, -100.0 },
+ 61211252.2338581, 292.954306151134,
+ new double[] {
+ -11.5902596937374, 13.2020628854665,
+ -0.403688070279258, 0.236665033746463
+ }), false);
+ }
+
+ @Test
+ public void testMinpackChebyquad() {
+ minpackTest(new ChebyquadFunction(1, 8, 1.0,
+ 1.88623796907732, 1.88623796907732,
+ new double[] { 0.5 }), false);
+ minpackTest(new ChebyquadFunction(1, 8, 10.0,
+ 5383344372.34005, 1.88424820499951,
+ new double[] { 0.9817314924684 }), false);
+ minpackTest(new ChebyquadFunction(1, 8, 100.0,
+ 0.118088726698392e19, 1.88424820499347,
+ new double[] { 0.9817314852934 }), false);
+ minpackTest(new ChebyquadFunction(8, 8, 1.0,
+ 0.196513862833975, 0.0593032355046727,
+ new double[] {
+ 0.0431536648587336, 0.193091637843267,
+ 0.266328593812698, 0.499999334628884,
+ 0.500000665371116, 0.733671406187302,
+ 0.806908362156733, 0.956846335141266
+ }), false);
+ minpackTest(new ChebyquadFunction(9, 9, 1.0,
+ 0.16994993465202, 0.0,
+ new double[] {
+ 0.0442053461357828, 0.199490672309881,
+ 0.23561910847106, 0.416046907892598,
+ 0.5, 0.583953092107402,
+ 0.764380891528940, 0.800509327690119,
+ 0.955794653864217
+ }), false);
+ minpackTest(new ChebyquadFunction(10, 10, 1.0,
+ 0.183747831178711, 0.0806471004038253,
+ new double[] {
+ 0.0596202671753563, 0.166708783805937,
+ 0.239171018813509, 0.398885290346268,
+ 0.398883667870681, 0.601116332129320,
+ 0.60111470965373, 0.760828981186491,
+ 0.833291216194063, 0.940379732824644
+ }), false);
+ }
+
+ @Test
+ public void testMinpackBrownAlmostLinear() {
+ minpackTest(new BrownAlmostLinearFunction(10, 0.5,
+ 16.5302162063499, 0.0,
+ new double[] {
+ 0.979430303349862, 0.979430303349862,
+ 0.979430303349862, 0.979430303349862,
+ 0.979430303349862, 0.979430303349862,
+ 0.979430303349862, 0.979430303349862,
+ 0.979430303349862, 1.20569696650138
+ }), false);
+ minpackTest(new BrownAlmostLinearFunction(10, 5.0,
+ 9765624.00089211, 0.0,
+ new double[] {
+ 0.979430303349865, 0.979430303349865,
+ 0.979430303349865, 0.979430303349865,
+ 0.979430303349865, 0.979430303349865,
+ 0.979430303349865, 0.979430303349865,
+ 0.979430303349865, 1.20569696650135
+ }), false);
+ minpackTest(new BrownAlmostLinearFunction(10, 50.0,
+ 0.9765625e17, 0.0,
+ new double[] {
+ 1.0, 1.0, 1.0, 1.0, 1.0,
+ 1.0, 1.0, 1.0, 1.0, 1.0
+ }), false);
+ minpackTest(new BrownAlmostLinearFunction(30, 0.5,
+ 83.476044467848, 0.0,
+ new double[] {
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 0.997754216442807,
+ 0.997754216442807, 1.06737350671578
+ }), false);
+ minpackTest(new BrownAlmostLinearFunction(40, 0.5,
+ 128.026364472323, 0.0,
+ new double[] {
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 1.00000000000002, 1.00000000000002,
+ 0.999999999999121
+ }), false);
+ }
+
+ @Test
+ public void testMinpackOsborne1() {
+ minpackTest(new Osborne1Function(new double[] { 0.5, 1.5, -1.0, 0.01, 0.02, },
+ 0.937564021037838, 0.00739249260904843,
+ new double[] {
+ 0.375410049244025, 1.93584654543108,
+ -1.46468676748716, 0.0128675339110439,
+ 0.0221227011813076
+ }), false);
+ }
+
+ @Test
+ public void testMinpackOsborne2() {
+ minpackTest(new Osborne2Function(new double[] {
+ 1.3, 0.65, 0.65, 0.7, 0.6,
+ 3.0, 5.0, 7.0, 2.0, 4.5, 5.5
+ },
+ 1.44686540984712, 0.20034404483314,
+ new double[] {
+ 1.30997663810096, 0.43155248076,
+ 0.633661261602859, 0.599428560991695,
+ 0.754179768272449, 0.904300082378518,
+ 1.36579949521007, 4.82373199748107,
+ 2.39868475104871, 4.56887554791452,
+ 5.67534206273052
+ }), false);
+ }
+
+ private void minpackTest(MinpackFunction function, boolean exceptionExpected) {
+ final double tol = 2.22044604926e-16;
+ LevenbergMarquardtOptimizer optimizer = LevenbergMarquardtOptimizer.create();
+ optimizer = optimizer
+ .withTuningParameters(optimizer.getInitialStepBoundFactor(),
+ FastMath.sqrt(tol),
+ FastMath.sqrt(tol),
+ tol,
+ optimizer.getRankingThreshold())
+ .withMaxEvaluations(400 * (function.getN() + 1))
+ .withMaxIterations(2000)
+ .withModelAndJacobian(function.getModelFunction(),
+ function.getModelFunctionJacobian())
+ .withTarget(function.getTarget())
+ .withWeight(new DiagonalMatrix(function.getWeight()))
+ .withStartPoint(function.getStartPoint());
+
+ try {
+ final double[] optimum = optimizer.optimize().getPoint();
+ Assert.assertFalse(exceptionExpected);
+ function.checkTheoreticalMinCost(optimizer.computeRMS(optimum));
+ function.checkTheoreticalMinParams(optimum);
+ } catch (TooManyEvaluationsException e) {
+ Assert.assertTrue(exceptionExpected);
+ }
+ }
+
+ private static abstract class MinpackFunction {
+ protected int n;
+ protected int m;
+ protected double[] startParams;
+ protected double theoreticalMinCost;
+ protected double[] theoreticalMinParams;
+ protected double costAccuracy;
+ protected double paramsAccuracy;
+
+ protected MinpackFunction(int m, double[] startParams,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ this.m = m;
+ this.n = startParams.length;
+ this.startParams = startParams.clone();
+ this.theoreticalMinCost = theoreticalMinCost;
+ this.theoreticalMinParams = theoreticalMinParams;
+ this.costAccuracy = 1.0e-8;
+ this.paramsAccuracy = 1.0e-5;
+ }
+
+ protected static double[] buildArray(int n, double x) {
+ double[] array = new double[n];
+ Arrays.fill(array, x);
+ return array;
+ }
+
+ public double[] getTarget() {
+ return buildArray(m, 0.0);
+ }
+
+ public double[] getWeight() {
+ return buildArray(m, 1.0);
+ }
+
+ public double[] getStartPoint() {
+ return startParams.clone();
+ }
+
+ protected void setCostAccuracy(double costAccuracy) {
+ this.costAccuracy = costAccuracy;
+ }
+
+ protected void setParamsAccuracy(double paramsAccuracy) {
+ this.paramsAccuracy = paramsAccuracy;
+ }
+
+ public int getN() {
+ return startParams.length;
+ }
+
+ public void checkTheoreticalMinCost(double rms) {
+ double threshold = costAccuracy * (1.0 + theoreticalMinCost);
+ Assert.assertEquals(theoreticalMinCost, FastMath.sqrt(m) * rms, threshold);
+ }
+
+ public void checkTheoreticalMinParams(double[] params) {
+ if (theoreticalMinParams != null) {
+ for (int i = 0; i < theoreticalMinParams.length; ++i) {
+ double mi = theoreticalMinParams[i];
+ double vi = params[i];
+ Assert.assertEquals(mi, vi, paramsAccuracy * (1.0 + FastMath.abs(mi)));
+ }
+ }
+ }
+
+ public MultivariateVectorFunction getModelFunction() {
+ return new MultivariateVectorFunction() {
+ public double[] value(double[] point) {
+ return computeValue(point);
+ }
+ };
+ }
+
+ public MultivariateMatrixFunction getModelFunctionJacobian() {
+ return new MultivariateMatrixFunction() {
+ public double[][] value(double[] point) {
+ return computeJacobian(point);
+ }
+ };
+ }
+
+ public abstract double[][] computeJacobian(double[] variables);
+ public abstract double[] computeValue(double[] variables);
+ }
+
+ private static class LinearFullRankFunction extends MinpackFunction {
+ private static final long serialVersionUID = -9030323226268039536L;
+
+ public LinearFullRankFunction(int m, int n, double x0,
+ double theoreticalStartCost,
+ double theoreticalMinCost) {
+ super(m, buildArray(n, x0), theoreticalMinCost,
+ buildArray(n, -1.0));
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double t = 2.0 / m;
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ jacobian[i] = new double[n];
+ for (int j = 0; j < n; ++j) {
+ jacobian[i][j] = (i == j) ? (1 - t) : -t;
+ }
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double sum = 0;
+ for (int i = 0; i < n; ++i) {
+ sum += variables[i];
+ }
+ double t = 1 + 2 * sum / m;
+ double[] f = new double[m];
+ for (int i = 0; i < n; ++i) {
+ f[i] = variables[i] - t;
+ }
+ Arrays.fill(f, n, m, -t);
+ return f;
+ }
+ }
+
+ private static class LinearRank1Function extends MinpackFunction {
+ private static final long serialVersionUID = 8494863245104608300L;
+
+ public LinearRank1Function(int m, int n, double x0,
+ double theoreticalStartCost,
+ double theoreticalMinCost) {
+ super(m, buildArray(n, x0), theoreticalMinCost, null);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ jacobian[i] = new double[n];
+ for (int j = 0; j < n; ++j) {
+ jacobian[i][j] = (i + 1) * (j + 1);
+ }
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double[] f = new double[m];
+ double sum = 0;
+ for (int i = 0; i < n; ++i) {
+ sum += (i + 1) * variables[i];
+ }
+ for (int i = 0; i < m; ++i) {
+ f[i] = (i + 1) * sum - 1;
+ }
+ return f;
+ }
+ }
+
+ private static class LinearRank1ZeroColsAndRowsFunction extends MinpackFunction {
+ private static final long serialVersionUID = -3316653043091995018L;
+
+ public LinearRank1ZeroColsAndRowsFunction(int m, int n, double x0) {
+ super(m, buildArray(n, x0),
+ FastMath.sqrt((m * (m + 3) - 6) / (2.0 * (2 * m - 3))),
+ null);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ jacobian[i] = new double[n];
+ jacobian[i][0] = 0;
+ for (int j = 1; j < (n - 1); ++j) {
+ if (i == 0) {
+ jacobian[i][j] = 0;
+ } else if (i != (m - 1)) {
+ jacobian[i][j] = i * (j + 1);
+ } else {
+ jacobian[i][j] = 0;
+ }
+ }
+ jacobian[i][n - 1] = 0;
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double[] f = new double[m];
+ double sum = 0;
+ for (int i = 1; i < (n - 1); ++i) {
+ sum += (i + 1) * variables[i];
+ }
+ for (int i = 0; i < (m - 1); ++i) {
+ f[i] = i * sum - 1;
+ }
+ f[m - 1] = -1;
+ return f;
+ }
+ }
+
+ private static class RosenbrockFunction extends MinpackFunction {
+ private static final long serialVersionUID = 2893438180956569134L;
+ public RosenbrockFunction(double[] startParams, double theoreticalStartCost) {
+ super(2, startParams, 0.0, buildArray(2, 1.0));
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ return new double[][] { { -20 * x1, 10 }, { -1, 0 } };
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ return new double[] { 10 * (x2 - x1 * x1), 1 - x1 };
+ }
+ }
+
+ private static class HelicalValleyFunction extends MinpackFunction {
+ private static final long serialVersionUID = 220613787843200102L;
+ public HelicalValleyFunction(double[] startParams,
+ double theoreticalStartCost) {
+ super(3, startParams, 0.0, new double[] { 1.0, 0.0, 0.0 });
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double tmpSquare = x1 * x1 + x2 * x2;
+ double tmp1 = twoPi * tmpSquare;
+ double tmp2 = FastMath.sqrt(tmpSquare);
+ return new double[][] {
+ { 100 * x2 / tmp1, -100 * x1 / tmp1, 10 },
+ { 10 * x1 / tmp2, 10 * x2 / tmp2, 0 },
+ { 0, 0, 1 }
+ };
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double tmp1;
+ if (x1 == 0) {
+ tmp1 = (x2 >= 0) ? 0.25 : -0.25;
+ } else {
+ tmp1 = FastMath.atan(x2 / x1) / twoPi;
+ if (x1 < 0) {
+ tmp1 += 0.5;
+ }
+ }
+ double tmp2 = FastMath.sqrt(x1 * x1 + x2 * x2);
+ return new double[] {
+ 10.0 * (x3 - 10 * tmp1),
+ 10.0 * (tmp2 - 1),
+ x3
+ };
+ }
+
+ private static final double twoPi = 2.0 * FastMath.PI;
+ }
+
+ private static class PowellSingularFunction extends MinpackFunction {
+ private static final long serialVersionUID = 7298364171208142405L;
+
+ public PowellSingularFunction(double[] startParams,
+ double theoreticalStartCost) {
+ super(4, startParams, 0.0, buildArray(4, 0.0));
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ return new double[][] {
+ { 1, 10, 0, 0 },
+ { 0, 0, sqrt5, -sqrt5 },
+ { 0, 2 * (x2 - 2 * x3), -4 * (x2 - 2 * x3), 0 },
+ { 2 * sqrt10 * (x1 - x4), 0, 0, -2 * sqrt10 * (x1 - x4) }
+ };
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ return new double[] {
+ x1 + 10 * x2,
+ sqrt5 * (x3 - x4),
+ (x2 - 2 * x3) * (x2 - 2 * x3),
+ sqrt10 * (x1 - x4) * (x1 - x4)
+ };
+ }
+
+ private static final double sqrt5 = FastMath.sqrt( 5.0);
+ private static final double sqrt10 = FastMath.sqrt(10.0);
+ }
+
+ private static class FreudensteinRothFunction extends MinpackFunction {
+ private static final long serialVersionUID = 2892404999344244214L;
+
+ public FreudensteinRothFunction(double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(2, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x2 = variables[1];
+ return new double[][] {
+ { 1, x2 * (10 - 3 * x2) - 2 },
+ { 1, x2 * ( 2 + 3 * x2) - 14, }
+ };
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ return new double[] {
+ -13.0 + x1 + ((5.0 - x2) * x2 - 2.0) * x2,
+ -29.0 + x1 + ((1.0 + x2) * x2 - 14.0) * x2
+ };
+ }
+ }
+
+ private static class BardFunction extends MinpackFunction {
+ private static final long serialVersionUID = 5990442612572087668L;
+
+ public BardFunction(double x0,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(15, buildArray(3, x0), theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double tmp1 = i + 1;
+ double tmp2 = 15 - i;
+ double tmp3 = (i <= 7) ? tmp1 : tmp2;
+ double tmp4 = x2 * tmp2 + x3 * tmp3;
+ tmp4 *= tmp4;
+ jacobian[i] = new double[] { -1, tmp1 * tmp2 / tmp4, tmp1 * tmp3 / tmp4 };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ double tmp1 = i + 1;
+ double tmp2 = 15 - i;
+ double tmp3 = (i <= 7) ? tmp1 : tmp2;
+ f[i] = y[i] - (x1 + tmp1 / (x2 * tmp2 + x3 * tmp3));
+ }
+ return f;
+ }
+
+ private static final double[] y = {
+ 0.14, 0.18, 0.22, 0.25, 0.29,
+ 0.32, 0.35, 0.39, 0.37, 0.58,
+ 0.73, 0.96, 1.34, 2.10, 4.39
+ };
+ }
+
+ private static class KowalikOsborneFunction extends MinpackFunction {
+ private static final long serialVersionUID = -4867445739880495801L;
+
+ public KowalikOsborneFunction(double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(11, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ if (theoreticalStartCost > 20.0) {
+ setCostAccuracy(2.0e-4);
+ setParamsAccuracy(5.0e-3);
+ }
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double tmp = v[i] * (v[i] + x3) + x4;
+ double j1 = -v[i] * (v[i] + x2) / tmp;
+ double j2 = -v[i] * x1 / tmp;
+ double j3 = j1 * j2;
+ double j4 = j3 / v[i];
+ jacobian[i] = new double[] { j1, j2, j3, j4 };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ f[i] = y[i] - x1 * (v[i] * (v[i] + x2)) / (v[i] * (v[i] + x3) + x4);
+ }
+ return f;
+ }
+
+ private static final double[] v = {
+ 4.0, 2.0, 1.0, 0.5, 0.25, 0.167, 0.125, 0.1, 0.0833, 0.0714, 0.0625
+ };
+
+ private static final double[] y = {
+ 0.1957, 0.1947, 0.1735, 0.1600, 0.0844, 0.0627,
+ 0.0456, 0.0342, 0.0323, 0.0235, 0.0246
+ };
+ }
+
+ private static class MeyerFunction extends MinpackFunction {
+ private static final long serialVersionUID = -838060619150131027L;
+
+ public MeyerFunction(double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(16, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ if (theoreticalStartCost > 1.0e6) {
+ setCostAccuracy(7.0e-3);
+ setParamsAccuracy(2.0e-2);
+ }
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double temp = 5.0 * (i + 1) + 45.0 + x3;
+ double tmp1 = x2 / temp;
+ double tmp2 = FastMath.exp(tmp1);
+ double tmp3 = x1 * tmp2 / temp;
+ jacobian[i] = new double[] { tmp2, tmp3, -tmp1 * tmp3 };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ f[i] = x1 * FastMath.exp(x2 / (5.0 * (i + 1) + 45.0 + x3)) - y[i];
+ }
+ return f;
+ }
+
+ private static final double[] y = {
+ 34780.0, 28610.0, 23650.0, 19630.0,
+ 16370.0, 13720.0, 11540.0, 9744.0,
+ 8261.0, 7030.0, 6005.0, 5147.0,
+ 4427.0, 3820.0, 3307.0, 2872.0
+ };
+ }
+
+ private static class WatsonFunction extends MinpackFunction {
+ private static final long serialVersionUID = -9034759294980218927L;
+
+ public WatsonFunction(int n, double x0,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(31, buildArray(n, x0), theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double[][] jacobian = new double[m][];
+
+ for (int i = 0; i < (m - 2); ++i) {
+ double div = (i + 1) / 29.0;
+ double s2 = 0.0;
+ double dx = 1.0;
+ for (int j = 0; j < n; ++j) {
+ s2 += dx * variables[j];
+ dx *= div;
+ }
+ double temp= 2 * div * s2;
+ dx = 1.0 / div;
+ jacobian[i] = new double[n];
+ for (int j = 0; j < n; ++j) {
+ jacobian[i][j] = dx * (j - temp);
+ dx *= div;
+ }
+ }
+
+ jacobian[m - 2] = new double[n];
+ jacobian[m - 2][0] = 1;
+
+ jacobian[m - 1] = new double[n];
+ jacobian[m - 1][0]= -2 * variables[0];
+ jacobian[m - 1][1]= 1;
+
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double[] f = new double[m];
+ for (int i = 0; i < (m - 2); ++i) {
+ double div = (i + 1) / 29.0;
+ double s1 = 0;
+ double dx = 1;
+ for (int j = 1; j < n; ++j) {
+ s1 += j * dx * variables[j];
+ dx *= div;
+ }
+ double s2 = 0;
+ dx = 1;
+ for (int j = 0; j < n; ++j) {
+ s2 += dx * variables[j];
+ dx *= div;
+ }
+ f[i] = s1 - s2 * s2 - 1;
+ }
+
+ double x1 = variables[0];
+ double x2 = variables[1];
+ f[m - 2] = x1;
+ f[m - 1] = x2 - x1 * x1 - 1;
+
+ return f;
+ }
+ }
+
+ private static class Box3DimensionalFunction extends MinpackFunction {
+ private static final long serialVersionUID = 5511403858142574493L;
+
+ public Box3DimensionalFunction(int m, double[] startParams,
+ double theoreticalStartCost) {
+ super(m, startParams, 0.0,
+ new double[] { 1.0, 10.0, 1.0 });
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double tmp = (i + 1) / 10.0;
+ jacobian[i] = new double[] {
+ -tmp * FastMath.exp(-tmp * x1),
+ tmp * FastMath.exp(-tmp * x2),
+ FastMath.exp(-i - 1) - FastMath.exp(-tmp)
+ };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ double tmp = (i + 1) / 10.0;
+ f[i] = FastMath.exp(-tmp * x1) - FastMath.exp(-tmp * x2)
+ + (FastMath.exp(-i - 1) - FastMath.exp(-tmp)) * x3;
+ }
+ return f;
+ }
+ }
+
+ private static class JennrichSampsonFunction extends MinpackFunction {
+ private static final long serialVersionUID = -2489165190443352947L;
+
+ public JennrichSampsonFunction(int m, double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(m, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double t = i + 1;
+ jacobian[i] = new double[] { -t * FastMath.exp(t * x1), -t * FastMath.exp(t * x2) };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ double temp = i + 1;
+ f[i] = 2 + 2 * temp - FastMath.exp(temp * x1) - FastMath.exp(temp * x2);
+ }
+ return f;
+ }
+ }
+
+ private static class BrownDennisFunction extends MinpackFunction {
+ private static final long serialVersionUID = 8340018645694243910L;
+
+ public BrownDennisFunction(int m, double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(m, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ setCostAccuracy(2.5e-8);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double temp = (i + 1) / 5.0;
+ double ti = FastMath.sin(temp);
+ double tmp1 = x1 + temp * x2 - FastMath.exp(temp);
+ double tmp2 = x3 + ti * x4 - FastMath.cos(temp);
+ jacobian[i] = new double[] {
+ 2 * tmp1, 2 * temp * tmp1, 2 * tmp2, 2 * ti * tmp2
+ };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ double temp = (i + 1) / 5.0;
+ double tmp1 = x1 + temp * x2 - FastMath.exp(temp);
+ double tmp2 = x3 + FastMath.sin(temp) * x4 - FastMath.cos(temp);
+ f[i] = tmp1 * tmp1 + tmp2 * tmp2;
+ }
+ return f;
+ }
+ }
+
+ private static class ChebyquadFunction extends MinpackFunction {
+ private static final long serialVersionUID = -2394877275028008594L;
+
+ private static double[] buildChebyquadArray(int n, double factor) {
+ double[] array = new double[n];
+ double inv = factor / (n + 1);
+ for (int i = 0; i < n; ++i) {
+ array[i] = (i + 1) * inv;
+ }
+ return array;
+ }
+
+ public ChebyquadFunction(int n, int m, double factor,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(m, buildChebyquadArray(n, factor), theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ jacobian[i] = new double[n];
+ }
+
+ double dx = 1.0 / n;
+ for (int j = 0; j < n; ++j) {
+ double tmp1 = 1;
+ double tmp2 = 2 * variables[j] - 1;
+ double temp = 2 * tmp2;
+ double tmp3 = 0;
+ double tmp4 = 2;
+ for (int i = 0; i < m; ++i) {
+ jacobian[i][j] = dx * tmp4;
+ double ti = 4 * tmp2 + temp * tmp4 - tmp3;
+ tmp3 = tmp4;
+ tmp4 = ti;
+ ti = temp * tmp2 - tmp1;
+ tmp1 = tmp2;
+ tmp2 = ti;
+ }
+ }
+
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double[] f = new double[m];
+
+ for (int j = 0; j < n; ++j) {
+ double tmp1 = 1;
+ double tmp2 = 2 * variables[j] - 1;
+ double temp = 2 * tmp2;
+ for (int i = 0; i < m; ++i) {
+ f[i] += tmp2;
+ double ti = temp * tmp2 - tmp1;
+ tmp1 = tmp2;
+ tmp2 = ti;
+ }
+ }
+
+ double dx = 1.0 / n;
+ boolean iev = false;
+ for (int i = 0; i < m; ++i) {
+ f[i] *= dx;
+ if (iev) {
+ f[i] += 1.0 / (i * (i + 2));
+ }
+ iev = ! iev;
+ }
+
+ return f;
+ }
+ }
+
+ private static class BrownAlmostLinearFunction extends MinpackFunction {
+ private static final long serialVersionUID = 8239594490466964725L;
+
+ public BrownAlmostLinearFunction(int m, double factor,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(m, buildArray(m, factor), theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ jacobian[i] = new double[n];
+ }
+
+ double prod = 1;
+ for (int j = 0; j < n; ++j) {
+ prod *= variables[j];
+ for (int i = 0; i < n; ++i) {
+ jacobian[i][j] = 1;
+ }
+ jacobian[j][j] = 2;
+ }
+
+ for (int j = 0; j < n; ++j) {
+ double temp = variables[j];
+ if (temp == 0) {
+ temp = 1;
+ prod = 1;
+ for (int k = 0; k < n; ++k) {
+ if (k != j) {
+ prod *= variables[k];
+ }
+ }
+ }
+ jacobian[n - 1][j] = prod / temp;
+ }
+
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double[] f = new double[m];
+ double sum = -(n + 1);
+ double prod = 1;
+ for (int j = 0; j < n; ++j) {
+ sum += variables[j];
+ prod *= variables[j];
+ }
+ for (int i = 0; i < n; ++i) {
+ f[i] = variables[i] + sum;
+ }
+ f[n - 1] = prod - 1;
+ return f;
+ }
+ }
+
+ private static class Osborne1Function extends MinpackFunction {
+ private static final long serialVersionUID = 4006743521149849494L;
+
+ public Osborne1Function(double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(33, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ double x5 = variables[4];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double temp = 10.0 * i;
+ double tmp1 = FastMath.exp(-temp * x4);
+ double tmp2 = FastMath.exp(-temp * x5);
+ jacobian[i] = new double[] {
+ -1, -tmp1, -tmp2, temp * x2 * tmp1, temp * x3 * tmp2
+ };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x1 = variables[0];
+ double x2 = variables[1];
+ double x3 = variables[2];
+ double x4 = variables[3];
+ double x5 = variables[4];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ double temp = 10.0 * i;
+ double tmp1 = FastMath.exp(-temp * x4);
+ double tmp2 = FastMath.exp(-temp * x5);
+ f[i] = y[i] - (x1 + x2 * tmp1 + x3 * tmp2);
+ }
+ return f;
+ }
+
+ private static final double[] y = {
+ 0.844, 0.908, 0.932, 0.936, 0.925, 0.908, 0.881, 0.850, 0.818, 0.784, 0.751,
+ 0.718, 0.685, 0.658, 0.628, 0.603, 0.580, 0.558, 0.538, 0.522, 0.506, 0.490,
+ 0.478, 0.467, 0.457, 0.448, 0.438, 0.431, 0.424, 0.420, 0.414, 0.411, 0.406
+ };
+ }
+
+ private static class Osborne2Function extends MinpackFunction {
+ private static final long serialVersionUID = -8418268780389858746L;
+
+ public Osborne2Function(double[] startParams,
+ double theoreticalStartCost,
+ double theoreticalMinCost,
+ double[] theoreticalMinParams) {
+ super(65, startParams, theoreticalMinCost,
+ theoreticalMinParams);
+ }
+
+ @Override
+ public double[][] computeJacobian(double[] variables) {
+ double x01 = variables[0];
+ double x02 = variables[1];
+ double x03 = variables[2];
+ double x04 = variables[3];
+ double x05 = variables[4];
+ double x06 = variables[5];
+ double x07 = variables[6];
+ double x08 = variables[7];
+ double x09 = variables[8];
+ double x10 = variables[9];
+ double x11 = variables[10];
+ double[][] jacobian = new double[m][];
+ for (int i = 0; i < m; ++i) {
+ double temp = i / 10.0;
+ double tmp1 = FastMath.exp(-x05 * temp);
+ double tmp2 = FastMath.exp(-x06 * (temp - x09) * (temp - x09));
+ double tmp3 = FastMath.exp(-x07 * (temp - x10) * (temp - x10));
+ double tmp4 = FastMath.exp(-x08 * (temp - x11) * (temp - x11));
+ jacobian[i] = new double[] {
+ -tmp1,
+ -tmp2,
+ -tmp3,
+ -tmp4,
+ temp * x01 * tmp1,
+ x02 * (temp - x09) * (temp - x09) * tmp2,
+ x03 * (temp - x10) * (temp - x10) * tmp3,
+ x04 * (temp - x11) * (temp - x11) * tmp4,
+ -2 * x02 * x06 * (temp - x09) * tmp2,
+ -2 * x03 * x07 * (temp - x10) * tmp3,
+ -2 * x04 * x08 * (temp - x11) * tmp4
+ };
+ }
+ return jacobian;
+ }
+
+ @Override
+ public double[] computeValue(double[] variables) {
+ double x01 = variables[0];
+ double x02 = variables[1];
+ double x03 = variables[2];
+ double x04 = variables[3];
+ double x05 = variables[4];
+ double x06 = variables[5];
+ double x07 = variables[6];
+ double x08 = variables[7];
+ double x09 = variables[8];
+ double x10 = variables[9];
+ double x11 = variables[10];
+ double[] f = new double[m];
+ for (int i = 0; i < m; ++i) {
+ double temp = i / 10.0;
+ double tmp1 = FastMath.exp(-x05 * temp);
+ double tmp2 = FastMath.exp(-x06 * (temp - x09) * (temp - x09));
+ double tmp3 = FastMath.exp(-x07 * (temp - x10) * (temp - x10));
+ double tmp4 = FastMath.exp(-x08 * (temp - x11) * (temp - x11));
+ f[i] = y[i] - (x01 * tmp1 + x02 * tmp2 + x03 * tmp3 + x04 * tmp4);
+ }
+ return f;
+ }
+
+ private static final double[] y = {
+ 1.366, 1.191, 1.112, 1.013, 0.991,
+ 0.885, 0.831, 0.847, 0.786, 0.725,
+ 0.746, 0.679, 0.608, 0.655, 0.616,
+ 0.606, 0.602, 0.626, 0.651, 0.724,
+ 0.649, 0.649, 0.694, 0.644, 0.624,
+ 0.661, 0.612, 0.558, 0.533, 0.495,
+ 0.500, 0.423, 0.395, 0.375, 0.372,
+ 0.391, 0.396, 0.405, 0.428, 0.429,
+ 0.523, 0.562, 0.607, 0.653, 0.672,
+ 0.708, 0.633, 0.668, 0.645, 0.632,
+ 0.591, 0.559, 0.597, 0.625, 0.739,
+ 0.710, 0.729, 0.720, 0.636, 0.581,
+ 0.428, 0.292, 0.162, 0.098, 0.054
+ };
+ }
+}
diff --git a/src/test/java/org/apache/commons/math3/fitting/leastsquares/RandomCirclePointGenerator.java b/src/test/java/org/apache/commons/math3/fitting/leastsquares/RandomCirclePointGenerator.java
new file mode 100644
index 000000000..3f9a20d11
--- /dev/null
+++ b/src/test/java/org/apache/commons/math3/fitting/leastsquares/RandomCirclePointGenerator.java
@@ -0,0 +1,91 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well44497b;
+import org.apache.commons.math3.util.MathUtils;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+import org.apache.commons.math3.distribution.NormalDistribution;
+import org.apache.commons.math3.geometry.euclidean.twod.Vector2D;
+
+/**
+ * Factory for generating a cloud of points that approximate a circle.
+ */
+public class RandomCirclePointGenerator {
+ /** RNG for the x-coordinate of the center. */
+ private final RealDistribution cX;
+ /** RNG for the y-coordinate of the center. */
+ private final RealDistribution cY;
+ /** RNG for the parametric position of the point. */
+ private final RealDistribution tP;
+ /** Radius of the circle. */
+ private final double radius;
+
+ /**
+ * @param x Abscissa of the circle center.
+ * @param y Ordinate of the circle center.
+ * @param radius Radius of the circle.
+ * @param xSigma Error on the x-coordinate of the circumference points.
+ * @param ySigma Error on the y-coordinate of the circumference points.
+ * @param seed RNG seed.
+ */
+ public RandomCirclePointGenerator(double x,
+ double y,
+ double radius,
+ double xSigma,
+ double ySigma,
+ long seed) {
+ final RandomGenerator rng = new Well44497b(seed);
+ this.radius = radius;
+ cX = new NormalDistribution(rng, x, xSigma,
+ NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+ cY = new NormalDistribution(rng, y, ySigma,
+ NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+ tP = new UniformRealDistribution(rng, 0, MathUtils.TWO_PI,
+ UniformRealDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+ }
+
+ /**
+ * Point generator.
+ *
+ * @param n Number of points to create.
+ * @return the cloud of {@code n} points.
+ */
+ public Vector2D[] generate(int n) {
+ final Vector2D[] cloud = new Vector2D[n];
+ for (int i = 0; i < n; i++) {
+ cloud[i] = create();
+ }
+ return cloud;
+ }
+
+ /**
+ * Create one point.
+ *
+ * @return a point.
+ */
+ private Vector2D create() {
+ final double t = tP.sample();
+ final double pX = cX.sample() + radius * FastMath.cos(t);
+ final double pY = cY.sample() + radius * FastMath.sin(t);
+
+ return new Vector2D(pX, pY);
+ }
+}
diff --git a/src/test/java/org/apache/commons/math3/fitting/leastsquares/RandomStraightLinePointGenerator.java b/src/test/java/org/apache/commons/math3/fitting/leastsquares/RandomStraightLinePointGenerator.java
new file mode 100644
index 000000000..1c60cd21a
--- /dev/null
+++ b/src/test/java/org/apache/commons/math3/fitting/leastsquares/RandomStraightLinePointGenerator.java
@@ -0,0 +1,98 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import java.awt.geom.Point2D;
+import org.apache.commons.math3.random.RandomGenerator;
+import org.apache.commons.math3.random.Well44497b;
+import org.apache.commons.math3.distribution.RealDistribution;
+import org.apache.commons.math3.distribution.UniformRealDistribution;
+import org.apache.commons.math3.distribution.NormalDistribution;
+
+/**
+ * Factory for generating a cloud of points that approximate a straight line.
+ */
+public class RandomStraightLinePointGenerator {
+ /** Slope. */
+ private final double slope;
+ /** Intercept. */
+ private final double intercept;
+ /** RNG for the x-coordinate. */
+ private final RealDistribution x;
+ /** RNG for the error on the y-coordinate. */
+ private final RealDistribution error;
+
+ /**
+ * The generator will create a cloud of points whose x-coordinates
+ * will be randomly sampled between {@code xLo} and {@code xHi}, and
+ * the corresponding y-coordinates will be computed as
+ *
+ *
+ * Minpack Copyright Notice (1999) University of Chicago.
+ * All rights reserved
+ *
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ *
This product includes software developed by the University of
+ * Chicago, as Operator of Argonne National Laboratory.
+ * Alternately, this acknowledgment may appear in the software itself,
+ * if and wherever such third-party acknowledgments normally appear.
+ * where {@code N(mean, sigma)} is a Gaussian distribution with the
+ * given mean and standard deviation.
+ *
+ * @param a Slope.
+ * @param b Intercept.
+ * @param sigma Standard deviation on the y-coordinate of the point.
+ * @param lo Lowest value of the x-coordinate.
+ * @param hi Highest value of the x-coordinate.
+ * @param seed RNG seed.
+ */
+ public RandomStraightLinePointGenerator(double a,
+ double b,
+ double sigma,
+ double lo,
+ double hi,
+ long seed) {
+ final RandomGenerator rng = new Well44497b(seed);
+ slope = a;
+ intercept = b;
+ error = new NormalDistribution(rng, 0, sigma,
+ NormalDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+ x = new UniformRealDistribution(rng, lo, hi,
+ UniformRealDistribution.DEFAULT_INVERSE_ABSOLUTE_ACCURACY);
+ }
+
+ /**
+ * Point generator.
+ *
+ * @param n Number of points to create.
+ * @return the cloud of {@code n} points.
+ */
+ public Point2D.Double[] generate(int n) {
+ final Point2D.Double[] cloud = new Point2D.Double[n];
+ for (int i = 0; i < n; i++) {
+ cloud[i] = create();
+ }
+ return cloud;
+ }
+
+ /**
+ * Create one point.
+ *
+ * @return a point.
+ */
+ private Point2D.Double create() {
+ final double abscissa = x.sample();
+ final double yModel = slope * abscissa + intercept;
+ final double ordinate = yModel + error.sample();
+
+ return new Point2D.Double(abscissa, ordinate);
+ }
+}
diff --git a/src/test/java/org/apache/commons/math3/fitting/leastsquares/StatisticalReferenceDataset.java b/src/test/java/org/apache/commons/math3/fitting/leastsquares/StatisticalReferenceDataset.java
new file mode 100644
index 000000000..5f3dbba07
--- /dev/null
+++ b/src/test/java/org/apache/commons/math3/fitting/leastsquares/StatisticalReferenceDataset.java
@@ -0,0 +1,370 @@
+/*
+ * 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.math3.fitting.leastsquares;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import org.apache.commons.math3.analysis.MultivariateVectorFunction;
+import org.apache.commons.math3.analysis.MultivariateMatrixFunction;
+import org.apache.commons.math3.util.MathArrays;
+
+/**
+ * This class gives access to the statistical reference datasets provided by the
+ * NIST (available
+ * here).
+ * Instances of this class can be created by invocation of the
+ * {@link StatisticalReferenceDatasetFactory}.
+ */
+public abstract class StatisticalReferenceDataset {
+ /** The name of this dataset. */
+ private final String name;
+ /** The total number of observations (data points). */
+ private final int numObservations;
+ /** The total number of parameters. */
+ private final int numParameters;
+ /** The total number of starting points for the optimizations. */
+ private final int numStartingPoints;
+ /** The values of the predictor. */
+ private final double[] x;
+ /** The values of the response. */
+ private final double[] y;
+ /**
+ * The starting values. {@code startingValues[j][i]} is the value of the
+ * {@code i}-th parameter in the {@code j}-th set of starting values.
+ */
+ private final double[][] startingValues;
+ /** The certified values of the parameters. */
+ private final double[] a;
+ /** The certified values of the standard deviation of the parameters. */
+ private final double[] sigA;
+ /** The certified value of the residual sum of squares. */
+ private double residualSumOfSquares;
+ /** The least-squares problem. */
+ private final LeastSquaresProblem problem;
+
+ /**
+ * Creates a new instance of this class from the specified data file. The
+ * file must follow the StRD format.
+ *
+ * @param in the data file
+ * @throws IOException if an I/O error occurs
+ */
+ public StatisticalReferenceDataset(final BufferedReader in)
+ throws IOException {
+
+ final ArrayList
+ * y = a x + b + N(0, error)
+ *
+ *
+ *
+ *
+ * The model functions are:
+ *
+ *
+ */
+class StraightLineProblem {
+ /** Cloud of points assumed to be fitted by a straight line. */
+ private final ArrayList