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 }