0% found this document useful (0 votes)
57 views12 pages

MD Simulation

This document provides an introduction to interactive molecular dynamics simulations. It discusses writing an MD program that includes animation and allows the user to interact with the running simulation. The program is organized using an event-driven approach where the computation only proceeds when the user is not interacting. The program uses the X Window System for graphics and Motif for the user interface. It provides global variables and data structures to store particle data and describes the main functions for performing an MD simulation step, including computing forces, integrating motion, applying periodic boundaries, and updating energies.

Uploaded by

Anand aashish
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
57 views12 pages

MD Simulation

This document provides an introduction to interactive molecular dynamics simulations. It discusses writing an MD program that includes animation and allows the user to interact with the running simulation. The program is organized using an event-driven approach where the computation only proceeds when the user is not interacting. The program uses the X Window System for graphics and Motif for the user interface. It provides global variables and data structures to store particle data and describes the main functions for performing an MD simulation step, including computing forces, integrating motion, applying periodic boundaries, and updating energies.

Uploaded by

Anand aashish
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 12

An introduction to interactive molecular dynamics simulations

D. C. Rapaport
In this column we explain what is involved in writing a molecular dynamics (MD) program that includes animation and, in addition, allows the user
to interact with the simulation while it is running.
The simplest of soft-disk models for a fluid is considered, but the techniques described here provide
a basis that can be readily adapted to a wide range
of problems, both elementary and advanced. Although some effort is required in getting started,
the benefits of knowing how to incorporate graphics and a user interface into a simulation are many.
For example, for the two-dimensional soft-disk system considered here, it is possible to observe the
onset of spatial order at high density, complete with
defects in the resulting crystalline structure. In contrast, at low density the particle motion is diffusive
in appearance. The interactive capability enables
the user to drive the system into states anywhere
between these two extremes.
The usual way of organizing interactive software
is by means of an event-driven approach. The most
conspicuous feature of event-driven software is that
responses to user actions occur promptly. Because
of this requirement, the overall organization of the
program is very different from the customary scientific computation. In the latter, the activity is centered around some computationally intensive task;
in the former, the computation is allowed to proceed only when the user is not busy doing something, such as changing a parameter setting or the
display contents.
The graphic environment on the typical UNIX
workstation is provided by the X11 Window System. This environment is a relatively flexible, although rather complex (especially for novice programmers) windowing system that allows the display and the application to reside on separate computers transparently linked over a network. This environment, which also allows multiple applications
to share space on the same display screen, is commonly referred to as X11, or X. There are two major

elements in X. The first is Xlib, a library of functions that the programmer can use for a multitude
of tasks, such as opening windows, performing basic
drawing operations, and receiving X events that are
user-generated by means of the mouse or keyboard
[1]. The other component is the X server; under X,
an application does not draw directly to the screen,
but packages graphic requests and sends them to
the server (on the same or another machine) which
carries out the graphic operations.
X is adequate for the simple graphics used in
this work. (For more complex rendering, for example three-dimensional graphics, software such as
OpenGL can be added to the environment described
here.) X is not suitable for programming a user interface because it works with windows and events
at much too detailed a level. Higher levels of abstraction can reduce the task of interface construction to comparative simplicity. This reduction is
the goal of Motif, a set of specifications for developing user interfaces that have a consistent look and
feel as far as the user is concerned [2]. From the
practical point of view, Motif consists of a set of
widgets and a library of functions for constructing
and manipulating the widgets. The term widget
embraces all manner of objects. Some of these are
visible on the screen, such as windows, scrollbars,
buttons, textual items, and menus, to name but a
few (all visible widgets actually have their own windows). Others are less conspicuous but nevertheless
extremely useful, because they help with the layout
of the visible objects, for example, in tabular form.
Another group, known as shells, provide a means
of communication between the windows belonging
to the application and whatever else is occurring on
the display screen. The screen contents are under
the control of the X server and, more immediately,
the window manager, whose responsibilities include
window positioning and framing.
Motif actually deals with only part of the wid-

Dennis Rapaport is a professor of physics in the Department of Physics, Bar-Ilan University, Ramat-Gan,
Israel 52900. E-mail: [email protected].
Computers in Physics 11 (1997) 337-347

getry and is built on top of a lower level library of


functions known as the X Toolkit Intrinsics (Xt) [3].
This library supplies some of the more basic widget
capability, but none of the visual effects or functionality that are part of Motif. The widgets themselves
are organized in a tree-like hierarchy, with parents
often having a significant say in the appearance and
behavior of their children.
It should not be surprising if the foregoing introduction is a little confusing. A full set of documentation for X, Xt, and Motif occupies literally thousands of pages, but it would be a pity if the reader
is deterred by such details. Much of what appears
in the documentation is not needed by the beginner (and a great deal is not even needed by more
advanced application developers), so the problem
is how to get started. Worked examples provide
an effective route, and although books such as [13] include sample programs, these examples do not
address the needs of the typical reader of this column. In an attempt to remedy the situation, we will
develop over the course of this tutorial a complete
MD application which you, the reader, can extend
or adapt at will. After you have seen the program
in operation, you may well be motivated to explore
the subject in greater depth. Functionally equivalent, but no less complicated, software exists on
other platforms, e.g., Windows and Macintosh. Although the overall approach is similar, the details
differ see [4] for examples.
The program is written in (ANSI-) C. Although C
is not the traditional language of the physics community, the reasons for this situation are entirely
historical. C not only provides a very convenient
means of interfacing with the graphics libraries, but
also allows for concise and readable programs. We
assume the reader to have a working knowledge of
C and its standard function libraries. The user interface itself will be constructed as part of the C
program; there are alternative approaches to interface building using specialized languages and design
tools, but these will not be considered here.
We start with the main program. Although it
is very short, its contents reflect the event-driven
nature of the program:
main (int argc, char **argv)
{

SetupGraphics (argc, argv);


InitializeRun ();
XtAppAddWorkProc (app, MdCycle, 0);
XtAppMainLoop (app);
}

