1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.commons.math.random;
19 import java.io.BufferedReader;
20 import java.io.IOException;
21 import java.io.InputStreamReader;
22 import java.net.MalformedURLException;
23 import java.net.URL;
24
25 import org.apache.commons.math.exception.MathIllegalStateException;
26 import org.apache.commons.math.exception.util.LocalizedFormats;
27
28 /**
29 * Generates values for use in simulation applications.
30 * <p>
31 * How values are generated is determined by the <code>mode</code>
32 * property.</p>
33 * <p>
34 * Supported <code>mode</code> values are: <ul>
35 * <li> DIGEST_MODE -- uses an empirical distribution </li>
36 * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
37 * <li> UNIFORM_MODE -- generates uniformly distributed random values with
38 * mean = <code>mu</code> </li>
39 * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
40 * with mean = <code>mu</code></li>
41 * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
42 * mean = <code>mu</code> and
43 * standard deviation = <code>sigma</code></li>
44 * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
45 *
46 * @version $Id: ValueServer.java 1178806 2011-10-04 14:14:02Z luc $
47 *
48 */
49 public class ValueServer {
50
51 /** Use empirical distribution. */
52 public static final int DIGEST_MODE = 0;
53
54 /** Replay data from valuesFilePath. */
55 public static final int REPLAY_MODE = 1;
56
57 /** Uniform random deviates with mean = μ. */
58 public static final int UNIFORM_MODE = 2;
59
60 /** Exponential random deviates with mean = μ. */
61 public static final int EXPONENTIAL_MODE = 3;
62
63 /** Gaussian random deviates with mean = μ, std dev = σ. */
64 public static final int GAUSSIAN_MODE = 4;
65
66 /** Always return mu */
67 public static final int CONSTANT_MODE = 5;
68
69 /** mode determines how values are generated. */
70 private int mode = 5;
71
72 /** URI to raw data values. */
73 private URL valuesFileURL = null;
74
75 /** Mean for use with non-data-driven modes. */
76 private double mu = 0.0;
77
78 /** Standard deviation for use with GAUSSIAN_MODE. */
79 private double sigma = 0.0;
80
81 /** Empirical probability distribution for use with DIGEST_MODE. */
82 private EmpiricalDistribution empiricalDistribution = null;
83
84 /** File pointer for REPLAY_MODE. */
85 private BufferedReader filePointer = null;
86
87 /** RandomDataImpl to use for random data generation. */
88 private final RandomDataImpl randomData;
89
90 // Data generation modes ======================================
91
92 /** Creates new ValueServer */
93 public ValueServer() {
94 randomData = new RandomDataImpl();
95 }
96
97 /**
98 * Construct a ValueServer instance using a RandomDataImpl as its source
99 * of random data.
100 *
101 * @param randomData the RandomDataImpl instance used to source random data
102 * @since 3.0
103 */
104 public ValueServer(RandomDataImpl randomData) {
105 this.randomData = randomData;
106 }
107
108 /**
109 * Returns the next generated value, generated according
110 * to the mode value (see MODE constants).
111 *
112 * @return generated value
113 * @throws IOException in REPLAY_MODE if a file I/O error occurs
114 */
115 public double getNext() throws IOException {
116 switch (mode) {
117 case DIGEST_MODE: return getNextDigest();
118 case REPLAY_MODE: return getNextReplay();
119 case UNIFORM_MODE: return getNextUniform();
120 case EXPONENTIAL_MODE: return getNextExponential();
121 case GAUSSIAN_MODE: return getNextGaussian();
122 case CONSTANT_MODE: return mu;
123 default: throw new MathIllegalStateException(
124 LocalizedFormats.UNKNOWN_MODE,
125 mode,
126 "DIGEST_MODE", DIGEST_MODE, "REPLAY_MODE", REPLAY_MODE,
127 "UNIFORM_MODE", UNIFORM_MODE, "EXPONENTIAL_MODE", EXPONENTIAL_MODE,
128 "GAUSSIAN_MODE", GAUSSIAN_MODE, "CONSTANT_MODE", CONSTANT_MODE);
129 }
130 }
131
132 /**
133 * Fills the input array with values generated using getNext() repeatedly.
134 *
135 * @param values array to be filled
136 * @throws IOException in REPLAY_MODE if a file I/O error occurs
137 */
138 public void fill(double[] values) throws IOException {
139 for (int i = 0; i < values.length; i++) {
140 values[i] = getNext();
141 }
142 }
143
144 /**
145 * Returns an array of length <code>length</code> with values generated
146 * using getNext() repeatedly.
147 *
148 * @param length length of output array
149 * @return array of generated values
150 * @throws IOException in REPLAY_MODE if a file I/O error occurs
151 */
152 public double[] fill(int length) throws IOException {
153 double[] out = new double[length];
154 for (int i = 0; i < length; i++) {
155 out[i] = getNext();
156 }
157 return out;
158 }
159
160 /**
161 * Computes the empirical distribution using values from the file
162 * in <code>valuesFileURL</code>, using the default number of bins.
163 * <p>
164 * <code>valuesFileURL</code> must exist and be
165 * readable by *this at runtime.</p>
166 * <p>
167 * This method must be called before using <code>getNext()</code>
168 * with <code>mode = DIGEST_MODE</code></p>
169 *
170 * @throws IOException if an I/O error occurs reading the input file
171 */
172 public void computeDistribution() throws IOException {
173 computeDistribution(EmpiricalDistributionImpl.DEFAULT_BIN_COUNT);
174 }
175
176 /**
177 * Computes the empirical distribution using values from the file
178 * in <code>valuesFileURL</code> and <code>binCount</code> bins.
179 * <p>
180 * <code>valuesFileURL</code> must exist and be readable by this process
181 * at runtime.</p>
182 * <p>
183 * This method must be called before using <code>getNext()</code>
184 * with <code>mode = DIGEST_MODE</code></p>
185 *
186 * @param binCount the number of bins used in computing the empirical
187 * distribution
188 * @throws IOException if an error occurs reading the input file
189 */
190 public void computeDistribution(int binCount)
191 throws IOException {
192 empiricalDistribution = new EmpiricalDistributionImpl(binCount, randomData);
193 empiricalDistribution.load(valuesFileURL);
194 mu = empiricalDistribution.getSampleStats().getMean();
195 sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
196 }
197
198 /**
199 * Returns the data generation mode. See {@link ValueServer the class javadoc}
200 * for description of the valid values of this property.
201 *
202 * @return Value of property mode.
203 */
204 public int getMode() {
205 return mode;
206 }
207
208 /**
209 * Sets the data generation mode.
210 *
211 * @param mode New value of the data generation mode.
212 */
213 public void setMode(int mode) {
214 this.mode = mode;
215 }
216
217 /**
218 * Returns the URL for the file used to build the empirical distribution
219 * when using {@link #DIGEST_MODE}.
220 *
221 * @return Values file URL.
222 */
223 public URL getValuesFileURL() {
224 return valuesFileURL;
225 }
226
227 /**
228 * Sets the {@link #getValuesFileURL() values file URL} using a string
229 * URL representation.
230 *
231 * @param url String representation for new valuesFileURL.
232 * @throws MalformedURLException if url is not well formed
233 */
234 public void setValuesFileURL(String url) throws MalformedURLException {
235 this.valuesFileURL = new URL(url);
236 }
237
238 /**
239 * Sets the the {@link #getValuesFileURL() values file URL}.
240 *
241 * @param url URL of the values file.
242 */
243 public void setValuesFileURL(URL url) {
244 this.valuesFileURL = url;
245 }
246
247 /**
248 * Returns the {@link EmpiricalDistribution} used when operating in {@value #DIGEST_MODE}.
249 *
250 * @return EmpircalDistribution built by {@link #computeDistribution()}
251 */
252 public EmpiricalDistribution getEmpiricalDistribution() {
253 return empiricalDistribution;
254 }
255
256 /**
257 * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>.
258 *
259 * @throws IOException if an error occurs opening the file
260 */
261 public void resetReplayFile() throws IOException {
262 if (filePointer != null) {
263 try {
264 filePointer.close();
265 filePointer = null;
266 } catch (IOException ex) {
267 // ignore
268 }
269 }
270 filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream()));
271 }
272
273 /**
274 * Closes {@code valuesFileURL} after use in REPLAY_MODE.
275 *
276 * @throws IOException if an error occurs closing the file
277 */
278 public void closeReplayFile() throws IOException {
279 if (filePointer != null) {
280 filePointer.close();
281 filePointer = null;
282 }
283 }
284
285 /**
286 * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE}
287 * or {@link #UNIFORM_MODE}. When operating in {@link #CONSTANT_MODE}, this is the constant
288 * value always returned. Calling {@link #computeDistribution()} sets this value to the
289 * overall mean of the values in the {@link #getValuesFileURL() values file}.
290 *
291 * @return Mean used in data generation.
292 */
293 public double getMu() {
294 return mu;
295 }
296
297 /**
298 * Sets the {@link #getMu() mean} used in data generation. Note that calling this method
299 * after {@link #computeDistribution()} has been called will have no effect on data
300 * generated in {@link #DIGEST_MODE}.
301 *
302 * @param mu new Mean value.
303 */
304 public void setMu(double mu) {
305 this.mu = mu;
306 }
307
308 /**
309 * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}.
310 * Calling {@link #computeDistribution()} sets this value to the overall standard
311 * deviation of the values in the {@link #getValuesFileURL() values file}. This
312 * property has no effect when the data generation mode is not
313 * {@link #GAUSSIAN_MODE}.
314 *
315 * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}.
316 */
317 public double getSigma() {
318 return sigma;
319 }
320
321 /**
322 * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}.
323 *
324 * @param sigma New standard deviation.
325 */
326 public void setSigma(double sigma) {
327 this.sigma = sigma;
328 }
329
330 /**
331 * Reseeds the random data generator.
332 *
333 * @param seed Value with which to reseed the {@link RandomDataImpl}
334 * used to generate random data.
335 */
336 public void reSeed(long seed) {
337 randomData.reSeed(seed);
338 }
339
340 //------------- private methods ---------------------------------
341
342 /**
343 * Gets a random value in DIGEST_MODE.
344 * <p>
345 * <strong>Preconditions</strong>: <ul>
346 * <li>Before this method is called, <code>computeDistribution()</code>
347 * must have completed successfully; otherwise an
348 * <code>IllegalStateException</code> will be thrown</li></ul></p>
349 *
350 * @return next random value from the empirical distribution digest
351 */
352 private double getNextDigest() {
353 if ((empiricalDistribution == null) ||
354 (empiricalDistribution.getBinStats().size() == 0)) {
355 throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED);
356 }
357 return empiricalDistribution.getNextValue();
358 }
359
360 /**
361 * Gets next sequential value from the <code>valuesFileURL</code>.
362 * <p>
363 * Throws an IOException if the read fails.</p>
364 * <p>
365 * This method will open the <code>valuesFileURL</code> if there is no
366 * replay file open.</p>
367 * <p>
368 * The <code>valuesFileURL</code> will be closed and reopened to wrap around
369 * from EOF to BOF if EOF is encountered. EOFException (which is a kind of
370 * IOException) may still be thrown if the <code>valuesFileURL</code> is
371 * empty.</p>
372 *
373 * @return next value from the replay file
374 * @throws IOException if there is a problem reading from the file
375 * @throws NumberFormatException if an invalid numeric string is
376 * encountered in the file
377 */
378 private double getNextReplay() throws IOException {
379 String str = null;
380 if (filePointer == null) {
381 resetReplayFile();
382 }
383 if ((str = filePointer.readLine()) == null) {
384 // we have probably reached end of file, wrap around from EOF to BOF
385 closeReplayFile();
386 resetReplayFile();
387 if ((str = filePointer.readLine()) == null) {
388 throw new MathIllegalStateException(LocalizedFormats.URL_CONTAINS_NO_DATA,
389 valuesFileURL);
390 }
391 }
392 return Double.valueOf(str).doubleValue();
393 }
394
395 /**
396 * Gets a uniformly distributed random value with mean = mu.
397 *
398 * @return random uniform value
399 */
400 private double getNextUniform() {
401 return randomData.nextUniform(0, 2 * mu);
402 }
403
404 /**
405 * Gets an exponentially distributed random value with mean = mu.
406 *
407 * @return random exponential value
408 */
409 private double getNextExponential() {
410 return randomData.nextExponential(mu);
411 }
412
413 /**
414 * Gets a Gaussian distributed random value with mean = mu
415 * and standard deviation = sigma.
416 *
417 * @return random Gaussian value
418 */
419 private double getNextGaussian() {
420 return randomData.nextGaussian(mu, sigma);
421 }
422
423 }