PHY 411-506 Home Home  |  Course Outline  |  Lectures  |  Homework  |  Files

Lecture 8: February 2


Generating the Gaussian distribution

The following applet is generated by Gaussian.java which generates the Gaussian distribution discussed in Problem 11.14 on page 367. This is a relatively long program because it does not use the comphys library classes: it was written before the library. It is meant to show you how you can write a rather sophisticated Java program to animate a simulation using only the Java API classes and methods.

Overview of Java graphics programming

The Gaussian.java program illustrates how you can write a rather sophisticated Java program to animate a simulation using the Java API classes and methods. Some of the most useful of these API methods are encapsulated in a convenient form in the comphys.graphics package. It is useful and interesting to learn how to use the API classes and to exploit their full power.

The following is a skeleton of the Gaussian.java program which summarizes the use of API classes and methods:

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
These are the three basic API packages which you will want to import and use in most graphics programs.
public class Gaussian extends    Applet 
                      implements ActionListener, ItemListener, Runnable {
The class Gaussian is derived from java.applet.Applet so it can be run as an applet in a Web browser as well as an application from the command line. Gaussian also implements three interfaces:
  1. java.awt.event.ActionListener, which will allow Gaussian to listen to ActionEvents from various buttons and text input fields,
  2. java.awt.event.ItemListener, which will allow Gaussian to listen to ItemEvents from a checkbox, and
  3. java.lang.Runnable, which forces Gaussian to define a run method which we will use to run a low-priority thread to perform the numerical calculations.

The following are the fields and methods which implement the Metropolis algorithm for the Gaussian weight function. Method definitions are omitted for simplicity.

    // define Gaussian probability function

    double A = 1;
    double sigma = 1;
    double xCenter = 0;

    boolean twoGaussians;
    double A2 = 0.7;
    double sigma2 = 0.5;
    double xCenter2 = 6;

    double p (double x) { }

    // Metropolis algorithm

    double x0 = -5;
    double x = x0;
    double delta = 1;

    boolean MetropolisStep () { }

    long naccept = 0;
    long ntotal = 0;
    int stepsToDiscard = 0;

    void Metropolis () { }

    // histogram

    int N = 401;
    int[] histogram = new int[N];
    int hMax;

    void initializeHistogram () { }

    double xMin = -10;
    double xMax = 10;
    int pN = N * 5 / 6;
    int[] probability = new int[N];

    void computeProbability () { }

    int xWalker;
    int deltaWalker;

    void locateWalker () { }

    void recordStep () { }
Next, we use a nested or inner class to create a screen area or "canvas" on which to draw the histogram and compare it with the Gaussian probability curve. This inner class named Picture extends java.awt.Canvas.
    class Picture extends Canvas {

        int pixels = N;

        Picture () {

            setSize(pixels, pixels);
            setBackground(new Color(255, 250, 240));

        }

        public void update (Graphics g) {

            paint(g);
            
        }

        Image offScreen;
        Graphics osg;
        Image walker;

The Picture class has a constructor method (which has the same name as the class). The default update(Graphics g) method of Canvas is called by Java when it wants to update the canvas. This method erases the canvas when it is called, and then it calls the paint(Graphics g). Because we don't want to erase the picture from movie frame to frame, we override update to simply call paint (which is redefined below) without first erasing Picture.

We also define two java.awt.Image instances which represent an array of screen pixels in memory which can be copied to the physical screen. The image offScreen is used to implement double buffering, which is necessary to create a smooth animation and avoid "flashing". A GIF file will be loaded into the image walker.

Graphics in the AWT are rendered by instances of java.awt.Graphics, which has a large number of methods you will want to become very familiar with! Note that we also create a Graphics instance named osg to draw on the offScreen image.

Finally, here is the paint(Graphics g) method of Picture which is called by Java and which we use to do all the required drawing. Note that we are careful to create offScreen, and to load walker.gif using the form of getImage() appropriate for an applet (running in a browser) or an application.

        public void paint (Graphics g) {

            if (offScreen == null) {

                offScreen = createImage(pixels, pixels);
                osg = offScreen.getGraphics();

                try {
                    
                    walker = getImage(getDocumentBase(), "walker.gif");
                    
                } catch (Exception e) {
                    
                    walker = Toolkit.getDefaultToolkit().getImage("walker.gif");                    
                }

            }

            osg.clearRect(0, 0, pixels, pixels);

            double scale = Math.max(hMax, N) / (double) pN;
            int N0 = N - N / 12;

            osg.setColor(Color.red);
            if (xWalker >= 0 && xWalker < N) {

                int yWalker = N0 - probability[xWalker];
                osg.fillOval(xWalker - 4, yWalker - 4, 8, 8);
                osg.drawImage(walker, xWalker - 15, yWalker - 20, this);

            }

            osg.setColor(Color.lightGray);
            for (int n = 0; n < N; n++)
                osg.drawLine(n, N0, n, N0 - (int) (histogram[n] / scale));

            osg.setColor(Color.green);
            for (int n = 1; n < N; n++)
                osg.drawLine(n - 1, N0 - probability[n - 1],
                             n, N0 - probability[n]);

            osg.setColor(Color.red);
            int dy = 16;
            osg.fillRect(xWalker - deltaWalker, N0 + dy, 2 * deltaWalker, 8);
            int dx = 16;
            dy = 12;
            int y = N0 + dy;
            int x = N / 2;
            osg.setColor(Color.blue);
            osg.drawLine(x, N0 - 2, x, N0 + 2);
            osg.drawString(" 0.0", x - dx, y);
            x = N / 4;
            osg.drawLine(x, N0 - 2, x, N0 + 2);
            osg.drawString("-5.0", x - dx, y);
            x = 3 * N / 4;
            osg.drawLine(x, N0 - 2, x, N0 + 2);
            osg.drawString("+5.0", x - dx, y);

            double accept = naccept;
            if (ntotal > 0)
                accept /= ntotal;
            osg.fillRect(10, 8, (int) (80 * accept), 8);
            osg.drawRect(10, 8, 80, 8);
            osg.drawString("" + (int) (accept * 100) + "% acceptance,"
                           + "  Total steps = " + ntotal,
                           100, 16);

            g.drawImage(offScreen, 0, 0, null);

        }

    }
Note how the picture is drawn using the Graphics instance osg of the offScreen image buffer: first, the buffer is erased using the clearRect() method; then various drawing commands are issued; and finally, the offScreen image is drawn to the screen.

Next, we declare an instance of Picture which we have just defined, four instances of java.awt.TextField to input parameter values, an instance of java.awt.Checkbox to select either a single or two Gaussians, and three instances of java.awt.Button to control the animation.

    Picture picture;
    TextField x0TextField;
    TextField deltaTextField;
    TextField discardTextField;
    TextField skipTextField;
    int skip;
    Checkbox twoGaussiansCheckbox;
    Button runButton;
    Button thermButton;
    Button resetButton;
We next override the init() method of Applet to create the various "widgets" (i.e., buttons, textFields, etc.) and add them to our Gaussian class, which is derived from java.applet.Applet. Note that we add "listeners" to "this" (the picture) to the widgets, so when the widget is activated by the user it will send an "event" to picture informing it about the user's action.
    public void init () {

        initializeHistogram();
        computeProbability();
        locateWalker();

        add(picture = new Picture());

        Panel p = new Panel();
        p.setLayout(new GridLayout(6, 2));

        p.add(new Label("Start point x0 = "));
        p.add(x0TextField = new TextField("" + x0));
        x0TextField.addActionListener(this);
        
        p.add(new Label("Step size delta = "));
        p.add(deltaTextField = new TextField("" + delta));
        deltaTextField.addActionListener(this);
        
        p.add(new Label("Steps to discard = "));
        p.add(discardTextField = new TextField("" + stepsToDiscard));
        discardTextField.addActionListener(this);

        p.add(new Label("Skip plot steps = "));
        p.add(skipTextField = new TextField("" + skip));
        skipTextField.addActionListener(this);
        
        p.add(thermButton = new Button("Junk Therm Steps"));
        thermButton.addActionListener(this);
        p.add(twoGaussiansCheckbox = new Checkbox("Two peaks"));
        twoGaussiansCheckbox.addItemListener(this);
        
        p.add(runButton = new Button("Start"));
        runButton.addActionListener(this);

        p.add(resetButton = new Button("Reset"));
        resetButton.addActionListener(this);

        add(p);

    }

    Thread runThread;
    boolean running;
We decare an instance of java.lang.Thread above: the numerical calculations will run in this thread in the background so that the applet will remain responsive to user input.

Because our applet has been registered as a listener with various widgets, we define an actionPerformed(ActionEvent event) method below to process the information sent to us by the widgets when the user interacts with them, for example by typing something in a textField and hitting the Enter key.

    public void actionPerformed (ActionEvent event) {

        if (event.getSource() == x0TextField) {

            try {

                x0 = new Double(event.getActionCommand()).doubleValue();

            } catch (NumberFormatException nfe) { }

            x0TextField.setText("" + x0);
            x = x0;
            locateWalker();

        } else if (event.getSource() == deltaTextField) {

            try {

                delta = new Double(event.getActionCommand()).doubleValue();

            } catch (NumberFormatException nfe) { }

            deltaTextField.setText("" + delta);

        } else if (event.getSource() == discardTextField) {

            try {

                stepsToDiscard = Integer.parseInt(event.getActionCommand());

            } catch (NumberFormatException nfe) { }

            discardTextField.setText("" + stepsToDiscard);

        } else if (event.getSource() == skipTextField) {

            try {

                skip = Integer.parseInt(event.getActionCommand());

            } catch (NumberFormatException nfe) { }

            skipTextField.setText("" + skip);

        } else if (event.getSource() == thermButton) {

            naccept = ntotal = 0;
            initializeHistogram();

        } else if (event.getSource() == runButton) {

            if (running) {

                running = false;
                runButton.setLabel("Start");

            } else {

                running = true;
                runThread = new Thread(this);
                runThread.start();
                runButton.setLabel("Stop");

            }

        } else if (event.getSource() == resetButton) {

            if (running) {

                running = false;

            }

            naccept = ntotal = 0;
            initializeHistogram();
            x = x0;
            locateWalker();
            picture.repaint();
            runButton.setLabel("Start");

        }

    }
You should study in particular the code executed when the source of the event is the runButton or the resetButton: in the case of "run", notice how a new Thread is created and started by calling its start() method, which in turn calls the run() method (see below).

Next, we define a method to process changes in the one/two Gaussian checkBox:

    public void itemStateChanged (ItemEvent itemEvent) {

        if (itemEvent.getSource() == twoGaussiansCheckbox) {

            twoGaussians = twoGaussiansCheckbox.getState();
            computeProbability();

        }

    }
Here is the run() method which we are forced to define because our Applet implements the java.lang.Runnable interface.
    public void run () {

        runThread.setPriority(Thread.MIN_PRIORITY);

        while (running) {

            Metropolis();
            recordStep();
            picture.repaint();

            for (int s = 0; s < skip; s++) {
                
                Metropolis();
                recordStep();

            }
            
            try {
                Thread.sleep(10);
            } catch (InterruptedException ie) { }

        }

        runThread = null;

    }
Finally, here is the main() method. This method is invoked by Java when the Gaussian is run as an application from the command line. An instance of java.awt.Frame is created, and the Gaussian instance is added to it. The statement frame.addWindowListener ... allows the user to close the frame by clicking on the standard window closing widget. If this code (which creates an "anonymous class" on the fly) is not added, the only way to kill the application is to hit Control-C in the console window.
    public static void main (String[] args) {

        Gaussian gaussian = new Gaussian();
        Frame frame = new Frame("The Gaussian distribution");
        frame.addWindowListener(new WindowAdapter () {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });
        
        frame.add(gaussian);
        gaussian.init();
        frame.setSize(700, 450);
        frame.setLocation(50, 50);
        frame.setVisible(true);

    }

}


UB Physics Home Questions or comments: phygons@acsu.buffalo.edu