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   * @version $Id: ValueServer.java 1587494 2014-04-15 10:02:54Z erans $
50   *
51   */
52  public class ValueServer {
53  
54      /** Use empirical distribution.  */
55      public static final int DIGEST_MODE = 0;
56  
57      /** Replay data from valuesFilePath. */
58      public static final int REPLAY_MODE = 1;
59  
60      /** Uniform random deviates with mean = &mu;. */
61      public static final int UNIFORM_MODE = 2;
62  
63      /** Exponential random deviates with mean = &mu;. */
64      public static final int EXPONENTIAL_MODE = 3;
65  
66      /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
67      public static final int GAUSSIAN_MODE = 4;
68  
69      /** Always return mu */
70      public static final int CONSTANT_MODE = 5;
71  
72      /** mode determines how values are generated. */
73      private int mode = 5;
74  
75      /** URI to raw data values. */
76      private URL valuesFileURL = null;
77  
78      /** Mean for use with non-data-driven modes. */
79      private double mu = 0.0;
80  
81      /** Standard deviation for use with GAUSSIAN_MODE. */
82      private double sigma = 0.0;
83  
84      /** Empirical probability distribution for use with DIGEST_MODE. */
85      private EmpiricalDistribution empiricalDistribution = null;
86  
87      /** File pointer for REPLAY_MODE. */
88      private BufferedReader filePointer = null;
89  
90      /** RandomDataImpl to use for random data generation. */
91      private final RandomDataImpl randomData;
92  
93      // Data generation modes ======================================
94  
95      /** Creates new ValueServer */
96      public ValueServer() {
97          randomData = new RandomDataImpl();
98      }
99  
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     @Deprecated
109     public ValueServer(RandomDataImpl randomData) {
110         this.randomData = randomData;
111     }
112 
113     /**
114      * Construct a ValueServer instance using a RandomGenerator as its source
115      * of random data.
116      *
117      * @since 3.1
118      * @param generator source of random data
119      */
120     public ValueServer(RandomGenerator generator) {
121         this.randomData = new RandomDataImpl(generator);
122     }
123 
124     /**
125      * Returns the next generated value, generated according
126      * to the mode value (see MODE constants).
127      *
128      * @return generated value
129      * @throws IOException in REPLAY_MODE if a file I/O error occurs
130      * @throws MathIllegalStateException if mode is not recognized
131      * @throws MathIllegalArgumentException if the underlying random generator thwrows one
132      */
133     public double getNext() throws IOException, MathIllegalStateException, MathIllegalArgumentException {
134         switch (mode) {
135             case DIGEST_MODE: return getNextDigest();
136             case REPLAY_MODE: return getNextReplay();
137             case UNIFORM_MODE: return getNextUniform();
138             case EXPONENTIAL_MODE: return getNextExponential();
139             case GAUSSIAN_MODE: return getNextGaussian();
140             case CONSTANT_MODE: return mu;
141             default: throw new MathIllegalStateException(
142                     LocalizedFormats.UNKNOWN_MODE,
143                     mode,
144                     "DIGEST_MODE",   DIGEST_MODE,   "REPLAY_MODE",      REPLAY_MODE,
145                     "UNIFORM_MODE",  UNIFORM_MODE,  "EXPONENTIAL_MODE", EXPONENTIAL_MODE,
146                     "GAUSSIAN_MODE", GAUSSIAN_MODE, "CONSTANT_MODE",    CONSTANT_MODE);
147         }
148     }
149 
150     /**
151      * Fills the input array with values generated using getNext() repeatedly.
152      *
153      * @param values array to be filled
154      * @throws IOException in REPLAY_MODE if a file I/O error occurs
155      * @throws MathIllegalStateException if mode is not recognized
156      * @throws MathIllegalArgumentException if the underlying random generator thwrows one
157      */
158     public void fill(double[] values)
159         throws IOException, MathIllegalStateException, MathIllegalArgumentException {
160         for (int i = 0; i < values.length; i++) {
161             values[i] = getNext();
162         }
163     }
164 
165     /**
166      * Returns an array of length <code>length</code> with values generated
167      * using getNext() repeatedly.
168      *
169      * @param length length of output array
170      * @return array of generated values
171      * @throws IOException in REPLAY_MODE if a file I/O error occurs
172      * @throws MathIllegalStateException if mode is not recognized
173      * @throws MathIllegalArgumentException if the underlying random generator thwrows one
174      */
175     public double[] fill(int length)
176         throws IOException, MathIllegalStateException, MathIllegalArgumentException {
177         double[] out = new double[length];
178         for (int i = 0; i < length; i++) {
179             out[i] = getNext();
180         }
181         return out;
182     }
183 
184     /**
185      * Computes the empirical distribution using values from the file
186      * in <code>valuesFileURL</code>, using the default number of bins.
187      * <p>
188      * <code>valuesFileURL</code> must exist and be
189      * readable by *this at runtime.</p>
190      * <p>
191      * This method must be called before using <code>getNext()</code>
192      * with <code>mode = DIGEST_MODE</code></p>
193      *
194      * @throws IOException if an I/O error occurs reading the input file
195      * @throws NullArgumentException if the {@code valuesFileURL} has not been set
196      * @throws ZeroException if URL contains no data
197      */
198     public void computeDistribution() throws IOException, ZeroException, NullArgumentException {
199         computeDistribution(EmpiricalDistribution.DEFAULT_BIN_COUNT);
200     }
201 
202     /**
203      * Computes the empirical distribution using values from the file
204      * in <code>valuesFileURL</code> and <code>binCount</code> bins.
205      * <p>
206      * <code>valuesFileURL</code> must exist and be readable by this process
207      * at runtime.</p>
208      * <p>
209      * This method must be called before using <code>getNext()</code>
210      * with <code>mode = DIGEST_MODE</code></p>
211      *
212      * @param binCount the number of bins used in computing the empirical
213      * distribution
214      * @throws NullArgumentException if the {@code valuesFileURL} has not been set
215      * @throws IOException if an error occurs reading the input file
216      * @throws ZeroException if URL contains no data
217      */
218     public void computeDistribution(int binCount) throws NullArgumentException, IOException, ZeroException {
219         empiricalDistribution = new EmpiricalDistribution(binCount, randomData);
220         empiricalDistribution.load(valuesFileURL);
221         mu = empiricalDistribution.getSampleStats().getMean();
222         sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
223     }
224 
225     /**
226      * Returns the data generation mode. See {@link ValueServer the class javadoc}
227      * for description of the valid values of this property.
228      *
229      * @return Value of property mode.
230      */
231     public int getMode() {
232         return mode;
233     }
234 
235     /**
236      * Sets the data generation mode.
237      *
238      * @param mode New value of the data generation mode.
239      */
240     public void setMode(int mode) {
241         this.mode = mode;
242     }
243 
244     /**
245      * Returns the URL for the file used to build the empirical distribution
246      * when using {@link #DIGEST_MODE}.
247      *
248      * @return Values file URL.
249      */
250     public URL getValuesFileURL() {
251         return valuesFileURL;
252     }
253 
254     /**
255      * Sets the {@link #getValuesFileURL() values file URL} using a string
256      * URL representation.
257      *
258      * @param url String representation for new valuesFileURL.
259      * @throws MalformedURLException if url is not well formed
260      */
261     public void setValuesFileURL(String url) throws MalformedURLException {
262         this.valuesFileURL = new URL(url);
263     }
264 
265     /**
266      * Sets the the {@link #getValuesFileURL() values file URL}.
267      *
268      * <p>The values file <i>must</i> be an ASCII text file containing one
269      * valid numeric entry per line.</p>
270      *
271      * @param url URL of the values file.
272      */
273     public void setValuesFileURL(URL url) {
274         this.valuesFileURL = url;
275     }
276 
277     /**
278      * Returns the {@link EmpiricalDistribution} used when operating in {@value #DIGEST_MODE}.
279      *
280      * @return EmpircalDistribution built by {@link #computeDistribution()}
281      */
282     public EmpiricalDistribution getEmpiricalDistribution() {
283         return empiricalDistribution;
284     }
285 
286     /**
287      * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>.
288      *
289      * @throws IOException if an error occurs opening the file
290      * @throws NullPointerException if the {@code valuesFileURL} has not been set.
291      */
292     public void resetReplayFile() throws IOException {
293         if (filePointer != null) {
294             try {
295                 filePointer.close();
296                 filePointer = null;
297             } catch (IOException ex) { //NOPMD
298                 // ignore
299             }
300         }
301         filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream(), "UTF-8"));
302     }
303 
304     /**
305      * Closes {@code valuesFileURL} after use in REPLAY_MODE.
306      *
307      * @throws IOException if an error occurs closing the file
308      */
309     public void closeReplayFile() throws IOException {
310         if (filePointer != null) {
311             filePointer.close();
312             filePointer = null;
313         }
314     }
315 
316     /**
317      * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE}
318      * or {@link #UNIFORM_MODE}.  When operating in {@link #CONSTANT_MODE}, this is the constant
319      * value always returned.  Calling {@link #computeDistribution()} sets this value to the
320      * overall mean of the values in the {@link #getValuesFileURL() values file}.
321      *
322      * @return Mean used in data generation.
323      */
324     public double getMu() {
325         return mu;
326     }
327 
328     /**
329      * Sets the {@link #getMu() mean} used in data generation.  Note that calling this method
330      * after {@link #computeDistribution()} has been called will have no effect on data
331      * generated in {@link #DIGEST_MODE}.
332      *
333      * @param mu new Mean value.
334      */
335     public void setMu(double mu) {
336         this.mu = mu;
337     }
338 
339     /**
340      * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}.
341      * Calling {@link #computeDistribution()} sets this value to the overall standard
342      * deviation of the values in the {@link #getValuesFileURL() values file}.  This
343      * property has no effect when the data generation mode is not
344      * {@link #GAUSSIAN_MODE}.
345      *
346      * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}.
347      */
348     public double getSigma() {
349         return sigma;
350     }
351 
352     /**
353      * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}.
354      *
355      * @param sigma New standard deviation.
356      */
357     public void setSigma(double sigma) {
358         this.sigma = sigma;
359     }
360 
361     /**
362      * Reseeds the random data generator.
363      *
364      * @param seed Value with which to reseed the {@link RandomDataImpl}
365      * used to generate random data.
366      */
367     public void reSeed(long seed) {
368         randomData.reSeed(seed);
369     }
370 
371     //------------- private methods ---------------------------------
372 
373     /**
374      * Gets a random value in DIGEST_MODE.
375      * <p>
376      * <strong>Preconditions</strong>: <ul>
377      * <li>Before this method is called, <code>computeDistribution()</code>
378      * must have completed successfully; otherwise an
379      * <code>IllegalStateException</code> will be thrown</li></ul></p>
380      *
381      * @return next random value from the empirical distribution digest
382      * @throws MathIllegalStateException if digest has not been initialized
383      */
384     private double getNextDigest() throws MathIllegalStateException {
385         if ((empiricalDistribution == null) ||
386             (empiricalDistribution.getBinStats().size() == 0)) {
387             throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED);
388         }
389         return empiricalDistribution.getNextValue();
390     }
391 
392     /**
393      * Gets next sequential value from the <code>valuesFileURL</code>.
394      * <p>
395      * Throws an IOException if the read fails.</p>
396      * <p>
397      * This method will open the <code>valuesFileURL</code> if there is no
398      * replay file open.</p>
399      * <p>
400      * The <code>valuesFileURL</code> will be closed and reopened to wrap around
401      * from EOF to BOF if EOF is encountered. EOFException (which is a kind of
402      * IOException) may still be thrown if the <code>valuesFileURL</code> is
403      * empty.</p>
404      *
405      * @return next value from the replay file
406      * @throws IOException if there is a problem reading from the file
407      * @throws MathIllegalStateException if URL contains no data
408      * @throws NumberFormatException if an invalid numeric string is
409      *   encountered in the file
410      */
411     private double getNextReplay() throws IOException, MathIllegalStateException {
412         String str = null;
413         if (filePointer == null) {
414             resetReplayFile();
415         }
416         if ((str = filePointer.readLine()) == null) {
417             // we have probably reached end of file, wrap around from EOF to BOF
418             closeReplayFile();
419             resetReplayFile();
420             if ((str = filePointer.readLine()) == null) {
421                 throw new MathIllegalStateException(LocalizedFormats.URL_CONTAINS_NO_DATA,
422                                                     valuesFileURL);
423             }
424         }
425         return Double.parseDouble(str);
426     }
427 
428     /**
429      * Gets a uniformly distributed random value with mean = mu.
430      *
431      * @return random uniform value
432      * @throws MathIllegalArgumentException if the underlying random generator thwrows one
433      */
434     private double getNextUniform() throws MathIllegalArgumentException {
435         return randomData.nextUniform(0, 2 * mu);
436     }
437 
438     /**
439      * Gets an exponentially distributed random value with mean = mu.
440      *
441      * @return random exponential value
442      * @throws MathIllegalArgumentException if the underlying random generator thwrows one
443      */
444     private double getNextExponential() throws MathIllegalArgumentException {
445         return randomData.nextExponential(mu);
446     }
447 
448     /**
449      * Gets a Gaussian distributed random value with mean = mu
450      * and standard deviation = sigma.
451      *
452      * @return random Gaussian value
453      * @throws MathIllegalArgumentException if the underlying random generator thwrows one
454      */
455     private double getNextGaussian() throws MathIllegalArgumentException {
456         return randomData.nextGaussian(mu, sigma);
457     }
458 
459 }