001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package org.apache.commons.math3.random; 019import java.io.BufferedReader; 020import java.io.IOException; 021import java.io.InputStreamReader; 022import java.net.MalformedURLException; 023import java.net.URL; 024 025import org.apache.commons.math3.exception.MathIllegalArgumentException; 026import org.apache.commons.math3.exception.MathIllegalStateException; 027import org.apache.commons.math3.exception.NullArgumentException; 028import org.apache.commons.math3.exception.ZeroException; 029import org.apache.commons.math3.exception.util.LocalizedFormats; 030 031/** 032 * Generates values for use in simulation applications. 033 * <p> 034 * How values are generated is determined by the <code>mode</code> 035 * property.</p> 036 * <p> 037 * Supported <code>mode</code> values are: <ul> 038 * <li> DIGEST_MODE -- uses an empirical distribution </li> 039 * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li> 040 * <li> UNIFORM_MODE -- generates uniformly distributed random values with 041 * mean = <code>mu</code> </li> 042 * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values 043 * with mean = <code>mu</code></li> 044 * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with 045 * mean = <code>mu</code> and 046 * standard deviation = <code>sigma</code></li> 047 * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p> 048 * 049 * 050 */ 051public class ValueServer { 052 053 /** Use empirical distribution. */ 054 public static final int DIGEST_MODE = 0; 055 056 /** Replay data from valuesFilePath. */ 057 public static final int REPLAY_MODE = 1; 058 059 /** Uniform random deviates with mean = μ. */ 060 public static final int UNIFORM_MODE = 2; 061 062 /** Exponential random deviates with mean = μ. */ 063 public static final int EXPONENTIAL_MODE = 3; 064 065 /** Gaussian random deviates with mean = μ, std dev = σ. */ 066 public static final int GAUSSIAN_MODE = 4; 067 068 /** Always return mu */ 069 public static final int CONSTANT_MODE = 5; 070 071 /** mode determines how values are generated. */ 072 private int mode = 5; 073 074 /** URI to raw data values. */ 075 private URL valuesFileURL = null; 076 077 /** Mean for use with non-data-driven modes. */ 078 private double mu = 0.0; 079 080 /** Standard deviation for use with GAUSSIAN_MODE. */ 081 private double sigma = 0.0; 082 083 /** Empirical probability distribution for use with DIGEST_MODE. */ 084 private EmpiricalDistribution empiricalDistribution = null; 085 086 /** File pointer for REPLAY_MODE. */ 087 private BufferedReader filePointer = null; 088 089 /** RandomDataImpl to use for random data generation. */ 090 private final RandomDataGenerator randomData; 091 092 // Data generation modes ====================================== 093 094 /** Creates new ValueServer */ 095 public ValueServer() { 096 randomData = new RandomDataGenerator(); 097 } 098 099 /** 100 * Construct a ValueServer instance using a RandomDataImpl as its source 101 * of random data. 102 * 103 * @param randomData the RandomDataImpl instance used to source random data 104 * @since 3.0 105 * @deprecated use {@link #ValueServer(RandomGenerator)} 106 */ 107 @Deprecated 108 public ValueServer(RandomDataImpl randomData) { 109 this.randomData = randomData.getDelegate(); 110 } 111 112 /** 113 * Construct a ValueServer instance using a RandomGenerator as its source 114 * of random data. 115 * 116 * @since 3.1 117 * @param generator source of random data 118 */ 119 public ValueServer(RandomGenerator generator) { 120 this.randomData = new RandomDataGenerator(generator); 121 } 122 123 /** 124 * Returns the next generated value, generated according 125 * to the mode value (see MODE constants). 126 * 127 * @return generated value 128 * @throws IOException in REPLAY_MODE if a file I/O error occurs 129 * @throws MathIllegalStateException if mode is not recognized 130 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 131 */ 132 public double getNext() throws IOException, MathIllegalStateException, MathIllegalArgumentException { 133 switch (mode) { 134 case DIGEST_MODE: return getNextDigest(); 135 case REPLAY_MODE: return getNextReplay(); 136 case UNIFORM_MODE: return getNextUniform(); 137 case EXPONENTIAL_MODE: return getNextExponential(); 138 case GAUSSIAN_MODE: return getNextGaussian(); 139 case CONSTANT_MODE: return mu; 140 default: throw new MathIllegalStateException( 141 LocalizedFormats.UNKNOWN_MODE, 142 mode, 143 "DIGEST_MODE", DIGEST_MODE, "REPLAY_MODE", REPLAY_MODE, 144 "UNIFORM_MODE", UNIFORM_MODE, "EXPONENTIAL_MODE", EXPONENTIAL_MODE, 145 "GAUSSIAN_MODE", GAUSSIAN_MODE, "CONSTANT_MODE", CONSTANT_MODE); 146 } 147 } 148 149 /** 150 * Fills the input array with values generated using getNext() repeatedly. 151 * 152 * @param values array to be filled 153 * @throws IOException in REPLAY_MODE if a file I/O error occurs 154 * @throws MathIllegalStateException if mode is not recognized 155 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 156 */ 157 public void fill(double[] values) 158 throws IOException, MathIllegalStateException, MathIllegalArgumentException { 159 for (int i = 0; i < values.length; i++) { 160 values[i] = getNext(); 161 } 162 } 163 164 /** 165 * Returns an array of length <code>length</code> with values generated 166 * using getNext() repeatedly. 167 * 168 * @param length length of output array 169 * @return array of generated values 170 * @throws IOException in REPLAY_MODE if a file I/O error occurs 171 * @throws MathIllegalStateException if mode is not recognized 172 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 173 */ 174 public double[] fill(int length) 175 throws IOException, MathIllegalStateException, MathIllegalArgumentException { 176 double[] out = new double[length]; 177 for (int i = 0; i < length; i++) { 178 out[i] = getNext(); 179 } 180 return out; 181 } 182 183 /** 184 * Computes the empirical distribution using values from the file 185 * in <code>valuesFileURL</code>, using the default number of bins. 186 * <p> 187 * <code>valuesFileURL</code> must exist and be 188 * readable by *this at runtime.</p> 189 * <p> 190 * This method must be called before using <code>getNext()</code> 191 * with <code>mode = DIGEST_MODE</code></p> 192 * 193 * @throws IOException if an I/O error occurs reading the input file 194 * @throws NullArgumentException if the {@code valuesFileURL} has not been set 195 * @throws ZeroException if URL contains no data 196 */ 197 public void computeDistribution() throws IOException, ZeroException, NullArgumentException { 198 computeDistribution(EmpiricalDistribution.DEFAULT_BIN_COUNT); 199 } 200 201 /** 202 * Computes the empirical distribution using values from the file 203 * in <code>valuesFileURL</code> and <code>binCount</code> bins. 204 * <p> 205 * <code>valuesFileURL</code> must exist and be readable by this process 206 * at runtime.</p> 207 * <p> 208 * This method must be called before using <code>getNext()</code> 209 * with <code>mode = DIGEST_MODE</code></p> 210 * 211 * @param binCount the number of bins used in computing the empirical 212 * distribution 213 * @throws NullArgumentException if the {@code valuesFileURL} has not been set 214 * @throws IOException if an error occurs reading the input file 215 * @throws ZeroException if URL contains no data 216 */ 217 public void computeDistribution(int binCount) throws NullArgumentException, IOException, ZeroException { 218 empiricalDistribution = new EmpiricalDistribution(binCount, randomData.getRandomGenerator()); 219 empiricalDistribution.load(valuesFileURL); 220 mu = empiricalDistribution.getSampleStats().getMean(); 221 sigma = empiricalDistribution.getSampleStats().getStandardDeviation(); 222 } 223 224 /** 225 * Returns the data generation mode. See {@link ValueServer the class javadoc} 226 * for description of the valid values of this property. 227 * 228 * @return Value of property mode. 229 */ 230 public int getMode() { 231 return mode; 232 } 233 234 /** 235 * Sets the data generation mode. 236 * 237 * @param mode New value of the data generation mode. 238 */ 239 public void setMode(int mode) { 240 this.mode = mode; 241 } 242 243 /** 244 * Returns the URL for the file used to build the empirical distribution 245 * when using {@link #DIGEST_MODE}. 246 * 247 * @return Values file URL. 248 */ 249 public URL getValuesFileURL() { 250 return valuesFileURL; 251 } 252 253 /** 254 * Sets the {@link #getValuesFileURL() values file URL} using a string 255 * URL representation. 256 * 257 * @param url String representation for new valuesFileURL. 258 * @throws MalformedURLException if url is not well formed 259 */ 260 public void setValuesFileURL(String url) throws MalformedURLException { 261 this.valuesFileURL = new URL(url); 262 } 263 264 /** 265 * Sets the the {@link #getValuesFileURL() values file URL}. 266 * 267 * <p>The values file <i>must</i> be an ASCII text file containing one 268 * valid numeric entry per line.</p> 269 * 270 * @param url URL of the values file. 271 */ 272 public void setValuesFileURL(URL url) { 273 this.valuesFileURL = url; 274 } 275 276 /** 277 * Returns the {@link EmpiricalDistribution} used when operating in {@value #DIGEST_MODE}. 278 * 279 * @return EmpircalDistribution built by {@link #computeDistribution()} 280 */ 281 public EmpiricalDistribution getEmpiricalDistribution() { 282 return empiricalDistribution; 283 } 284 285 /** 286 * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>. 287 * 288 * @throws IOException if an error occurs opening the file 289 * @throws NullPointerException if the {@code valuesFileURL} has not been set. 290 */ 291 public void resetReplayFile() throws IOException { 292 if (filePointer != null) { 293 try { 294 filePointer.close(); 295 filePointer = null; 296 } catch (IOException ex) { //NOPMD 297 // ignore 298 } 299 } 300 filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream(), "UTF-8")); 301 } 302 303 /** 304 * Closes {@code valuesFileURL} after use in REPLAY_MODE. 305 * 306 * @throws IOException if an error occurs closing the file 307 */ 308 public void closeReplayFile() throws IOException { 309 if (filePointer != null) { 310 filePointer.close(); 311 filePointer = null; 312 } 313 } 314 315 /** 316 * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE} 317 * or {@link #UNIFORM_MODE}. When operating in {@link #CONSTANT_MODE}, this is the constant 318 * value always returned. Calling {@link #computeDistribution()} sets this value to the 319 * overall mean of the values in the {@link #getValuesFileURL() values file}. 320 * 321 * @return Mean used in data generation. 322 */ 323 public double getMu() { 324 return mu; 325 } 326 327 /** 328 * Sets the {@link #getMu() mean} used in data generation. Note that calling this method 329 * after {@link #computeDistribution()} has been called will have no effect on data 330 * generated in {@link #DIGEST_MODE}. 331 * 332 * @param mu new Mean value. 333 */ 334 public void setMu(double mu) { 335 this.mu = mu; 336 } 337 338 /** 339 * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}. 340 * Calling {@link #computeDistribution()} sets this value to the overall standard 341 * deviation of the values in the {@link #getValuesFileURL() values file}. This 342 * property has no effect when the data generation mode is not 343 * {@link #GAUSSIAN_MODE}. 344 * 345 * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}. 346 */ 347 public double getSigma() { 348 return sigma; 349 } 350 351 /** 352 * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}. 353 * 354 * @param sigma New standard deviation. 355 */ 356 public void setSigma(double sigma) { 357 this.sigma = sigma; 358 } 359 360 /** 361 * Reseeds the random data generator. 362 * 363 * @param seed Value with which to reseed the {@link RandomDataImpl} 364 * used to generate random data. 365 */ 366 public void reSeed(long seed) { 367 randomData.reSeed(seed); 368 } 369 370 //------------- private methods --------------------------------- 371 372 /** 373 * Gets a random value in DIGEST_MODE. 374 * <p> 375 * <strong>Preconditions</strong>: <ul> 376 * <li>Before this method is called, <code>computeDistribution()</code> 377 * must have completed successfully; otherwise an 378 * <code>IllegalStateException</code> will be thrown</li></ul></p> 379 * 380 * @return next random value from the empirical distribution digest 381 * @throws MathIllegalStateException if digest has not been initialized 382 */ 383 private double getNextDigest() throws MathIllegalStateException { 384 if ((empiricalDistribution == null) || 385 (empiricalDistribution.getBinStats().size() == 0)) { 386 throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED); 387 } 388 return empiricalDistribution.getNextValue(); 389 } 390 391 /** 392 * Gets next sequential value from the <code>valuesFileURL</code>. 393 * <p> 394 * Throws an IOException if the read fails.</p> 395 * <p> 396 * This method will open the <code>valuesFileURL</code> if there is no 397 * replay file open.</p> 398 * <p> 399 * The <code>valuesFileURL</code> will be closed and reopened to wrap around 400 * from EOF to BOF if EOF is encountered. EOFException (which is a kind of 401 * IOException) may still be thrown if the <code>valuesFileURL</code> is 402 * empty.</p> 403 * 404 * @return next value from the replay file 405 * @throws IOException if there is a problem reading from the file 406 * @throws MathIllegalStateException if URL contains no data 407 * @throws NumberFormatException if an invalid numeric string is 408 * encountered in the file 409 */ 410 private double getNextReplay() throws IOException, MathIllegalStateException { 411 String str = null; 412 if (filePointer == null) { 413 resetReplayFile(); 414 } 415 if ((str = filePointer.readLine()) == null) { 416 // we have probably reached end of file, wrap around from EOF to BOF 417 closeReplayFile(); 418 resetReplayFile(); 419 if ((str = filePointer.readLine()) == null) { 420 throw new MathIllegalStateException(LocalizedFormats.URL_CONTAINS_NO_DATA, 421 valuesFileURL); 422 } 423 } 424 return Double.parseDouble(str); 425 } 426 427 /** 428 * Gets a uniformly distributed random value with mean = mu. 429 * 430 * @return random uniform value 431 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 432 */ 433 private double getNextUniform() throws MathIllegalArgumentException { 434 return randomData.nextUniform(0, 2 * mu); 435 } 436 437 /** 438 * Gets an exponentially distributed random value with mean = mu. 439 * 440 * @return random exponential value 441 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 442 */ 443 private double getNextExponential() throws MathIllegalArgumentException { 444 return randomData.nextExponential(mu); 445 } 446 447 /** 448 * Gets a Gaussian distributed random value with mean = mu 449 * and standard deviation = sigma. 450 * 451 * @return random Gaussian value 452 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 453 */ 454 private double getNextGaussian() throws MathIllegalArgumentException { 455 return randomData.nextGaussian(mu, sigma); 456 } 457 458}