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.math3.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.math3.exception.MathIllegalArgumentException;
026    import org.apache.commons.math3.exception.MathIllegalStateException;
027    import org.apache.commons.math3.exception.NullArgumentException;
028    import org.apache.commons.math3.exception.ZeroException;
029    import org.apache.commons.math3.exception.util.LocalizedFormats;
030    
031    /**
032     * Generates values for use in simulation applications.
033     * <p>
034     * How values are generated is determined by the <code>mode</code>
035     * property.</p>
036     * <p>
037     * Supported <code>mode</code> values are: <ul>
038     * <li> DIGEST_MODE -- uses an empirical distribution </li>
039     * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
040     * <li> UNIFORM_MODE -- generates uniformly distributed random values with
041     *                      mean = <code>mu</code> </li>
042     * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
043     *                         with mean = <code>mu</code></li>
044     * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
045     *                       mean = <code>mu</code> and
046     *                       standard deviation = <code>sigma</code></li>
047     * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
048     *
049     * @version $Id: ValueServer.java 1422350 2012-12-15 20:47:47Z psteitz $
050     *
051     */
052    public class ValueServer {
053    
054        /** Use empirical distribution.  */
055        public static final int DIGEST_MODE = 0;
056    
057        /** Replay data from valuesFilePath. */
058        public static final int REPLAY_MODE = 1;
059    
060        /** Uniform random deviates with mean = &mu;. */
061        public static final int UNIFORM_MODE = 2;
062    
063        /** Exponential random deviates with mean = &mu;. */
064        public static final int EXPONENTIAL_MODE = 3;
065    
066        /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
067        public static final int GAUSSIAN_MODE = 4;
068    
069        /** Always return mu */
070        public static final int CONSTANT_MODE = 5;
071    
072        /** mode determines how values are generated. */
073        private int mode = 5;
074    
075        /** URI to raw data values. */
076        private URL valuesFileURL = null;
077    
078        /** Mean for use with non-data-driven modes. */
079        private double mu = 0.0;
080    
081        /** Standard deviation for use with GAUSSIAN_MODE. */
082        private double sigma = 0.0;
083    
084        /** Empirical probability distribution for use with DIGEST_MODE. */
085        private EmpiricalDistribution empiricalDistribution = null;
086    
087        /** File pointer for REPLAY_MODE. */
088        private BufferedReader filePointer = null;
089    
090        /** RandomDataImpl to use for random data generation. */
091        private final RandomDataImpl randomData;
092    
093        // Data generation modes ======================================
094    
095        /** Creates new ValueServer */
096        public ValueServer() {
097            randomData = new RandomDataImpl();
098        }
099    
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        public ValueServer(RandomDataImpl randomData) {
109            this.randomData = randomData;
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 RandomDataImpl(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);
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         */
290        public void resetReplayFile() throws IOException {
291            if (filePointer != null) {
292                try {
293                    filePointer.close();
294                    filePointer = null;
295                } catch (IOException ex) { //NOPMD
296                    // ignore
297                }
298            }
299            filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream(), "UTF-8"));
300        }
301    
302        /**
303         * Closes {@code valuesFileURL} after use in REPLAY_MODE.
304         *
305         * @throws IOException if an error occurs closing the file
306         */
307        public void closeReplayFile() throws IOException {
308            if (filePointer != null) {
309                filePointer.close();
310                filePointer = null;
311            }
312        }
313    
314        /**
315         * Returns the mean used when operating in {@link #GAUSSIAN_MODE}, {@link #EXPONENTIAL_MODE}
316         * or {@link #UNIFORM_MODE}.  When operating in {@link #CONSTANT_MODE}, this is the constant
317         * value always returned.  Calling {@link #computeDistribution()} sets this value to the
318         * overall mean of the values in the {@link #getValuesFileURL() values file}.
319         *
320         * @return Mean used in data generation.
321         */
322        public double getMu() {
323            return mu;
324        }
325    
326        /**
327         * Sets the {@link #getMu() mean} used in data generation.  Note that calling this method
328         * after {@link #computeDistribution()} has been called will have no effect on data
329         * generated in {@link #DIGEST_MODE}.
330         *
331         * @param mu new Mean value.
332         */
333        public void setMu(double mu) {
334            this.mu = mu;
335        }
336    
337        /**
338         * Returns the standard deviation used when operating in {@link #GAUSSIAN_MODE}.
339         * Calling {@link #computeDistribution()} sets this value to the overall standard
340         * deviation of the values in the {@link #getValuesFileURL() values file}.  This
341         * property has no effect when the data generation mode is not
342         * {@link #GAUSSIAN_MODE}.
343         *
344         * @return Standard deviation used when operating in {@link #GAUSSIAN_MODE}.
345         */
346        public double getSigma() {
347            return sigma;
348        }
349    
350        /**
351         * Sets the {@link #getSigma() standard deviation} used in {@link #GAUSSIAN_MODE}.
352         *
353         * @param sigma New standard deviation.
354         */
355        public void setSigma(double sigma) {
356            this.sigma = sigma;
357        }
358    
359        /**
360         * Reseeds the random data generator.
361         *
362         * @param seed Value with which to reseed the {@link RandomDataImpl}
363         * used to generate random data.
364         */
365        public void reSeed(long seed) {
366            randomData.reSeed(seed);
367        }
368    
369        //------------- private methods ---------------------------------
370    
371        /**
372         * Gets a random value in DIGEST_MODE.
373         * <p>
374         * <strong>Preconditions</strong>: <ul>
375         * <li>Before this method is called, <code>computeDistribution()</code>
376         * must have completed successfully; otherwise an
377         * <code>IllegalStateException</code> will be thrown</li></ul></p>
378         *
379         * @return next random value from the empirical distribution digest
380         * @throws MathIllegalStateException if digest has not been initialized
381         */
382        private double getNextDigest() throws MathIllegalStateException {
383            if ((empiricalDistribution == null) ||
384                (empiricalDistribution.getBinStats().size() == 0)) {
385                throw new MathIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED);
386            }
387            return empiricalDistribution.getNextValue();
388        }
389    
390        /**
391         * Gets next sequential value from the <code>valuesFileURL</code>.
392         * <p>
393         * Throws an IOException if the read fails.</p>
394         * <p>
395         * This method will open the <code>valuesFileURL</code> if there is no
396         * replay file open.</p>
397         * <p>
398         * The <code>valuesFileURL</code> will be closed and reopened to wrap around
399         * from EOF to BOF if EOF is encountered. EOFException (which is a kind of
400         * IOException) may still be thrown if the <code>valuesFileURL</code> is
401         * empty.</p>
402         *
403         * @return next value from the replay file
404         * @throws IOException if there is a problem reading from the file
405         * @throws MathIllegalStateException if URL contains no data
406         * @throws NumberFormatException if an invalid numeric string is
407         *   encountered in the file
408         */
409        private double getNextReplay() throws IOException, MathIllegalStateException {
410            String str = null;
411            if (filePointer == null) {
412                resetReplayFile();
413            }
414            if ((str = filePointer.readLine()) == null) {
415                // we have probably reached end of file, wrap around from EOF to BOF
416                closeReplayFile();
417                resetReplayFile();
418                if ((str = filePointer.readLine()) == null) {
419                    throw new MathIllegalStateException(LocalizedFormats.URL_CONTAINS_NO_DATA,
420                                                        valuesFileURL);
421                }
422            }
423            return Double.valueOf(str).doubleValue();
424        }
425    
426        /**
427         * Gets a uniformly distributed random value with mean = mu.
428         *
429         * @return random uniform value
430         * @throws MathIllegalArgumentException if the underlying random generator thwrows one
431         */
432        private double getNextUniform() throws MathIllegalArgumentException {
433            return randomData.nextUniform(0, 2 * mu);
434        }
435    
436        /**
437         * Gets an exponentially distributed random value with mean = mu.
438         *
439         * @return random exponential value
440         * @throws MathIllegalArgumentException if the underlying random generator thwrows one
441         */
442        private double getNextExponential() throws MathIllegalArgumentException {
443            return randomData.nextExponential(mu);
444        }
445    
446        /**
447         * Gets a Gaussian distributed random value with mean = mu
448         * and standard deviation = sigma.
449         *
450         * @return random Gaussian value
451         * @throws MathIllegalArgumentException if the underlying random generator thwrows one
452         */
453        private double getNextGaussian() throws MathIllegalArgumentException {
454            return randomData.nextGaussian(mu, sigma);
455        }
456    
457    }