summaryrefslogtreecommitdiff
path: root/src/main/java/org/apache/commons/math/random/ValueServer.java
blob: 9146e69cd6c0883ee557ddd5409562ffb3a8dc1f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math.random;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;

import org.apache.commons.math.MathRuntimeException;
import org.apache.commons.math.exception.util.LocalizedFormats;

/**
 * Generates values for use in simulation applications.
 * <p>
 * How values are generated is determined by the <code>mode</code>
 * property.</p>
 * <p>
 * Supported <code>mode</code> values are: <ul>
 * <li> DIGEST_MODE -- uses an empirical distribution </li>
 * <li> REPLAY_MODE -- replays data from <code>valuesFileURL</code></li>
 * <li> UNIFORM_MODE -- generates uniformly distributed random values with
 *                      mean = <code>mu</code> </li>
 * <li> EXPONENTIAL_MODE -- generates exponentially distributed random values
 *                         with mean = <code>mu</code></li>
 * <li> GAUSSIAN_MODE -- generates Gaussian distributed random values with
 *                       mean = <code>mu</code> and
 *                       standard deviation = <code>sigma</code></li>
 * <li> CONSTANT_MODE -- returns <code>mu</code> every time.</li></ul></p>
 *
 * @version $Revision: 1003886 $ $Date: 2010-10-02 23:04:44 +0200 (sam. 02 oct. 2010) $
 *
 */
public class ValueServer {

    /** Use empirical distribution.  */
    public static final int DIGEST_MODE = 0;

    /** Replay data from valuesFilePath. */
    public static final int REPLAY_MODE = 1;

    /** Uniform random deviates with mean = &mu;. */
    public static final int UNIFORM_MODE = 2;

    /** Exponential random deviates with mean = &mu;. */
    public static final int EXPONENTIAL_MODE = 3;

    /** Gaussian random deviates with mean = &mu;, std dev = &sigma;. */
    public static final int GAUSSIAN_MODE = 4;

    /** Always return mu */
    public static final int CONSTANT_MODE = 5;

    /** mode determines how values are generated. */
    private int mode = 5;

    /** URI to raw data values. */
    private URL valuesFileURL = null;

    /** Mean for use with non-data-driven modes. */
    private double mu = 0.0;

    /** Standard deviation for use with GAUSSIAN_MODE. */
    private double sigma = 0.0;

    /** Empirical probability distribution for use with DIGEST_MODE. */
    private EmpiricalDistribution empiricalDistribution = null;

    /** File pointer for REPLAY_MODE. */
    private BufferedReader filePointer = null;

    /** RandomDataImpl to use for random data generation. */
    private final RandomData randomData;

    // Data generation modes ======================================

    /** Creates new ValueServer */
    public ValueServer() {
        randomData = new RandomDataImpl();
    }

    /**
     * Construct a ValueServer instance using a RandomData as its source
     * of random data.
     *
     * @param randomData the RandomData instance used to source random data
     * @since 1.1
     */
    public ValueServer(RandomData randomData) {
        this.randomData = randomData;
    }

    /**
     * Returns the next generated value, generated according
     * to the mode value (see MODE constants).
     *
     * @return generated value
     * @throws IOException in REPLAY_MODE if a file I/O error occurs
     */
    public double getNext() throws IOException {
        switch (mode) {
            case DIGEST_MODE: return getNextDigest();
            case REPLAY_MODE: return getNextReplay();
            case UNIFORM_MODE: return getNextUniform();
            case EXPONENTIAL_MODE: return getNextExponential();
            case GAUSSIAN_MODE: return getNextGaussian();
            case CONSTANT_MODE: return mu;
            default: throw MathRuntimeException.createIllegalStateException(
                    LocalizedFormats.UNKNOWN_MODE,
                    mode,
                    "DIGEST_MODE",   DIGEST_MODE,   "REPLAY_MODE",      REPLAY_MODE,
                    "UNIFORM_MODE",  UNIFORM_MODE,  "EXPONENTIAL_MODE", EXPONENTIAL_MODE,
                    "GAUSSIAN_MODE", GAUSSIAN_MODE, "CONSTANT_MODE",    CONSTANT_MODE);
        }
    }

    /**
     * Fills the input array with values generated using getNext() repeatedly.
     *
     * @param values array to be filled
     * @throws IOException in REPLAY_MODE if a file I/O error occurs
     */
    public void fill(double[] values) throws IOException {
        for (int i = 0; i < values.length; i++) {
            values[i] = getNext();
        }
    }

    /**
     * Returns an array of length <code>length</code> with values generated
     * using getNext() repeatedly.
     *
     * @param length length of output array
     * @return array of generated values
     * @throws IOException in REPLAY_MODE if a file I/O error occurs
     */
    public double[] fill(int length) throws IOException {
        double[] out = new double[length];
        for (int i = 0; i < length; i++) {
            out[i] = getNext();
        }
        return out;
    }

    /**
     * Computes the empirical distribution using values from the file
     * in <code>valuesFileURL</code>, using the default number of bins.
     * <p>
     * <code>valuesFileURL</code> must exist and be
     * readable by *this at runtime.</p>
     * <p>
     * This method must be called before using <code>getNext()</code>
     * with <code>mode = DIGEST_MODE</code></p>
     *
     * @throws IOException if an I/O error occurs reading the input file
     */
    public void computeDistribution() throws IOException {
        empiricalDistribution = new EmpiricalDistributionImpl();
        empiricalDistribution.load(valuesFileURL);
    }

