diff options
Diffstat (limited to 'src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java')
-rw-r--r-- | src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java new file mode 100644 index 0000000..42cdf89 --- /dev/null +++ b/src/main/java/org/apache/commons/math3/fitting/leastsquares/LeastSquaresFactory.java @@ -0,0 +1,532 @@ +/* + * 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.exception.MathIllegalStateException; +import org.apache.commons.math3.exception.util.LocalizedFormats; +import org.apache.commons.math3.analysis.MultivariateMatrixFunction; +import org.apache.commons.math3.analysis.MultivariateVectorFunction; +import org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem.Evaluation; +import org.apache.commons.math3.linear.Array2DRowRealMatrix; +import org.apache.commons.math3.linear.ArrayRealVector; +import org.apache.commons.math3.linear.DiagonalMatrix; +import org.apache.commons.math3.linear.EigenDecomposition; +import org.apache.commons.math3.linear.RealMatrix; +import org.apache.commons.math3.linear.RealVector; +import org.apache.commons.math3.optim.AbstractOptimizationProblem; +import org.apache.commons.math3.optim.ConvergenceChecker; +import org.apache.commons.math3.optim.PointVectorValuePair; +import org.apache.commons.math3.util.FastMath; +import org.apache.commons.math3.util.Incrementor; +import org.apache.commons.math3.util.Pair; + +/** + * A Factory for creating {@link LeastSquaresProblem}s. + * + * @since 3.3 + */ +public class LeastSquaresFactory { + + /** Prevent instantiation. */ + private LeastSquaresFactory() {} + + /** + * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem} + * from the given elements. There will be no weights applied (unit weights). + * + * @param model the model function. Produces the computed values. + * @param observed the observed (target) values + * @param start the initial guess. + * @param weight the weight matrix + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @param lazyEvaluation Whether the call to {@link Evaluation#evaluate(RealVector)} + * will defer the evaluation until access to the value is requested. + * @param paramValidator Model parameters validator. + * @return the specified General Least Squares problem. + * + * @since 3.4 + */ + public static LeastSquaresProblem create(final MultivariateJacobianFunction model, + final RealVector observed, + final RealVector start, + final RealMatrix weight, + final ConvergenceChecker<Evaluation> checker, + final int maxEvaluations, + final int maxIterations, + final boolean lazyEvaluation, + final ParameterValidator paramValidator) { + final LeastSquaresProblem p = new LocalLeastSquaresProblem(model, + observed, + start, + checker, + maxEvaluations, + maxIterations, + lazyEvaluation, + paramValidator); + if (weight != null) { + return weightMatrix(p, weight); + } else { + return p; + } + } + + /** + * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem} + * from the given elements. There will be no weights applied (unit weights). + * + * @param model the model function. Produces the computed values. + * @param observed the observed (target) values + * @param start the initial guess. + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @return the specified General Least Squares problem. + */ + public static LeastSquaresProblem create(final MultivariateJacobianFunction model, + final RealVector observed, + final RealVector start, + final ConvergenceChecker<Evaluation> checker, + final int maxEvaluations, + final int maxIterations) { + return create(model, + observed, + start, + null, + checker, + maxEvaluations, + maxIterations, + false, + null); + } + + /** + * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem} + * from the given elements. + * + * @param model the model function. Produces the computed values. + * @param observed the observed (target) values + * @param start the initial guess. + * @param weight the weight matrix + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @return the specified General Least Squares problem. + */ + public static LeastSquaresProblem create(final MultivariateJacobianFunction model, + final RealVector observed, + final RealVector start, + final RealMatrix weight, + final ConvergenceChecker<Evaluation> checker, + final int maxEvaluations, + final int maxIterations) { + return weightMatrix(create(model, + observed, + start, + checker, + maxEvaluations, + maxIterations), + weight); + } + + /** + * Create a {@link org.apache.commons.math3.fitting.leastsquares.LeastSquaresProblem} + * from the given elements. + * <p> + * This factory method is provided for continuity with previous interfaces. Newer + * applications should use {@link #create(MultivariateJacobianFunction, RealVector, + * RealVector, ConvergenceChecker, int, int)}, or {@link #create(MultivariateJacobianFunction, + * RealVector, RealVector, RealMatrix, ConvergenceChecker, int, int)}. + * + * @param model the model function. Produces the computed values. + * @param jacobian the jacobian of the model with respect to the parameters + * @param observed the observed (target) values + * @param start the initial guess. + * @param weight the weight matrix + * @param checker convergence checker + * @param maxEvaluations the maximum number of times to evaluate the model + * @param maxIterations the maximum number to times to iterate in the algorithm + * @return the specified General Least Squares problem. + */ + public static LeastSquaresProblem create(final MultivariateVectorFunction model, + final MultivariateMatrixFunction jacobian, + final double[] observed, + final double[] start, + final RealMatrix weight, + final ConvergenceChecker<Evaluation> checker, + final int maxEvaluations, + final int maxIterations) { + return create(model(model, jacobian), + new ArrayRealVector(observed, false), + new ArrayRealVector(start, false), + weight, + checker, + maxEvaluations, + maxIterations); + } + + /** + * Apply a dense weight matrix to the {@link LeastSquaresProblem}. + * + * @param problem the unweighted problem + * @param weights the matrix of weights + * @return a new {@link LeastSquaresProblem} with the weights applied. The original + * {@code problem} is not modified. + */ + public static LeastSquaresProblem weightMatrix(final LeastSquaresProblem problem, + final RealMatrix weights) { + final RealMatrix weightSquareRoot = squareRoot(weights); + return new LeastSquaresAdapter(problem) { + /** {@inheritDoc} */ + @Override + public Evaluation evaluate(final RealVector point) { + return new DenseWeightedEvaluation(super.evaluate(point), weightSquareRoot); + } + }; + } + + /** + * Apply a diagonal weight matrix to the {@link LeastSquaresProblem}. + * + * @param problem the unweighted problem + * @param weights the diagonal of the weight matrix + * @return a new {@link LeastSquaresProblem} with the weights applied. The original + * {@code problem} is not modified. + */ + public static LeastSquaresProblem weightDiagonal(final LeastSquaresProblem problem, + final RealVector weights) { + // TODO more efficient implementation + return weightMatrix(problem, new DiagonalMatrix(weights.toArray())); + } + + /** + * Count the evaluations of a particular problem. The {@code counter} will be + * incremented every time {@link LeastSquaresProblem#evaluate(RealVector)} is called on + * the <em>returned</em> problem. + * + * @param problem the problem to track. + * @param counter the counter to increment. + * @return a least squares problem that tracks evaluations + */ + public static LeastSquaresProblem countEvaluations(final LeastSquaresProblem problem, + final Incrementor counter) { + return new LeastSquaresAdapter(problem) { + + /** {@inheritDoc} */ + @Override + public Evaluation evaluate(final RealVector point) { + counter.incrementCount(); + return super.evaluate(point); + } + + // Delegate the rest. + }; + } + + /** + * View a convergence checker specified for a {@link PointVectorValuePair} as one + * specified for an {@link Evaluation}. + * + * @param checker the convergence checker to adapt. + * @return a convergence checker that delegates to {@code checker}. + */ + public static ConvergenceChecker<Evaluation> evaluationChecker(final ConvergenceChecker<PointVectorValuePair> checker) { + return new ConvergenceChecker<Evaluation>() { + /** {@inheritDoc} */ + public boolean converged(final int iteration, + final Evaluation previous, + final Evaluation current) { + return checker.converged( + iteration, + new PointVectorValuePair( + previous.getPoint().toArray(), + previous.getResiduals().toArray(), + false), + new PointVectorValuePair( + current.getPoint().toArray(), + current.getResiduals().toArray(), + false) + ); + } + }; + } + + /** + * Computes the square-root of the weight matrix. + * + * @param m Symmetric, positive-definite (weight) matrix. + * @return the square-root of the weight matrix. + */ + private static RealMatrix squareRoot(final 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(); + } + } + + /** + * Combine a {@link MultivariateVectorFunction} with a {@link + * MultivariateMatrixFunction} to produce a {@link MultivariateJacobianFunction}. + * + * @param value the vector value function + * @param jacobian the Jacobian function + * @return a function that computes both at the same time + */ + public static MultivariateJacobianFunction model(final MultivariateVectorFunction value, + final MultivariateMatrixFunction jacobian) { + return new LocalValueAndJacobianFunction(value, jacobian); + } + + /** + * Combine a {@link MultivariateVectorFunction} with a {@link + * MultivariateMatrixFunction} to produce a {@link MultivariateJacobianFunction}. + * + * @param value the vector value function + * @param jacobian the Jacobian function + * @return a function that computes both at the same time + */ + private static class LocalValueAndJacobianFunction + implements ValueAndJacobianFunction { + /** Model. */ + private final MultivariateVectorFunction value; + /** Model's Jacobian. */ + private final MultivariateMatrixFunction jacobian; + + /** + * @param value Model function. + * @param jacobian Model's Jacobian function. + */ + LocalValueAndJacobianFunction(final MultivariateVectorFunction value, + final MultivariateMatrixFunction jacobian) { + this.value = value; + this.jacobian = jacobian; + } + + /** {@inheritDoc} */ + public Pair<RealVector, RealMatrix> value(final RealVector point) { + //TODO get array from RealVector without copying? + final double[] p = point.toArray(); + + // Evaluate. + return new Pair<RealVector, RealMatrix>(computeValue(p), + computeJacobian(p)); + } + + /** {@inheritDoc} */ + public RealVector computeValue(final double[] params) { + return new ArrayRealVector(value.value(params), false); + } + + /** {@inheritDoc} */ + public RealMatrix computeJacobian(final double[] params) { + return new Array2DRowRealMatrix(jacobian.value(params), false); + } + } + + + /** + * A private, "field" immutable (not "real" immutable) implementation of {@link + * LeastSquaresProblem}. + * @since 3.3 + */ + private static class LocalLeastSquaresProblem + extends AbstractOptimizationProblem<Evaluation> + implements LeastSquaresProblem { + + /** Target values for the model function at optimum. */ + private final RealVector target; + /** Model function. */ + private final MultivariateJacobianFunction model; + /** Initial guess. */ + private final RealVector start; + /** Whether to use lazy evaluation. */ + private final boolean lazyEvaluation; + /** Model parameters validator. */ + private final ParameterValidator paramValidator; + + /** + * Create a {@link LeastSquaresProblem} from the given data. + * + * @param model the model function + * @param target the observed data + * @param start the initial guess + * @param checker the convergence checker + * @param maxEvaluations the allowed evaluations + * @param maxIterations the allowed iterations + * @param lazyEvaluation Whether the call to {@link Evaluation#evaluate(RealVector)} + * will defer the evaluation until access to the value is requested. + * @param paramValidator Model parameters validator. + */ + LocalLeastSquaresProblem(final MultivariateJacobianFunction model, + final RealVector target, + final RealVector start, + final ConvergenceChecker<Evaluation> checker, + final int maxEvaluations, + final int maxIterations, + final boolean lazyEvaluation, + final ParameterValidator paramValidator) { + super(maxEvaluations, maxIterations, checker); + this.target = target; + this.model = model; + this.start = start; + this.lazyEvaluation = lazyEvaluation; + this.paramValidator = paramValidator; + + if (lazyEvaluation && + !(model instanceof ValueAndJacobianFunction)) { + // Lazy evaluation requires that value and Jacobian + // can be computed separately. + throw new MathIllegalStateException(LocalizedFormats.INVALID_IMPLEMENTATION, + model.getClass().getName()); + } + } + + /** {@inheritDoc} */ + public int getObservationSize() { + return target.getDimension(); + } + + /** {@inheritDoc} */ + public int getParameterSize() { + return start.getDimension(); + } + + /** {@inheritDoc} */ + public RealVector getStart() { + return start == null ? null : start.copy(); + } + + /** {@inheritDoc} */ + public Evaluation evaluate(final RealVector point) { + // Copy so optimizer can change point without changing our instance. + final RealVector p = paramValidator == null ? + point.copy() : + paramValidator.validate(point.copy()); + + if (lazyEvaluation) { + return new LazyUnweightedEvaluation((ValueAndJacobianFunction) model, + target, + p); + } else { + // Evaluate value and jacobian in one function call. + final Pair<RealVector, RealMatrix> value = model.value(p); + return new UnweightedEvaluation(value.getFirst(), + value.getSecond(), + target, + p); + } + } + + /** + * Container with the model evaluation at a particular point. + */ + private static class UnweightedEvaluation extends AbstractEvaluation { + /** Point of evaluation. */ + private final RealVector point; + /** Derivative at point. */ + private final RealMatrix jacobian; + /** Computed residuals. */ + private final RealVector residuals; + + /** + * Create an {@link Evaluation} with no weights. + * + * @param values the computed function values + * @param jacobian the computed function Jacobian + * @param target the observed values + * @param point the abscissa + */ + private UnweightedEvaluation(final RealVector values, + final RealMatrix jacobian, + final RealVector target, + final RealVector point) { + super(target.getDimension()); + this.jacobian = jacobian; + this.point = point; + this.residuals = target.subtract(values); + } + + /** {@inheritDoc} */ + public RealMatrix getJacobian() { + return jacobian; + } + + /** {@inheritDoc} */ + public RealVector getPoint() { + return point; + } + + /** {@inheritDoc} */ + public RealVector getResiduals() { + return residuals; + } + } + + /** + * Container with the model <em>lazy</em> evaluation at a particular point. + */ + private static class LazyUnweightedEvaluation extends AbstractEvaluation { + /** Point of evaluation. */ + private final RealVector point; + /** Model and Jacobian functions. */ + private final ValueAndJacobianFunction model; + /** Target values for the model function at optimum. */ + private final RealVector target; + + /** + * Create an {@link Evaluation} with no weights. + * + * @param model the model function + * @param target the observed values + * @param point the abscissa + */ + private LazyUnweightedEvaluation(final ValueAndJacobianFunction model, + final RealVector target, + final RealVector point) { + super(target.getDimension()); + // Safe to cast as long as we control usage of this class. + this.model = model; + this.point = point; + this.target = target; + } + + /** {@inheritDoc} */ + public RealMatrix getJacobian() { + return model.computeJacobian(point.toArray()); + } + + /** {@inheritDoc} */ + public RealVector getPoint() { + return point; + } + + /** {@inheritDoc} */ + public RealVector getResiduals() { + return target.subtract(model.computeValue(point.toArray())); + } + } + } +} + |