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