The first two function calls (discussed below) initialize the graphics and bring the system to a state
from which a run can be started by the user. The
latter two calls are part of the Xt library, and ensure that the program carries out the desired computations and responds to the users actions. The
first of these functions called, XtAppAddWorkProc,
specifies the function MdCycle to be a work procedure. The implication is that whenever no events
are awaiting processing, this function, which is responsible for the MD computations, will be called.
(The variable app is defined later.) The second
function, XtAppMainLoop, contains the central dispatching loop that ensures all events are processed
and that the work procedure is called. The function MdCycle checks if the simulation is actually
running, and if so, calls for the computations required for a single timestep (the value returned by
the function is dictated by Xt):
Boolean MdCycle ()
{
if (running) SingleStep ();
return (False);
}

Molecular dynamics
The discussion of the program begins with a description of the functions and data associated with
the MD computations themselves, starting with the
function SingleStep where the computational work
actually occurs. The reader is assumed to be familiar with the ideas underlying the MD approach
[5-7]. The description of those parts of the program
that create and manage the user interface, as well
as the graphic output, will appear later on.
There are a variety of methods that can be used
to organize the data used to store the particle (i.e.,
atomic) coordinates, velocities and accelerations.
The most natural approach is to arrange the data
in a way that mirrors the mathematics of the problem, namely, to use data structures representing
vectors. Thus we define two-dimensional vectors
for both real (double precision) and integer quantities:
Computers in Physics 11 (1997) 337-347

typedef struct {double x, y;} VecD;


typedef struct {int x, y;} VecI;

In terms of these structures, the coordinate and


other arrays, which will be dynamically allocated
at the start of the run, can be defined using pointers:
VecD *r = NULL, *rv = NULL, *ra = NULL,
region;

The two components of region are the dimensions


of the simulation box. The integer version of the
vector is used to specify the dimensions of the cell
array used in the force computation, the number
of particles in the system (expressed as a unit-cell
array), and the size of the window used to display
the image of the MD system:
VecI cells, initUcell, wDrawSize;

Other global variables used in this program are


double deltaT, density, kinEnergy,
potEnergy, rCut, temperature, uSum;
int *cellList = NULL, changeDens,
changeTemp, nAtom, nAtomEdge, running,
stepCount, stepDraw;

The roles of some of these variables will only become


apparent later on.
The processing required to advance the simulation over a single timestep entails computing all the
forces, numerically integrating the equations of motion, allowing for any wraparound due to periodic
boundaries, updating the display at regular intervals (not necessarily at every timestep, because the
graphics involves substantial computation), and, if
requested by the user, changing the values of the
temperature or density by a small amount:
void SingleStep ()
{
++ stepCount;
ComputeForces ();
LeapfrogIntegrate ();
CheckPeriodicWrap ();
CalcEnergy ();
if (stepCount % stepDraw == 0) {
if (changeTemp)
SetNewTemp (1 + 0.01 * changeTemp);
if (changeDens)
SetNewDens (1 - 0.01 * changeDens);
Redraw ();
}
}

The force computation (in reduced MD units,


forces and accelerations are equivalent) employs the
cell method [5]. In principle, for a system of N par-

ticles there are 0(N 2 ) particle pairs that must be


examined for possible interactions. By introducing a fictitious array of cells that covers the system
and assigning each particle to its appropriate cell,
the amount of work can be reduced to 0(N ) due to
the fact that if the cell size exceeds the interaction
range, rc , only particles in the same and adjacent
cells can be separated by a distance less than r c .
To shorten the program text a number of macros
are defined. These macros handle the loop over particles, the traversal of a linked list of cell occupants,
and the offset due to periodic boundaries:
#define DO_ATOM for (n = 0; n < nAtom; n ++)
#define DO_CELL(j,m) \
for (j = cellList[m]; j >= 0; \
j = cellList[j])
#define WRAP_C(t) \
if (m2v.t >= cells.t) \
m2v.t = 0, shift.t = region.t; \
else if (m2v.t < 0) \
m2v.t = cells.t - 1, shift.t = -region.t

A further series of macro definitions are used to


concisely express various vector operations:
#define VSet(v,sx,sy) v.x = sx, v.y = sy
#define VZero(v) VSet (v, 0, 0)
#define VSCopy(v2,s1,v1) \
v2.x = (s1) * v1.x, v2.y = (s1) * v1.y
#define VScale(v,s) VSCopy (v, s, v)
#define VAdd(v1,v2,v3) \
v1.x = v2.x + v3.x, v1.y = v2.y + v3.y
#define VMul(v1,v2,v3) \
v1.x = v2.x * v3.x, v1.y = v2.y * v3.y
#define VSAdd(v1,v2,s3,v3) \
v1.x = v2.x + (s3) * v3.x, \
v1.y = v2.y + (s3) * v3.y
#define VVSAdd(v1,s2,v2) \
VSAdd (v1, v1, s2, v2)

Operations VSub and VDiv are analogously defined,


