The number sequence methods for generating random
numbers discussed in Chapter
4: Tech generally create uniform, or flat, distributions over
some range. However, what if you need some other type of non-uniform
distribution such as a Gaussian, Poisson, or even a custom distribution?
You can use one of two basic methods to generate a
non-uniform random distribution.
- Transformation Method - put the random value from the
uniform random value generator through a function to generate
a new value that follows a non-uniform distribution.
- Rejection Method - Best explained graphically (see below),
in this approach you need to generate two values y
and z
from unform random number generators. The first value y
is accepted as the output value if z
is less than p(y),
a standard curve that follows the desired shape. If z
is greater than p(y),
then y
is rejected and another pair of random values must be generated
and the test repeated.
Transformation
Method
The transformation method generate values of x
with a uniform random number generator and then transforms the x
values with a function y=g(x)
that results in a distribution following the desired probability
function f(y).
This method requires that the integral of f(y)exists
and that the integral be invertable. (The following derivation comes
from Press)
We define our probability function as p(y).
We want it to follow the shape of a particular function f(y)
such as a Gaussian or Poisson. That is,
p(y)
= f(y)
However, our uniform random number generator outputs
x with
p(x)
= 1
for x
between certain limits, such as 0 to 1, and is 0 otherwise. So we
need a function g(x)
y
= g(x)
that results in a distribution of y values that follows
the desired f(y).
Regardless of the transformation, the probabilities
will go as
|p(y)dy|
= |p(x)dx|
or
p(y)
= p(x) * dx/dy
Thus for the uniform random generator we obtain
p(y)
= dx/dy
= f(y)
An indefinite integral of f(y)*dy
gives
x = F(y)
Inverting this leads to
y(x)
= F-1 (x) = g(x)
So if our desired function is integrable and this
integral can be inverted, we have our analytical g(x)
transformation function. For example, for a sample of a radioactive
material, the number of decays would follow an exponential with
the half-life tau
p(y)
= exp(-t/tau) = f(y)
then
dx/dy
= exp(-y/tau)
This leads to
x
= tau(1 -
exp(-y/tau))
and then
y(x)
= -ln( 1 - x/tau)
for 0
<= x < tau. Since the (1
- x/tau) ranges randomly between 0 and 1.0, you can also
just use -ln(r)
for r generated
between 0 and 1.0.
The following applet shows a distribution generated with y(x)
= -ln(1-x/tau).
RanDistTransformApplet.java
- generate
a distribution in a histogram that follows a radioactive
decay distribution using the transform method discussed
above.
+ Previous
classes:
Chapter
6:Tech: Histogram.java,
HistPanel.java
Chapter
6:Tech: PlotPanel.java,
PlotFormat.java
|
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* This program will run as an applet
inside
* an application frame.
*
* The applet uses the HistPanel to display contents
of
* an instance of Histogram. HistFormat
used by HistPanel to
* format the scale values.
*
* Includes "Go" button to add random
values from a Gaussian
* distribution to the histogram.
The number of values taken from
* entry in a JTextField. "Clear" button
clears the histogram.
* In standalone mode, the Exit button
closes the program.
*
**/
public class RanDistTransformApplet extends JApplet
implements ActionListener
{
// Use the HistPanel JPanel subclass here
HistPanel fOutputPanel;
Histogram fHistogram ;
int fNumDataPoints = 1000;
// A text field for input strings
JTextField fTextField;
// Flag for whether the applet is in a browser
// or running via the main () below.
boolean fInBrowser = true;
//Buttons
JButton fGoButton;
JButton fClearButton;
JButton fExitButton;
/**
* Create a User Interface with histograms and
buttons to
* control the program. A textfield holds number
of entries
* to be generated for the histogram.
*/
public void init () {
Container content_pane = getContentPane
();
JPanel panel = new JPanel (new BorderLayout
());
// Create a histogram with Gaussian
distribution.
makeHist ();
// JPanel subclass here.
fOutputPanel = new HistPanel (fHistogram);
panel.add (fOutputPanel,"Center");
// Use a textfield for an input
parameter.
fTextField =
new JTextField (Integer.toString
(fNumDataPoints), 10);
// If return hit after entering
text, the
// actionPerformed will be invoked.
fTextField.addActionListener (this);
fGoButton = new JButton ("Go");
fGoButton.addActionListener (this);
fClearButton = new JButton ("Clear");
fClearButton.addActionListener (this);
fExitButton = new JButton ("Exit");
fExitButton.addActionListener (this);
JPanel control_panel = new JPanel
();
control_panel.add (fTextField);
control_panel.add (fGoButton);
control_panel.add (fClearButton);
control_panel.add (fExitButton);
if (fInBrowser) fExitButton.setEnabled
(false);
panel.add (control_panel,"South");
// Add text area with scrolling
to the content_pane.
content_pane.add (panel);
} // init
/** Respond to the buttons. **/
public void actionPerformed (ActionEvent e)
{
Object source = e.getSource ();
if (source == fGoButton || source
== fTextField) {
String strNumDataPoints
= fTextField.getText ();
try {
fNumDataPoints
= Integer.parseInt (strNumDataPoints);
}
catch (NumberFormatException
ex) {
//
Could open an error dialog here but just
//
display a message on the browser status line.
showStatus
("Bad input value");
return;
}
makeHist
();
repaint
();
} else if (source == fClearButton)
{
fHistogram.clear
();
repaint
();
} else if (!fInBrowser)
System.exit
(0);
} // actionPerformed
/** Create a histogram and fill it with data
using
* transformation algorithm to simulate
radioactive
* decay lifetimes.
**/
void makeHist () {
// Create an instance of the Random
class for
// producing our random values.
java.util.Random r = new java.util.Random
();
// Create an instance of our basic
histogram class.
// Make it wide enough enough to
include most of the
// gaussian values.
if (fHistogram == null)
fHistogram
= new Histogram ("Radioactive Lifetimes",
"Arbitrary
Units",
20,0.0,5.0);
// Use the transformation method
to generate a
// radioactive decay distribution
for (int i=0; i < fNumDataPoints;
i++) {
// y (x)
= -ln (1-x/tau);
// Generate
ran FP between 0 & 1
double x
= r.nextDouble ();
double val
= - Math.log (1-x);
fHistogram.add
(val);
}
} // makeHist
/** Use main() to run in standalone mode. **/
public static void main (String[] args) {
//
int frame_width=450;
int frame_height=300;
// Create an instance of this applet
an add to a frame.
RanDistTransformApplet applet =
new RanDistTransformApplet ();
applet.fInBrowser = false;
applet.init ();
// Following anonymous class used
to close window & exit program
JFrame f = new JFrame ("Demo");
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
// Add applet to the frame
f.getContentPane ().add ( applet);
f.setSize (new Dimension (frame_width,frame_height));
f.setVisible (true);
} // main
} // class RanDistTransformApplet
|
Note from the figure that instead of an analytical inversion, we
can use a graphical approach. Generate a uniform random value x
value along the vertical axis and then find the y that corresponds
to the integral up to that x value.
Gaussian Distributions
Though the java.util.Random
class provides the method nextGaussian()
to generate a random Gaussian distribution, it is still useful to
know how to write your own version in case you want to modify it
in some way.
The polar transformation [See Ref. Press and Tang] is the technique
used in java.util.Random
for generating Gaussians from uniform distributions. The Gaussian
function goes as
g(v)
= 1/sqrt(2*pi*sigma) * e[-v2/2sigma2]
A product of a uniform distribution p(x) over [0,1] with an exponential
distribution r(z)= e-z for z over the range [0, infinity]
can relate to a product of two Gaussians as follows:
p(x) dx r(z)dz = g(y1) dy1 g(y2)
dy2
This becomes (using sigma = 1.0):
2pi
* e-zdxdz = exp[-(y1)2 + (y2)2)/2]
dy1 dy2
Relating the two exponent factors, we see that this is equivalent
to a transformation from a rectangular coordinate system in
y1, y2
to a polar system where
r
= sqrt(2z)
and
y1
= sqrt(2z)cos(2pi*x)
y2 = sqrt(2z)sin(2pi*x)
The random z
exponential can be taken from an exponential distribution as shown
above:
y1
= sqrt(-2ln r)cos(2pi*x)
y2 =
sqrt(-2ln r)sin(2pi*x)
where now both r and x range from 0 to 1. So both y1
and y2
provide Gaussian distributed values.
A variation on this [Ref. Press] is to choose a random point (v1,v2)
within a unit circle. We use R = v12+v22 and then the cosine and
sine terms become v1/sqrt(R) and v2/sqrt(R), respectively. So
y1
= v1 * sqrt(-2(ln
R)/R)
y2 =
v2 * sqrt(-2(ln R)/R)
The applet here uses this technique:
RanDistGaussianApplet.java
-
generate a Gaussian distribution of numbers using a uniform
random number generator and the polar transform method
discussed above.
+ Previous classes:
Chapter
6:Tech: Histogram.java,
HistPanel.java
Chapter
6:Tech: PlotPanel.java,
PlotFormat.java
|
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
/**
* This program will run as an applet
inside
* an application frame.
*
* The applet uses the HistPanel to display contents
of
* an instance of Histogram. HistFormat
used by HistPanel to
* format the scale values.
*
* Includes "Go" button to add random
values from a Gaussian
* distribution to the histogram.
The number of values taken from
* entry in a JTextField. "Clear" button
clears the histogram.
* In standalone mode, the Exit button
closes the program.
*
**/
public class RanDistGaussianApplet extends JApplet
implements ActionListener
{
// Use the HistPanel JPanel subclass here
HistPanel fOutputPanel;
Histogram fHistogram;
int fNumDataPoints = 5000;
// A text field for input strings
JTextField fTextField;
// Flag for whether the applet is in a browser
// or running via the main () below.
boolean fInBrowser = true;
//Buttons
JButton fGoButton;
JButton fClearButton;
JButton fExitButton;
/**
* Create a User Interface with histograms and
buttons to
* control the program. A textfield holds number
of entries
* to be generated for the histogram.
*/
public void init () {
Container content_pane = getContentPane
();
JPanel panel = new JPanel (new BorderLayout
());
// Create a histogram with Gaussian
distribution.
makeHist ();
// JPanel subclass here.
fOutputPanel = new HistPanel (fHistogram);
panel.add (fOutputPanel,"Center");
// Use a textfield for an input
parameter.
fTextField =
new JTextField (Integer.toString
(fNumDataPoints), 10);
// If return hit after entering
text, the
// actionPerformed will be invoked.
fTextField.addActionListener (this);
fGoButton = new JButton ("Go");
fGoButton.addActionListener (this);
fClearButton = new JButton ("Clear");
fClearButton.addActionListener (this);
fExitButton = new JButton ("Exit");
fExitButton.addActionListener (this);
JPanel controlPanel = new JPanel
();
controlPanel.add (fTextField);
controlPanel.add (fGoButton);
controlPanel.add (fClearButton);
controlPanel.add (fExitButton);
if (fInBrowser) fExitButton.setEnabled
(false);
panel.add (controlPanel,"South");
// Add text area with scrolling
to the content_pane.
content_pane.add (panel);
}
/** Respond to the buttons. **/
public void actionPerformed (ActionEvent e)
{
Object source = e.getSource ();
if (source == fGoButton || source
== fTextField) {
String strNumDataPoints
= fTextField.getText ();
try {
fNumDataPoints
= Integer.parseInt (strNumDataPoints);
}
catch (NumberFormatException
ex) {
//
Could open an error dialog here but just
//
display a message on the browser status line.
showStatus
("Bad input value");
return;
}
makeHist
();
repaint
();
}
else if (source == fClearButton)
{
fHistogram.clear
();
repaint
();
} else if (!fInBrowser)
System.exit
(0);
} // actionPerformed
/** Creat the histogram and use Guassian - Polar
Transform
* algorithm to fill it with random
data.
**/
void makeHist () {
// Create an instance of the Random
class for
// producing our random values.
java.util.Random r = new java.util.Random
();
// Create an instance of our basic
histogram class.
// Make it wide enough enough to
include most of the
// gaussian values.
if (fHistogram == null)
fHistogram
= new Histogram ("Gaussian - Polar Transform",
"Arbitrary
Units",
25,-4.0,4.0);
// Use the transformation method
to generate a
// radioactive decay distribution
for (int i=0; i < fNumDataPoints;
i++) {
// Generate
random vals -1.0 to 1.0
double v1
= 2.0 * r.nextDouble () - 1.0;
double v2
= 2.0 * r.nextDouble () - 1.0;
// Restrict
them to inside a unit circle
double R
= v1*v1 + v2*v2;
if (R <
1.0) {
double
val = v1 * Math.sqrt (-2.0* (Math.log (R))/R);
fHistogram.add
(val);
}
}
} // makeHist
/** Use main() to run in standalone mode. **/
public static void main (String[] args) {
//
int frame_width=450;
int frame_height=300;
// Create an instance of this applet
an add to a frame.
RanDistGaussianApplet applet = new
RanDistGaussianApplet ();
applet.fInBrowser = false;
applet.init ();
// Following anonymous class used
to close window & exit program
JFrame f = new JFrame ("Demo");
f.setDefaultCloseOperation (JFrame.EXIT_ON_CLOSE);
// Add applet to the frame
f.getContentPane ().add ( applet);
f.setSize (new Dimension (frame_width,
frame_height));
f.setVisible (true);
} // main
} // class RanDistGaussianAppelt
|
Resources & Web References
Latest update: Nov. 4, 2004
|