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