and several other useful operations also can be condensed into macro form:
#define
#define
#define
#define

SQ(x) ((x) * (x))


VLenSq(v) (SQ (v.x) + SQ (v.y))
VProd(v) (v.x * v.y)
VLinear(p,s) p.y * s.x + p.x

The function for computing forces and potential


energy begins as follows:
void ComputeForces ()
{
VecD dr, invWid, rs, shift;
VecI cc, m1v, m2v,
iof[] = {0,0, 1,0, 1,1, 0,1, -1,1};
double fcVal, rr, rrCut, rri, rri3;
int c, j1, j2, m1, m1x, m1y, m2, n, offset;
DO_ATOM VZero (ra[n]);
uSum = 0;

Computers in Physics 11 (1997) 337-347

rrCut = SQ (rCut);
VDiv (invWid, cells, region);

The next part of this function assigns each particle


to its appropriate cell. The contents of each cell are
represented by a linked list: a typical array element
cellList[nAtom+n] points to one of the particles
in cell n, say j; cellList[j] then points to the
next particle (if any), and so on, until a -1 flags the
end of the list for this cell. Exactly one element of
the array cellList is needed for each particle and
for each cell, with the pointers between particles
appearing first.
for (n = 0; n < VProd (cells); n ++)
cellList[nAtom + n] = -1;
DO_ATOM {
VSAdd (rs, r[n], 0.5, region);
VMul (cc, rs, invWid);
c = VLinear (cc, cells) + nAtom;
cellList[n] = cellList[c];
cellList[c] = n;
}

The remainder of this function consists of a set of


nested loops. The outermost pair cover all cells:
for (m1y = 0; m1y < cells.y; m1y ++) {
for (m1x = 0; m1x < cells.x; m1x ++) {
VSet (m1v, m1x, m1y);
m1 = VLinear (m1v, cells) + nAtom;

The next loop covers the possible offsets between


adjacent cells; the correction for the effect of periodic boundaries on the interactions (for all particles
in the corresponding cell pair) is determined at this
stage:
for (offset = 0; offset < 5; offset ++) {
VAdd (m2v, m1v, iof[offset]);
VZero (shift); WRAP_C (x); WRAP_C (y);
m2 = VLinear (m2v, cells) + nAtom;

The two innermost loops consider all pairs of particles in the two chosen cells, with a test being made
to ensure that if the two cells are one and the same,
then each particle pair is considered only once. If
the particles are within range, the pair interaction
is computed and the accelerations and potential energy sum updated. The force law used here corresponds to a Lennard-Jones potential that is truncated at its minimum, namely


U (rij ) = 4 (/rij )12 (/rij )6 + ,
for rij < rc = 21/6 . Reduced units are used in
which , and the particle mass are all unity.
DO_CELL (j1, m1) {

DO_CELL (j2, m2) {


if (m1 != m2 || j2 < j1) {
VSub (dr, r[j1], r[j2]);
VSub (dr, dr, shift);
rr = VLenSq (dr);
if (rr < rrCut) {
rri = 1 / rr;
rri3 = rri * rri * rri;
fcVal = 48 * rri3 *
(rri3 - 0.5) * rri;
VVSAdd (ra[j1], fcVal, dr);
VVSAdd (ra[j2], - fcVal, dr);
uSum += 4 * rri3 * (rri3 - 1) + 1;
}}}}}}}
}

The integration of the equations of motion uses


the popular (and highly stable from the energetic
point of view) leapfrog method:
~vn (t + t/2) = ~vn (t t/2) + ~an (t)t
~rn (t + t) = ~rn (t) + ~vn (t + t/2)t
void LeapfrogIntegrate ()
{
int n;
DO_ATOM {
VVSAdd (rv[n], deltaT, ra[n]);
VVSAdd (r[n], deltaT, rv[n]);
}
}

The updated coordinates must be examined to see


whether periodic wraparound has occurred. The
following macro is introduced for brevity:
#define WRAP_R(t) \
if (r[n].t >= 0.5 * region.t) \
r[n].t -= region.t; \
else if (r[n].t < -0.5 * region.t) \
r[n].t += region.t

and the function is then simply:


void CheckPeriodicWrap ()
{
int n;
DO_ATOM {
WRAP_R (x); WRAP_R (y);
}
}

The kinetic and potential energy can be computed as follows:


void CalcEnergy ()
{
VecD v; double vvSum; int n;
vvSum = 0;
DO_ATOM {
VSAdd (v, rv[n], -0.5 * deltaT, ra[n]);
vvSum += VLenSq (v);
}
kinEnergy = vvSum / (2 * nAtom);

Computers in Physics 11 (1997) 337-347

potEnergy = uSum / nAtom;


}

Note that because of the leapfrog integrator, the


velocity has been adjusted by a half timestep so that
the energies can be evaluated at the same point in
time.
At the start of a run the system is initialized
with all particles located at the sites of a slightly
stretched (in the y-direction) triangular lattice. The
velocities are assigned a common magnitude based
on the temperature and random directions, but are
subsequently adjusted so that the center of mass of
the system is stationary.
void InitialState ()
{
VecD gap, p, vSum;
double ang, vMag;
int n, nx, ny;
stepCount = 0;
VDiv (gap, region, initUcell);
n = 0;
for (ny = 0; ny < initUcell.y; ny ++) {
for (nx = 0; nx < initUcell.x; nx ++) {
VSet (p, nx + 0.25, ny + 0.25);
VMul (p, p, gap);
VVSAdd (p, -0.5, region); r[n ++] = p;
VVSAdd (p, 0.5, gap); r[n ++] = p;
}
}
vMag = sqrt (2 * temperature);
VZero (vSum);
DO_ATOM {
ang = 2 * M_PI * drand48 ();
VSet (rv[n], cos (ang), sin (ang));
VScale (rv[n], vMag);
VAdd (vSum, vSum, rv[n]);
}
DO_ATOM VVSAdd (rv[n], -1 / nAtom, vSum);
}

