LeastSquaresConverter.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. package org.apache.commons.math4.legacy.optim.nonlinear.scalar;

  18. import org.apache.commons.math4.legacy.analysis.MultivariateFunction;
  19. import org.apache.commons.math4.legacy.analysis.MultivariateVectorFunction;
  20. import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
  21. import org.apache.commons.math4.legacy.linear.RealMatrix;

  22. /**
  23.  * This class converts
  24.  * {@link MultivariateVectorFunction vectorial objective functions} to
  25.  * {@link MultivariateFunction scalar objective functions}
  26.  * when the goal is to minimize them.
  27.  * <br>
  28.  * This class is mostly used when the vectorial objective function represents
  29.  * a theoretical result computed from a point set applied to a model and
  30.  * the models point must be adjusted to fit the theoretical result to some
  31.  * reference observations. The observations may be obtained for example from
  32.  * physical measurements whether the model is built from theoretical
  33.  * considerations.
  34.  * <br>
  35.  * This class computes a possibly weighted squared sum of the residuals, which is
  36.  * a scalar value. The residuals are the difference between the theoretical model
  37.  * (i.e. the output of the vectorial objective function) and the observations. The
  38.  * class implements the {@link MultivariateFunction} interface and can therefore be
  39.  * minimized by any optimizer supporting scalar objectives functions.This is one way
  40.  * to perform a least square estimation. There are other ways to do this without using
  41.  * this converter, as some optimization algorithms directly support vectorial objective
  42.  * functions.
  43.  * <br>
  44.  * This class support combination of residuals with or without weights and correlations.
  45.   *
  46.  * @see MultivariateFunction
  47.  * @see MultivariateVectorFunction
  48.  * @since 2.0
  49.  */

  50. public class LeastSquaresConverter implements MultivariateFunction {
  51.     /** Underlying vectorial function. */
  52.     private final MultivariateVectorFunction function;
  53.     /** Observations to be compared to objective function to compute residuals. */
  54.     private final double[] observations;
  55.     /** Optional weights for the residuals. */
  56.     private final double[] weights;
  57.     /** Optional scaling matrix (weight and correlations) for the residuals. */
  58.     private final RealMatrix scale;

  59.     /**
  60.      * Builds a simple converter for uncorrelated residuals with identical
  61.      * weights.
  62.      *
  63.      * @param function vectorial residuals function to wrap
  64.      * @param observations observations to be compared to objective function to compute residuals
  65.      */
  66.     public LeastSquaresConverter(final MultivariateVectorFunction function,
  67.                                  final double[] observations) {
  68.         this.function     = function;
  69.         this.observations = observations.clone();
  70.         this.weights      = null;
  71.         this.scale        = null;
  72.     }

  73.     /**
  74.      * Builds a simple converter for uncorrelated residuals with the
  75.      * specified weights.
  76.      * <p>
  77.      * The scalar objective function value is computed as:
  78.      * <div style="white-space: pre"><code>
  79.      * objective = &sum;weight<sub>i</sub>(observation<sub>i</sub>-objective<sub>i</sub>)<sup>2</sup>
  80.      * </code></div>
  81.      *
  82.      * <p>
  83.      * Weights can be used for example to combine residuals with different standard
  84.      * deviations. As an example, consider a residuals array in which even elements
  85.      * are angular measurements in degrees with a 0.01&deg; standard deviation and
  86.      * odd elements are distance measurements in meters with a 15m standard deviation.
  87.      * In this case, the weights array should be initialized with value
  88.      * 1.0/(0.01<sup>2</sup>) in the even elements and 1.0/(15.0<sup>2</sup>) in the
  89.      * odd elements (i.e. reciprocals of variances).
  90.      * </p>
  91.      * <p>
  92.      * The array computed by the objective function, the observations array and the
  93.      * weights array must have consistent sizes or a {@link DimensionMismatchException}
  94.      * will be triggered while computing the scalar objective.
  95.      * </p>
  96.      *
  97.      * @param function vectorial residuals function to wrap
  98.      * @param observations observations to be compared to objective function to compute residuals
  99.      * @param weights weights to apply to the residuals
  100.      * @throws DimensionMismatchException if the observations vector and the weights
  101.      * vector dimensions do not match (objective function dimension is checked only when
  102.      * the {@link #value(double[])} method is called)
  103.      */
  104.     public LeastSquaresConverter(final MultivariateVectorFunction function,
  105.                                  final double[] observations,
  106.                                  final double[] weights) {
  107.         if (observations.length != weights.length) {
  108.             throw new DimensionMismatchException(observations.length, weights.length);
  109.         }
  110.         this.function     = function;
  111.         this.observations = observations.clone();
  112.         this.weights      = weights.clone();
  113.         this.scale        = null;
  114.     }

  115.     /**
  116.      * Builds a simple converter for correlated residuals with the
  117.      * specified weights.
  118.      * <p>
  119.      * The scalar objective function value is computed as:
  120.      * <div style="white-space: pre"><code>
  121.      * objective = y<sup>T</sup>y with y = scale&times;(observation-objective)
  122.      * </code></div>
  123.      *
  124.      * <p>
  125.      * The array computed by the objective function, the observations array and the
  126.      * the scaling matrix must have consistent sizes or a {@link DimensionMismatchException}
  127.      * will be triggered while computing the scalar objective.
  128.      * </p>
  129.      *
  130.      * @param function vectorial residuals function to wrap
  131.      * @param observations observations to be compared to objective function to compute residuals
  132.      * @param scale scaling matrix
  133.      * @throws DimensionMismatchException if the observations vector and the scale
  134.      * matrix dimensions do not match (objective function dimension is checked only when
  135.      * the {@link #value(double[])} method is called)
  136.      */
  137.     public LeastSquaresConverter(final MultivariateVectorFunction function,
  138.                                  final double[] observations,
  139.                                  final RealMatrix scale) {
  140.         if (observations.length != scale.getColumnDimension()) {
  141.             throw new DimensionMismatchException(observations.length, scale.getColumnDimension());
  142.         }
  143.         this.function     = function;
  144.         this.observations = observations.clone();
  145.         this.weights      = null;
  146.         this.scale        = scale.copy();
  147.     }

  148.     /** {@inheritDoc} */
  149.     @Override
  150.     public double value(final double[] point) {
  151.         // compute residuals
  152.         final double[] residuals = function.value(point);
  153.         if (residuals.length != observations.length) {
  154.             throw new DimensionMismatchException(residuals.length, observations.length);
  155.         }
  156.         for (int i = 0; i < residuals.length; ++i) {
  157.             residuals[i] -= observations[i];
  158.         }

  159.         // compute sum of squares
  160.         double sumSquares = 0;
  161.         if (weights != null) {
  162.             for (int i = 0; i < residuals.length; ++i) {
  163.                 final double ri = residuals[i];
  164.                 sumSquares +=  weights[i] * ri * ri;
  165.             }
  166.         } else if (scale != null) {
  167.             for (final double yi : scale.operate(residuals)) {
  168.                 sumSquares += yi * yi;
  169.             }
  170.         } else {
  171.             for (final double ri : residuals) {
  172.                 sumSquares += ri * ri;
  173.             }
  174.         }

  175.         return sumSquares;
  176.     }
  177. }