MDX Tutorial
Using the MDIO Library
The MDIO library reads X-Plor force field parameter files and a PSF file, and will then assemble the array representation for the molecular system. There are also routines to read and write PDB coordinate files and NAMD-style binary restart files and routines to write DCD files.
Reading force field parameters:
This is done using the
mdio_Param
class.
mdio_Param *param; param = mdio_createParam(); mdio_readParam(param, PARAM_FILE);
Reading topology:
This is done using the
mdio_Topo
class.
mdio_Topo *topo; topo = mdio_createTopo(); mdio_readTopo(topo, PSF_FILE);
Indexing force field parameters with topology:
The following is considered to be a
mdio_Topo
class method that also acts on a
mdio_Param
object.
mdio_indexParamTopo(topo, param);
Reading coordinate PDB file:
This is done using the
mdio_Pdbcoord
class.
mdio_Pdbcoord *pdb; pdb = mdio_createPdbcoord(); mdio_readPdbcoord(pdb, PDB_FILE, natoms); /* natoms is number of atoms */
Using the Force Library
The
Force library
evaluates a CHARMM-style force field with cutoff nonbonded interactions.
You configure its behavior using bit flags to select which
potential functions will be contributing to the array of total force.
You can also receive the separated force contributions for each atom,
if desired.
The primary class is Force
with private members.
There are three helper classes:
-
ForceParam
- used for setup -
ForceEnergy
- provides container for computed potential energies -
ForceResult
- provides container for array of total forces and any desired partial force contributions
Before initializing the helper objects, it is very important to clear their memory contents to all zeros:
ForceParam fp; ForceEnergy fe; ForceResult fr; memset(&fp, 0, sizeof(ForceParam)); memset(&fe, 0, sizeof(ForceEnergy)); memset(&fr, 0, sizeof(ForceResult));
Use the
mdio_Param
object to set up the
ForceParam
force field parameters:
fp.atomprm = mdio_getAtomParam(param, &n); fp.atomprm_len = n; fp.bondprm = mdio_getBondParam(param, &n); fp.bondprm_len = n; fp.angleprm = mdio_getAngleParam(param, &n); fp.angleprm_len = n; fp.dihedprm = mdio_getDihedParam(param, &n); fp.dihedprm_len = n; fp.imprprm = mdio_getImprParam(param, &n); fp.imprprm_len = n; fp.nbfixprm = mdio_getNbfixParam(param, &n); fp.nbfixprm_len = n;
Use the
mdio_Topo
object to set up the
ForceParam
topology:
fp.atom = mdio_getAtomTopo(topo, &natoms); fp.atom_len = natoms; /* number of atoms in system */ fp.bond = mdio_getBondTopo(topo, &n); fp.bond_len = n; fp.angle = mdio_getAngleTopo(topo, &n); fp.angle_len = n; fp.dihed = mdio_getDihedTopo(topo, &n); fp.dihed_len = n; fp.impr = mdio_getImprTopo(topo, &n); fp.impr_len = n; fp.excl = mdio_getExclTopo(topo, &n); fp.excl_len = n;
Set up the other needed
ForceParam
fields:
fp.cutoff = CUTOFF; fp.switchdist = SWITCHDIST; fp.elec_const = MD_COULOMB; /* MD_COULOMB is from mdtypes.h */ fp.dielectric = DIELECTRIC; fp.scaling14 = SCALING14; fp.flags = FORCE_MD_VACUUM; /* FORCE_MD_VACUUM is collection of flags for smooth cutoff MD, scaled 1-4 exclusions, no boundary restraints */
Provide to
ForceResult
an array buffer space for the total forces:
MD_Dvec *f; f = (MD_Dvec *) calloc(natoms, sizeof(MD_Dvec)); fr.f = f;
Now that we have the helper objects properly initialized, we can call the Force constructor and setup functions:
Force feval; force_init(&feval); force_setup(&feval, &fp, &fe, &fr);
Using the Step Library
The
Step library
performs time stepping with leapfrog (velocity Verlet) integration.
It also can initialize velocities and compute kinetic energy and temperature
reductions.
The primary class is Step
with private members.
There are two helper classes:
-
StepParam
- used for setup -
StepSystem
- provides arrays for trajectories, returns kinetic energy and temperature values
As before, it is very important to reset the memory contents of the helper objects:
StepParam sp; StepSystem sys; memset(&sp, 0, sizeof(StepParam)); memset(&sys, 0, sizeof(StepSystem));
Use the
mdio_Topo
object to set up the
StepParam
topology:
sp.atom = mdio_getAtomTopo(topo, &n); sp.natoms = n;
Use the
mdio_Pdbcoord
object to set up the
StepSystem
force field parameters:
MD_Dvec *pos; pos = mdio_getPdbcoord(pdb, &n); sys.pos = pos;
Provide to
StepSystem
an array buffer space for the atom velocities:
MD_Dvec *vel; vel = (MD_Dvec *) calloc(natoms, sizeof(MD_Dvec)); sys.vel = vel;
For this example, the Force
class
is our only resource for force evaluation,
so it is OK to also assign to
StepSystem
the array buffer space allocated previously for the
ForceResult
total forces:
sys.force = f;
Set up the remaining
StepParam
fields:
sp.random_seed = SEED; sp.timestep = TIMESTEP; sp.force_compute = my_force_compute; /* our own force computation routine */ sp.force_object = (void *) (&feval); /* and data that must be passed to it */ sp.ndegfreedom = 3*natoms - 3; /* subtract 3 because we will be removing center of mass motion */
This is the tricky part!
The my_force_compute
from above is
a function that is called by the Step
method step_compute()
in order to receive the system forces.
Here is the definition of my_force_compute
:
int my_force_compute(void *vfeval, MD_Dvec *f, MD_Dvec *pos) { Force *feval = (Force *) vfeval; /* Force class already has pointer to the f array */ return force_compute(feval, pos, NULL); /* set "wrap" array to NULL */ }We recover the
Force
object by typecasting
the address assigned to sp.force_object
and use this to invoke the force_compute()
method.
Since we previously assigned sys.force
to the same array buffer space as fr.f
,
we do not need to explicitly deal with the force array
f
expected by step_compute()
(it is automatically populated).
Note that if we were doing more than this for the force evaluation,
e.g. computing long-range electrostatic forces using the
Mgrid
multilevel summation solver,
we would use my_force_compute()
to manage
calls to other force evaluation libraries
and explicitly sum the results into the f
array.
Now we are ready to initialize the
Step
object and determine the initial velocities:
Step step; /* initialize Step class */ step_init(&step, &sp); /* determine initial velocities */ step_set_random_velocities(&step, &sys, TEMPERATURE); step_remove_com_motion(&step, &sys);
The time stepping and the reporting of results after each step is done by the following loop:
/* perform time stepping */ for (i = 0; i < NUMSTEPS; i++) { /* print results after each step */ step_compute(&step, &sys, 1); step_find_reductions(&step, &sys); printf(" %5d %11.6f %11.6f %11.6f %11.6f\n", i+1, fe.pe + sys.kinetic_energy, sys.temperature, sys.kinetic_energy, fe.pe); }
Once the simulation is finished, our explicit memory allocations along with that done implicitly through the objects can all be freed:
/* free memory our memory allocations */ free(vel); free(f); /* finished with objects */ step_done(&step); force_done(&feval); mdio_destroyPdbcoord(pdb); mdio_destroyTopo(topo); mdio_destroyParam(param);