“任何一个系统的存在及描述都离不开组成(material cause)、结构(formal cause)、相互作用(efficient cause)和功能(final cause)这四个因素。” –亚里士多德(Aristotle)的系统哲学:“四因说”


eMD用户手册–Tcl/VMD扩展

eMD开发Tcl扩展模块在于提供一套命令行编程工具,和其Python扩展模块提供一套编程语言库,两者设计目标是一致的:形成一套适合分子动力学模拟的编程框架库。成熟MD软件LAMMPS设计了一套特有的input脚本编程逻辑,形式上类似命令行,以至于有人真把它都成shell来使用(见其源代码tools/lammps-shell);但实际上这种命令行并不完善,if条件和loop循环流程并不是很成熟,使用上受限很大。而NAMD的配置引入了Tcl脚本编程,虽然有了Tcl的编程功能,但是NAMD的建模与模拟为较为封闭式模块,并不是面向可编程开放式设计,整个模拟过程用户很难干预,用户缺乏可编程的MD模块库,而这才是MD模拟的灵魂。 eMD设计则尽量克服现有软件的缺点,不仅提供一套C++实现的MD内核和建模辅助库,而且提供丰富的上层用户接口,通过SWIG工具实现Tcl和Python等多种语言的扩展;而这种扩展维系了eMD C++核心库的接口,不管是Tcl命令行,还是Python编程都是一样的“API接口”,可以灵活编程满足各种场合应用的需要。另外eMD选择了JSON配置格式,它们“自然地”对应了Tcl的{}字符串和Python的dict字典类型,对用户认识具有统一性。eMD提供Tcl扩展还有一个重要的原因是实现与VMD通过Tcl进行交互,VMD作为建模的前后处理工具,缺乏MD计算功能,虽然它可以与NAMD进行配合使用,但这种配合方式是非常松耦合的形式,数据传递等过程并不是非常有效,主要原因是NAMD开放的接口太少。

eMD的Tcl命令行编程框架的几大优势: 1. 采用SWIG包装了底层C/C++代码框架,Tcl层接近C/C++层的计算速度 2. 与底层C/C++同一套API,提供建模前后期和动力学计算接口,提供::emd命名工具和终端命令行补齐 3. 实现了std::vector等数据结构与Tcl两维列表参数映射,便于上层用户专注建模编程过程 4. 可与VMD工具深度协作,使得VMD具备动力学计算能力,同时eMD也添加了VMD前后期建模能力。

eMD可以编译为Tcl的package方式,这样可以在VMD的Tcl解释器中很容易引入,通过Tcl使用上eMD提供的功能。为了使用上的简便,eMD的Tcl扩展被编译为VMD的一个插件,可与VMD进行软件捆绑发布。已在MacOS X上提供VMD和eMD的封装App,该App已将eMD编译好,直接运行即可。

安装简介

eMD Tcl模块编译依赖于Tcl8和Swig工具,推荐使用Tcl 8.6。需要常用的MPICXX编译器(如OpenMPI),且支持C++11;如果需编译GPU加速模块,则需要CUDA 8.0+或HIP 2.0+的SDK环境。编译过程会调用Swig生成emd_wrap.cpp包装文件,这个文件最后会编译成名为emd.so的Tcl模块文件。
eMD Tcl如果需要与VMD混合使用,那么eMD和VMD应该基于同一个Tcl/Tk库(或Tcl/Tk framework)来编译,这样在Tcl/Tk库调用时不会崩溃。在MacOS X上已经提供了编译发布的App。
编译通过CMake方式,需要添加-DUSE_TCL=ON选项,进入eMD源代码根目录之后,一般编译过程为:

mkdir -p build
cd build
rm -rf *
cmake -L -DCMAKE_BUILD_TYPE=Release $(TCL_FLAGS) -DUSE_TCL=ON -DCMAKE_INSTALL_PREFIX=${EMD_ROOT} ..
make -j4 VERBOSE=1
cd ../tcl
tclsh ./mkcomp.tcl
cd emd
tclsh ./mkindex.tcl
cd ../../build
make install
其中${EMD_ROOT}变量指定安装目录。可以从CMake命令行传入参数定义: - TCL_LIBRARY - TK_LIBRARY - TCL_INCLUDE_PATH - TK_INCLUDE_PATH