A few numerical constants appear in the program,


and other quantities are assigned default values that
can be altered by the user. The following function
will be called near the start of a run to make these
assignments; a check is also made to ensure the user
did not request a window size that is too small (appData is defined later):
void SetDefaults ()
{
wDrawSize.x = appData.wSize;
if (wDrawSize.x < 500) wDrawSize.x = 500;
wDrawSize.y = wDrawSize.x;
nAtomEdge = 20;
stepDraw = 10;
deltaT = 0.004;
rCut = pow (2., 1./6.);
}

Whenever the user starts a new run several things


happen: the computation is reinitialized, the sizes
of the dynamically allocated arrays are computed,
and the necessary memory allocation is performed.
Starting values for quantities that can be changed
by the user during the run are also assigned. The
system is set to its initial state, the energy is computed, and the display updated. Other variables
are set whose roles will become clear subsequently.
Note that the simulation region has a square shape
(hence the stretched triangular lattice form of the
initial state).
void InitializeRun ()
{
temperature = 1.; density = 0.8;
VSet (initUcell, nAtomEdge,
nAtomEdge / sqrt (3.));
region.x = initUcell.x /
sqrt (density * sqrt (3.) / 2);
region.y = region.x;
nAtom = 2 * VProd (initUcell);
density = nAtom / VProd (region);
VSCopy (cells, 1 / rCut, region);
ALLOC (r, nAtom, VecD);
ALLOC (rv, nAtom, VecD);
ALLOC (ra, nAtom, VecD);
ALLOC (cellList, nAtom +
VProd (cells), int);
InitialState ();
ComputeForces ();
CalcEnergy ();
running = 0;
changeTemp = 0; changeDens = 0;
Redraw ();
}

The memory allocation macro ALLOC used here is


defined as
#define ALLOC(y,x,t) \
y = (t *) realloc (y, (x) * sizeof (t))

User interface
We now focus on implementing the graphics and
user interface. The first stage is the opening of a
window on the screen filled with various user controls, a space for drawing, and a small area for textual information. All this is done by the function
SetupGraphics below; this function turns out to
be rather lengthy, not because of its profundity,
but because it has to take care of numerous details. While the use of widgets hides much of the
difficult work that goes into constructing the interface, it is still necessary to specify how the controls
and other visible entities are to be arranged, the
Computers in Physics 11 (1997) 337-347

values of widget properties that cannot be left at


their default settings, and which functions are to
be called in response to the user performing certain
actions. These and related issues account for the
length. Access to the Motif and X literature (see,
e.g., [1,2]), or to the online manpages detailing the
functions mentioned here, should help clarify the
more obscure points.
A number of additional variables need to be specified; some are used directly in the program, while
others must be passed untouched to functions in the
graphics libraries:
XtAppContext app; Display *dpy;
GC gc; Pixmap pixmap;
Widget wButtonS, wScaleA, wScaleD,
wDraw, wText;

The widgets just listed can be thought of as pointers


to data structures, and are globally defined so that
they can be referenced elsewhere in the program;
additional widgets will be defined inside the function SetupGraphics that are not referenced elsewhere. Another macro is introduced to save typing:
#define T_STRING(s) \
XmRString, s, strlen (s) + 1

The reader will come to learn that Xt and Motif


abound with mnemonic constants having the prefixes Xt and Xm.
We need to provide a few colors for the graphics.
The most flexible (and officially sanctioned) way is
to supply default values for these resources that can
be overridden by the user when the program is run.
The ability to specify the size of the display window is also supported by the same general resource
mechanism. The program must provide a structure
for storing the values of these resources, a resource
list, and an option table for those resources that can
be set from the command line. (Resources can also
be specified in resource files.) The following (or its
equivalent) must appear in the program:
typedef struct {
int wSize;
Pixel pixel[2];
} AppData;
AppData appData;
#define COLOR_RES(t,n,v) \
{t, t, XmRPixel, sizeof (Pixel), \
XtOffsetOf (AppData, pixel[n]), \
XmRString, (char *) v}
XtResource resources[] = {

{"wSize", "wSize", XmRInt, sizeof (int),


XtOffsetOf (AppData, wSize),
XmRImmediate, (XtPointer) 650},
COLOR_RES ("color0", 0, "black"),
COLOR_RES ("color1", 1, "green"),
};
XrmOptionDescRec options[] = {
{"-s", "wSize", XrmoptionSepArg, NULL},
{"-c0", "color0", XrmoptionSepArg, NULL},
{"-c1", "color1", XrmoptionSepArg, NULL},
};

If the executable version of the program is called


