The Ising Model Applet

To skip to the explanation, click here.

Here's the Java source code:

import java.applet.*;
import java.awt.*;
import java.util.*;

    
/***************************************************************************

     2-d Ising Model simulation - Java version of Program Ising, page 575
     of An Intorduction to Computer Simulation Methods by Harvey Gould and
     Jan Tobochnik, Addison-Wesley, 1996.

 ***************************************************************************/
public class Ising extends Applet implements Runnable {
  int L = 20;                                 // dimension of lattice
  int N = L*L;                                // number of spins
  int spin[][] = new int[L][L]; // array of walker coordinates
  double w[] = new double[10];               // Boltzmann weights
    int t = 0;                                  // time in MCS per spin
    double T;                           // initial temperature = Tc
    Thread runner;
    boolean running = false;                //true if simulation is running
    boolean gridState = false;

    Button bhot,bcold,bstop,bcont,grid;
    Scrollbar sTemp;
    TextField tTemp,tTime;
    TextField tMag,tChi,tEner,tSH,tAccept;
    Label lTemp;
    MovingGraph TMagGraph, TEnerGraph;
    
    int x,y;                            // current spin coordinates
    Image buffer;
    int shiftx=60, shifty=100;                      // x and y location of lattice corner
    int wcell = 5;
   double M = 0,M2 = 0,E = 0,E2 = 0;
    double Accept;

/***************************************************************************/


    public void init ()  {   //set up window with buttons, textfields and scrollbar
    	setLayout(null);
    	setBackground (Color.white);
        tTime = new TextField ("t = " + String.valueOf(t));
        tTime.reshape(10,10,50,20);
        add(tTime);
        bhot = new Button("start hot");      // add buttons
        bhot.reshape(40,50,80,20);
        add(bhot);
        bcold = new Button("start cold");
        bcold.reshape(140,50,80,20);
	    add(bcold);
        bstop = new Button("stop");
        bstop.reshape(240,50,80,20);
        add(bstop);
       
      
        grid = new Button("Grid On");
        grid.reshape(340,50,80,20);
        add(grid);
      
        lTemp = new Label("Temperature");   // Temperature Label
        lTemp.reshape(200,10,100,20);
        add(lTemp);
       
        // Temperature Scrollbar
        sTemp = new Scrollbar (Scrollbar.HORIZONTAL,(int) (100.0*T),5,0,500);	//Temperature Scrollbar
	sTemp.reshape(280,10,100,20);
	add(sTemp);
        
        tTemp = new TextField (String.valueOf(T));  // Temperature Textfield
        tTemp.reshape(400,10,50,20);
        add(tTemp);
        boltzmann();
        buffer = createImage(size().width, size().height);

        // Textfields for output of averages
        tMag = new TextField ("M/N = ");
        tMag.reshape(190,220,100,30);
        add(tMag);
        
        tChi = new TextField ("Susc = ");
        tChi.reshape(10,260,150,20);
        add(tChi);
        
        tEner = new TextField ("E/N = ");
        tEner.reshape(320,220,100,20);
        add(tEner);
        
		tSH = new TextField ("C = ");
        tSH.reshape(190,260,150,20);
        add(tSH);	
	        
        tAccept = new TextField ("Acceptance Ratio = ");
        tAccept.reshape(370,260,100,30);
        add(tAccept);
        
        //add graph for magnetization
        TMagGraph = new MovingGraph(50,-1.0,1.0);
        TMagGraph.reshape(190, 100, 100, 100);
        add(TMagGraph);
    
    	//add graph for energy per spin
        TEnerGraph = new MovingGraph(50,-2.0,0.0);
        TEnerGraph.reshape(320, 100, 100, 100);
        add(TEnerGraph);
    }

 
    public void stop()  {    // stop thread
         runner.stop();
       }

    public void run() {      // run thread
        while(true) {
          try {Thread.sleep(100);}           // check for events
          catch (InterruptedException e){};
          if (running) {      // check to see if stop button pushed
             MonteCarlo();
             averages();
          } 
        }
    }
   