例如使用VMD中的TCL/TK环境。 在MacOS X上也可以定义TCLTK_FRAMEWORK_PATH变量来指定Framework路径,如: -DTCLTK_FRAMEWORK_PATH=$HOME/Applications/VMD\ 1.9.4a43-Catalina-Rev6.app/Contents/Frameworks

同时也可以组合GPU编译方式-DUSE_CUDA=ON或-DUSE_HIP=ON

mkdir -p build && cd build 
rm -rf *
cmake -L -DCMAKE_BUILD_TYPE=Release -DUSE_TCL=ON -DCUDA_NVCC_FLAGS="-std=c++11;-Wno-deprecated-gpu-targets" \
        -DUSE_CUDA=ON -DCMAKE_BUILD_TESTING=ON -DCMAKE_INSTALL_PREFIX=${EMD_ROOT} ..
make -j4 VERBOSE=1
make install
编译之后,可以在eMD源代码tcl目录中,找到一些测试实例:
cd ../tcl
mpirun -n 2 tclsh emd/lj_from_json.tcl
将emd Tcl模块路径添加到auto_path之后,就可以在Tclsh中通过package调用:
package require emd

使用简介

eMD的Tcl模块名为emd,其中提供了多个实例文件,使用时推荐参考这些实例,这些实例覆盖大量应用场景。

  • array_check.tcl 对eMD的Tcl package功能简单的测试,主要在核心数组的数据结构操作上
  • lj_from_json.tcl 使用eMD模块级的JSON对象启动Lennard Jones模拟,对应Python扩展的lj_from_json.py
  • lj_from_func.tcl 使用eMD函数调用方式启动Lennard Jones模拟,对应Python扩展的lj_from_func.py
  • lj_from_class.tcl 通过构建eMD继承类调用方式启动Lennard Jones模拟
  • lj_with_vmd.tcl 显示VMD中调用eMD进行抽样和构象绘图
  • lj_create_vmd.tcl 通过eMD的配置,生成晶体结构的体系,将结果导入VMD中

导入emd库的方式有如:

package require emd

在和VMD混合使用时,也可以在VMD提供的Tcl解释器终端中输入以上命令,导入emd模块。

::emd::EMD_print_config 
eMD的命令都在::emd命名空间下,如以上将显示eMD的编译配置参数。eMD核心库函数被映射为Tclsh的命令语句,C++类中static函数都是以类名为前缀,如EMD类中的static函数print_config,在Tclsh中调用的格式为EMD_print_config。

在EMD对象构建之前,需要生成ContextInitializer的对象,用于初始化运行环境:

set mpi_need [::emd::CMPIComm_NeedInitialize]
::emd::ContextInitializer initializer {emd} $mpi_need

if { [::emd::Comm_is_master_rank] } {
    ::emd::EMD_print_config
}

::emd::EMD emd_obj
类对象名放在类名字之后,返回的对象直接关联到该变量上,使用时不需要$前缀。

之后就先后调用EMD对象的init和setup操作,但EMD中的Context、Comm、Particles和Sample类对象都是shared_ptr<>形式的对象变量,Tclsh并不支持shared_ptr<>类型变量的引用,故eMD的Tcl扩展中,提供返回类的原始指针的接口,如下:

set context [emd_obj get_context_ptr]
set comm [emd_obj get_comm_ptr]
set particles [emd_obj get_particles_ptr]
set sample [emd_obj get_sample_ptr]
::emd::Value val
$context init val
$comm init val

另外C++类的成员变量,需要通过cget操作获取:

# get rank after comm.init
set comm_world [$comm cget -comm_world] 
set rank [$comm_world get_rank]

JSON配置以字符串的形式给出,本身JSON对象使用{}符号,但Tcl也是通过{}将文本内容字符串化,因此看上去是嵌套的方式{{...}}

set lat [$particles cget -lattice_list]
$lat append {{
                    "units" : "lj",
                    "type" : "fcc",
                    "scale" : 0.8442
        }}

set reg [$particles cget -region_list]
$reg append {{
                   "type" : "block",
                    "args" : [
                    0, 4, 0,
                    4, 0, 4
                    ]
        }} [$particles cget -domain]
