In this unit, you will learn about NAMD's TclForces functionality. Although many user needs were anticipated in the development of NAMD, to anticipate them all specifically would be impossible. TclForces allows users to easily supplement the built-in constraint and force functionality with script-based forces.
The basic idea of tclForces is very simple: we provide NAMD
with a Tcl script which tells NAMD to apply certain forces to certain
atoms. The only special requirement of this script is that it define a
command named calcforces. This command is called by NAMD each
timestep. Within the script, we can access atomic positions and
masses, as well as parameters set in the NAMD configuration file. In
this unit, we will see how to use tclForces through four examples
of increasing complexity.
For our first exposure to tclForces, we will do one of the
simplest thing one could imagine: apply a constant net force to a
system.
| tclforces on | |
| set linaccel "30 0 0" | |
| tclforcesscript push.tcl |
The first line simply turns tclForces on. The second sets a parameter for the acceleration that we'll use in the script, as we'll soon see. The last line is the name of the script which actually does the work.
| set numatoms 1231 |
| set atoms {} | |
| for { set i 1 } { $i <= $numatoms } { incr i } { | |
| lappend atoms $i | |
| } |
| foreach atom $atoms { | |
| addatom $atom | |
| } |
The structure we're using is ubiquitin, the same protein as that used in the VMD and NAMD Tutorials. Here, we're using the protein alone, in vacuum, and so the structure contains just 1231 atoms. We first build a list of the atomic indices (NAMD atom indices, unlike VMD atom indices, start at 1.) For each atom, we then call the addatom command. This tells NAMD that we want access to this atom's coordinates. Forces may be applied to an atom without this call.
| set linaccel_namd [vecscale [expr 1.0/418.68] $linaccel] | |
| print "Linear acceleration imparted: ($linaccel) Ang*ps^ -2" |
Note that to print messages, we must use the print command, instead of the normal Tcl command puts.
| proc calcforces { } { | |
| global atoms numatoms linaccel_namd | |
| loadcoords coords | |
| loadmasses masses | |
| set comsum "0 0 0" | |
| set totalmass 0 | |
| foreach atom $atoms { | |
| set force [vecscale $masses($atom) $linaccel_namd] | |
| addforce $atom $force | |
| set comsum [vecadd $comsum |
|
| [vecscale $masses($atom) $coords($atom)]] | |
| set totalmass [expr $totalmass + $masses($atom)] | |
| } | |
| print "Center of mass = [vecscale [expr 1.0/$totalmass] |
|
| $comsum]" | |
| } |
The first line of the command is very important. It basically imports the variables named, so that they can be accessed in the calcforces command.
Next we call the command loadcoords. This sets the variable coords to a Tcl array of coordinates of the atoms that have been added using addatom. In this case, of course, that is all the atoms of the system. Array elements in Tcl are accessed using parentheses. Similarly, loadmasses retrieves the atomic masses, putting them in an array named masses.
Then we loop through the atoms, applying the specified force to each using the addforce command. Along the way, we also calculate the center of mass of the protein. We see two more Tcl commands in actions here: vecadd and vecscale. These add two vectors and multiply a vector by a scalar, respectively. There is a third vector command, vecsub, which we will see a bit later.
namd2 push.namd > push.log
set log push.log
if you named the log file push.log. To run the script, either type
vmd -e plot.tcl
from the command line, or open VMD and source the script file in the Tk Console:
source plot.tcl
mol load psf common/ubiquitin.psf dcd push/push.dcd
If you could not produce the trajectory, there is a sample one in the example-output directory. Notice that the Langevin dynamics prevent the protein from moving too quickly.
We will now add a layer of complexity: we will force atoms
differently based on their coordinates. In this example, we will force
atoms in such a way that, in addition to a linear force, the protein
also rotates about an axis parallel to the z-axis and through the
protein's center of mass. Note that this represents a perturbation
which cannot be achieved using NAMD's built-in constant force and
rotating constraint functionalities.
| tclforces on | |
| set linaccel "30 0 0" | |
| set angaccel 1 | |
| tclforcesscript rot-a.tcl |
This is very similar to the previous example, but here we provide an additional scalar to describe the angular acceleration.
| set linaccel_namd [vecscale [expr 1.0/418.68] $linaccel] | |
| set angaccel_namd [expr double($angaccel)/418.68] | |
| set PI 3.1415926535898 |
| print "Linear acceleration imparted: ($linaccel) Ang*ps^ -2" | |
| print "Angular acceleration imparted: (0 0 $angaccel) Rad*ps^ -2" |
We convert from natural units to NAMD units, just like before. Then we print some informational text using the print command.
| proc calcforces { } { | |
| global atoms numatoms linaccel_namd angaccel_namd | |
| global PI |
| loadcoords coords | |
| loadmasses masses |
| set comsum "0 0 0" | |
| set totalmass 0 | |
| foreach atom $atoms { | |
| set comsum [vecadd $comsum [vecscale $masses($atom) |
|
| $coords($atom)]] | |
| set totalmass [expr $totalmass + $masses($atom)] | |
| } | |
| set com [vecscale [expr 1.0/$totalmass] $comsum] | |
| print "Center of mass = $com" |
| foreach atom $atoms { | |
| set linforce [vecscale $masses($atom) $linaccel_namd] | |
| set r [vecsub $coords($atom) $com] | |
| set x [lindex $r 0] | |
| set y [lindex $r 1] | |
| set rho [expr sqrt(pow($x, 2) + pow($y, 2))] | |
| set phi [expr atan2($y, $x) + $PI/2] |
| if { $atom == 1 } { | |
| print "atom $atom: phi = $phi" | |
| } |
| set angdir "[expr cos($phi)] [expr sin($phi)] 0.0" | |
| set angforce [vecscale [expr $masses($atom) * |
|
| $angaccel_namd * $rho] $angdir] | |
| set force [vecadd $linforce $angforce] | |
| addforce $atom $force | |
| } | |
| } |
The first section above calculates the distance from the rotation
axis, rho, and the angle from the x-axis at which the force
should be applied, phi (
from the angle of the
position vector, hence the addition of
radians.)
Fig. 3 shows how these variables are related.
After printing the force angle associated with one atom so we can monitor the rotation, we find a unit vector (i.e. a vector with length one) pointing in the phi direction. We then multiply this by the mass of the atom and its distance from our rotation axis, giving us the correct angular force for the angular acceleration requested in the NAMD configuration file. Finally, we add the linear and angular forces to get the total force.
namd2 rot-a.namd > rot-a.log
This should again take just a few minutes.
You should see something like Fig. 4. Plot the center of mass as well, by opening plot.tcl and changing the var variable to "Center of mass". This can be accomplished by simply commenting the first set var line, and uncommenting the other:
| #set var "atom 1: phi" | |
| set var "Center of mass" |
mol load psf common/ubiquitin.psf dcd rot-a/rot-a.dcd
Challenge: Find the dependence of the terminal linear and angular velocities on the langevinDamping parameter of the simulation, set in the configuration file, as well as the dependence on the applied force.
This example deals with the important ability to force a subset
of your atoms. In practice, you almost never want to apply force to
all atoms in the system. In this section, we will apply forces only to
the backbone atoms of the protein. We accomplish this by making a
special ``target atom'' PDB file, using the beta column to mark the
atoms we want to apply force to. In addition, we will use the
occupancy column to tell our script the atomic masses, in order to
demonstrate how to provide the script with additional information.
Look at the end of the file, where we set up tclForces. We see a new line:
set targetAtomPdb ../common/ubiquitin_solvate_backbone.pdb
With this line, we specify the aforementioned target atom PDB file. Thus, our first task is to produce that file. We will use a VMD script to accomplish this.
| set pdb common/ubiquitin_solvate.pdb | |
| set psf common/ubiquitin_solvate.psf | |
| set targetPdb common/ubiquitin_solvate_backbone.pdb |
Next, we set the selection text we will use to select our atoms, and the beta value that these atoms will receive:
| set selection "protein and backbone" | |
| set targetMark "1.00" |
| mol load psf $psf pdb $pdb | |
| set all [atomselect top all] | |
| $all set beta 0 | |
| $all set occupancy 0 |
| set target [atomselect top $selection] | |
| set masses [$target get mass] | |
| $target set beta $targetMark | |
| $target set occupancy $masses |
| $all writepdb $targetPdb | |
| exit |
vmd -dispdev text -e ../makeTargetAtomPdb.tcl
(Note: For Windows users, open the VMD GUI and source the script file in the Tk Console.)
If you were unable to run the script, copy the file example-output/
ubiquitin_solvate_backbone.pdb into the common
directory.
set targetMark "1.00"
| set targets {} | |
| set masses {} | |
| set inStream [open $targetAtomPdb r] | |
| foreach line [split [read $inStream] |
|
| set string1 [string range $line 0 3] | |
| set string2 [string range $line 6 10] | |
| set string3 [string range $line 17 20] | |
| set string4 [string range $line 12 15] | |
| set string5 [string range $line 46 53] | |
| set string6 [string range $line 72 75] | |
| set string673 [string range $line 73 75] | |
| set string7 [string range $line 22 25] | |
| set string8 [string range $line 62 65] | |
| set string9 [string range $line 55 60] |
| if { ([string equal $string1 {ATOM}] || |
|
| [string equal $string1 {HETA}] ) && | |
| [string equal $targetMark $string8] } { | |
| lappend targets "[string trim $string6] |
|
| [string trim $string7] [string trim $string4]" | |
| lappend masses "[string trim $string9]" | |
| } | |
| } | |
| close $inStream |
| set atoms {} | |
| foreach target $targets { | |
| foreach {segname resid atom} $target { break } | |
| set atomindex [atomid $segname $resid $atom] | |
| lappend atoms $atomindex | |
| addatom $atomindex | |
| } |
Notice the third line. This is a common trick in Tcl for succinctly extracting the elements of a list into individual scalar variables.
| set numatoms [llength $atoms] | |
| if { $numatoms > 0 } { | |
| set applyforce 1 | |
| } else { | |
| print "WARNING: no target atoms have been detected" | |
| set applyforce 0 | |
| } |
| set linaccel_namd [vecscale [expr 1.0/418.68] $linaccel] | |
| set angaccel_namd [expr double($angaccel)/418.68] | |
| set PI 3.1415926535898 | |
| print "Linear acceleration imparted: ($linaccel) Ang*ps^ -2" | |
| print "Angular acceleration imparted: (0 0 $angaccel) Rad*ps^ -2" |
| proc calcforces { } { | |
| global atoms numatoms masses linaccel_namd angaccel_namd | |
| global applyforce | |
| global PI |
| if { $applyforce } { | |
| loadcoords coords | |
| set comsum "0 0 0" | |
| set totalmass 0 | |
| foreach atom $atoms mass $masses { | |
| set comsum [vecadd $comsum [vecscale $mass $coords($atom)]] | |
| set totalmass [expr $totalmass + $mass] | |
| } | |
| set com [vecscale [expr 1.0/$totalmass] $coordsum] | |
| print "Center = $com" |
| foreach atom $atoms mass $masses { | |
| set linforce [vecscale $mass $linaccel_namd] | |
| set r [vecsub $coords($atom) $com] | |
| set x [lindex $r 0] | |
| set y [lindex $r 1] | |
| set rho [expr sqrt(pow($x, 2) + pow($y, 2))] | |
| set phi [expr atan2($y, $x) + $PI/2] |
| if { $atom == 1 } { | |
| print "atom $atom: phi = $phi" | |
| } |
| set angdir "[expr cos($phi)] [expr sin($phi)] 0.0" | |
| set angforce [vecscale [expr $angaccel_namd * $rho * |
|
| $mass] $angdir] | |
| set force [vecadd $linforce $angforce] | |
| addforce $atom $force | |
| } | |
| } | |
| } |
namd2 rot-b-short.namd > rot-b-short.log
If you have access to a cluster, run it on the cluster--refer to local instructions for how this is done. This simulation will run for just 100 steps, so that the benchmark times are printed. In the next section, we will see how to improve this performance.
If you are interested, examine the files rot-b-long.log and rot-b-long.dcd in the example-output directory, which were produced with a slightly longer simulation. When loading the trajectory in VMD, use the PSF common/ubiquitin_solvate.psf
In most cases, it's unnecessary to recalculate the force values
applied at each time step. Depending on the situation, one may be able
to recalculate every 1000 steps, saving a lot of computational effort
and therefore increasing the speed of the simulation. In this section,
we will see how to do that.
set forcesRecalcFreq 10
As its name implies, this parameter sets how often our forces will be recalculated.
| set forces {} | |
| foreach index $atoms { | |
| lappend forces "0.0 0.0 0.0" | |
| } |
| set forcecount $forcesRecalcFreq | |
| set printcount 0 |
We first set up a forces list. This is the variable which holds the values of the forces we will apply to the atoms. We also set up some counter variables: forcecount and printcount are both incremented each timestep. When forcecount equals forcesRecalcFreq, the forces are recalculated, and forcecount is reset to zero; otherwise, the forces are not recalculated.
| proc calcforces { } { | |
| global atoms numatoms forcemult masses avgmass forces | |
| global applyforce forcesRecalcFreq | |
| global forcecount printcount | |
| global PI |
| if { $applyforce } { | |
| foreach atom $atoms force $forces { | |
| addforce $atom $force | |
| } |
| if { $forcecount == [expr $forcesRecalcFreq - 1] } { | |
| print "Adding atoms prior to reconfiguring forces at |
|
| $printcount" | |
| foreach atom $atoms { | |
| addatom $atom | |
| } | |
| } |
As we will see shortly, we call clearconfig after the forces are recalculated. This call erases all addatom records; without it, the coordinates of the atoms added will be available every timestep, independently of whether we call loadcoords, and therefore much of the potential speed gain will be lost. However, this means that addatom must be called again each time we want to recalculate forces, and because of technical details it has to be done at least one step before the coordinates will be used.
| if { $forcecount == $forcesRecalcFreq } { | |
| print "Recalculating forces at $printcount" |
| loadcoords coords | |
| set comsum "0 0 0" | |
| set totalmass 0 | |
| foreach atom $atoms mass $masses { | |
| set comsum [vecadd $comsum [vecscale $mass $coords($atom)]] | |
| set totalmass [expr $totalmass + $mass] | |
| } | |
| set com [vecscale [expr 1.0/$totalmass] $comsum] | |
| print "Center of mass = $com" |
| set forces {} | |
| foreach atom $atoms mass $masses { | |
| set linforce [vecscale $mass $linaccel_namd] | |
| set r [vecsub $coords($atom) $com] | |
| set x [lindex $r 0] | |
| set y [lindex $r 1] | |
| set rho [expr sqrt(pow($x, 2) + pow($y, 2))] | |
| set phi [expr atan2($y, $x) + $PI/2] |
| if { $atom == 1 } { | |
| print "atom $atom: phi = $phi" | |
| } |
| set angdir "[expr cos($phi)] [expr sin($phi)] 0.0" | |
| set angforce [vecscale [expr $angaccel_namd * $rho * |
|
| $mass] $angdir] | |
| set force [vecadd $linforce $angforce] | |
| lappend forces $force | |
| } |
| print "Step ${printcount}: Recalculated |
|
| [llength $forces] forces" | |
| set forcecount 0 | |
| clearconfig | |
| } | |
| incr forcecount | |
| } | |
| incr printcount | |
| return | |
| } |
namd2 rot-c-short.namd > rot-c-short.log
Again, this is a short simulation, running 100 steps, just long enough for benchmark timing information to be printed. Compare the speed of this simulation to the last. There should be a significant difference--comparing the example files rot-b-short.log and rot-c-short.log, which were run on just a single CPU, there is nearly a 15% speed increase. As the size of the system and the number of CPUs increase, applying tclForces efficiently becomes even more important: comparing rot-c-long.log with rot-b-long.log, both run on 20 processors, rot-c is almost 60% faster!
Once again, there are sample output files, rot-c-long.dcd and rot-c-long.log in the example-output directory, and should again be loaded together with the PSF file common/ubiquitin_solvate.psf.
Challenge: Take the provided file kcsa_closed.pdb, and
use the partial structure kcsa_open.pdb as a target to open
the channel using tclForces.
You now know the basics of tclForces. In the next section, you will
learn about a similar feature of NAMD called tclBC, which is suitable for
different circumstances.