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