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.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */

  17. /*
  18.  * This is not the original file distributed by the Apache Software Foundation
  19.  * It has been modified by the Hipparchus project
  20.  */

  21. package org.hipparchus.optim.nonlinear.scalar;

  22. import org.hipparchus.analysis.MultivariateFunction;
  23. import org.hipparchus.analysis.MultivariateVectorFunction;
  24. import org.hipparchus.exception.LocalizedCoreFormats;
  25. import org.hipparchus.exception.MathIllegalArgumentException;
  26. import org.hipparchus.linear.RealMatrix;

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

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

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

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

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

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

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

  178.         return sumSquares;
  179.     }
  180. }