mddisk, the user can type, for example, mddisk c0 yellow -c1 red -s 900 to obtain red disks on
a yellow background, with a window size of 900 pixels. Furthermore, if -bg pink is added to the command line, all the widgets appear in pink. Space
prevents any further elaboration on what we have
just accomplished.
There are many ways of achieving results equivalent to those of the function SetupGraphics that
follows. Alternative library functions can be used,
and there are other ways of specifying widget properties. One can devote considerable effort to this
subject, or one can first produce a working program
and then (as with many other aspects of X/Motif)
ponder over the more subtle issues later.
void SetupGraphics (int argc, char **argv)
{
Widget wArrow, wForm, wFrame, wRowCol,
wRowCol2, wTop;
char *buttonName[] = {"Temp: ", "Dens: "};
int j, n, scrn;

The user interface and the output graphics are


contained in a single window. Rather than deal with
this window directly using X, we create a top-level
ApplicationShell widget and let Motif handle the
details. Both this widget and the subsequent widgets are created with Xt functions that are prefixed
by XtVa and employ NULL-terminated, variablelength argument lists. This particular function also
initializes Xt, creates an application context, and
opens the display for the application:
wTop = XtVaAppInitialize (&app, "Xmd",
options, XtNumber (options),
&argc, argv, NULL, XmNmwmFunctions,
MWM_FUNC_ALL | MWM_FUNC_RESIZE |
MWM_FUNC_MAXIMIZE, NULL);

We have stipulated that following its creation, the


size of the window corresponding to the shell cannot be altered (the actual size will be determined by
Computers in Physics 11 (1997) 337-347

its widget contents). In this way we avoid having


to address the issue of what to do with the simulation imagery should the user decide to change the
window size.
Once the ApplicationShell widget has been created, it is possible to access the resources that the
user has specified, such as color and window size:
XtGetApplicationResources (wTop,
&appData, resources,
XtNumber (resources), NULL, 0);
SetDefaults ();

These resource values can now be used elsewhere in


the program.
Widgets
In creating a widget it is necessary to specify the
identity of its parent, so the full widget tree can be
constructed by the underlying Xt software. Each
widget has certain default properties, and other
properties that must be specified during the creation process; if the defaults are unsatisfactory they
can be overridden. The first three arguments of the
widget creation function used below are an optional
name for the particular instance of the widget, the
widget class, and the parent widget. The remaining arguments generally appear as a list of pairs
(exceptions will be encountered), each specifying
a widget resource and its value. In many cases
the resource names (all of which are prefixed by
XmN) are self-explanatory. Space prevents a more
detailed explanation of widgetry, another subject
about which much has been written.
One of the widgets available to assist with the
layout of other widgets is the Form widget. This
widget will be used as a container for the remaining
widgets introduced subsequently. The layout of all
the widgets used in the program is shown schematically in Figure 1. The Form widget is created by the
following call; it will eventually wrap itself around
its contents and attach them in ways to be specified:
wForm = XtVaCreateManagedWidget ("",
xmFormWidgetClass, wTop,
XmNmarginWidth,
5,
XmNmarginHeight,
5,
NULL);

The next widget is used to organize the various


controls that make up the user interface. It is a
RowColumn widget, and it arranges its widget chil-

dren in matrix form; here it is used to pack them


vertically in the order that they are defined:
wRowCol = XtVaCreateManagedWidget ("",
xmRowColumnWidgetClass, wForm,
XmNpacking,
XmPACK_TIGHT,
XmNspacing,
15,
XmNorientation,
XmVERTICAL,
XmNleftAttachment,
XmATTACH_FORM,
XmNtopAttachment,
XmATTACH_FORM,
NULL);

The . . .Attachment resources specify that certain


edges of this widget are to attach themselves to the
corresponding edges of the Form parent; this kind
of specification helps make layout much easier.
RowColumn

Frame (+ RowColumn)

ArrowButton

Scale

Label

PushButton
DrawingArea
Text

window frame

Form (+Shell)

Frame

Figure 1. Schematic layout of the widgets as constructed by the program.


The first two widget controls placed inside the
RowColumn widget are Scale widgets. These contain sliders that the user can drag using the mouse,
and so provide a convenient method for changing
the parameters of the simulation. The first Scale
specifies the number of unit cells in the horizontal direction, the second, the rate (in timesteps) at
which the display is updated. The rather cumbersome method used to add a title to each widget
(made shorter by the macro defined earlier) is a
fact of life. Following the creation of each widget, a
callback function is added whose task is responding
to user activity; while a widget is generally responsible for its visual appearance, dealing with the conComputers in Physics 11 (1997) 337-347

sequences of a user-requested change is the task of


the program. Here the common callback function
is named CbScale, with the final calling argument
(a 0 or 1) used to identify the widget responsible.
The callback is activated when the Scale value is
changed. Merely dragging the slider does not constitute a change; the mouse button must be released
at the end of the drag operation. (Mouse clicking
and keyboard arrows also generate changes Motif
specifies such activities in great detail.)
wScaleA = XtVaCreateManagedWidget ("",
xmScaleWidgetClass, wRowCol,
XmNorientation,
XmHORIZONTAL,
XmNwidth,
60,
XmNshowValue,
True,
XmNminimum,
10,
XmNmaximum,
60,
XmNvalue,
nAtomEdge,
XtVaTypedArg, XmNtitleString,
T_STRING ("Atoms"),
NULL);
XtAddCallback (wScaleA,
XmNvalueChangedCallback, CbScale,
(XtPointer) 0);
wScaleD = XtVaCreateManagedWidget ("",
xmScaleWidgetClass, wRowCol,
XmNorientation,
XmHORIZONTAL,
XmNwidth,
60,
XmNshowValue,
True,
XmNminimum,
1,
XmNmaximum,
100,
XmNvalue,
stepDraw,
XtVaTypedArg, XmNtitleString,
T_STRING ("Update"),
NULL);
XtAddCallback (wScaleD,
XmNvalueChangedCallback, CbScale,
(XtPointer) 1);

The control of both the density and the temperature is by means of pairs of ArrowButton widgets,
with a small value change being made each time
the display is refreshed until the button is released.
Each pair of ArrowButtons, together with a descriptive Label widget, are the children of a horizontal RowColumn widget, and this widget in turn is
placed inside a Frame widget that supplies the impression of an etched-in frame. The callback function is provided with an argument that not only distinguishes between widgets, but indicates whether
the button was pushed (armed) or released (disarmed).
for (n = 0; n < 2; n ++) {
wFrame = XtVaCreateManagedWidget ("",
xmFrameWidgetClass, wRowCol,
XmNshadowType,
XmSHADOW_ETCHED_IN,

NULL);
wRowCol2 = XtVaCreateManagedWidget ("",
xmRowColumnWidgetClass, wFrame,
XmNorientation,
XmHORIZONTAL,
XmNspacing,
5,
NULL);
XtVaCreateManagedWidget (buttonName[n],
xmLabelWidgetClass, wRowCol2, NULL);
for (j = 0; j < 2; j ++) {
wArrow = XtVaCreateManagedWidget ("",
xmArrowButtonWidgetClass, wRowCol2,
XmNarrowDirection, (j > 0) ?
XmARROW_UP : XmARROW_DOWN,
NULL);
XtAddCallback (wArrow, XmNarmCallback,
CbArrow, (XtPointer) (3 * n + 2 * j));
XtAddCallback (wArrow, XmNdisarmCallback,
CbArrow, (XtPointer) (3 * n + 1));
}
}

The next control widget to be added to the original RowColumn widget is the PushButton used to
start and stop the run. The activate callback indicates that the PushButton has been pressed and
then released by clicking the left mouse button.
wButtonS = XtVaCreateManagedWidget ("",
xmPushButtonWidgetClass, wRowCol,
XtVaTypedArg, XmNlabelString,
T_STRING ("Start"),
NULL);
XtAddCallback (wButtonS,
XmNactivateCallback, CbButton, 0);

We now come to the portion of the window where


the actual rendering of the MD system occurs. The
drawing operation involves a DrawingArea widget,
which, for aesthetic reasons is enclosed by a Frame
widget designed to impart a feeling of depth. The
Frame itself is attached to three of the four edges
of the bounding Form widget. The desired screen
dimensions of the DrawingArea must be provided.
It is also necessary to specify a callback function to
be invoked whenever the widget contents have to
be redrawn after an exposure event, which occurs
whenever a hidden part of the widget (covered by
some other window on the screen) becomes visible.
Unlike most widgets, which handle their own imagery, the DrawingArea has too great a variety of
uses for this redrawing to be performed automatically.
wFrame = XtVaCreateManagedWidget ("",
xmFrameWidgetClass, wForm,
XmNrightAttachment,
XmATTACH_FORM,
XmNtopAttachment,
XmATTACH_FORM,
XmNbottomAttachment,
XmATTACH_FORM,

Computers in Physics 11 (1997) 337-347

XmNshadowType,
XmSHADOW_IN,
NULL);
wDraw = XtVaCreateManagedWidget ("",
xmDrawingAreaWidgetClass, wFrame,
XmNwidth,
wDrawSize.x,
XmNheight,
wDrawSize.y,
NULL);
XtAddCallback (wDraw,
XmNexposeCallback, CbExpose, 0);

The final widget used here is for displaying textual output (alternatively, we could have overlaid
the text on the MD imagery). The Text widget is
designed for both input and output, and includes
all manner of text editing capabilities; here we use
it merely for output. Certain resources must be
specified to obtain the required appearance and behavior, and the widget is positioned by attaching it
not only to the Form on the left side but also to the
Frame on the right and the RowColumn above.
wText = XtVaCreateManagedWidget ("",
xmTextWidgetClass, wForm,
XmNcolumns,
12,
XmNrows,
4,
XmNeditable,
False,
XmNeditMode,
XmMULTI_LINE_EDIT,
XmNcursorPositionVisible, False,
XmNhighlightThickness, 0,
XmNshadowType,
XmSHADOW_IN,
XmNleftAttachment,
XmATTACH_FORM,
XmNrightAttachment,
XmATTACH_WIDGET,
XmNrightWidget,
wFrame,
XmNrightOffset,
10,
XmNtopAttachment,
XmATTACH_WIDGET,
XmNtopWidget,
wRowCol,
XmNtopOffset,
40,
NULL);

Two small tasks remain to complete the display


setup. The first is to create a graphic context (GC)
that will be used to hold information about how
X rendering is to be done the current drawing
color for example. The second is a consequence
of the need for smooth animation. Rather than
drawing directly to the screen after erasing the previous image and producing intolerable amounts of
flicker, the image is drawn to offscreen memory using a pixmap. This offscreen memory is subsequently copied (very rapidly) to the display buffer.
Thus we have to create (and initially clear see
later) a pixmap of identical size to the DrawingArea
widget.
dpy = XtDisplay (wTop);
scrn = DefaultScreen (dpy);
gc = XCreateGC (dpy, RootWindow (dpy,
scrn), 0, NULL);

pixmap = XCreatePixmap (dpy,


RootWindow (dpy, scrn),
wDrawSize.x, wDrawSize.y,
DefaultDepth (dpy, scrn));
XSetForeground (dpy, gc, appData.pixel[0]);
XFillRectangle (dpy, pixmap, gc, 0, 0,
wDrawSize.x, wDrawSize.y);

This completes the creation of the graphic interface,


but nothing has yet appeared on the screen; the
entire widget tree must now be realized:
XtRealizeWidget (wTop);
}

