View Javadoc

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 = &mu;. */
58      public static final int UNIFORM_MODE = 2;
59  
60      /** Exponential random deviates with mean = &mu;. */
61      public static final int EXPONENTIAL_MODE = 3;
62  
63      /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
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 }