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.math.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.math.MathRuntimeException;
026    
027    /**
028     * Generates values for use in simulation applications.
029     * <p>
030     * How values are generated is determined by the <code>mode</code>
031     * property.</p>
032     * <p>
033     * Supported <code>mode</code> values are: <ul>
034     * <li> DIGEST_MODE -- uses an empirical distribution </li>
035     * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
036     * <li> UNIFORM_MODE -- generates uniformly distributed random values with
037     *                      mean = <code>mu</code> </li>
038     * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
039     *                         with mean = <code>mu</code></li>
040     * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
041     *                       mean = <code>mu</code> and
042     *                       standard deviation = <code>sigma</code></li>
043     * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
044     *
045     * @version $Revision: 811827 $ $Date: 2009-09-06 11:32:50 -0400 (Sun, 06 Sep 2009) $
046     *
047     */
048    public class ValueServer {
049    
050        /** Use empirical distribution.  */
051        public static final int DIGEST_MODE = 0;
052    
053        /** Replay data from valuesFilePath. */
054        public static final int REPLAY_MODE = 1;
055    
056        /** Uniform random deviates with mean = &mu;. */
057        public static final int UNIFORM_MODE = 2;
058    
059        /** Exponential random deviates with mean = &mu;. */
060        public static final int EXPONENTIAL_MODE = 3;
061    
062        /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
063        public static final int GAUSSIAN_MODE = 4;
064    
065        /** Always return mu */
066        public static final int CONSTANT_MODE = 5;
067    
068        /** mode determines how values are generated. */
069        private int mode = 5;
070    
071        /** URI to raw data values. */
072        private URL valuesFileURL = null;
073    
074        /** Mean for use with non-data-driven modes. */
075        private double mu = 0.0;
076    
077        /** Standard deviation for use with GAUSSIAN_MODE. */
078        private double sigma = 0.0;
079    
080        /** Empirical probability distribution for use with DIGEST_MODE. */
081        private EmpiricalDistribution empiricalDistribution = null;
082    
083        /** File pointer for REPLAY_MODE. */
084        private BufferedReader filePointer = null;
085    
086        /** RandomDataImpl to use for random data generation. */
087        private RandomData randomData = new RandomDataImpl();
088    
089        // Data generation modes ======================================
090    
091        /** Creates new ValueServer */
092        public ValueServer() {
093        }
094    
095        /**
096         * Construct a ValueServer instance using a RandomData as its source
097         * of random data.
098         *
099         * @param randomData the RandomData instance used to source random data
100         * @since 1.1
101         */
102        public ValueServer(RandomData randomData) {
103            this.randomData = randomData;
104        }
105    
106        /**
107         * Returns the next generated value, generated according
108         * to the mode value (see MODE constants).
109         *
110         * @return generated value
111         * @throws IOException in REPLAY_MODE if a file I/O error occurs
112         */
113        public double getNext() throws IOException {
114            switch (mode) {
115                case DIGEST_MODE: return getNextDigest();
116                case REPLAY_MODE: return getNextReplay();
117                case UNIFORM_MODE: return getNextUniform();
118                case EXPONENTIAL_MODE: return getNextExponential();
119                case GAUSSIAN_MODE: return getNextGaussian();
120                case CONSTANT_MODE: return mu;
121                default: throw MathRuntimeException.createIllegalStateException(
122                        "unknown mode {0}, known modes: " +
123                        "{1} ({2}), {3} ({4}), {5} ({6}), {7} ({8}), {9} ({10}) and {11} ({12})",
124                        mode,
125                        "DIGEST_MODE",   DIGEST_MODE,   "REPLAY_MODE",      REPLAY_MODE,
126                        "UNIFORM_MODE",  UNIFORM_MODE,  "EXPONENTIAL_MODE", EXPONENTIAL_MODE,
127                        "GAUSSIAN_MODE", GAUSSIAN_MODE, "CONSTANT_MODE",    CONSTANT_MODE);
128            }
129        }
130    
131        /**
132         * Fills the input array with values generated using getNext() repeatedly.
133         *
134         * @param values array to be filled
135         * @throws IOException in REPLAY_MODE if a file I/O error occurs
136         */
137        public void fill(double[] values) throws IOException {
138            for (int i = 0; i < values.length; i++) {
139                values[i] = getNext();
140            }
141        }
142    
143        /**
144         * Returns an array of length <code>length</code> with values generated
145         * using getNext() repeatedly.
146         *
147         * @param length length of output array
148         * @return array of generated values
149         * @throws IOException in REPLAY_MODE if a file I/O error occurs
150         */
151        public double[] fill(int length) throws IOException {
152            double[] out = new double[length];
153            for (int i = 0; i < length; i++) {
154                out[i] = getNext();
155            }
156            return out;
157        }
158    
159        /**
160         * Computes the empirical distribution using values from the file
161         * in <code>valuesFileURL</code>, using the default number of bins.
162         * <p>
163         * <code>valuesFileURL</code> must exist and be
164         * readable by *this at runtime.</p>
165         * <p>
166         * This method must be called before using <code>getNext()</code>
167         * with <code>mode = DIGEST_MODE</code></p>
168         *
169         * @throws IOException if an I/O error occurs reading the input file
170         */
171        public void computeDistribution() throws IOException {
172            empiricalDistribution = new EmpiricalDistributionImpl();
173            empiricalDistribution.load(valuesFileURL);
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);
193            empiricalDistribution.load(valuesFileURL);
194            mu = empiricalDistribution.getSampleStats().getMean();
195            sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
196        }
197    
198        /** Getter for property mode.
199         * @return Value of property mode.
200         */
201        public int getMode() {
202            return mode;
203        }
204    
205        /** Setter for property mode.
206         * @param mode New value of property mode.
207         */
208        public void setMode(int mode) {
209            this.mode = mode;
210        }
211    
212        /**
213         * Getter for <code>valuesFileURL<code>
214         * @return Value of property valuesFileURL.
215         */
216        public URL getValuesFileURL() {
217            return valuesFileURL;
218        }
219    
220        /**
221         * Sets the <code>valuesFileURL</code> using a string URL representation
222         * @param url String representation for new valuesFileURL.
223         * @throws MalformedURLException if url is not well formed
224         */
225        public void setValuesFileURL(String url) throws MalformedURLException {
226            this.valuesFileURL = new URL(url);
227        }
228    
229        /**
230         * Sets the <code>valuesFileURL</code>
231         * @param url New value of property valuesFileURL.
232         */
233        public void setValuesFileURL(URL url) {
234            this.valuesFileURL = url;
235        }
236    
237        /** Getter for property empiricalDistribution.
238         * @return Value of property empiricalDistribution.
239         */
240        public EmpiricalDistribution getEmpiricalDistribution() {
241            return empiricalDistribution;
242        }
243    
244        /**
245         * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>.
246         *
247         * @throws IOException if an error occurs opening the file
248         */
249        public void resetReplayFile() throws IOException {
250            if (filePointer != null) {
251                try {
252                    filePointer.close();
253                    filePointer = null;
254                } catch (IOException ex) {
255                    // ignore
256                }
257            }
258            filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream()));
259        }
260    
261        /**
262         * Closes <code>valuesFileURL</code> after use in REPLAY_MODE.
263         *
264         * @throws IOException if an error occurs closing the file
265         */
266        public void closeReplayFile() throws IOException {
267            if (filePointer != null) {
268                filePointer.close();
269                filePointer = null;
270            }
271        }
272    
273        /** Getter for property mu.
274         * @return Value of property mu.
275         */
276        public double getMu() {
277            return mu;
278        }
279    
280        /** Setter for property mu.
281         * @param mu New value of property mu.
282         */
283        public void setMu(double mu) {
284            this.mu = mu;
285        }
286    
287        /** Getter for property sigma.
288         * @return Value of property sigma.
289         */
290        public double getSigma() {
291            return sigma;
292        }
293    
294        /** Setter for property sigma.
295         * @param sigma New value of property sigma.
296         */
297        public void setSigma(double sigma) {
298            this.sigma = sigma;
299        }
300    
301        //------------- private methods ---------------------------------
302    
303        /**
304         * Gets a random value in DIGEST_MODE.
305         * <p>
306         * <strong>Preconditions</strong>: <ul>
307         * <li>Before this method is called, <code>computeDistribution()</code>
308         * must have completed successfully; otherwise an
309         * <code>IllegalStateException</code> will be thrown</li></ul></p>
310         *
311         * @return next random value from the empirical distribution digest
312         */
313        private double getNextDigest() {
314            if ((empiricalDistribution == null) ||
315                (empiricalDistribution.getBinStats().size() == 0)) {
316                throw MathRuntimeException.createIllegalStateException("digest not initialized");
317            }
318            return empiricalDistribution.getNextValue();
319        }
320    
321        /**
322         * Gets next sequential value from the <code>valuesFileURL</code>.
323         * <p>
324         * Throws an IOException if the read fails.</p>
325         * <p>
326         * This method will open the <code>valuesFileURL</code> if there is no
327         * replay file open.</p>
328         * <p>
329         * The <code>valuesFileURL</code> will be closed and reopened to wrap around
330         * from EOF to BOF if EOF is encountered. EOFException (which is a kind of
331         * IOException) may still be thrown if the <code>valuesFileURL</code> is
332         * empty.</p>
333         *
334         * @return next value from the replay file
335         * @throws IOException if there is a problem reading from the file
336         * @throws NumberFormatException if an invalid numeric string is
337         *   encountered in the file
338         */
339        private double getNextReplay() throws IOException {
340            String str = null;
341            if (filePointer == null) {
342                resetReplayFile();
343            }
344            if ((str = filePointer.readLine()) == null) {
345                // we have probably reached end of file, wrap around from EOF to BOF
346                closeReplayFile();
347                resetReplayFile();
348                if ((str = filePointer.readLine()) == null) {
349                    throw MathRuntimeException.createEOFException("URL {0} contains no data",
350                                                                  valuesFileURL);
351                }
352            }
353            return Double.valueOf(str).doubleValue();
354        }
355    
356        /**
357         * Gets a uniformly distributed random value with mean = mu.
358         *
359         * @return random uniform value
360         */
361        private double getNextUniform() {
362            return randomData.nextUniform(0, 2 * mu);
363        }
364    
365        /**
366         * Gets an exponentially distributed random value with mean = mu.
367         *
368         * @return random exponential value
369         */
370        private double getNextExponential() {
371            return randomData.nextExponential(mu);
372        }
373    
374        /**
375         * Gets a Gaussian distributed random value with mean = mu
376         * and standard deviation = sigma.
377         *
378         * @return random Gaussian value
379         */
380        private double getNextGaussian() {
381            return randomData.nextGaussian(mu, sigma);
382        }
383    
384    }