Graphical output
Drawing the current state of the MD system requires rendering a set of suitably positioned disks,
with extra copies needed for any periodic replicas
that intrude into the region. These drawing operations are handled by Xlib, but because the function
for drawing a filled circle has many arguments, we
define a briefer macro alternative:
#define FILL_DISK(x,y) \
XFillArc (dpy, pixmap, gc, x, y, \
diam, diam, 0, 360 * 64)

The first task is to clear the pixmap by painting


the entire rectangular area black. (The index of
this color in the X colormap was placed in appData.pixel[0] when the resources were processed
earlier.) The drawing color is then switched to green
(whose colormap index is in appData.pixel[1]),
and the disks together with any visible parts
of their periodic images are drawn, all suitably
scaled:
void Redraw ()
{
char buff[60]; double scale;
int diam, dx, dy, inxHi, inxLo,
inyHi, inyLo, n, x, y;
XSetForeground (dpy, gc, appData.pixel[0]);
XFillRectangle (dpy, pixmap, gc, 0, 0,
wDrawSize.x, wDrawSize.y);
XSetForeground (dpy, gc, appData.pixel[1]);
scale = wDrawSize.x / region.x;
diam = 0.9 * scale;
DO_ATOM {
x = scale * (0.5 * region.x + r[n].x) diam / 2;
y = scale * (0.5 * region.y - r[n].y) diam / 2;
FILL_DISK (x, y);
inxLo = (x >= 0);
inxHi = (x <= wDrawSize.x - diam);
inyLo = (y >= 0);
inyHi = (y <= wDrawSize.y - diam);
if (! (inxLo && inxHi &&
inyLo && inyHi)) {
Computers in Physics 11 (1997) 337-347

dx = (inxLo ? -1 : 1) * wDrawSize.x;
dy = (inyLo ? -1 : 1) * wDrawSize.y;
if (inyLo && inyHi)
FILL_DISK (x + dx, y);
else if (inxLo && inxHi)
FILL_DISK (x, y + dy);
else {
FILL_DISK (x + dx, y);
FILL_DISK (x, y + dy);
FILL_DISK (x + dx, y + dy);
}
}
}

The function Refresh (below) does the copying of


the pixmap to the DrawingArea widget. The output to appear in the Text widget is written to a
character array which is then used to update the
widget. Finally, the program and the X server are
synchronized by flushing the X queue.
Refresh ();
sprintf (buff,
"Step: %d \nE_tot: %.3f \n"
"E_kin: %.3f \nDens: %.2f",
stepCount, kinEnergy + potEnergy,
kinEnergy, density);
XmTextSetString (wText, buff);
XSync (dpy, False);
}
void Refresh ()
{
XCopyArea (dpy, pixmap, XtWindow (wDraw),
gc, 0, 0, wDrawSize.x, wDrawSize.y,
0, 0);
}

