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.math3.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.math3.exception.MathIllegalArgumentException;
26  import org.apache.commons.math3.exception.MathIllegalStateException;
27  import org.apache.commons.math3.exception.NullArgumentException;
28  import org.apache.commons.math3.exception.ZeroException;
29  import org.apache.commons.math3.exception.util.LocalizedFormats;
30  
31  /**
32   * Generates values for use in simulation applications.
33   * <p>
34   * How values are generated is determined by the <code>mode</code>
35   * property.</p>
36   * <p>
37   * Supported <code>mode</code> values are: <ul>
38   * <li> DIGEST_MODE -- uses an empirical distribution </li>
39   * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
40   * <li> UNIFORM_MODE -- generates uniformly distributed random values with
41   *                      mean = <code>mu</code> </li>
42   * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
43   *                         with mean = <code>mu</code></li>
44   * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
45   *                       mean = <code>mu</code> and
46   *                       standard deviation = <code>sigma</code></li>
47   * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
48   *
49   *
50   */
51  public class ValueServer {
52  
53      /** Use empirical distribution.  */
54      public static final int DIGEST_MODE = 0;
55  
56      /** Replay data from valuesFilePath. */
57      public static final int REPLAY_MODE = 1;
58  
59      /** Uniform random deviates with mean = &mu;. */
60      public static final int UNIFORM_MODE = 2;
61  
62      /** Exponential random deviates with mean = &mu;. */
63      public static final int EXPONENTIAL_MODE = 3;
64  
65      /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
66      public static final int GAUSSIAN_MODE = 4;
67  
68      /** Always return mu */
69      public static final int CONSTANT_MODE = 5;
70  
71      /** mode determines how values are generated. */
72      private int mode = 5;
73  
74      /** URI to raw data values. */
75      private URL valuesFileURL = null;
76  
77      /** Mean for use with non-data-driven modes. */
78      private double mu = 0.0;
79  
80      /** Standard deviation for use with GAUSSIAN_MODE. */
81      private double sigma = 0.0;
82  
83      /** Empirical probability distribution for use with DIGEST_MODE. */
84      private EmpiricalDistribution empiricalDistribution = null;
85  
86      /** File pointer for REPLAY_MODE. */
87      private BufferedReader filePointer = null;
88  
89      /** RandomDataImpl to use for random data generation. */
90      private final RandomDataGenerator randomData;
91  
92      // Data generation modes ======================================
93  
94      /** Creates new ValueServer */
95      public ValueServer() {
96          randomData = new RandomDataGenerator();
97      }
98  
99      /**
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 }