在Particles对象初始化化完成之后,可以创建一个AtomFile对象,输出体系的构象信息到文件中,然后使用VMD进行加载:
set trajfile [::emd::AtomFile_create emd_obj pdb]
$trajfile init {{"flush": true, "sort_tag": 1, "unwrap": 0}}
$trajfile setup
$trajfile write_file traj_out.pdb

mol new traj_out.pdb type pdb waitfor 1
其中$trajfile是eMD的AtomFile类对象,它可以输出构象文件,这里为traj_out.pdb,然后通过VMD的mol命令进行加载。由于这个过程在同一个内存空间,执行速度很快,需要将$trajfile的文件写出flush属性设置为true,以保证立即写入输出文件traj_out.pdb。如果在Sample对象初始化之后,通过其get_traj_ptr接口也可以获得一个AtomFile对象。
emd模块提供了几个测试实例,array_check.tcl可对eMD模块进行单元测试,主要实例使用方法。执行它们可以用于测试emd模块是否正确,也可为使用参考。lj_with_vmd.tcl 提供了VMD中调用eMD进行抽样和构象绘图的实例。
这里给一个较完整的例子(见tcl/emd/lj_from_func.tcl),实现EMD主要类的初始化和运行过程,展示了Tcl中如果构建一个模拟体系,以下为脚本内容:
#!/usr/bin/tclsh

#eMD Tcl interface.
#
#Copyright (2020) Shun Xu <xushun@cnic.cn>.
#This software is distributed under the GNU General Public License.
#----------------------------------------------------------------------
#Run by:
#$ tclsh lj_from_func.tcl
#$ mpirun -n 2 tclsh ./lj_from_func.tcl

lappend auto_path [pwd]
package require emd


proc TclEMD_init_all { emd_obj } {
    global context comm particles sample
    set context [emd_obj get_context_ptr]
    set comm [emd_obj get_comm_ptr]
    set particles [emd_obj get_particles_ptr]
    set sample [emd_obj get_sample_ptr]

    ::emd::Value val
    $context init val
    $comm init val

    # get rank after comm.init
    set comm_world [$comm cget -comm_world] 
    set rank [$comm_world get_rank]

    # get logfile after context.init
    global logfile
    set logfile [::emd::Context_get_logfile ]

    set lat [$particles cget -lattice_list]
    $lat append {{
                    "units" : "lj",
                    "type" : "fcc",
                    "scale" : 0.8442
        }}

    set reg [$particles cget -region_list]
    $reg append {{
                   "type" : "block",
                    "args" : [
                    0, 4, 0,
                    4, 0, 4
                    ]
        }} [$particles cget -domain]

    $particles init {{
                    "position" : {
                    "dim" : 3,
                    "boundary" : ["p", "p", "p" ] ,
                    "create_atoms" : {
                        "region" : 1,
                        "lattice" :1,
                        "basis_types" : [ 1, 1, 2, 2]
                    }
                     }
                    ,
                    "topology" : {
                    "atom_type" :[
                        {
                        "type" : "Kr",
                        "mass" : 1.0 }
                        ,
                        [
                        "Ar",
                        1.0
                        ] 
                    ]
                    }
        }}

    emd_obj set_force_field moly
    global force_field
    set force_field [emd_obj get_force_field_ptr]
    $force_field init {{
          "version" : 1.0,
           "units" : "lj",
           "type" : "moly",
           "newton": true,
           "ghost_vel": false}
           )}

    $force_field init_pair {{
              "type" : "lj_cut",
              "cutoff" : 2.5,
              "shift" : 0,
              "tail" : 0

           }}

    global neighbor
    set neighbor [$force_field get_neighbor_ptr]
    $neighbor init {{
            "every_step" : 2,
            "skin" : 0.3,
            "pair_search" : "bin"}} [emd_obj get_particles]

    $force_field print_summary $logfile

    emd_obj set_mechanics mdvv
    global mechanics
    set mechanics [emd_obj get_mechanics_ptr]
    variable nsteps 2000

    $mechanics init [format {{
            "run_steps": %d,
            "timestep": 0.01
        }} $nsteps]

    $mechanics init_slot {{
        "type" : "temp_berendsen",
        "tstart" : 1.44,
        "tstop" : 1.44,
        "tau" : 0.12,
        "seed" : 3434   
        }}

    $mechanics slot_change_box

    $sample init_statis {[
           {
               "type" : "force",
               "every_step" : 100,
               "fields" : [
               "temp",
               "epot",
               "ekin",
               "etotal",
               "press"
               ] }
           ,
           {
               "type" : "domain",
               "every_step" : 100,
               "start_step" : 20,
               "fields" : [
               "vol",
               "density",
               "lx"
               ] }
           ]
        }
    $sample init_trajectory {{
        "every_step": 10,
        "file": "traj_out.dcd"}
        }
    if { $rank == 0 } {
        ::emd::Context_log_puts "Create trajectory AtomFile: dcd\n"
        }
}

