# BigDCD-v2
# Justin Gullingsrud
# updates by Axel Kohlmeyer
# vmd@ks.uiuc.edu
################################################################################
# Purpose: Use this script to analyze one or more trajectory files that 
# don't fit into memory.  The script will arrage for your analysis function 
# to be called each time a frame is loaded, then delete the frame from memory.
# The analysis script must accept one argument; BigDCD will keep track of how
# many timesteps have been loaded and call your script with that number.
#
# How to include this function: either source the script directory, or 
# (better) place the script in one of the directories in your auto_path 
# variable and include "package require bigdcd" in your script.
#
# New in version 2:
#
# The bigdcd command accepts an (optional) argument that defines
# the format type of the trajectory file. Its default is "auto", i.e.
# let VMD guess it from the file name.
#
# The function bigdcd_wait can be called to have a script wait until
# all frames have been processed. This is most useful for batch processing
# of analysis script. By construction bigdcd executes in the background 
# and will return the control to VMD after the last trajectory file has 
# been scheduled for reading. Thus analysis scripts will terminate prematurely.
################################################################################

# Example 1: 
# This computes the center of mass for each frame in the DCD file.
#
# proc mycenter { frame } {
#   global all
#   puts "$frame: [measure center $all weight mass]"
# }
# set $mol [mol new alanin.psf type psf waitfor all]
# set all [atomselect $mol all]
# $all global
# bigdcd mycenter auto alanin.dcd 
#

# Example 2:
# This computes the RMS distance between each frame in
# a sequence of xyz files and a reference pdb file.  
# this example even works in batch mode.
#
# proc myrmsd { frame } {
#   global ref sel all
#   $all move [measure fit $sel $ref]
#   puts "$frame: [measure rmsd $sel $ref]"
# }
# set mol [mol new protein.psf type psf waitfor all]
# set all [atomselect $mol all]
# set ref [atomselect $mol "name CA" frame 0]
# set sel [atomselect $mol "name CA"]
# mol addfile protein.pdb waitfor all
# bigdcd myrmsd xyz eq01.xyz eq02.xyz eq03.xyz
# bigdcd_wait
# quit

proc bigdcd { script type args } {
    global bigdcd_frame bigdcd_proc bigdcd_firstframe vmd_frame bigdcd_running
  
    set bigdcd_running 1
    set bigdcd_frame 0
    set bigdcd_firstframe [molinfo top get numframes]
    set bigdcd_proc $script

    # backwards "compatibility". type flag is omitted.
    if {[file exists $type]} { 
        set args [linsert $args 0 $type] 
        set type auto
    }
  
    uplevel #0 trace variable vmd_frame w bigdcd_callback
    foreach dcd $args {
        if { $type == "auto" } {
            mol addfile $dcd waitfor all
        } else {
            mol addfile $dcd type $type waitfor all
        }
    }
    after idle bigdcd_wait
}

proc bigdcd_callback { tracedvar mol op } {
    global bigdcd_frame bigdcd_proc bigdcd_firstframe vmd_frame
    set msg {}
 
    # If we're out of frames, we're also done 
    # AK: (can this happen at all these days???). XXX
    set thisframe $vmd_frame($mol)
    if { $thisframe < $bigdcd_firstframe } {
        puts "end of frames"
        bigdcd_done
        return
    }
 
    incr bigdcd_frame
    if { [catch {uplevel #0 $bigdcd_proc $bigdcd_frame} msg] } { 
        puts stderr "bigdcd aborting at frame $bigdcd_frame\n$msg"
        bigdcd_done
        return
    }
    animate delete beg $thisframe end $thisframe $mol
    return $msg
}

proc bigdcd_done { } {
    global bigdcd_running
    
    if {$bigdcd_running > 0} then {
        uplevel #0 trace vdelete vmd_frame w bigdcd_callback
        puts "bigdcd_done"
        set bigdcd_running 0
    }
}

proc bigdcd_wait { } {
    global bigdcd_running bigdcd_frame
    while {$bigdcd_running > 0} {
        global bigdcd_oldframe
        set bigdcd_oldframe $bigdcd_frame
        # run global processing hooks (including loading of scheduled frames)
        display update ui
        # if we have read a new frame during then the two should be different.
        if { $bigdcd_oldframe == $bigdcd_frame } {bigdcd_done}
    }
}
