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;
}
}
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():

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:

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:

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:
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[].


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.