proc TclEMD_setup_all { emd_obj } {
    global context comm particles sample force_field neighbor mechanics

    $particles setup
    $particles setup_velocity {{
        "type" : "create",
        "rndseed" : 87287,
        "temp" : 1.44,
        "dist" : "uniform",
        "loop" : "geom" 
    }}


    set pair [$force_field get_pair_ptr]
    $pair set_coeff_list {[
        {
        "itype" : 1,
        "jtype" : 1,
        "epsilon" : 1,
        "sigma" : 1 }
        ,
        [ 2, 2, 1, 1 ]
    ]}

    $pair setup

    # setup pairs before bonds
    $pair setup_after_topo;
    $neighbor setup emd_obj
    variable skin [$force_field cget -skin]
    $neighbor check_box_cutoff [$particles cget -domain] $skin
    $neighbor print_info 1

    $comm print_node_usage
    $sample setup
    $mechanics setup

}

proc TclEMD_run_all { emd_obj } {
    global mechanics sample
    set timer [$sample get_timer_ptr]
    set time_start [$timer stamp_local]
    set time_cputime [::emd::Timer_cpu_time]

    set s [$mechanics run 2000  [$sample cget -timer]]

    $timer stamp $::emd::T_TOTAL $time_start
    set cputime2 [::emd::Timer_cpu_time] 
    set time_end [expr $cputime2 - $time_cputime]
    $timer add_elapsed $::emd::T_CPU_TIME $time_end
    return $s
}

##################
set mpi_need [::emd::CMPIComm_NeedInitialize]
::emd::ContextInitializer initializer {emd} $mpi_need

if { [::emd::Comm_is_master_rank] } {
    ::emd::EMD_print_config
}

::emd::EMD emd_obj
TclEMD_init_all emd_obj
TclEMD_setup_all emd_obj

set i  [TclEMD_run_all emd_obj ]

emd_obj final $i

emd_obj -delete
initializer -delete
调用命令行为
mpirun -n 2 tclsh ./lj_from_func.tcl
以上例子参考源代码中文件tcl/lj_from_func.tcl,举例说明了如果使用Tcl语言构建eMD模拟。

与VMD交互

eMD与VMD交互是一个重要的应用,主要有两种方式: 1. Tcl package方式,eMD作为VMD的一个Plugin使用 2. IMD 交互式,eMD作为MD后端进行动力学计算过程,VMD作为MD前端进行分子结构图视和操纵。

eMD提供Tcl扩展,VMD本身也被编译成为了一个(自定义)Tcl解释器,两者在Tcl语言上交互是自然的,同时在MD建模过程也是功能互补的。eMD可以借助VMD进行计算模拟的前后和中间过程的定制化,VMD也可以借助eMD进行功能补充。提供一个操作实例见**视频**:VMD与eMD的Tcl交互。另外在VMD中Console可以导入emd package,并运行实例。如:

vmd > package require emd
vmd > source $env(EMDDIR)/lj_from_func.tcl

交互MD(Interactive MD, IMD)可以实现跨网络分子动力学操作。eMD已经实现了VMD指定的一套IMD交互协议。可提交eMD计算到远程服务器上,并开启IMD对接的IP端口;而VMD在本地开启IMD客户端服务,去访问eMD开启IMD的IP端口,从而构建一套交互式模拟过程。提供一个操作实例见**视频**:eMD+VMD=IMD


最后修改: 2024年9月7日, Shun Xu