    public void MonteCarlo()  {
        int i,dE;
        for (i = 1;i <= N;i++)   {  // Begin Monte Carlo code
        	x = (int) (L*Math.random());  // random number between 0 and L-1
	    	y = (int) (L*Math.random());
        	dE = spin[x][y]*(spin[pbc(x-1)][y] + spin[pbc(x+1)][y]
                           + spin[x][pbc(y-1)] + spin[x][pbc(y+1)]);
            if(w[dE+4] > Math.random()) {
                  spin[x][y] = -spin[x][y];
                  drawSpin();   
                  Accept++;
            }
        }
        t++;
    }                             // increment time

    public void drawSpin() {   // draw spins
         Graphics g = getGraphics();
         //g.drawString("Magnetization/spin",180,210);
         //g.drawString("Energy/spin",190,210);
         g.drawString("time",190,210);
         g.drawString("time",320,210);
         if(spin[x][y] == 1)  
	     	g.setColor(Color.red);
         else 
         	g.setColor(Color.blue);
            
         if (gridState)  {  
			g.fillRect(shiftx+x*wcell+1,shifty+y*wcell+1,wcell-1,wcell-1);
         	g.dispose();
		 }
		else  { 	
			g.fillRect(shiftx+x*wcell,shifty+y*wcell,wcell,wcell);
         	g.dispose();
		}
    }
    
    public void averages()  {
       int i,j;
       double m=0,e=0,SH,Chi;
       for(i=0;i < L; i++)
         for(j=0;j < L; j++)
            {
              m += spin[i][j];
              e -= spin[i][j]*(spin[i][pbc(j+1)] + spin[pbc(i+1)][j]);
            }
	   M +=  m;
       M2 += m*m;
       E += e;
       E2 += e*e;
       Chi = (1.0/(T*N))*((M2/t) - (M/t)*(M/t));
       SH = (1.0/(T*T*N))*((E2/t) - (E/t)*(E/t));  
       // Print averages
       
       tTime.setText("t = " + String.valueOf(t));
       
       tMag.setText("Mag/N = " + String.valueOf(M/(t*N))); 
       TMagGraph.newData(M/(t*N));
       TMagGraph.repaint(); 
       
       tChi.setText("Susc. = " + String.valueOf(Chi));
       tEner.setText("E/N = " + String.valueOf(E/(t*N)));
       TEnerGraph.newData(E/(t*N));
       TEnerGraph.repaint();
       
       tSH.setText("C = " + String.valueOf(SH));
       tAccept.setText("Acceptance Ratio = " + String.valueOf(Accept/(t*N)));
       }

	public int pbc(int s)  {
		if (s == -1)
    		return(L-1);
    	else if(s == L)
        	return(0);
    	else
           return(s);
    }
   
    public void boltzmann()  {
    	for(int i=-4;i <= 4; i++)
        	w[i+4] = Math.exp(-2.0*i/T);
        }

    public boolean handleEvent(Event evt)  {
		if (evt.target instanceof Scrollbar)  {
			case EVENT.SCROLL_LINE_UP:
			case EVENT.SCROLL_LINE_DOWN:
			case EVENT.SCROLL_PAGE_UP:
			case EVENT.SCROLL_PAGE_DOWN:
			case EVENT.SCROLL_ABSOLUTE:
			int 
			T = ((Scrollbar)evt.target).getValue();
			tTemp.setText(Double.toString(T)); 
			System.out.println("T = "+T);
			return true;
		}	
		return false;
	}	
    
