CorrelatedRandomVectorGenerator.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.random;

  22. import org.hipparchus.exception.LocalizedCoreFormats;
  23. import org.hipparchus.exception.MathIllegalArgumentException;
  24. import org.hipparchus.linear.RealMatrix;
  25. import org.hipparchus.linear.RectangularCholeskyDecomposition;

  26. /**
  27.  * A {@link RandomVectorGenerator} that generates vectors with with
  28.  * correlated components.
  29.  * <p>
  30.  * Random vectors with correlated components are built by combining
  31.  * the uncorrelated components of another random vector in such a way that
  32.  * the resulting correlations are the ones specified by a positive
  33.  * definite covariance matrix.
  34.  * <p>
  35.  * The main use for correlated random vector generation is for Monte-Carlo
  36.  * simulation of physical problems with several variables, for example to
  37.  * generate error vectors to be added to a nominal vector. A particularly
  38.  * interesting case is when the generated vector should be drawn from a <a
  39.  * href="http://en.wikipedia.org/wiki/Multivariate_normal_distribution">
  40.  * Multivariate Normal Distribution</a>. The approach using a Cholesky
  41.  * decomposition is quite usual in this case. However, it can be extended
  42.  * to other cases as long as the underlying random generator provides
  43.  * {@link NormalizedRandomGenerator normalized values} like {@link
  44.  * GaussianRandomGenerator} or {@link UniformRandomGenerator}.
  45.  * <p>
  46.  * Sometimes, the covariance matrix for a given simulation is not
  47.  * strictly positive definite. This means that the correlations are
  48.  * not all independent from each other. In this case, however, the non
  49.  * strictly positive elements found during the Cholesky decomposition
  50.  * of the covariance matrix should not be negative either, they
  51.  * should be null. Another non-conventional extension handling this case
  52.  * is used here. Rather than computing <code>C = U<sup>T</sup>.U</code>
  53.  * where <code>C</code> is the covariance matrix and <code>U</code>
  54.  * is an upper-triangular matrix, we compute <code>C = B.B<sup>T</sup></code>
  55.  * where <code>B</code> is a rectangular matrix having
  56.  * more rows than columns. The number of columns of <code>B</code> is
  57.  * the rank of the covariance matrix, and it is the dimension of the
  58.  * uncorrelated random vector that is needed to compute the component
  59.  * of the correlated vector. This class handles this situation
  60.  * automatically.
  61.  */
  62. public class CorrelatedRandomVectorGenerator
  63.     implements RandomVectorGenerator {
  64.     /** Mean vector. */
  65.     private final double[] mean;
  66.     /** Underlying generator. */
  67.     private final NormalizedRandomGenerator generator;
  68.     /** Storage for the normalized vector. */
  69.     private final double[] normalized;
  70.     /** Root of the covariance matrix. */
  71.     private final RealMatrix root;

  72.     /**
  73.      * Builds a correlated random vector generator from its mean
  74.      * vector and covariance matrix.
  75.      *
  76.      * @param mean Expected mean values for all components.
  77.      * @param covariance Covariance matrix.
  78.      * @param small Diagonal elements threshold under which  column are
  79.      * considered to be dependent on previous ones and are discarded
  80.      * @param generator underlying generator for uncorrelated normalized
  81.      * components.
  82.      * @throws org.hipparchus.exception.MathIllegalArgumentException
  83.      * if the covariance matrix is not strictly positive definite.
  84.      * @throws MathIllegalArgumentException if the mean and covariance
  85.      * arrays dimensions do not match.
  86.      */
  87.     public CorrelatedRandomVectorGenerator(double[] mean,
  88.                                            RealMatrix covariance, double small,
  89.                                            NormalizedRandomGenerator generator) {
  90.         int order = covariance.getRowDimension();
  91.         if (mean.length != order) {
  92.             throw new MathIllegalArgumentException(LocalizedCoreFormats.DIMENSIONS_MISMATCH,
  93.                                                    mean.length, order);
  94.         }
  95.         this.mean = mean.clone();

  96.         final RectangularCholeskyDecomposition decomposition =
  97.             new RectangularCholeskyDecomposition(covariance, small);
  98.         root = decomposition.getRootMatrix();

  99.         this.generator = generator;
  100.         normalized = new double[decomposition.getRank()];

  101.     }

  102.     /**
  103.      * Builds a null mean random correlated vector generator from its
  104.      * covariance matrix.
  105.      *
  106.      * @param covariance Covariance matrix.
  107.      * @param small Diagonal elements threshold under which  column are
  108.      * considered to be dependent on previous ones and are discarded.
  109.      * @param generator Underlying generator for uncorrelated normalized
  110.      * components.
  111.      * @throws org.hipparchus.exception.MathIllegalArgumentException
  112.      * if the covariance matrix is not strictly positive definite.
  113.      */
  114.     public CorrelatedRandomVectorGenerator(RealMatrix covariance, double small,
  115.                                            NormalizedRandomGenerator generator) {
  116.         int order = covariance.getRowDimension();
  117.         mean = new double[order];
  118.         for (int i = 0; i < order; ++i) {
  119.             mean[i] = 0;
  120.         }

  121.         final RectangularCholeskyDecomposition decomposition =
  122.             new RectangularCholeskyDecomposition(covariance, small);
  123.         root = decomposition.getRootMatrix();

  124.         this.generator = generator;
  125.         normalized = new double[decomposition.getRank()];

  126.     }

  127.     /** Get the underlying normalized components generator.
  128.      * @return underlying uncorrelated components generator
  129.      */
  130.     public NormalizedRandomGenerator getGenerator() {
  131.         return generator;
  132.     }

  133.     /** Get the rank of the covariance matrix.
  134.      * The rank is the number of independent rows in the covariance
  135.      * matrix, it is also the number of columns of the root matrix.
  136.      * @return rank of the square matrix.
  137.      * @see #getRootMatrix()
  138.      */
  139.     public int getRank() {
  140.         return normalized.length;
  141.     }

  142.     /** Get the root of the covariance matrix.
  143.      * The root is the rectangular matrix <code>B</code> such that
  144.      * the covariance matrix is equal to <code>B.B<sup>T</sup></code>
  145.      * @return root of the square matrix
  146.      * @see #getRank()
  147.      */
  148.     public RealMatrix getRootMatrix() {
  149.         return root;
  150.     }

  151.     /** Generate a correlated random vector.
  152.      * @return a random vector as an array of double. The returned array
  153.      * is created at each call, the caller can do what it wants with it.
  154.      */
  155.     @Override
  156.     public double[] nextVector() {

  157.         // generate uncorrelated vector
  158.         for (int i = 0; i < normalized.length; ++i) {
  159.             normalized[i] = generator.nextNormalizedDouble();
  160.         }

  161.         // compute correlated vector
  162.         double[] correlated = new double[mean.length];
  163.         for (int i = 0; i < correlated.length; ++i) {
  164.             correlated[i] = mean[i];
  165.             for (int j = 0; j < root.getColumnDimension(); ++j) {
  166.                 correlated[i] += root.getEntry(i, j) * normalized[j];
  167.             }
  168.         }

  169.         return correlated;
  170.     }

  171. }