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 }