/pbcwrap.tcl

http://github.com/olenz/pbctools · TCL · 552 lines · 376 code · 51 blank · 125 comment · 69 complexity · 607b9f1087ac697d035c29996a4c206c MD5 · raw file

  1. ############################################################
  2. #
  3. # This file contains procedures to wrap atoms into the central
  4. # image of a system with periodic boundary conditions. The procedures
  5. # required the VMD unit cell properties to be set. Use the procedure
  6. # pbcset on this behalf.
  7. #
  8. # $Id$
  9. #
  10. package provide pbctools 3.0
  11. namespace eval ::PBCTools:: {
  12. namespace export pbc*
  13. ############################################################
  14. #
  15. # pbcwrap [OPTIONS...]
  16. #
  17. # OPTIONS:
  18. # -molid $molid|top
  19. # -first $first|first|now
  20. # -last $last|last|now
  21. # -all|allframes
  22. # -now
  23. # -compact|-parallelepiped|-brick
  24. # -sel $sel
  25. # -nocompound|-compound res[idue]|seg[ment]|chain|fragment
  26. # -nocompoundref|-compoundref $sel
  27. # -center origin|unitcell|com|centerofmass|bb|boundingbox
  28. # -centersel $sel
  29. # -shiftcenter $shift
  30. # -shiftcenterrel $shift
  31. # -verbose
  32. #
  33. # AUTHORS: Jan, Olaf, David M. Rogers
  34. #
  35. proc pbcwrap { args } {
  36. # Set the defaults
  37. set molid "top"
  38. set first "now"
  39. set last "now"
  40. set wraptype "brick"
  41. set sel "all"
  42. set compound ""
  43. set compoundref ""
  44. set center "unitcell"
  45. set centerseltext "all"
  46. set shiftcenter {0 0 0}
  47. set shiftcenterrel {}
  48. set verbose 0
  49. # Parse options
  50. for { set argnum 0 } { $argnum < [llength $args] } { incr argnum } {
  51. set arg [ lindex $args $argnum ]
  52. set val [ lindex $args [expr $argnum + 1]]
  53. switch -- $arg {
  54. "-molid" { set molid $val; incr argnum; }
  55. "-first" { set first $val; incr argnum }
  56. "-last" { set last $val; incr argnum }
  57. "-allframes" -
  58. "-all" { set last "last"; set first "first" }
  59. "-now" { set last "now"; set first "now" }
  60. "-sel" { set sel $val; incr argnum }
  61. "-nocompound" { set compound "" }
  62. "-compound" { set compound $val; incr argnum }
  63. "-nocompoundref" { set compoundref "" }
  64. "-compoundref" { set compoundref $val; incr argnum }
  65. "-cell" { set wraptype $val; incr argnum }
  66. "-center" { set center $val; incr argnum }
  67. "-centersel" { set centerseltext $val; incr argnum }
  68. "-shiftcenter" { set shiftcenter $val; incr argnum }
  69. "-shiftcenterrel" { set shiftcenterrel $val; incr argnum }
  70. "-verbose" { set verbose 1 }
  71. "-noverbose" { set verbose 0 }
  72. default { error "error: pbcwrap: unknown option: $arg" }
  73. }
  74. }
  75. if { $molid=="top" } then { set molid [ molinfo top ] }
  76. # Save the current frame number
  77. set frame_before [ molinfo $molid get frame ]
  78. # handle first and last frame
  79. if { $first=="now" } then { set first $frame_before }
  80. if { $first=="first" || $first=="start" || $first=="begin" } then {
  81. set first 0
  82. }
  83. if { $last=="now" } then { set last $frame_before }
  84. if { $last=="last" || $last=="end" } then {
  85. set last [molinfo $molid get numframes]
  86. incr last -1
  87. }
  88. # handle compounds
  89. switch -- $compound {
  90. "" {}
  91. "res" -
  92. "resid" -
  93. "residue" { set compound "residue" }
  94. "seg" -
  95. "segid" { set compound "segid" }
  96. "chain" { set compound "chain" }
  97. "fragment" { set compound "fragment" }
  98. default {
  99. error "error: pbcwrap: bad argument to -compound: $compound"
  100. }
  101. }
  102. # Handle the reference selection
  103. # $wrapsel will be used as format string,
  104. # specifying which group to wrap
  105. if { [string length $compound] } then {
  106. if { [string length $compoundref] } then {
  107. set wrapsel "($sel) and (same $compound as (($compoundref) and (%s)))"
  108. } else {
  109. set wrapsel "($sel) and (same $compound as (%s))"
  110. }
  111. } else {
  112. # no compound case
  113. set wrapsel "($sel) and (%s)"
  114. }
  115. if { $verbose } then { vmdcon -info "wrapsel=$wrapsel" }
  116. if { $verbose } then { vmdcon -info "Wrapping..." }
  117. set next_time [clock clicks -milliseconds]
  118. set show_step 1000
  119. set fac [expr 100.0/($last - $first + 1)]
  120. # Loop over all frames
  121. for { set frame $first } { $frame <= $last } { incr frame } {
  122. # Switch to the next frame
  123. molinfo $molid set frame $frame
  124. # get the unit cell data
  125. set cell [lindex [ pbcget -check -namd -now -molid $molid ] 0]
  126. set A [lindex $cell 0]
  127. set B [lindex $cell 1]
  128. set C [lindex $cell 2]
  129. set Ax [lindex $A 0]
  130. set By [lindex $B 1]
  131. set Cz [lindex $C 2]
  132. # compute the origin (lower left corner)
  133. switch -- $wraptype {
  134. "compact" {
  135. set origin [vecscale -0.5 [vecadd $A $B $C]]
  136. }
  137. "para" -
  138. "parallelepiped" {
  139. set origin [vecscale -0.5 [vecadd $A $B $C]]
  140. }
  141. "orthorhombic" -
  142. "rectangular" -
  143. "brick" {
  144. set origin [vecscale -0.5 [list $Ax $By $Cz]]
  145. }
  146. default {
  147. error "error: pbcwrap: bad argument to -cell: $wraptype"
  148. }
  149. }
  150. # compute the center of the box
  151. switch -- $center {
  152. "unitcell" { set origin { 0 0 0 } }
  153. "origin" {}
  154. "com" -
  155. "centerofmass" {
  156. # set the origin to the center-of-mass of the selection
  157. set centersel [atomselect $molid "($centerseltext)"]
  158. if { [$centersel num] == 0 } then {
  159. vmdcon -warn "pbcwrap: selection \"$centerseltext\" is empty!"
  160. }
  161. set sum [measure sumweights $centersel weight mass]
  162. if { $sum > 0.0 } then {
  163. set com [measure center $centersel weight mass]
  164. } else {
  165. set com [measure center $centersel]
  166. }
  167. $centersel delete
  168. set origin [vecadd $origin $com]
  169. }
  170. "bb" -
  171. "boundingbox" {
  172. # set the origin to the center of the bounding box
  173. # around the selection
  174. set centersel [atomselect $molid "($centerseltext)"]
  175. if { [$centersel num] == 0 } then {
  176. vmdcon -warn "pbcwrap: selection \"$centerseltext\" is empty!"
  177. }
  178. set minmax [measure minmax $centersel]
  179. set centerbb \
  180. [vecscale 0.5 \
  181. [vecadd \
  182. [lindex $minmax 0] \
  183. [lindex $minmax 1] \
  184. ]]
  185. $centersel delete
  186. set origin [vecadd $origin $centerbb]
  187. }
  188. default {
  189. # error "error: pbcwrap: bad argument to -center: $center"
  190. # for backwards compatibility
  191. vmdcon -warn "Using a selection as argument for the option \"-center\" is deprecated."
  192. vmdcon -warn "Please use the option \"-centersel\" to specify the selection!"
  193. set centerseltext $center
  194. # set the origin to the center-of-mass of the selection
  195. set centersel [atomselect $molid "($centerseltext)"]
  196. if { [$centersel num] == 0 } then {
  197. vmdcon -warn "pbcwrap: selection \"$centerseltext\" is empty!"
  198. }
  199. set sum [measure sumweights $centersel weight mass]
  200. if { $sum > 0.0 } then {
  201. set com [measure center $centersel weight mass]
  202. } else {
  203. set com [measure center $centersel]
  204. }
  205. $centersel delete
  206. set origin [vecadd $origin $com]
  207. }
  208. }
  209. # shift the origin
  210. set origin [vecadd $origin $shiftcenter]
  211. if { [llength $shiftcenterrel] } then {
  212. set shifta [lindex $shiftcenterrel 0]
  213. set shiftb [lindex $shiftcenterrel 1]
  214. set shiftc [lindex $shiftcenterrel 2]
  215. set origin [vecadd $origin \
  216. [vecscale $shifta $A] \
  217. [vecscale $shiftb $B] \
  218. [vecscale $shiftc $C] \
  219. ]
  220. }
  221. # Wrap it
  222. switch -- $wraptype {
  223. "compact" {
  224. wrap_compact \
  225. $molid $A $B $C $origin $wrapsel
  226. }
  227. "para" -
  228. "parallelepiped" {
  229. wrap_para \
  230. $molid $A $B $C $origin $wrapsel
  231. }
  232. "orthorhombic" -
  233. "rectangular" -
  234. "brick" {
  235. wrap_brick \
  236. $molid $A $B $C $origin $wrapsel
  237. }
  238. }
  239. # print timestamp
  240. set time [clock clicks -milliseconds]
  241. if {$verbose || $frame == $last || $time >= $next_time} then {
  242. set percentage [format "%3.1f" [expr $fac*($frame-$first+1)]]
  243. vmdcon -info "$percentage% complete (frame $frame)"
  244. set next_time [expr $time + $show_step]
  245. }
  246. }
  247. # print final timestamp
  248. if { $verbose } then {
  249. set percentage [format "%3.1f" 100]
  250. vmdcon -info "$percentage% complete (frame $frame)"
  251. vmdcon -info "Wrapping complete."
  252. }
  253. # Rewind to original frame
  254. if { $frame_before != $last } then {
  255. if { $verbose } then { vmdcon -info "Rewinding to frame $frame_before." }
  256. #animate goto $frame_before
  257. molinfo $molid set frame $frame_before
  258. }
  259. }
  260. #########################################################
  261. # Wrap the selection $wrapsel of molecule $molid
  262. # in the current frame into the unitcell parallelepiped
  263. # defined by $A, $B, $C and $origin.
  264. #########################################################
  265. proc wrap_para { molid A B C origin wrapsel } {
  266. set L [transtranspose [list $A $B $C]]
  267. # Transform into 4x4 matrix:
  268. set recip2cart [transmult [transoffset $origin] [trans_from_rotate $L]]
  269. set cart2recip [measure inverse $recip2cart]
  270. # apply the full selection
  271. set usersel [atomselect $molid [format $wrapsel "all"]]
  272. # Transform the unit cell to reciprocal space
  273. $usersel move $cart2recip
  274. # Now we can easily select the atoms outside the cell and wrap them
  275. wrap_brick $molid {1 0 0} {0 1 0} {0 0 1} {0 0 0} $wrapsel
  276. $usersel move $recip2cart
  277. $usersel delete
  278. }
  279. ########################################################
  280. # Wrap all atoms in $wrapsel to their closest approach
  281. # to the origin. This ignores molecule selections
  282. # because there doesn't seem to be a way to loop
  283. # over residues / chains / etc. inside the selection.
  284. #
  285. # TODO:
  286. # Maybe there's a way to select the first atom of each residue,
  287. # (or the cm of each) and only wrap it.
  288. # Then sum translation vectors over whole residues to distribute
  289. # to all atoms in each residue.
  290. ########################################################
  291. proc wrap_compact { molid A B C origin wrapsel } {
  292. package require pbc_core 3.0
  293. set usersel [atomselect $molid [format $wrapsel "all"]]
  294. $usersel set {x y z} [wrap_min [list $A $B $C] $origin [$usersel get {x y z}]]
  295. }
  296. ########################################################
  297. # Wrap the selection $wrapsel of molecule $molid
  298. # in the current frame into the orthorhombic unitcell
  299. # defined by $Ax, $By, $Cz and $origin.
  300. # $wrapsel is a format string to select groups to shift.
  301. ########################################################
  302. proc wrap_brick { molid A B C origin wrapsel } {
  303. foreach {ox oy oz} $origin {break}
  304. set cx [expr $ox + [lindex $A 0]]
  305. set cy [expr $oy + [lindex $B 1]]
  306. set cz [expr $oz + [lindex $C 2]]
  307. shift_sel $molid [format $wrapsel "z>=$cz"] [vecinvert $C]
  308. shift_sel $molid [format $wrapsel "z<$oz"] $C
  309. shift_sel $molid [format $wrapsel "y>=$cy"] [vecinvert $B]
  310. shift_sel $molid [format $wrapsel "y<$oy"] $B
  311. shift_sel $molid [format $wrapsel "x>=$cx"] [vecinvert $A]
  312. shift_sel $molid [format $wrapsel "x<$ox"] $A
  313. }
  314. ########################################################
  315. # Shift the selection $seltext of molecule $molid in #
  316. # the current frame by $shift, until the selection is #
  317. # empty. #
  318. ########################################################
  319. proc shift_sel { molid seltext shift {iter 500}} {
  320. set sel [atomselect $molid $seltext]
  321. set shifted_atoms [$sel num]
  322. set i 0
  323. while { [$sel num] > 0 && $i<$iter} {
  324. $sel moveby $shift
  325. $sel update
  326. incr i
  327. }
  328. $sel delete
  329. return $shifted_atoms
  330. }
  331. ########################################################
  332. # Scale a 4x4 matrix by factors $s1 $s2 $s3 along the #
  333. # coordinate axes. #
  334. ########################################################
  335. proc scale_mat { s1 s2 s3 } {
  336. set v1 [list $s1 0 0 0]
  337. set v2 [list 0 $s2 0 0]
  338. set v3 [list 0 0 $s3 0]
  339. return [list $v1 $v2 $v3 {0.0 0.0 0.0 1.0}]
  340. }
  341. # Wrap the coordinates in the variables referenced by var_xs,
  342. # var_ys and var_zs into the unitcell centered around the
  343. # coordinates in $rxs, $rys, $rzs.
  344. # The lists referenced by var_xs, var_ys and var_zs have to have
  345. # the same lengths. $rxs, $rys and $rzs may either be lists of the
  346. # same length, or scalar values.
  347. proc pbcwrap_coordinates {A B C var_xs var_ys var_zs rxs rys rzs} {
  348. upvar $var_xs xs $var_ys ys $var_zs zs
  349. # If rxs, rys and rzs are single values, create a list of
  350. # the length of $xs,
  351. if {[llength $rxs] == 1} then {
  352. set rx $rxs
  353. for {set i 1} {$i < [llength $xs]} {incr i} {
  354. lappend rxs $rx
  355. }
  356. } elseif {[llength $rxs] != [llength $xs]} then {
  357. error "pbcwrap_coordinates: rxs either has to be of length 1 or of the same length as $var_xs!"
  358. }
  359. if {[llength $rys] == 1} then {
  360. set ry $rys
  361. for {set i 1} {$i < [llength $ys]} {incr i} {
  362. lappend rys $ry
  363. }
  364. } elseif {[llength $rys] != [llength $ys]} then {
  365. error "pbcwrap_coordinates: rys either has to be of length 1 or of the same length as $var_ys!"
  366. }
  367. if {[llength $rzs] == 1} then {
  368. set rz $rzs
  369. for {set i 1} {$i < [llength $zs]} {incr i} {
  370. lappend rzs $rz
  371. }
  372. } elseif {[llength $rzs] != [llength $zs]} then {
  373. error "pbcwrap_coordinates: rzs either has to be of length 1 or of the same length as $var_zs!"
  374. }
  375. # get the cell vectors
  376. set Ax [lindex $A 0]
  377. set Bx [lindex $B 0]
  378. set By [lindex $B 1]
  379. set Cx [lindex $C 0]
  380. set Cy [lindex $C 1]
  381. set Cz [lindex $C 2]
  382. set Ax2 [expr 0.5*$Ax]
  383. set By2 [expr 0.5*$By]
  384. set Cz2 [expr 0.5*$Cz]
  385. set iAx [expr 1.0/$Ax]
  386. set iBy [expr 1.0/$By]
  387. set iCz [expr 1.0/$Cz]
  388. # create lists of the right lengths
  389. set shiftAs $xs
  390. set shiftBs $xs
  391. set shiftCs $xs
  392. # compute the differences in the z coordinate
  393. set dzs [vecsub $zs $rzs]
  394. # compute the required shift
  395. set i 0
  396. foreach dz $dzs {
  397. set shift 0
  398. if { $dz > $Cz2 } then {
  399. incr shift -1
  400. while { $dz+$shift*$Cz > $Cz2 } { incr shift -1 }
  401. } elseif { $dz < -$Cz2 } then {
  402. incr shift
  403. while { $dz+$shift*$Cz < -$Cz2 } { incr shift }
  404. }
  405. lset shiftCs $i $shift
  406. incr i
  407. }
  408. # apply shiftCs to zs
  409. set zs [vecadd $zs [vecscale $Cz $shiftCs]]
  410. # apply shiftC to ys
  411. set ys [vecadd $ys [vecscale $Cy $shiftCs]]
  412. # compute the differences in the y coordinate
  413. set dys [vecsub $ys $rys]
  414. # compute the required shift
  415. set i 0
  416. foreach dy $dys {
  417. set shift 0
  418. if { $dy > $By2 } then {
  419. incr shift -1
  420. while { $dy+$shift*$By > $By2 } { incr shift -1 }
  421. } elseif { $dy < -$By2 } then {
  422. incr shift
  423. while { $dy+$shift*$By < -$By2 } { incr shift }
  424. }
  425. lset shiftBs $i $shift
  426. incr i
  427. }
  428. # apply shiftB to ys
  429. set ys [vecadd $ys [vecscale $By $shiftBs]]
  430. # get the current x coordinates and apply shiftC and shiftB
  431. set xs [vecadd $xs [vecscale $Cx $shiftCs] [vecscale $Bx $shiftBs]]
  432. # compute the differences in the x coordinate
  433. set dxs [vecsub $xs $rxs]
  434. # compute the required shift
  435. set i 0
  436. foreach dx $dxs {
  437. set shift 0
  438. if { $dx > $Ax2 } then {
  439. incr shift -1
  440. while { $dx+$shift*$Ax > $Ax2 } { incr shift -1 }
  441. } elseif { $dx < -$Ax2 } then {
  442. incr shift
  443. while { $dx+$shift*$Ax < -$Ax2 } { incr shift }
  444. }
  445. lset shiftAs $i $shift
  446. incr i
  447. }
  448. # apply shiftA to xs
  449. set xs [vecadd $xs [vecscale $Ax $shiftAs]]
  450. return [list $shiftAs $shiftBs $shiftCs]
  451. }
  452. # Return a list of lists of atom indices. The atoms in a sublist
  453. # are all atoms that belong to a connected subset of $sel.
  454. proc get_connected {bondlist} {
  455. # recursive function that tags untagged atoms
  456. # and returns a list of atoms connected to this one
  457. proc grow_connected {pid} {
  458. upvar 1 "tagged" tagged "bonds" bonds
  459. if { ! [lindex $tagged $pid] } then {
  460. # mark the atom
  461. lset tagged $pid 1
  462. # add it to the list
  463. set res [list $pid]
  464. foreach pid2 $bonds($pid) {
  465. foreach pid3 [grow_connected $pid2] {
  466. lappend res $pid3
  467. }
  468. }
  469. return $res
  470. } else {
  471. return {}
  472. }
  473. }
  474. # get the bond structure
  475. set n [llength $bondlist]
  476. # put the bondlist into an array
  477. set pid 0
  478. foreach bs $bondlist {
  479. lappend tagged 0
  480. set bonds($pid) $bs
  481. incr pid
  482. }
  483. # make links bidirectional
  484. set pid 0
  485. foreach bs $bondlist {
  486. foreach pid2 $bs { lappend bonds($pid2) $pid }
  487. incr pid
  488. }
  489. # remove duplicate links
  490. for { set pid 0 } { $pid < $n } { incr pid } {
  491. set bonds($pid) [lsort -unique -integer $bonds($pid) ]
  492. }
  493. # grow connected structures recursively
  494. for { set pid 0 } { $pid < $n } { incr pid } {
  495. if { ! [lindex $tagged $pid] } then {
  496. lappend connected [lsort -integer [grow_connected $pid]]
  497. }
  498. }
  499. return $connected
  500. }
  501. }