Documents/NamdSample/namd210/lib/abf/abf_script.tcl (602 lines of code) (raw):
#############################################
# Generic ABF code #
# Jerome Henin <jerome.henin@uhp-nancy.fr> #
#############################################
#######################################################
# Separate source files for coordinate-specific stuff #
#######################################################
namespace eval ::ABF {
print ABF> ---------------------------------------------
print ABF> Adaptive Biasing Force protocol version $version
print ABF> ---------------------------------------------
print ABF>
print "ABF> Using coordinate type : $coordinate"
set sourceFile [format "%s/%s.tcl" $ABFdir $coordinate]
if ![file exists $sourceFile] {
error "ABF> TCL file $sourceFile not found"
} else { source $sourceFile }
# Print info about the coordinate-specific code
if [info exists ABFcoordID] { print "ABF> $ABFcoordID" }
array set forceDist {}
#####################################################
# Default values for optional parameters #
#####################################################
# SFM : Scaled-Force Method / Langevin
# This is in development phase, and not official
# For more info, read the code and E. Darve & A. Pohorille, J. Chem. Phys. (2001)
# First, use coordinate-specific defaults
if {[array exists ABFoptions]} {foreach option [array names ABFoptions] {
if {! [info exists $option]} {
set $option $ABFoptions($option)
if {[lsearch $silent $option] == -1} {
print [format "ABF> %16s : %-14s \[default\]" $option [expr $$option]]
}
} else {
if {[lsearch $silent $option] == -1} {
print [format "ABF> %16s : %s" $option [expr $$option]]
}
}
}}
# Then, the generic ones
foreach option [array names defaults] {
if {! [info exists $option]} {
set $option $defaults($option)
if {[lsearch $silent $option] == -1} {
print [format "ABF> %16s : %-14s \[default\]" $option [expr $$option]]
}
} elseif { (![array exists ABFoptions] || [lsearch [array names ABFoptions] $option] == -1)} {
if {[lsearch $silent $option] == -1} {
print [format "ABF> %16s : %s" $option [expr $$option]]
}
}
}
# Test mandatory parameters
foreach var $mandatory {
if {! [info exists $var]} {
error "ABF> Mandatory parameter $var is not set -- cannot start ABF"
} elseif { (![array exists ABFoptions] || [lsearch [array names ABFoptions] $var] == -1)\
&& ([lsearch [array names defaults] $var] == -1) \
&& ($var != "coordinate") } {
print [format "ABF> %16s : %s" $var [expr $$var]]
}
}
set timestep 0
# These are useful when using "moving boundaries"
# xiMin0 and xiMin0 + nMax * dxi are the limits of the data arrays
# xiMin and xiMax are the limits between the
# sampling/biasing domain and the confining biases
set xiMin0 $xiMin
set nMax [expr {int (($xiMax - $xiMin)/ $dxi) - 1}]
# this is a bit awkward, but it repairs truncation errors
if { [expr {$xiMin + ($nMax + 1.0) * $dxi}] < $xiMax } {incr nMax}
# These variables will keep track of moving boundaries
set firstBin 0
set lastBin $nMax
for {set i 0} {$i <= $nMax} {incr i} {
array set samples "$i 0"
array set accForce "$i 0"
}
if { [info exist SFM ] } {
print "ABF>"
print "ABF> *****************************************************"
print "ABF> Will be using the Scaled Force Method, not ABF"
print "ABF> *****************************************************"
print "ABF>"
}
if { $applyBias != "yes" } {
print "ABF>"
print "ABF> *****************************************************"
print "ABF> *** WARNING --- Biasing force will NOT be applied ***"
print "ABF> *****************************************************"
print "ABF>"
set applyBias no
}
if { $usMode != "no" } {
for {set i 0} {$i <= $nMax} {incr i} {
array set Usampling "$i 0"
}
print "ABF>"
print "ABF> **********************************************"
print "ABF> *** WARNING --- entering US mode, not ABF ***"
print "ABF> **********************************************"
print "ABF>"
print "ABF> Accumulating sampling data in [expr {$nMax + 1}] bins"
} else {
print "ABF> Accumulating force data in [expr {$nMax + 1}] bins"
}
set dSmooth [expr {$dSmooth / $dxi} ]
set minSamples [expr {($fullSamples > 2) ? ($fullSamples / 2) : 1}]
# Remove existing history file
if { $historyFile != "none" } {
set history [open $historyFile w]
close $history
}
##################################################
# Read previous results if available #
##################################################
foreach name $inFiles {
set in [open $name r]
print "ABF> Loading previous force data from $name ..."
# Skip initial comment lines
set ret [gets $in line]
while { ($ret > -1) && ("[string index $line 0]" == "#") } {
set ret [gets $in line]
}
set count 0
while { $ret > -1 } {
set xi [lindex $line 0]
set f [lindex $line 2]
set n [lindex $line 3]
if { $n && ($xi >= $xiMin) && ($xi < $xiMax) } {
set i [expr {int(($xi - $xiMin)/$dxi)}] ;# Current bin
incr samples($i) $n
set accForce($i) [expr {$accForce($i) + $f * $n}]
incr count $n
}
set ret [gets $in line]
}
print "ABF> $count sampling points retrieved"
close $in
}
}
# The calls for startup procedures are after procedure definitions
# In root namespace (::) for all procedures
# We HAVE to add the following procedure definition
proc veclength {v} {
return [expr {sqrt([veclength2 $v])}]
}
# Source a file from VMD
source $ABF::ABFdir/vectors.tcl
###################################################
# Procedure to be called by NAMD at each timestep #
###################################################
proc calcforces {} {
namespace eval ::ABF {
# First timestep : we don't have forces
if { $timestep == 0 } {
# must not be equal to $timestep - 1
set timeStored -2
set xi [ABFcoord] ;# for timestep 1
set cur [expr {int(($xi - $xiMin0)/$dxi)}] ;# Current bin
writeData ;# write initial data, if any
if { $restraintsOn } { restraints }
if { $writeXiFreq } { print ABF> Xi at timestep 0 : $xi}
incr timestep
return ;# nothing more to do in this timestep
}
# ***** Collect the force along the RC *****
if { ($xi >= $xiMin) && ($xi < $xiMax) } {
# To do only if we're in the right range of xi
# Force data is from previous timestep
# Same thing for coordinates
set fxi [expr {($timeStored == $timestep - 1)? \
[ABForce] - $storedForce : [ABForce]}]
if { $usMode != "no" } {
incr Usampling($cur)
# don't touch forces : just keep track of sampling
} else {
incr samples($cur)
set accForce($cur) [expr {$accForce($cur) + $fxi}]
}
if { $distFile != "none" } { forceDistrib } ;# Store force distribution in bin n
} elseif { [info exists SFM] } {
# we need it anyway if we use SFM
set fxi [expr {($timeStored == $timestep - 1)? \
[ABForce] - $storedForce : [ABForce]}]
}
if { $writeFxiFreq && [expr {$timestep % $writeFxiFreq} ] == 0 } {
print "ABF> Fxi at timestep $timestep : $fxi"
}
# Get coord data for current timestep
set old_xi $xi ;# for SFM
set xi [ABFcoord]
set cur [expr {int(($xi - $xiMin0)/$dxi)}] ;# Current bin
if { $restraintsOn } { restraints }
if { $writeXiFreq && [expr {$timestep % $writeXiFreq} ] == 0 } {
print "ABF> Xi at timestep $timestep : $xi"
}
# If requested, use moving boundaries
if { $moveBoundary } {
if { ($samples($firstBin) > $moveBoundary) && ($cur > $firstBin) && ($firstBin < [expr {$lastBin + 1} ]) } {
set xiMin [expr {$xiMin + $dxi}]
incr firstBin
print "ABF> Increased lower boundary to $xiMin"
}
if { ($samples($lastBin) > $moveBoundary) && ($cur < $lastBin) && ($firstBin < [expr {$lastBin + 1} ]) } {
set xiMax [expr {$xiMax - $dxi}]
set lastBin [expr {$lastBin - 1}]
print "ABF> Decreased upper boundary to $xiMax"
}
}
if { $xi >= $xiMin && $xi < $xiMax && ($applyBias != "no")} {
if {![info exists SFM]} {
# We're inside the reaction coordinate range : try to apply ABF
set F [fSmoothed]
set storedForce [ABFapply $F]
set timeStored $timestep
} else {
# now we're doing SFM
set ts 0.001 ;# ps
set friction 50.0 ;# amu / ps
set RT [ expr {8.314 * 300.0} ] ;# J/mol
# velocity along xi
set vxi [expr {($xi - $old_xi) / $ts}]
# stochastic force RMS (kcal/mol/A)
set sigma [expr {(1.0/4.18e3) * sqrt( 2 * $RT * $friction * 10.0 / $ts)}]
# normally distributed pseudo-random number
set sq 2.0
while { $sq >= 1.0 || $sq == 0.0 } {
set v1 [expr {2.0 * rand() - 1.0}]
set v2 [expr {2.0 * rand() - 1.0}]
set sq [expr {$v1*$v1 + $v2*$v2}]
}
set random [expr {$v1 * sqrt(-2.0 * log($sq) / $sq)}]
set FL [ expr {$sigma * $random - $friction * $vxi} ]
# Langevin force minus (approximate) force along xi
set storedForce [ABFapply [expr {$FL - $fxi}] ]
set timeStored $timestep
}
} else {
# We're out of the reaction coordinate range : consider applying bounding restraints
# (if applyBias is "no" and xiMin <= xi <= xiMax, we end up here and do nothing )
# special treatment for angles
if {($coordinate == "dihedral") && ($forceConst > 0.0) && (($xi < $xiMin) || ($xi >= $xiMax))} {
# compute the "real" differences
set dm [expr abs(fmod(abs($xi-$xiMin)+180, 360) - 180)]
set dM [expr abs(fmod(abs($xi-$xiMax)+180, 360) - 180)]
# if we are closer to xiMax, restrain down towards xiMax
# else restrain up towards xiMin
set rest [expr {($dm > $dM) ? -$forceConst * $dM : $forceConst * $dm}]
ABFapply $rest
} else {
if { ($xi < $xiMin) && ($forceConst > 0.0) } {
set rest [expr {$forceConst * ($xiMin - $xi)}]
ABFapply $rest
}
if { ($xi > $xiMax ) && ($forceConst > 0.0) } {
set rest [expr {$forceConst * ($xiMax - $xi)}]
ABFapply $rest
}
}
}
if { ($outputFreq > 0) && ([expr {$timestep % $outputFreq}]==0) } { writeData }
incr timestep ;# Count timesteps
return
} ;# namespace
}
#########################################
# This computes a smooth weighted #
# average force #
#########################################
proc fSmoothed {} {
namespace eval ::ABF {
if { $dSmooth < 1.0 } {
# Use the data in one bin
set i [expr {int( ($xi - $xiMin0) / $dxi)}]
set n $samples($i)
if { $n < $minSamples } { return 0 }
set F [expr {- $accForce($i)/$n}]
if { $fullSamples < 2} {
set factor 1.0
} else {
set factor [expr {($n-$minSamples)/(.0+$fullSamples-$minSamples)}]
}
if { $factor >= 1.0 } { return $F }
return [expr {$factor * $F}]
}
# ($xi - $xiMin0) / $dxi is between 0 and nMax
set x [expr {($xi - $xiMin0) / $dxi - 0.5}]
# Now x is between -0.5 and nBins - 0.5
set iMin [expr {($x-$dSmooth < 0.0) ? 0 : int($x-$dSmooth)+1}]
set iMax [expr {($x+$dSmooth >= $nMax ) ? $nMax : int($x+$dSmooth)}]
set norm 0.0
set n 0.0
set F 0.0
for { set i $iMin} { $i <= $iMax } { incr i } {
set c [expr {$dSmooth - abs($i-$x)}]
# c is the weight of bin i
set norm [expr {$norm + $c}]
set n [expr {$n + $c * $samples($i)}]
set F [expr {$F + $c * $accForce($i)}]
}
if { $n == 0.0 } { return 0 }
set F [expr {- $F / $n}]
# F is the opposite of our average force
set n [expr {$n / $norm}]
# n is the (weighted) average amount of sampling
if { $n < $minSamples } { return 0 }
if { $fullSamples < 2 } {
set factor 1.0
} else {
set factor [expr {($n-$minSamples)/(.0+$fullSamples-$minSamples)}]
}
if { $factor >= 1.0 } { return $F }
return [expr {$F * $factor}]
} ;# namespace
}
#########################################
# Called by calcforces at last timestep #
# and every outputFreq timesteps #
#########################################
proc writeData {} {
namespace eval ::ABF {
if { $historyFile != "none" && [expr {($timestep / $outputFreq) % 10 }] == 0 } {
set history [open $historyFile a]
puts $history "# Timestep $timestep"
puts $history "# xi A(xi) av_force n_samples"
set histFlag 1
} else {
set histFlag 0
}
set out [open $outFile w]
puts $out "# xi A(xi) av_force n_samples"
set pmf 0.0
set force 0
for {set i 0} {$i < [array size samples]} {incr i} {
set oldForce $force
set n $samples($i)
if {$n > 0} {
set force [expr {$accForce($i) / $n}]
} else {
set force 0.0
}
if { $i > 0 } {
set pmf [expr {$pmf - 0.5 * $dxi * ($force + $oldForce)}]
}
if { $usMode != "no" } {
puts $out [format "%12.3f %12.4f %12.4f %12d"\
[expr {$xiMin0 + ($i + 0.5) * $dxi}] $pmf $force $Usampling($i)]
if { $histFlag } {
puts $history [format "%12.3f %12.4f %12.4f %12d"\
[expr {$xiMin0 + ($i + 0.5) * $dxi}] $pmf $force $Usampling($i)]
}
} else {
puts $out [format "%12.3f %12.4f %12.4f %12d"\
[expr {$xiMin0 + ($i + 0.5) * $dxi}] $pmf $force $n]
if { $histFlag } {
puts $history [format "%12.3f %12.4f %12.4f %12d"\
[expr {$xiMin0 + ($i + 0.5) * $dxi}] $pmf $force $n]
}
}
}
close $out
if { $histFlag } {
puts $history "&"
close $history
}
if {$distFile != "none"} {
set dist [open $distFile w]
for {set i 0} {$i < [array size samples]} {incr i} {
puts $dist "&"
puts $dist "\# [expr {$xiMin0 + ($i + 0.5) * $dxi}]"
for {set f [expr {- $fMax}]} {$f < $fMax} {set f [expr {$f + $df}]} {
if {[info exists forceDist($i\ $f)]} {
puts $dist [format "%12.4f %d" $f $forceDist($i\ $f)] }
}
}
close $dist
}
print "ABF> Data written to output files at timestep $timestep"
} ;# namespace
}
#########################################
# Collect force distribution #
#########################################
proc forceDistrib {} {
namespace eval ::ABF {
set f [expr {floor($fxi/$df + 0.5) * $df}]
if {[info exists forceDist($cur\ $f)]} {
incr forceDist($cur\ $f)
} else {
set forceDist($cur\ $f) 1
}
} ;# namespace
}
#########################################
# More or less self explanatory #
#########################################
proc restraints_init {} {
# this is called only once
namespace eval ::ABF {
set PI 3.1415926536
foreach r [array names rArray] {
set type [ lindex $rArray($r) 0 ]
set ok 0
switch $type {
"dist" {
set ok 1
print "ABF> Restraint $r is a distance"
set atoms($r) {}
foreach i { 1 2 } {
set seg [lindex [lindex $rArray($r) $i] 0]
set res [lindex [lindex $rArray($r) $i] 1]
set atom [lindex [lindex $rArray($r) $i] 2]
lappend atoms($r) [atomid $seg $res $atom]
}
foreach a $atoms($r) {addatom $a}
print [format "ABF> Atoms: %-16s k: %6.1f kcal/mol/A� Ref: %5.1f A" \
"($atoms($r))" [lindex $rArray($r) 3] [lindex $rArray($r) 4] ]
}
"distLin" {
set ok 1
print "ABF> Restraint $r is a distance (linear)"
set atoms($r) {}
foreach i { 1 2 } {
set seg [lindex [lindex $rArray($r) $i] 0]
set res [lindex [lindex $rArray($r) $i] 1]
set atom [lindex [lindex $rArray($r) $i] 2]
lappend atoms($r) [atomid $seg $res $atom]
}
foreach a $atoms($r) {addatom $a}
print [format "ABF> Atoms: %-16s F: %6.1f kcal/mol/A Bounds: %5.1f to %5.1f A" \
"($atoms($r))" [lindex $rArray($r) 3] [lindex $rArray($r) 4] [lindex $rArray($r) 5] ]
}
"dihe" {
set ok 1
print ABF> Restraint $r is a dihedral angle
set atoms($r) {}
foreach i { 1 2 3 4 } {
set seg [lindex [lindex $rArray($r) $i] 0]
set res [lindex [lindex $rArray($r) $i] 1]
set atom [lindex [lindex $rArray($r) $i] 2]
lappend atoms($r) [atomid $seg $res $atom]
}
foreach a $atoms($r) {addatom $a}
print [format "ABF> Atoms: %-16s k : %6.1f kcal/mol/rad� Ref: %5.1f deg" \
"($atoms($r))" [lindex $rArray($r) 5] [lindex $rArray($r) 6] ]
}
"angle" {
set ok 1
print "ABF> Restraint $r is a valence angle"
set atoms($r) {}
foreach i { 1 2 3 } {
set seg [lindex [lindex $rArray($r) $i] 0]
set res [lindex [lindex $rArray($r) $i] 1]
set atom [lindex [lindex $rArray($r) $i] 2]
lappend atoms($r) [atomid $seg $res $atom]
}
foreach a $atoms($r) {addatom $a}
print [format "ABF> Atoms: %-16s k: %6.1f kcal/mol/rad� Ref: %5.1f deg" \
"($atoms($r))" [lindex $rArray($r) 4] [lindex $rArray($r) 5] ]
}
"cyl" {
set ok 1
print "ABF> Restraint $r is a cylindrical restraint"
set atoms($r) {}
foreach i [lindex $rArray($r) 1] {
set seg [lindex $i 0]
set res [lindex $i 1]
set atom [lindex $i 2]
lappend atoms($r) [atomid $seg $res $atom]
}
set dir [lindex $rArray($r) 4]
# normalize direction and add the group ID at end of list
set rArray($r) [lreplace $rArray($r) 4 4 [vecnorm $dir] [addgroup $atoms($r)]]
print [format "ABF> Atoms: %-16s k: %6.1f kcal/mol/A� Ref: %s Dir: %s" \
"($atoms($r))" [lindex $rArray($r) 2] [lindex $rArray($r) 3] $dir ]
}
"harm" {
set ok 1
print "ABF> Restraint $r is a generic harmonic restraint"
set seg [lindex [lindex $rArray($r) 1] 0]
set res [lindex [lindex $rArray($r) 1] 1]
set name [lindex [lindex $rArray($r) 1] 2]
set atoms($r) [atomid $seg $res $name]
addatom $atoms($r)
print [format "ABF> Atom: %-17s k: %s kcal/mol/A� Ref: %s" \
$atoms($r) [lindex $rArray($r) 2] [lindex $rArray($r) 3] ]
}
}
if { $ok == 0 } {
print "ABF> Restraint $r is of unknown type $type !"
}
} ;# foreach
} ;# namespace
} ;# proc restraints_init
#########################################
# More or less self explanatory #
#########################################
proc restraints {} {
namespace eval ABF {
loadcoords coords
# Loop on requested restraints
foreach r [ array names atoms ] {
set type [ lindex $rArray($r) 0 ]
switch $type {
"dist" {
# Apply a harmonic restraint to a distance
foreach { a1 a2 } $atoms($r) {}
set k [lindex $rArray($r) 3]
set ref [lindex $rArray($r) 4]
set d [getbond $coords($a1) $coords($a2)]
set vect [vecscale [expr {1.0 / $d}] [vecsub $coords($a2) $coords($a1)]]
set force [expr {-$k * ($d - $ref)}]
addforce $a1 [vecscale [expr {- $force}] $vect]
addforce $a2 [vecscale $force $vect]
}
"distLin" {
# Apply a linear bias to a distance
foreach { a1 a2 } $atoms($r) {}
set d1 [lindex $rArray($r) 4]
set d2 [lindex $rArray($r) 5]
set d [getbond $coords($a1) $coords($a2)]
if { $d >= $d1 && $d < $d2 } {
set vect [vecscale [expr {1.0 / $d}] [vecsub $coords($a2) $coords($a1)]]
set force [expr {- [lindex $rArray($r) 3] }]
addforce $a1 [vecscale [expr {- $force}] $vect]
addforce $a2 [vecscale $force $vect]
}
}
"dihe" {
# Apply a restraint to a dihedral
foreach { a1 a2 a3 a4 } $atoms($r) {}
set k [lindex $rArray($r) 5]
set ref [lindex $rArray($r) 6]
set phi [getdihedral $coords($a1) $coords($a2) $coords($a3) $coords($a4)]
# we need phi between $ref - 180.0 and ref + 180.0
set phi [ expr {$phi + 360.0 * floor( ($ref + 180.0 - $phi) / 360.0)}]
# (in radians)
set diff [expr {($phi - $ref) * $PI/180.0}]
set force [expr {-$k * $diff}]
foreach {g1 g2 g3 g4} [dihedralgrad $coords($a1) $coords($a2) $coords($a3) $coords($a4)] {}
addforce $a1 [vecscale $g1 $force]
addforce $a2 [vecscale $g2 $force]
addforce $a3 [vecscale $g3 $force]
addforce $a4 [vecscale $g4 $force]
}
"angle" {
# Apply a restraint to a valence angle
foreach { a1 a2 a3 } $atoms($r) {}
set k [lindex $rArray($r) 4]
set ref [lindex $rArray($r) 5]
set phi [getangle $coords($a1) $coords($a2) $coords($a3)]
# (in radians)
set diff [expr {($phi - $ref) * $PI/180.0}]
set force [expr {-$k * $diff}]
foreach {g1 g2 g3} [anglegrad $coords($a1) $coords($a2) $coords($a3)] {}
addforce $a1 [vecscale $g1 $force]
addforce $a2 [vecscale $g2 $force]
addforce $a3 [vecscale $g3 $force]
}
"cyl" {
set k [lindex $rArray($r) 2]
set ref [lindex $rArray($r) 3]
set u [lindex $rArray($r) 4]
set group [lindex $rArray($r) 5]
set pos $coords($group)
set vecd [vecsub $pos $ref]
set v [vecnorm [veccross $vecd $u]]
set vech [veccross $v $u]
set dot [vecdot $u $vecd]
set d [veclength $vecd]
set h [expr {sqrt($d*$d - $dot*$dot)} ]
addforce $group [vecscale [expr {$k * $h} ] $vech ]
}
"harm" {
set a $atoms($r)
foreach { x y z } $coords($a) {}
foreach { kx ky kz } [lindex $rArray($r) 2] {}
foreach { x0 y0 z0 } [lindex $rArray($r) 3] {}
foreach { Fx Fy Fz } { 0. 0. 0.} {}
if { $kx } { set Fx [expr {$kx * ($x0 - $x)} ] }
if { $ky } { set Fy [expr {$ky * ($y0 - $y)} ] }
if { $kz } { set Fz [expr {$kz * ($z0 - $z)} ] }
addforce $a [list $Fx $Fy $Fz]
}
} ;# switch $type
} ;# foreach
} ;# namespace
} ;# proc restraints
#################################################
# End of procedure definitions #
#################################################
namespace eval ::ABF {
# coordinate-specific startup procedure
ABFstartup
if { [info exists restraintList] } {
array set rArray $restraintList
set restraintsOn 1
restraints_init
} else {set restraintsOn 0}
} ;# namespace
return ;# otherwise return value is 0