View Javadoc
1   /*
2    * Copyright (C) 2016 Alberto Irurueta Carro (alberto@irurueta.com)
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *         http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.irurueta.statistics;
18  
19  import com.irurueta.algebra.AlgebraException;
20  import com.irurueta.algebra.CholeskyDecomposer;
21  import com.irurueta.algebra.Matrix;
22  import com.irurueta.algebra.WrongSizeException;
23  
24  import java.util.Random;
25  
26  /**
27   * Generates pseudo-random values following a multivariate Gaussian distribution
28   * having the specified mean and covariance. By default, mean is equal to zero
29   * and the covariance is equal to the identity (unitary standard deviation).
30   */
31  public class MultivariateGaussianRandomizer {
32  
33      /**
34       * Contains mean values to be used for random value generation.
35       */
36      private double[] mean;
37  
38      /**
39       * Covariance matrix.
40       */
41      private Matrix covariance;
42  
43      /**
44       * Lower triangular Cholesky decomposition of covariance matrix.
45       */
46      private Matrix l;
47  
48      /**
49       * Instance in charge of generating pseudo-random values. Secure instances
50       * can be used if the generated values need to be ensured "more" random at
51       * the expense of higher computational cost.
52       */
53      private final Random internalRandom;
54  
55      /**
56       * Constructor.
57       */
58      public MultivariateGaussianRandomizer() {
59          this(new Random());
60      }
61  
62      /**
63       * Constructor.
64       * Because neither mean nor covariance are provided, default values will be
65       * used instead.
66       *
67       * @param internalRandom internal random instance in charge of generating
68       *                       pseudo-random values.
69       * @throws NullPointerException thrown if provided internal random is null.
70       */
71      public MultivariateGaussianRandomizer(final Random internalRandom) {
72          if (internalRandom == null) {
73              throw new NullPointerException();
74          }
75  
76          this.internalRandom = internalRandom;
77          mean = new double[1];
78          try {
79              covariance = l = Matrix.identity(1, 1);
80          } catch (final WrongSizeException ignore) {
81              // never thrown
82          }
83      }
84  
85      /**
86       * Constructor.
87       *
88       * @param mean       mean to be set.
89       * @param covariance covariance to be set.
90       * @throws WrongSizeException               if mean length is not compatible with
91       *                                          covariance size. Mean length must be equal to size of square covariance
92       *                                          matrix.
93       * @throws InvalidCovarianceMatrixException if provided covariance matrix is
94       *                                          not symmetric positive definite.
95       */
96      public MultivariateGaussianRandomizer(final double[] mean, final Matrix covariance)
97              throws WrongSizeException, InvalidCovarianceMatrixException {
98          this(new Random(), mean, covariance);
99      }
100 
101     /**
102      * Constructor.
103      *
104      * @param internalRandom internal random instance in charge of generating
105      *                       pseudo-random values.
106      * @param mean           mean to be set.
107      * @param covariance     covariance to be set.
108      * @throws WrongSizeException               if mean length is not compatible with
109      *                                          covariance size. Mean length must be equal to size of square covariance
110      *                                          matrix.
111      * @throws InvalidCovarianceMatrixException if provided covariance matrix is
112      *                                          not symmetric positive definite.
113      * @throws NullPointerException             thrown if provided internal random is null.
114      */
115     public MultivariateGaussianRandomizer(
116             final Random internalRandom, final double[] mean, final Matrix covariance) throws WrongSizeException,
117             InvalidCovarianceMatrixException {
118         if (internalRandom == null) {
119             throw new NullPointerException();
120         }
121 
122         this.internalRandom = internalRandom;
123         setMeanAndCovariance(mean, covariance);
124     }
125 
126     /**
127      * Returns mean value to be used for Gaussian random value generation.
128      *
129      * @return mean value.
130      */
131     public double[] getMean() {
132         return mean;
133     }
134 
135     /**
136      * Returns covariance to be used for Gaussian random value generation.
137      *
138      * @return covariance.
139      */
140     public Matrix getCovariance() {
141         return covariance;
142     }
143 
144     /**
145      * Sets mean and covariance to generate multivariate Gaussian random values.
146      *
147      * @param mean       mean to be set.
148      * @param covariance covariance to be set.
149      * @throws WrongSizeException               if mean length is not compatible with
150      *                                          covariance size. Mean length must be equal to size of square covariance
151      *                                          matrix.
152      * @throws InvalidCovarianceMatrixException if provided covariance matrix is
153      *                                          not symmetric positive definite.
154      */
155     public final void setMeanAndCovariance(final double[] mean, final Matrix covariance)
156             throws WrongSizeException, InvalidCovarianceMatrixException {
157         final var length = mean.length;
158         if (covariance.getRows() != length || covariance.getColumns() != length) {
159             throw new WrongSizeException("mean must have same covariance size");
160         }
161 
162         try {
163             final var decomposer = new CholeskyDecomposer(covariance);
164             decomposer.decompose();
165             if (!decomposer.isSPD()) {
166                 throw new InvalidCovarianceMatrixException(
167                         "covariance matrix must be symmetric positive definite (non singular)");
168             }
169 
170             this.mean = mean;
171             this.covariance = covariance;
172 
173             l = decomposer.getL();
174         } catch (final AlgebraException e) {
175             throw new InvalidCovarianceMatrixException("covariance matrix must be square", e);
176         }
177     }
178 
179     /**
180      * Generate next set of multivariate Gaussian random values having current
181      * mean and covariance of this instance.
182      *
183      * @param values array where generated random values will be stored.
184      * @throws IllegalArgumentException if provided array length does not have the same length
185      *                                  as provided mean array.
186      */
187     public void next(final double[] values) {
188         final var n = values.length;
189 
190         if (n != mean.length) {
191             throw new IllegalArgumentException("values must have mean length");
192         }
193 
194         // generate initial Gaussian values with zero mean and unitary standard
195         // deviation
196         final var tmp = new double[n];
197         for (var i = 0; i < n; i++) {
198             tmp[i] = internalRandom.nextGaussian();
199         }
200 
201         // multiply square root of covariance (its Lower triangular Cholesky
202         // decomposition) by the generated values and add mean
203         for (var i = 0; i < n; i++) {
204             values[i] = 0.0;
205 
206             for (var j = 0; j < n; j++) {
207                 if (i >= j) {
208                     // only evaluate lower triangular portion
209                     values[i] += l.getElementAt(i, j) * tmp[j];
210                 }
211             }
212 
213             // add mean
214             values[i] += mean[i];
215         }
216     }
217 
218     /**
219      * Generate next set of multivariate Gaussian random values having current
220      * mean and covariance of this instance.
221      *
222      * @return a new array containing generated random values.
223      */
224     public double[] next() {
225         final var values = new double[mean.length];
226         next(values);
227         return values;
228     }
229 }