    public boolean action (Event evt, Object arg)  {
    	if (evt.target instanceof Scrollbar)  {
			int v = ((Scrollbar)evt.target).getValue();
			tTemp.setText(Double.toString(v)); 
			//tTemp.setText(String.valueOf(v));
		}
    	else if (evt.target instanceof Button) {
    		System.out.println("Button "+arg+"pressed");  
			if ("continue".equals(arg)) {		     //continue
				bstop.setLabel("stop");
				runner.resume();
              	running = true;
              	return(true);
         	}
         	else if ("start hot".equals(arg))  {     // start hot
         	    running = false;
          		for (x=0; x<L; ++x)  {
            		for (y=0; y<L; ++y)  {
              			if(Math.random() > 0.5)
              				spin[x][y] = 1;
	  		  			else 
              				spin[x][y] = -1;
             			drawSpin();
              		}
              	TMagGraph.clear();
              	TEnerGraph.clear();
              	bstop.setLabel("stop");
              	}
            
         		if (runner == null) {
         			runner = new Thread(this);
					runner.start();
         			running = true;
         			return(true);
         		}
         		else 
         			runner.resume();
         			running = true;
         			return(true);
        	}
        	else if ("start cold".equals(arg))  {        // start cold
         	    running = false;
          		for (x=0; x<L; ++x) {
           			for (y=0; y<L; ++y) {
               			spin[x][y] = -1;
              			drawSpin();
             		}
              	TMagGraph.clear();
              	TEnerGraph.clear();
            	bstop.setLabel("stop");
            	}
            	if (runner == null)  {
            		runner = new Thread(this);
            		runner.start();
            		running = true;
            		return(true);
				}
        		else 
        			runner.resume();
        			running = true;
        			return(true);	
        	}
        	else if ("stop".equals(arg))  {           
           		bstop.setLabel("continue");
           		runner.suspend();
            	running = false;
            	return(true);
            }
       		else if ("Grid On".equals(arg))  {
       			grid.setLabel("Grid Off");
       			gridState = true;
       			drawGrid();
				return(true);
			}
			       
       		else if ("Grid Off".equals(arg))  {
       			grid.setLabel("Grid On");
       			gridState = false;
	   			paintField();							
				return(true);
       		}	
       }
       return false;  
   }
  
	public void paint(Graphics g) {   //draw spins
	    if (running)  {
	    	runner.suspend();
			running = false;
	    }
	    bhot.paint(g);
	    bcold.paint(g);
	    grid.paint(g);
	    sTemp.paint(g);
	    tTemp.paint(g);
	    tTime.paint(g);
	    tMag.paint(g);
	    tChi.paint(g);
	    tEner.paint(g);
	    tSH.paint(g);
	    tAccept.paint(g);
	    lTemp.paint(g);
	    TMagGraph.paint(g);
	    TEnerGraph.paint(g);
	    paintField();
	}	
	
	public void paintField() {
        Graphics g = getGraphics();
		if (running)  {
	    	runner.suspend();
			running = false;
	    }
		for (int x = 0; x < L; x++) 
		  for (int y = 0; y < L; y++) {
		    g.setColor((spin[x][y] == 1)? Color.red : Color.blue);
		    if (gridState)
				g.fillRect(shiftx+x*wcell+1,shifty+y*wcell+1,wcell-1,wcell-1);
			else
				g.fillRect(shiftx+x*wcell,shifty+y*wcell,wcell,wcell);
    	  }
    	runner.resume();
    	running = true;
    }

	public void drawGrid()  {
		Graphics g = getGraphics();
		if (gridState)  {
   			running = false;
   			runner.suspend();
   			g.setColor(Color.black);
			g.drawRect(shiftx-1,wcell*L-1,wcell*L+1,shifty+1);
			for (int i=5; i<L*wcell; i+=5) {
				g.drawLine(shiftx-1,shifty+i,shiftx+100,shifty+i);
				g.drawLine(shiftx+i,shifty-1,shiftx+i,shifty+100);
            }
        	running = true;
        	runner.resume();
        	drawSpin();
        	return;
        } 
		else
			drawSpin();
			return;
	}

}		

Explanation

I give an explanation of each method as it is invoked throughout the lifecycle of the applet. If the reader prefers to skip ahead to read about a specific method, see the table below.

init()boltzmann()handleEvent()
drawSpin()clear()run()
MonteCarlo()pbc()averages()
drawGrid()MovingGraph 

When the applet is first loaded, the init() method is called by the browser or applet viewer. It performs the initialization tasks necessary for the applet to run properly. In this applet, I do the following in init():

Besides setting up the interface, the method boltzmann() is called from init().

The method boltzmann() calculates a probability for each possible energy state. The applet saves time by calculating the probabilities before the simulation begins, which increases the speed during the execution of the applet. The Boltzmann probability comes from statistical mechanics and is not derived here.

After the boltzmann() method is executed, the applet goes into a waiting mode until the user gives input. Let us assume the user first changes the temperature by dragging the temperature scrollbar. In this case, the event is intercepted by the handleEvent() method. This method only recognizes certain scrollbar behaviors. It recognizes the following:


The value of the scrollbar is stored in the variable "T", and T is printed to the textfield "tTemp" after converting its type from a double to a string. Since T is a global variable, it will be used later on in calculations.