Event processing
The callback functions come next. Each is invoked
by one or more widgets in response to certain user
activities. The arguments of these functions have a
particular form. The first points to the widget itself,
the second is the user-defined data item specified
when the callback was created, and the third is a
pointer to a data structure associated with the X
event to which the widget is responding.
The first of these callback functions handles the
two Scale widgets. We start by determining which
widget was involved from the second callback argument, and the new value set by the user. If it
was the particle count that was changed, then the
simulation is reinitialized. If it was the interval between display updates, then this value is simply updated.
void CbScale (Widget w,
XtPointer clientData, XtPointer callData)

{
int id, v;
id = (int) clientData;
v = ((XmScaleCallbackStruct *)
callData)->value;
if (id == 0) {
nAtomEdge = v;
InitializeRun ();
} else stepDraw = v;
}

The next pair of callbacks handle the ArrowButtons and PushButton. For the former it is simply
a matter of interpreting the second callback argument to decide what is required. For the latter, the
state of the simulation is switched between running
and not running, and the label on the PushButton
is updated accordingly. Changes to the number of
particles while the simulation is in progress are disallowed by desensitizing the relevant Scale widget.
void CbArrow (Widget w,
XtPointer clientData, XtPointer callData)
{
int id;
id = (int) clientData;
if (id < 3) changeTemp = id - 1;
else changeDens = id - 4;
}
void CbButton (Widget w,
XtPointer clientData, XtPointer callData)
{
running = ! running;
XtVaSetValues (wButtonS, XtVaTypedArg,
XmNlabelString,
T_STRING (running ? "Stop" : "Start"),
NULL);
XtSetSensitive (wScaleA, ! running);
}

The final callback handles exposure events for the


DrawingArea widget. Several such events can occur
together as distinct rectangular parts of the widget
are exposed; the function below identifies the last
event of the sequence (in the manner shown) to ensure that the image is refreshed only once:
void CbExpose (Widget w,
XtPointer clientData,
XmDrawingAreaCallbackStruct *cbs)
{
if (((XExposeEvent *)
cbs->event)->count == 0) Refresh ();
}

The two remaining functions needed to complete


the program are those for changing the temperature and density in response to user requests. In
both cases a check is made to ensure that the new
Computers in Physics 11 (1997) 337-347

