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    import org.apache.commons.math.exception.util.LocalizedFormats;
027    
028    /**
029     * Generates values for use in simulation applications.
030     * <p>
031     * How values are generated is determined by the <code>mode</code>
032     * property.</p>
033     * <p>
034     * Supported <code>mode</code> values are: <ul>
035     * <li> DIGEST_MODE -- uses an empirical distribution </li>
036     * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
037     * <li> UNIFORM_MODE -- generates uniformly distributed random values with
038     *                      mean = <code>mu</code> </li>
039     * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
040     *                         with mean = <code>mu</code></li>
041     * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
042     *                       mean = <code>mu</code> and
043     *                       standard deviation = <code>sigma</code></li>
044     * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
045     *
046     * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
047     *
048     */
049    public class ValueServer {
050    
051        /** Use empirical distribution.  */
052        public static final int DIGEST_MODE = 0;
053    
054        /** Replay data from valuesFilePath. */
055        public static final int REPLAY_MODE = 1;
056    
057        /** Uniform random deviates with mean = &mu;. */
058        public static final int UNIFORM_MODE = 2;
059    
060        /** Exponential random deviates with mean = &mu;. */
061        public static final int EXPONENTIAL_MODE = 3;
062    
063        /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
064        public static final int GAUSSIAN_MODE = 4;
065    
066        /** Always return mu */
067        public static final int CONSTANT_MODE = 5;
068    
069        /** mode determines how values are generated. */
070        private int mode = 5;
071    
072        /** URI to raw data values. */
073        private URL valuesFileURL = null;
074    
075        /** Mean for use with non-data-driven modes. */
076        private double mu = 0.0;
077    
078        /** Standard deviation for use with GAUSSIAN_MODE. */
079        private double sigma = 0.0;
080    
081        /** Empirical probability distribution for use with DIGEST_MODE. */
082        private EmpiricalDistribution empiricalDistribution = null;
083    
084        /** File pointer for REPLAY_MODE. */
085        private BufferedReader filePointer = null;
086    
087        /** RandomDataImpl to use for random data generation. */
088        private final RandomData randomData;
089    
090        // Data generation modes ======================================
091    
092        /** Creates new ValueServer */
093        public ValueServer() {
094            randomData = new RandomDataImpl();
095        }
096    
097        /**
098         * Construct a ValueServer instance using a RandomData as its source
099         * of random data.
100         *
101         * @param randomData the RandomData instance used to source random data
102         * @since 1.1
103         */
104        public ValueServer(RandomData 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 MathRuntimeException.createIllegalStateException(
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            empiricalDistribution = new EmpiricalDistributionImpl();
174            empiricalDistribution.load(valuesFileURL);
175        }
176    
177        /**
178         * Computes the empirical distribution using values from the file
179         * in <code>valuesFileURL</code> and <code>binCount</code> bins.
180         * <p>
181         * <code>valuesFileURL</code> must exist and be readable by this process
182         * at runtime.</p>
183         * <p>
184         * This method must be called before using <code>getNext()</code>
185         * with <code>mode = DIGEST_MODE</code></p>
186         *
187         * @param binCount the number of bins used in computing the empirical
188         * distribution
189         * @throws IOException if an error occurs reading the input file
190         */
191        public void computeDistribution(int binCount)
192                throws IOException {
193            empiricalDistribution = new EmpiricalDistributionImpl(binCount);
194            empiricalDistribution.load(valuesFileURL);
195            mu = empiricalDistribution.getSampleStats().getMean();
196            sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
197        }
198    
199        /** Getter for property mode.
200         * @return Value of property mode.
201         */
202        public int getMode() {
203            return mode;
204        }
205    
206        /** Setter for property mode.
207         * @param mode New value of property mode.
208         */
209        public void setMode(int mode) {
210            this.mode = mode;
211        }
212    
213        /**
214         * Getter for <code>valuesFileURL<code>
215         * @return Value of property valuesFileURL.
216         */
217        public URL getValuesFileURL() {
218            return valuesFileURL;
219        }
220    
221        /**
222         * Sets the <code>valuesFileURL</code> using a string URL representation
223         * @param url String representation for new valuesFileURL.
224         * @throws MalformedURLException if url is not well formed
225         */
226        public void setValuesFileURL(String url) throws MalformedURLException {
227            this.valuesFileURL = new URL(url);
228        }
229    
230        /**
231         * Sets the <code>valuesFileURL</code>
232         * @param url New value of property valuesFileURL.
233         */
234        public void setValuesFileURL(URL url) {
235            this.valuesFileURL = url;
236        }
237    
238        /** Getter for property empiricalDistribution.
239         * @return Value of property empiricalDistribution.
240         */
241        public EmpiricalDistribution getEmpiricalDistribution() {
242            return empiricalDistribution;
243        }
244    
245        /**
246         * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>.
247         *
248         * @throws IOException if an error occurs opening the file
249         */
250        public void resetReplayFile() throws IOException {
251            if (filePointer != null) {
252                try {
253                    filePointer.close();
254                    filePointer = null;
255                } catch (IOException ex) {
256                    // ignore
257                }
258            }
259            filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream()));
260        }
261    
262        /**
263         * Closes <code>valuesFileURL</code> after use in REPLAY_MODE.
264         *
265         * @throws IOException if an error occurs closing the file
266         */
267        public void closeReplayFile() throws IOException {
268            if (filePointer != null) {
269                filePointer.close();
270                filePointer = null;
271            }
272        }
273    
274        /** Getter for property mu.
275         * @return Value of property mu.
276         */
277        public double getMu() {
278            return mu;
279        }
280    
281        /** Setter for property mu.
282         * @param mu New value of property mu.
283         */
284        public void setMu(double mu) {
285            this.mu = mu;
286        }
287    
288        /** Getter for property sigma.
289         * @return Value of property sigma.
290         */
291        public double getSigma() {
292            return sigma;
293        }
294    
295        /** Setter for property sigma.
296         * @param sigma New value of property sigma.
297         */
298        public void setSigma(double sigma) {
299            this.sigma = sigma;
300        }
301    
302        //------------- private methods ---------------------------------
303    
304        /**
305         * Gets a random value in DIGEST_MODE.
306         * <p>
307         * <strong>Preconditions</strong>: <ul>
308         * <li>Before this method is called, <code>computeDistribution()</code>
309         * must have completed successfully; otherwise an
310         * <code>IllegalStateException</code> will be thrown</li></ul></p>
311         *
312         * @return next random value from the empirical distribution digest
313         */
314        private double getNextDigest() {
315            if ((empiricalDistribution == null) ||
316                (empiricalDistribution.getBinStats().size() == 0)) {
317                throw MathRuntimeException.createIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED);
318            }
319            return empiricalDistribution.getNextValue();
320        }
321    
322        /**
323         * Gets next sequential value from the <code>valuesFileURL</code>.
324         * <p>
325         * Throws an IOException if the read fails.</p>
326         * <p>
327         * This method will open the <code>valuesFileURL</code> if there is no
328         * replay file open.</p>
329         * <p>
330         * The <code>valuesFileURL</code> will be closed and reopened to wrap around
331         * from EOF to BOF if EOF is encountered. EOFException (which is a kind of
332         * IOException) may still be thrown if the <code>valuesFileURL</code> is
333         * empty.</p>
334         *
335         * @return next value from the replay file
336         * @throws IOException if there is a problem reading from the file
337         * @throws NumberFormatException if an invalid numeric string is
338         *   encountered in the file
339         */
340        private double getNextReplay() throws IOException {
341            String str = null;
342            if (filePointer == null) {
343                resetReplayFile();
344            }
345            if ((str = filePointer.readLine()) == null) {
346                // we have probably reached end of file, wrap around from EOF to BOF
347                closeReplayFile();
348                resetReplayFile();
349                if ((str = filePointer.readLine()) == null) {
350                    throw MathRuntimeException.createEOFException(LocalizedFormats.URL_CONTAINS_NO_DATA,
351                                                                  valuesFileURL);
352                }
353            }
354            return Double.valueOf(str).doubleValue();
355        }
356    
357        /**
358         * Gets a uniformly distributed random value with mean = mu.
359         *
360         * @return random uniform value
361         */
362        private double getNextUniform() {
363            return randomData.nextUniform(0, 2 * mu);
364        }
365    
366        /**
367         * Gets an exponentially distributed random value with mean = mu.
368         *
369         * @return random exponential value
370         */
371        private double getNextExponential() {
372            return randomData.nextExponential(mu);
373        }
374    
375        /**
376         * Gets a Gaussian distributed random value with mean = mu
377         * and standard deviation = sigma.
378         *
379         * @return random Gaussian value
380         */
381        private double getNextGaussian() {
382            return randomData.nextGaussian(mu, sigma);
383        }
384    
385    }