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
018package org.apache.commons.math3.random;
019import java.io.BufferedReader;
020import java.io.IOException;
021import java.io.InputStreamReader;
022import java.net.MalformedURLException;
023import java.net.URL;
024
025import org.apache.commons.math3.exception.MathIllegalArgumentException;
026import org.apache.commons.math3.exception.MathIllegalStateException;
027import org.apache.commons.math3.exception.NullArgumentException;
028import org.apache.commons.math3.exception.ZeroException;
029import 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 1587494 2014-04-15 10:02:54Z erans $
050 *
051 */
052public 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    @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}