value is within the allowed range. For temperature changes the current velocities are rescaled; for
the density both the region size and (if the region
is made smaller) the coordinates are rescaled, and
a fresh memory allocation for the cell list is made
based on the new region size.
void SetNewTemp (double f)
{
double vvSum; int n;
if (f > 1 && temperature > 5 ||
f < 1 && temperature < 0.02) return;
vvSum = 0;
DO_ATOM {
VScale (rv[n], f);
vvSum += VLenSq (rv[n]);
}
temperature = vvSum / (2 * nAtom);
}

control widgets is unspecified, as are the text font


and character sizes used for the widget labels and
textual output. Also unspecified are the sizes of
some of the graphical features associated with the
widgets, such as the borders surrounding the buttons. For this reason, what actually appears on the
screen may differ slightly from one type of machine
to another, and also depend on the way the system
has been configured. The general approach recommended for X programming is to avoid specifying
such details within the program, unless there is a
particular reason for doing so, and leave the choice
up to the user. For example, we showed earlier how
the user can choose the widget background color.

void SetNewDens (double f)


{
int n;
if (f > 1 && density < 0.05 ||
f < 1 && density > 1.25) return;
if (f < 1) {
DO_ATOM VScale (r[n], f);
}
density /= SQ (f); VScale (region, f);
VSCopy (cells, 1 / rCut, region);
ALLOC (cellList, nAtom +
VProd (cells), int);
}

Additional details
Only a few minor details remain. A number of include files must be specified. Those required for a
typical C application are math.h, stdlib.h, and
stdio.h. Most widgets also require their own include files, all located in subdirectory Xm of the
include-file directory. In our case the list is ArrowB.h, DrawingA.h, Form.h, Frame.h, Label.h,
PushB.h, RowColumn.h, Scale.h, Text.h, and MwmUtil.h; these in turn include other files. All functions should be prototyped as a matter of habit.
Finally, when compiling the program, the libraries
should be listed in the order: -lXm for Motif, -lXt
for the X Toolkit, -lX11 for Xlib itself, and -lm for
the C mathematical library.
The end result of this discussion should be an interactive MD simulation which looks very similar to
the one shown in Figure 2. In a number of details
connected with visual appearance we have chosen to
rely on the default settings in effect. Thus the background color of the surrounding window and all the

Figure 2. A picture of what is typically seen on the


screen. The colors, fonts and other minor details
may differ, depending on the type of computer used.
As increasingly complex models are studied, the
ability to visualize the behavior and interact with
the simulation (for example, by changing the problem parameters or choosing different ways to display
the behavior) takes on added importance. At first
sight, X and Motif may appear complicated, but the
same approach introduced in this article can be used
in much more complex applications. New concepts
and programming techniques must be mastered in
order to gain a foothold in this perhaps unfamiliar computing milieu, only some of which could be
covered here. However, once the reader overcomes
this obstacle, computer simulation especially MD
becomes a vastly richer experience.
Computers in Physics 11 (1997) 337-347

Suggestions for further study


(1) Download the source code, mddisk.c, from
ftp:// www.aip.org/cip/cip sourcecode. Compile
it without any changes and see what the program
does. Melting, freezing, dislocations, and diffusion
are examples of phenomena that can be examined
visually while running the simulation. Starting
from the initial state, gradually vary the temperature and/or the density in either direction to
observe how the system responds.
(2) Modify the program source to allow optional
trajectory plots (the X function for line drawing
XDrawLine will be needed).
(3) Measure the instantaneous pressure using the
virial definition [5]. Show the results as numerical
values, or for example, as a scrolling graph in a
separate window. (The latter will require learning
more about X and Motif [1,2,8]).
(4) Extend the range of the force to include the
attractive part of the Lennard-Jones interaction
(e.g., rc = 2.2) so that cluster formation can be
observed (e.g., at density 0.5 and temperature 0.4).
(5) Use neighbor lists [5] to improve the efficiency
of the interaction computations.
(6) Consider more extensive modifications of the
program source (further discussion appears in [5]).
For example, rigidly join several disks to form a linear body and then simulate a fluid of these objects
to study a nematic liquid crystal; flexibly link a series of disks to model polymer chains; extend the
simulations to three dimensions (with the addition
of three-dimensional graphics).

4. D. C. Rapaport, Comput. Phys. 3(5), 18 (1989);


D. C. Rapaport and M. Meyer, in Fractals in Science, A. Bunde and S. Havlin, eds. (Springer,
1994), p.257.
5. D. C. Rapaport, The Art of Molecular Dynamics
Simulation (Cambridge University Press, 1995).
6. M. P. Allen and D. J. Tildesley, Computer Simulation of Liquids (Oxford University Press, 1987).
7. H. Gould and J. Tobochnik, An Introduction to
Computer Simulation Methods: Applications to
Physical Systems (Addison-Wesley, 1996).
8. There is a great deal of useful information about
X and Motif available on the web; one of the many
places to begin looking is http://www.x.org.

From the editors.


More information on molecular dynamics is available on the authors web site, http://www.ph.biu.
ac.il/rapaport. Please send us your comments,
suggestions and manuscripts for future columns.
References
1. D. Heller and P. Ferguson, Motif Programming
Manual, 2nd ed. (OReilly, 1994).
2. A. Nye, Xlib Programming Manual, 3rd ed.
(OReilly, 1992).
3. A. Nye and T. OReilly, X Toolkit Intrinsics
Programming Manual, Motif Edition, 2nd ed.
(OReilly, 1992).
Computers in Physics 11 (1997) 337-347

You might also like