    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    }