// Brownian motion applet // ---------------------- // Educational demo program showing that the variance of the distance between // two particles each undergoing brownian motion increases over time. This // property is the basis of the continuous character model used by Joe // Felsenstein in the method of independent contrasts. // // Author: // Paul O. Lewis // Department of Ecology and // Evolutionary Biology // The University of Connecticut // 75 N. Eagleville Road, Unit 3043 // Storrs, CT 06269-3043 // // Tel: (860) 486-2069 // Fax: (860) 486-6364 // E-mail: paul.lewis@uconn.edu // http://hydrodictyon.eeb.uconn.edu/people/plewis/ // // Created: 22 April 1998 // Copyright (C) 1998 by Paul O. Lewis // // While now modified almost beyond recognition, this applet grew out of // playing with the Graph.java applet created by Jim Hagen and available // through the web site java.sun.com // // Permission to use, copy, modify, and distribute this software // and its documentation for NON-COMMERCIAL purposes and // without fee is hereby granted. import java.applet.Applet; import java.awt.*; import java.awt.event.*; import java.util.*; import java.text.*; import java.io.*; class ReportArea extends TextArea { public Dimension getPreferredSize() { Dimension d = new Dimension( 100, 100 ); return d; } } class GraphPoint { double x, y; public GraphPoint( double xx, double yy ) { x = xx; y = yy; } } class Graph extends Canvas { Vector points; double x_extent, x_unit; double y_extent, y_unit; int width, height; int x0, y0; boolean first_time; public Graph( double maxx, double maxy ) { points = new Vector( 10, 10 ); x_extent = maxx; y_extent = maxy; x_unit = 0.0; y_unit = 0.0; width = 0; height = 0; x0 = 0; y0 = 0; first_time = true; } public void reset() { first_time = true; points.removeAllElements(); repaint(); } public void addSegment( double x, double y ) { GraphPoint gp = new GraphPoint( x, y ); points.addElement(gp); repaint(); } public void update( Graphics g ) { //first_time = true; if( first_time ) { Dimension sz = getSize(); width = sz.width; height = sz.height; System.out.println( "width = " + width + ", height = " + height ); x_unit = x_extent / (double)(9*width/10); y_unit = y_extent / (double)(9*height/10); x0 = width/10; y0 = 9*height/10; // draw background g.setColor( Color.blue ); g.fillRect( 0, 0, width, height ); // draw x and y axes g.setColor( Color.yellow ); g.drawLine( x0, y0, 9*width/10, y0 ); g.drawLine( x0, y0, x0, height/10 ); first_time = false; } // draw all segments g.setColor( Color.cyan ); int num_segments = points.size(); int prev_x = x0; int prev_y = y0; for( int i = 0; i < num_segments; i++ ) { GraphPoint gp = (GraphPoint)points.elementAt(i); int x = x0 + (int)( gp.x / x_unit ); int y = y0 - (int)( gp.y / y_unit ); g.drawLine( prev_x, prev_y, x, y ); prev_x = x; prev_y = y; } } public void paint( Graphics g ) { update(g); } public Dimension getPreferredSize() { Dimension d = new Dimension( 100, 100 ); return d; } } class ResultPair { int gen; double dist; public ResultPair( int g, double d ) { gen = g; dist = d; } } class PlotResult { Vector results = null; public PlotResult() { results = new Vector( 50, 50 ); } public void addResult( int g, double d ) { ResultPair rp = new ResultPair( g, d ); results.addElement(rp); } // should check mostRecent() first, and if this returns a // negative value (i.e., -1) don't even call resultAt(int i) public double resultAt( int i ) { ResultPair rp = (ResultPair)results.elementAt(i); return rp.dist; } // should check mostRecent() first, and if this returns a // negative value (i.e., -1) don't even call generationAt(int i) public int generationAt( int i ) { ResultPair rp = (ResultPair)results.elementAt(i); return rp.gen; } public void flush() { if( results.size() > 0 ) results.removeAllElements(); } public int mostRecent() { return results.size(); } } class ResultManager { int n; ReportArea textarea; Graph graph; Vector plotresults; int reportPos = 0; boolean nothing_to_report = true; int last_report = -1; static final int stepUnit = 1; public ResultManager( int nplots, ReportArea reportSpace, Graph progressPlot ) { textarea = reportSpace; graph = progressPlot; n = nplots; plotresults = new Vector(n); for( int i = 0; i < n; i++ ) { plotresults.addElement( new PlotResult() ); } } public void reset() { graph.reset(); for( int i = 0; i < n; i++ ) { PlotResult pr = (PlotResult)plotresults.elementAt(i); pr.flush(); } String s = "\n"; textarea.insert( s, reportPos ); reportPos += s.length(); nothing_to_report = true; } public void addResult( int id, int g, double d ) { PlotResult pr = (PlotResult)plotresults.elementAt(id); pr.addResult( g, d ); nothing_to_report = false; } public void updateReport() { if( nothing_to_report ) return; int i; // first find last generation filled in for all plots PlotResult pr = (PlotResult)plotresults.elementAt(0); int smallest = pr.mostRecent(); for( i = 1; i < n; i++ ) { pr = (PlotResult)plotresults.elementAt(i); int this_one = pr.mostRecent(); if( this_one < smallest ) smallest = this_one; } if( smallest <= 0 ) return; if( smallest == last_report ) return; last_report = smallest; double ss = 0.0; double sum = 0.0; for( i = 0; i < n; i++ ) { pr = (PlotResult)plotresults.elementAt(i); double dist = pr.resultAt(smallest-1); sum += dist; ss += ( dist * dist ); } double num = (double)n; double numMinusOne = num - 1.0; double mean = sum / num; double variance1 = ( ss - ( num * mean * mean ) ) / numMinusOne; double variance2 = ( ss / num ); double variance = variance1; graph.addSegment( (double)( stepUnit * smallest ), variance ); NumberFormat nf = NumberFormat.getInstance(); nf.setMaximumFractionDigits(2); String varstr = nf.format(variance); String s = "variance at time " + ( stepUnit*smallest ) + " is " + varstr + "\n"; textarea.insert( s, reportPos ); reportPos += s.length(); } } class PathPoint { int x; int y; public PathPoint( int x_coord, int y_coord ) { x = x_coord; y = y_coord; } } class Particle { int x, x0, prev_x; int y, y0, prev_y; int x_incr, y_incr; int curr, max; boolean paint_entire_path; Random rng; boolean decisions[]; public Particle( Random r, int starting_x, int starting_y, int max_segments, int increment_x, int increment_y ) { rng = r; setParameters( starting_x, starting_y, max_segments, increment_x, increment_y ); } public void setParameters( int starting_x, int starting_y, int max_segments, int increment_x, int increment_y ) { max = max_segments; decisions = new boolean[max]; x0 = starting_x; y0 = starting_y; x_incr = increment_x; y_incr = increment_y; reset(); } public void reset() { for( int i = 0; i < max; i++ ) { double d = rng.nextDouble(); if( d <= 0.5 ) decisions[i] = false; else decisions[i] = true; } prev_x = x0; prev_y = y0; if( decisions[0] ) x = x0 + x_incr; else x = x0 - x_incr; y = y0 - y_incr; curr = 1; } public boolean finished() { if( curr >= max ) return true; else return false; } public void addPathPoint( boolean take_step ) { if( finished() ) return; if( decisions[curr] ) x += x_incr; else x -= x_incr; if( take_step ) { y -= y_incr; curr++; } } public void paint( Graphics g ) { g.drawLine( prev_x, prev_y, x, y ); prev_x = x; prev_y = y; } } class PlotCanvas extends Canvas implements Runnable { Color particle_color; Particle particle1 = null; Particle particle2 = null; int w, h; int index; Random rng; boolean redraw_background; Thread thread = null; String thread_name = "unnamed"; ResultManager resultMgr; public PlotCanvas( Random r, Color c, int i, ResultManager rm ) { resultMgr = rm; index = i; rng = r; redraw_background = true; particle_color = c; w = 0; h = 0; } public void update( Graphics g ) { for( int k = 0; k < ResultManager.stepUnit-1; k++ ) { particle1.addPathPoint( false ); particle2.addPathPoint( false ); } particle1.addPathPoint( true ); particle2.addPathPoint( true ); int ht = particle1.curr; double dist = particle1.x - particle2.x; resultMgr.addResult( index, ht, dist ); paint(g); } public void paint( Graphics g ) { if( thread == null || !thread.isAlive() ) return; if( redraw_background ) { g.setColor( Color.black ); g.fillRect( 1, 1, w-2, h-2 ); redraw_background = false; } g.setColor( particle_color ); particle1.paint(g); particle2.paint(g); } public void Go() { thread = new Thread(this); thread_name = thread.getName(); if( thread.isAlive() ) thread.stop(); Dimension sz = getSize(); w = sz.width; h = sz.height; int maxht = ( 4 * h / 5 ); if( particle1 == null ) particle1 = new Particle( rng, w/2, 9*h/10, maxht, 1, 1 ); else particle1.setParameters( w/2, 9*h/10, maxht, 1, 1 ); if( particle2 == null ) particle2 = new Particle( rng, w/2, 9*h/10, maxht, 1, 1 ); else particle2.setParameters( w/2, 9*h/10, maxht, 1, 1 ); particle1.reset(); particle2.reset(); redraw_background = true; thread.start(); } public void run() { while( !particle1.finished() && !particle2.finished() ) { repaint(); try { Thread.sleep(100); } catch( InterruptedException e ) {} } } } public class Brownian extends Applet implements ActionListener, WindowListener, Runnable { Frame appletFrame; Button goButton; Thread thread; ReportArea reportSpace; Graph graph; ResultManager rm = null; PlotCanvas plot1 = null; PlotCanvas plot2 = null; PlotCanvas plot3 = null; PlotCanvas plot4 = null; PlotCanvas plot5 = null; PlotCanvas plot6 = null; PlotCanvas plot7 = null; PlotCanvas plot8 = null; PlotCanvas plot9 = null; PlotCanvas plot10 = null; PlotCanvas plot11 = null; PlotCanvas plot12 = null; PlotCanvas plot13 = null; PlotCanvas plot14 = null; PlotCanvas plot15 = null; PlotCanvas plot16 = null; Random rng; final static int appletSize = 400; public void actionPerformed( ActionEvent e ) { if( e.getSource() == goButton ) { plot1.Go(); plot2.Go(); plot3.Go(); plot4.Go(); plot5.Go(); plot6.Go(); plot7.Go(); plot8.Go(); plot9.Go(); plot10.Go(); plot11.Go(); plot12.Go(); plot13.Go(); plot14.Go(); plot15.Go(); plot16.Go(); rm.reset(); } } public void init() { thread = new Thread(this); setLayout( new BorderLayout() ); Panel topPanel = new Panel(); goButton = new Button("Go"); goButton.addActionListener(this); topPanel.add( goButton ); add( "North", topPanel ); Panel bottomPanel = new Panel(); GridBagLayout gbl = new GridBagLayout(); GridBagConstraints gbc = new GridBagConstraints(); bottomPanel.setLayout(gbl); gbc.weightx = 10; gbc.fill = GridBagConstraints.HORIZONTAL; reportSpace = new ReportArea(); gbl.setConstraints( reportSpace, gbc ); bottomPanel.add( reportSpace ); graph = new Graph( 50.0, 150.0 ); gbc.weightx = 1; gbc.gridx = GridBagConstraints.RELATIVE; gbc.fill = GridBagConstraints.HORIZONTAL; gbl.setConstraints( graph, gbc ); bottomPanel.add( graph ); add( "South", bottomPanel ); rng = new Random(); Panel centerPanel = new Panel(); centerPanel.setLayout( new GridLayout(4,4) ); rm = new ResultManager( 16, reportSpace, graph ); plot1 = new PlotCanvas( rng, Color.green, 0, rm ); centerPanel.add( plot1 ); plot2 = new PlotCanvas( rng, Color.green, 1, rm ); centerPanel.add( plot2 ); plot3 = new PlotCanvas( rng, Color.green, 2, rm ); centerPanel.add( plot3 ); plot4 = new PlotCanvas( rng, Color.green, 3, rm ); centerPanel.add( plot4 ); plot5 = new PlotCanvas( rng, Color.green, 4, rm ); centerPanel.add( plot5 ); plot6 = new PlotCanvas( rng, Color.green, 5, rm ); centerPanel.add( plot6 ); plot7 = new PlotCanvas( rng, Color.green, 6, rm ); centerPanel.add( plot7 ); plot8 = new PlotCanvas( rng, Color.green, 7, rm ); centerPanel.add( plot8 ); plot9 = new PlotCanvas( rng, Color.green, 8, rm ); centerPanel.add( plot9 ); plot10 = new PlotCanvas( rng, Color.green, 9, rm ); centerPanel.add( plot10 ); plot11 = new PlotCanvas( rng, Color.green, 10, rm ); centerPanel.add( plot11 ); plot12 = new PlotCanvas( rng, Color.green, 11, rm ); centerPanel.add( plot12 ); plot13 = new PlotCanvas( rng, Color.green, 12, rm ); centerPanel.add( plot13 ); plot14 = new PlotCanvas( rng, Color.green, 13, rm ); centerPanel.add( plot14 ); plot15 = new PlotCanvas( rng, Color.green, 14, rm ); centerPanel.add( plot15 ); plot16 = new PlotCanvas( rng, Color.green, 15, rm ); centerPanel.add( plot16 ); add( "Center", centerPanel ); } public void setAppletFrame( Frame f ) { appletFrame = f; f.addWindowListener(this); } public void start() { thread.start(); } public void stop() { thread.stop(); } public void run() { while(true) { rm.updateReport(); try { Thread.sleep(200); } catch( InterruptedException e ) {} } } public void windowOpened( WindowEvent e ) {} public void windowClosing( WindowEvent e ) { System.exit(0); } public void windowClosed( WindowEvent e ) {} public void windowIconified( WindowEvent e ) {} public void windowDeiconified( WindowEvent e ) {} public void windowActivated( WindowEvent e ) {} public void windowDeactivated( WindowEvent e ) {} public static void main( String args[] ) { Frame f = new Frame( "Brownian Motion Simulation" ); Brownian brownian = new Brownian(); brownian.init(); brownian.setAppletFrame(f); brownian.start(); f.add( "Center", brownian ); f.setSize( brownian.appletSize, brownian.appletSize ); f.show(); } }