Let us assume that after adjusting the temperature using the scrollbar, the user decides to start the simulation. When the user presses either the "start hot" or "start cold" button, the event is intercepted by the action() method.

If the user pressed the "start hot" button, each site in the 20x20 lattice has a 50% probability of being assigned a value of 1, else it is assigned a value of -1.

If the user pressed the "start cold" button instead, all the sites are given a value of -1.

The initial configuration of lattice sites is the only difference between the hot and cold starts. After determining the initial configuration, whether hot or cold, the action() method does the following:

The action() method performs other functions which will be explained later on.

After action() assigns a value of either 1 or -1 to every site, the method drawSpin() is called to draw the lattice. If the spin has a value of 1, the site is painted red, symbolizing hot. If the spin has a value of -1, the site is painted blue, symbolizing cold.

The action() method also calls the method clear() within the MovingGraph class, which sets all the elements in the data array to NaN (not a number) by dividing 0.0/0.0. Since all the elements in the array are not numbers, the points are not plotted and thus the screen appears blank.

Finally if the thread was null when either the "start hot" or "start cold" buttons were pushed, the start() method is called which starts the thread running. In this case there was no need to override the default method, so it does not appear in the listing.

The start() method calls run(). Run() is also called by resume(), which is called after a thread has been unpaused. Run() tries to catch an error by sleeping for 100 milliseconds. If it does catch an error, it creates an object called e, which is an instance of an InterruptedException. This a a general Exception object which allows the runtime system to handle the exception.

While the boolean variable running is true, run() calls the methods MonteCarlo() and averages().

The method MonteCarlo() is the heart of this applet. It has a for loop in which a site within the lattice is randomly chosen to be updated. This process is completed N (=400) times before the for loop is left. In this applet, the Metroplis algorithm is used to update the lattice sites. The process can be described as follows:

  1. Establish an initial microstate.
  2. Choose a site at random site and flip it. Calculate the energy of the new microstate by adding the energies, either +1 or -1, of the nearest neighbors and multipying the sum of neighboring energies by the energy of the chosen site.
  3. The the quantity Etrial-Eold is calculated.
  4. If the change in energy is less than or equal to 0, the change is accepted.
  5. If the change in energy is positive, the Boltzmann number associated with this energy is compared to a random number.
  6. If the Boltzmann number is greater than the random number, the new microstate is accepted
In this applet, step 4 is omitted. Rather than automatically accepting a new microstate with less energy, all microstates are paired with the proper Boltzmann number, of which there are 8 possibilities. Remember that the boltzmann probabilities were calculated in boltzmann(). If the new microstate has less energy than the previous state, the Boltzmann probability turns out to be greater than 1. Because the Boltzmann is compared with a random number between 0 and 1, a microstate with less energy is always accepted. If the new microstate is accepted, drawSpin() is called to draw the flipped spin.

If the new microstate has more energy, the spin is not flipped.

It is also important to mention that the method pbc(), which allows for period boundary conditions, is employed in MonteCarlo().

Right after run() calls MonteCarlo(), it calls averages(). Averages() prints several quantities of interest to the textfields. In addition, the energy per spin and magnetization data are sent to the newData() method in Class MovingGraph. Also, averages() calls repaint() within the MovingGraph class.

The method newData() takes the calculated energy per spin and magnetization (separately) and stores the values in an array called data[].

When averages() calls repaint() inside the MovingGraph class, the array data[] is graphed in the paint() method. In this method, taking the modulo of the next+1 element in the data[] array and the total number of points, nPoints, allows the data to be plotted as a moving graph. Paint() draws horizontal yellow bars to represent the data points for each graph.

Let us suppose that at some point during the execution of the applet, the user decides to view the lattice with a grid. If so, the user pushes the "Grid On" button, an event that is intercepted by the action() method. The action() method calls the drawGrid() method, and changes the button label to "Grid Off". DrawGrid() pauses the thread and paints every spin 1 pixel short on either side. In the extra space between spins, the grid is drawn, and then the thread is resumed. If the user decides to turn the grid off, the event is again intercepted by the action() method, which changes the button label back to "Grid On" and calls drawSpin() to redraw all the spins 1 pixel wider on either side, thereby erasing the grid.



Please send comments and suggestions to jdecarolis@clarku.edu
Last updated 8 December 1997