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 018 package org.apache.commons.math3.random; 019 import java.io.BufferedReader; 020 import java.io.IOException; 021 import java.io.InputStreamReader; 022 import java.net.MalformedURLException; 023 import java.net.URL; 024 025 import org.apache.commons.math3.exception.MathIllegalArgumentException; 026 import org.apache.commons.math3.exception.MathIllegalStateException; 027 import org.apache.commons.math3.exception.NullArgumentException; 028 import org.apache.commons.math3.exception.ZeroException; 029 import 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 * @version $Id: ValueServer.java 1422350 2012-12-15 20:47:47Z psteitz $ 050 * 051 */ 052 public class ValueServer { 053 054 /** Use empirical distribution. */ 055 public static final int DIGEST_MODE = 0; 056 057 /** Replay data from valuesFilePath. */ 058 public static final int REPLAY_MODE = 1; 059 060 /** Uniform random deviates with mean = μ. */ 061 public static final int UNIFORM_MODE = 2; 062 063 /** Exponential random deviates with mean = μ. */ 064 public static final int EXPONENTIAL_MODE = 3; 065 066 /** Gaussian random deviates with mean = μ, std dev = σ. */ 067 public static final int GAUSSIAN_MODE = 4; 068 069 /** Always return mu */ 070 public static final int CONSTANT_MODE = 5; 071 072 /** mode determines how values are generated. */ 073 private int mode = 5; 074 075 /** URI to raw data values. */ 076 private URL valuesFileURL = null; 077 078 /** Mean for use with non-data-driven modes. */ 079 private double mu = 0.0; 080 081 /** Standard deviation for use with GAUSSIAN_MODE. */ 082 private double sigma = 0.0; 083 084 /** Empirical probability distribution for use with DIGEST_MODE. */ 085 private EmpiricalDistribution empiricalDistribution = null; 086 087 /** File pointer for REPLAY_MODE. */ 088 private BufferedReader filePointer = null; 089 090 /** RandomDataImpl to use for random data generation. */ 091 private final RandomDataImpl randomData; 092 093 // Data generation modes ====================================== 094 095 /** Creates new ValueServer */ 096 public ValueServer() { 097 randomData = new RandomDataImpl(); 098 } 099 100 /** 101 * Construct a ValueServer instance using a RandomDataImpl as its source 102 * of random data. 103 * 104 * @param randomData the RandomDataImpl instance used to source random data 105 * @since 3.0 106 * @deprecated use {@link #ValueServer(RandomGenerator)} 107 */ 108 public ValueServer(RandomDataImpl randomData) { 109 this.randomData = randomData; 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 RandomDataImpl(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); 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 */ 290 public void resetReplayFile() throws IOException { 291 if (filePointer != null) { 292 try { 293 filePointer.close(); 294 filePointer = null; 295 } catch (IOException ex) { //NOPMD 296 // ignore 297 } 298 } 299 filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream(), "UTF-8")); 300 } 301 302 /** 303 * Closes {@code valuesFileURL} after use in REPLAY_MODE. 304 * 305 * @throws IOException if an error occurs closing the file 306 */ 307 public void closeReplayFile() throws IOException { 308 if (filePointer != null) { 309 filePointer.close(); 310 filePointer = null; 311 } 312 } 313 314 /** 315 * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE} 316 * or {@link #UNIFORM_MODE}. When operating in {@link #CONSTANT_MODE}, this is the constant 317 * value always returned. Calling {@link #computeDistribution()} sets this value to the 318 * overall mean of the values in the {@link #getValuesFileURL() values file}. 319 * 320 * @return Mean used in data generation. 321 */ 322 public double getMu() { 323 return mu; 324 } 325 326 /** 327 * Sets the {@link #getMu() mean} used in data generation. Note that calling this method 328 * after {@link #computeDistribution()} has been called will have no effect on data 329 * generated in {@link #DIGEST_MODE}. 330 * 331 * @param mu new Mean value. 332 */ 333 public void setMu(double mu) { 334 this.mu = mu; 335 } 336 337 /** 338 * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}. 339 * Calling {@link #computeDistribution()} sets this value to the overall standard 340 * deviation of the values in the {@link #getValuesFileURL() values file}. This 341 * property has no effect when the data generation mode is not 342 * {@link #GAUSSIAN_MODE}. 343 * 344 * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}. 345 */ 346 public double getSigma() { 347 return sigma; 348 } 349 350 /** 351 * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}. 352 * 353 * @param sigma New standard deviation. 354 */ 355 public void setSigma(double sigma) { 356 this.sigma = sigma; 357 } 358 359 /** 360 * Reseeds the random data generator. 361 * 362 * @param seed Value with which to reseed the {@link RandomDataImpl} 363 * used to generate random data. 364 */ 365 public void reSeed(long seed) { 366 randomData.reSeed(seed); 367 } 368 369 //------------- private methods --------------------------------- 370 371 /** 372 * Gets a random value in DIGEST_MODE. 373 * <p> 374 * <strong>Preconditions</strong>: <ul> 375 * <li>Before this method is called, <code>computeDistribution()</code> 376 * must have completed successfully; otherwise an 377 * <code>IllegalStateException</code> will be thrown</li></ul></p> 378 * 379 * @return next random value from the empirical distribution digest 380 * @throws MathIllegalStateException if digest has not been initialized 381 */ 382 private double getNextDigest() throws MathIllegalStateException { 383 if ((empiricalDistribution == null) || 384 (empiricalDistribution.getBinStats().size() == 0)) { 385 throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED); 386 } 387 return empiricalDistribution.getNextValue(); 388 } 389 390 /** 391 * Gets next sequential value from the <code>valuesFileURL</code>. 392 * <p> 393 * Throws an IOException if the read fails.</p> 394 * <p> 395 * This method will open the <code>valuesFileURL</code> if there is no 396 * replay file open.</p> 397 * <p> 398 * The <code>valuesFileURL</code> will be closed and reopened to wrap around 399 * from EOF to BOF if EOF is encountered. EOFException (which is a kind of 400 * IOException) may still be thrown if the <code>valuesFileURL</code> is 401 * empty.</p> 402 * 403 * @return next value from the replay file 404 * @throws IOException if there is a problem reading from the file 405 * @throws MathIllegalStateException if URL contains no data 406 * @throws NumberFormatException if an invalid numeric string is 407 * encountered in the file 408 */ 409 private double getNextReplay() throws IOException, MathIllegalStateException { 410 String str = null; 411 if (filePointer == null) { 412 resetReplayFile(); 413 } 414 if ((str = filePointer.readLine()) == null) { 415 // we have probably reached end of file, wrap around from EOF to BOF 416 closeReplayFile(); 417 resetReplayFile(); 418 if ((str = filePointer.readLine()) == null) { 419 throw new MathIllegalStateException(LocalizedFormats.URL_CONTAINS_NO_DATA, 420 valuesFileURL); 421 } 422 } 423 return Double.valueOf(str).doubleValue(); 424 } 425 426 /** 427 * Gets a uniformly distributed random value with mean = mu. 428 * 429 * @return random uniform value 430 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 431 */ 432 private double getNextUniform() throws MathIllegalArgumentException { 433 return randomData.nextUniform(0, 2 * mu); 434 } 435 436 /** 437 * Gets an exponentially distributed random value with mean = mu. 438 * 439 * @return random exponential value 440 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 441 */ 442 private double getNextExponential() throws MathIllegalArgumentException { 443 return randomData.nextExponential(mu); 444 } 445 446 /** 447 * Gets a Gaussian distributed random value with mean = mu 448 * and standard deviation = sigma. 449 * 450 * @return random Gaussian value 451 * @throws MathIllegalArgumentException if the underlying random generator thwrows one 452 */ 453 private double getNextGaussian() throws MathIllegalArgumentException { 454 return randomData.nextGaussian(mu, sigma); 455 } 456 457 }