    /**
     * Computes the empirical distribution using values from the file
     * in <code>valuesFileURL</code> and <code>binCount</code> bins.
     * <p>
     * <code>valuesFileURL</code> must exist and be readable by this process
     * at runtime.</p>
     * <p>
     * This method must be called before using <code>getNext()</code>
     * with <code>mode = DIGEST_MODE</code></p>
     *
     * @param binCount the number of bins used in computing the empirical
     * distribution
     * @throws IOException if an error occurs reading the input file
     */
    public void computeDistribution(int binCount)
            throws IOException {
        empiricalDistribution = new EmpiricalDistributionImpl(binCount);
        empiricalDistribution.load(valuesFileURL);
        mu = empiricalDistribution.getSampleStats().getMean();
        sigma = empiricalDistribution.getSampleStats().getStandardDeviation();
    }

    /** Getter for property mode.
     * @return Value of property mode.
     */
    public int getMode() {
        return mode;
    }

    /** Setter for property mode.
     * @param mode New value of property mode.
     */
    public void setMode(int mode) {
        this.mode = mode;
    }

    /**
     * Getter for <code>valuesFileURL<code>
     * @return Value of property valuesFileURL.
     */
    public URL getValuesFileURL() {
        return valuesFileURL;
    }

    /**
     * Sets the <code>valuesFileURL</code> using a string URL representation
     * @param url String representation for new valuesFileURL.
     * @throws MalformedURLException if url is not well formed
     */
    public void setValuesFileURL(String url) throws MalformedURLException {
        this.valuesFileURL = new URL(url);
    }

    /**
     * Sets the <code>valuesFileURL</code>
     * @param url New value of property valuesFileURL.
     */
    public void setValuesFileURL(URL url) {
        this.valuesFileURL = url;
    }

    /** Getter for property empiricalDistribution.
     * @return Value of property empiricalDistribution.
     */
    public EmpiricalDistribution getEmpiricalDistribution() {
        return empiricalDistribution;
    }

    /**
     * Resets REPLAY_MODE file pointer to the beginning of the <code>valuesFileURL</code>.
     *
     * @throws IOException if an error occurs opening the file
     */
    public void resetReplayFile() throws IOException {
        if (filePointer != null) {
            try {
                filePointer.close();
                filePointer = null;
            } catch (IOException ex) {
                // ignore
            }
        }
        filePointer = new BufferedReader(new InputStreamReader(valuesFileURL.openStream()));
    }

    /**
     * Closes <code>valuesFileURL</code> after use in REPLAY_MODE.
     *
     * @throws IOException if an error occurs closing the file
     */
    public void closeReplayFile() throws IOException {
        if (filePointer != null) {
            filePointer.close();
            filePointer = null;
        }
    }

    /** Getter for property mu.
     * @return Value of property mu.
     */
    public double getMu() {
        return mu;
    }

    /** Setter for property mu.
     * @param mu New value of property mu.
     */
    public void setMu(double mu) {
        this.mu = mu;
    }

    /** Getter for property sigma.
     * @return Value of property sigma.
     */
    public double getSigma() {
        return sigma;
    }

    /** Setter for property sigma.
     * @param sigma New value of property sigma.
     */
    public void setSigma(double sigma) {
        this.sigma = sigma;
    }

    //------------- private methods ---------------------------------

    /**
     * Gets a random value in DIGEST_MODE.
     * <p>
     * <strong>Preconditions</strong>: <ul>
     * <li>Before this method is called, <code>computeDistribution()</code>
     * must have completed successfully; otherwise an
     * <code>IllegalStateException</code> will be thrown</li></ul></p>
     *
     * @return next random value from the empirical distribution digest
     */
    private double getNextDigest() {
        if ((empiricalDistribution == null) ||
            (empiricalDistribution.getBinStats().size() == 0)) {
            throw MathRuntimeException.createIllegalStateException(LocalizedFormats.DIGEST_NOT_INITIALIZED);
        }
        return empiricalDistribution.getNextValue();
    }

    /**
     * Gets next sequential value from the <code>valuesFileURL</code>.
     * <p>
     * Throws an IOException if the read fails.</p>
     * <p>
     * This method will open the <code>valuesFileURL</code> if there is no
     * replay file open.</p>
     * <p>
     * The <code>valuesFileURL</code> will be closed and reopened to wrap around
     * from EOF to BOF if EOF is encountered. EOFException (which is a kind of
     * IOException) may still be thrown if the <code>valuesFileURL</code> is
     * empty.</p>
     *
     * @return next value from the replay file
     * @throws IOException if there is a problem reading from the file
     * @throws NumberFormatException if an invalid numeric string is
     *   encountered in the file
     */
    private double getNextReplay() throws IOException {
        String str = null;
        if (filePointer == null) {
            resetReplayFile();
        }
        if ((str = filePointer.readLine()) == null) {
            // we have probably reached end of file, wrap around from EOF to BOF
            closeReplayFile();
            resetReplayFile();
            if ((str = filePointer.readLine()) == null) {
                throw MathRuntimeException.createEOFException(LocalizedFormats.URL_CONTAINS_NO_DATA,
                                                              valuesFileURL);
            }
        }
        return Double.valueOf(str).doubleValue();
    }

    /**
     * Gets a uniformly distributed random value with mean = mu.
     *
     * @return random uniform value
     */
    private double getNextUniform() {
        return randomData.nextUniform(0, 2 * mu);
    }

    /**
     * Gets an exponentially distributed random value with mean = mu.
     *
     * @return random exponential value
     */
    private double getNextExponential() {
        return randomData.nextExponential(mu);
    }

    /**
     * Gets a Gaussian distributed random value with mean = mu
     * and standard deviation = sigma.
     *
     * @return random Gaussian value
     */
    private double getNextGaussian() {
        return randomData.nextGaussian(mu, sigma);
    }

}