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