简介:围绕 Autodock Vina 批量分子对接与 Slurm 集群调度整理的实战型 PDF 文档,面向生物信息学、计算生物学及药物设计方向的学习者与科研人员,可用于在 Ubuntu 18.04 上搭建对接环境,并把单次对接扩展为集群批量任务。文档以 1 个 PDF 文件交付,压缩包约 1013KB,篇幅紧凑,便于随查随用。内容覆盖 Open Babel、MGLTools 与 Vina 的安装配置,受体和配体预处理、conf.txt 参数设置及 pdbqt 结果转换,并延伸至 Slurm 的 munge 认证、slurm.conf 编写与服务启动,形成从软件部署到作业调度的完整链路。已有 5257 人学习,适合需要理解批量对接流程、集群资源分配与常见配置思路的读者参考。
1. 从一台机器上的 500 个配体说起:这套 Autodock Vina + Slurm 组合解决什么问题
一个中等规模的虚拟筛选,500 个配体,Autodock Vina 单次对接十几秒到两分钟,串行跑完是四到八个小时。这期间终端不能断、机器不能重启,而最可能的结果是:跑到第 400 多个时,某个 mol2 里混进一个 Vina 不认识的原子类型,脚本当场退出,前面几百个中间结果因为用了rm清理,全白跑。分子对接的计算形态极其规整——任务之间完全独立、单个任务耗时不长、总量却很大,这种形态天生该并行,硬扛在单机上纯属浪费。
这份材料做的事情就是把链路跑通:Ubuntu 18.04 上装 Open Babel、Autodock Vina、MGLTools 三件套,用一段vina.sh把 sdf 拆成一个个配体批量对接、结果合并;再装 Slurm,把串行脚本改成 sbatch 作业,扩到三个节点,用 NFS 共享同一份工作目录并行跑。适合刚接触分子对接、手里攥着一批分子要筛、又被串行脚本卡住的人;也适合已经会跑单机 Vina、想把它搬上调度器但不知道 slurm.conf 从哪下手的开发者。
2. 环境搭建:Ubuntu 18.04 上把 Vina、MGLTools、Open Babel 串成一条 PATH 链
2.1 依赖清单:每个包都在给某个组件补依赖
MGLTools 1.5.6 这个版本是 Python2 + Tk + OpenGL 时代的产物,install.py会现场编译几个 C 扩展,缺任何一个头文件都直接失败。libxt-dev、libglu1-mesa-dev、libxmu-dev、libxi-dev是给 AutoDockTools 的 GUI 和它依赖的 Togl 组件提供 X11/OpenGL 头文件——哪怕你只打算跑命令行脚本,编译阶段照样会去找X11/Xlib.h。libpng-dev、libjpeg-dev、libtiff-dev是给 PIL 用的,csh是因为 MGLTools 里还残留一批 csh 脚本,python-tk提供 tkinter,python-pmw是 AutoDockTools 界面用的组件库,python-numpy处理坐标矩阵,python-dev提供Python.h。
还有一条隐性依赖:Ubuntu 18.04 里python默认指向 2.7,这恰好是这套老工具需要的东西。如果你的机器上装了某个科学计算发行版并把它的 bin 目录放到了 PATH 前面,python install.py会直接抛SyntaxError: Missing parentheses in call to 'print',第一眼看像安装包坏了,其实就是解释器不对。
sudo apt-get update sudo apt-get install -y build-essential libxt-dev libglu1-mesa-dev libxmu-dev libxi-dev \ libpng-dev libjpeg-dev libtiff-dev csh python-tk python-numpy python-dev python-pmw # Open Babel 直接走 apt,2.4.x 在这个流程里完全够用 sudo apt-get install -y openbabel obabel -Vobabel -V能打出类似2.4.1的版本号就说明转换器就位。有人习惯源码编译 3.x,其实这条链路里只用到 sdf↔mol2、pdbqt↔sdf 这几组转换,2.4 足够,别为了版本号给自己堆一堆编译依赖。
2.2 目录约定:工具装本地盘,工作目录收在 /vina
单机阶段工具放哪都行,一旦上集群,路径就必须三个节点完全一致,因为作业脚本里写的是绝对路径。我一般把工具装在本地盘的/opt/dock(每个节点各一份,跟着镜像走),把受体、配体、脚本、结果这些「会变的东西」放在 NFS 共享的/vina上。这样脚本改一次,三个节点看到的是同一份,不用挨个同步。
sudo mkdir -p /opt/dock && cd /opt/dock # 解压 Vina 预编译包,里面就是 bin/vina 一个可执行文件 sudo tar -xzf autodock_vina_1_1_2_linux_x86.tgz # MGLTools 需要跑一次安装脚本,过程中会让你确认 license sudo tar -xzf mgltools_x86_64Linux2_1.5.6.tar.gz cd mgltools_x86_64Linux2_1.5.6 sudo python install.py装完先做两件检查。Vina 的预编译二进制对系统库比较挑,用ldd看有没有悬空依赖:
ldd /opt/dock/autodock_vina_1_1_2_linux_x86/bin/vina | grep -i "not found" /opt/dock/autodock_vina_1_1_2_linux_x86/bin/vina --version第一条没有输出、第二条打出AutoDock Vina 1.1.2才算干净。MGLTools 那边看MGLTools-1.5.6/bin/pythonsh在不在——这个文件是 MGLTools 自带的解释器包装,后面Utilities24目录下的所有脚本都靠它启动。
2.3 三行 PATH 解决的是三个不同目录
批量脚本开头那三行export PATH,看着像模板,其实每一行都指着不同的东西:
# vina 可执行文件 export PATH=/opt/dock/autodock_vina_1_1_2_linux_x86/bin:$PATH # MGLTools 的 pythonsh 和 adt 等入口 export PATH=/opt/dock/mgltools_x86_64Linux2_1.5.6/bin:$PATH # prepare_receptor4.py / prepare_ligand4.py 所在目录 export PATH=/opt/dock/mgltools_x86_64Linux2_1.5.6/MGLToolsPckgs/AutoDockTools/Utilities24:$PATH第三行最关键,也最容易被漏。Utilities24下的脚本第一行是#!/usr/bin/env pythonsh,所以脚本里必须直接写prepare_ligand4.py -l ...,让系统通过 PATH 找到pythonsh再由它解释执行。如果自作聪明写成python prepare_ligand4.py,用系统 python 去加载 MGLTools 的包,导入阶段就会失败,报一堆No module named。
还有一个必须记住的点:这三行export只对当前 shell 生效。sbatch提交出去的作业是另一套环境,不继承你登录时的 PATH,作业脚本里要把这三行原样再写一遍。这一点在后面改并行脚本时会反复遇到。
3. vina.sh 拆解:一个批量对接脚本从 sdf 到 output.sdf 的完整链路
3.1 受体准备只做一次,别放进循环
prepare_receptor4.py干三件事:读 PDB、检查并补全原子类型、输出带 AutoDock 原子类型和电荷的 pdbqt。它不是给「每个配体跑一遍」用的,整个任务里只该执行一次。
# -r 输入受体 PDB,-o 输出 pdbqt # -A hydrogens 让 AutoDockTools 补极性氢 # -e 删除非标准残基(水、离子、多数杂原子) prepare_receptor4.py -r pro.pdb -A hydrogens -e -o receptor.pdbqt参数里最需要斟酌的是-e。它会清掉所有非标准残基,好处是避免 pdbqt 里出现 Vina 不认识的原子类型导致直接报错;坏处是如果你的活性口袋依赖某个结构水或者金属离子做桥接,删掉之后对接结果就没有参考价值了。判断方法很土但有效:打开原始 PDB 看结合位点周围 5 Å 内有没有 HOH 或金属,有就老老实实手动挑出来保留,别一删了之。
准备完抽查一下,grep -c "^ATOM" receptor.pdbqt和原 PDB 的原子数量级应该对得上,head -3 receptor.pdbqt看第一列是不是ATOM、最后一列是不是 AutoDock 原子类型(A/C/N/O/HD 之类)。
3.2 配体转换:obabel 分两步走,-m是关键
配体这边要做的是把「一个多分子 SDF」拆成「一堆单分子 mol2」。分两步做比一步到位好处多——中间结果能检查。
# 第一步:整个 SDF 转成一个多分子 mol2(内容是首尾相接的多个分子块) obabel -isd testset.sdf -omol2 -O testset.mol2 # 第二步:-m 表示按分子拆分,输出 vs1.mol2、vs2.mol2、vs3.mol2 ... obabel testset.mol2 -O vs.mol2 -m-i和-o显式指定输入输出格式,-O指定文件名,-m才是拆分开关。拆分后的文件名是vs1.mol2、vs2.mol2这样的序号,和原始 SDF 里的分子名没有任何关系。这一点在做结果汇总时非常致命:你最后拿到的output.sdf里全是序号,对不回化合物库的 ID。稳妥的做法是在拆分后立刻生成一份序号 -> 原始名称的映射表存下来,后面写 CSV 时用得上。
另外注意电荷。obabel 从 sdf 转 mol2 默认不重新计算电荷,而prepare_ligand4.py会按 AutoDock 的类型规则重新赋电荷。如果配体里带形式电荷(羧酸根、季铵盐之类),转换后随手抽一个看@<TRIPOS>ATOM段的电荷列是不是合理值,比跑到一半再回头查省事得多。
3.3 主循环:靠「固定文件名」把 conf.txt 保住
循环体是整个脚本的核心,写得很朴素:
cd mols for mol2 in *.mol2 do # 配体预处理,统一输出成 ligand.pdbqt prepare_ligand4.py -l "$mol2" -o ligand.pdbqt -A hydrogens # 对接,--cpu 1 表示只用单核 vina --config ../conf.txt --cpu 1 --out out.pdbqt --log log.txt # pdbqt 转 sdf,一个 pdbqt 里的多个 MODEL 会变成 sdf 里的多个分子 obabel -ipdbqt out.pdbqt -osd -O temp.sdf cat temp.sdf >> output.sdf # 清掉中间文件,避免下一轮互相干扰 rm -f log.txt ligand.pdbqt out.pdbqt temp.sdf done cd ..这段代码里有两个设计值得说。
第一个是「固定文件名」。conf.txt里写的是ligand = ligand.pdbqt,所以循环里每个配体都必须输出成同一个名字,靠覆盖来完成输入切换。看着土,但它让conf.txt只需要维护一份——配体有八百个也不用生成八百份配置。批量脚本里这是最省事、也最不容易出错的做法。
第二个是--cpu 1。单机跑的时候这个值可以设成物理核数,Vina 自己会开多线程;但一旦上了调度器,一个作业占一个核,让 Vina 再去抢多核就是跟 Slurm 对着干,节点负载会莫名其妙飙高。
for mol2 in *.mol2也别换成for mol2 in `ls *.mol2`,后者遇到文件名带空格就直接崩,这种低级问题在批量任务里排查起来特别费时间。
3.4 conf.txt:盒子定在哪,比调什么参数都重要
配置文件就这么十来行:
receptor = receptor.pdbqt ligand = ligand.pdbqt center_x = 12.480 center_y = -3.215 center_z = 18.902 size_x = 22 size_y = 22 size_z = 22 exhaustiveness = 8 num_modes = 9 energy_range = 3 seed = 20240101| 参数 | 含义 | 常见取值与说明 |
|---|---|---|
| center_x/y/z | 搜索盒中心坐标 | 从共晶配体几何中心算,或活性残基 Cα 质心 |
| size_x/y/z | 盒子边长(Å) | 20~25 之间,小于 18 容易把口袋切掉一半 |
| exhaustiveness | 搜索穷尽程度 | 默认 8,调到 16 结果更稳但时间翻倍 |
| num_modes | 输出构象数 | 默认 9,写进 output.sdf 的就是这么多 |
| energy_range | 构象能量跨度 | 默认 3 kcal/mol,只保留接近最优的构象 |
| seed | 随机种子 | 不写就用时间戳,写死才能复现 |
盒子中心怎么定是新手最容易翻车的地方。最靠谱的是找共晶配体:如果原始 PDB 里带着共结晶的小分子,直接算它的几何中心,误差最小。没有共晶结构就退一步,把文献里报道的关键残基挑出来算 Cα 质心,再把盒子边长放大到 24 Å 兜住整个口袋。拿 MGLTools 的图形界面画盒子当然也行,但它需要在有 X11 转发的环境里跑,集群上一般不具备条件,还是命令行算坐标更实际。
seed这一项强烈建议写死。Vina 不写 seed 时用系统时间初始化随机数,同一批分子跑两次能量值会有零点几的波动,做复现实验或者跟别人对结果时,这点差异足够让你怀疑人生。
3.5 结果长什么样,怎么快速判断有没有跑歪
每轮会生成一个log.txt,内容形如:
mode | affinity | dist from best mode | (kcal/mol) | rmsd l.b.| rmsd u.b. -----+------------+----------+---------- 1 -7.3 0.000 0.000 2 -6.9 1.412 2.038第一行mode 1的 affinity 就是这次对接的评分,负得越多结合越可能强。output.sdf里每个配体会对应num_modes个构象,顺序就是 mode 顺序,所以取第一个分子块就是最优构象。
一个必须强调的细节:output.sdf用的是>>追加。如果不清空直接重跑,第二次的结果会接着上一次往下写,文件里就成了两批数据的混合体,后续分析全乱。跑之前老老实实rm -f output.sdf或者干脆给每次运行建一个带时间戳的目录。
4. Slurm 落地:munge、slurmctld、slurmd 三件套与 slurm.conf 关键字段
4.1 先让 munge 通,再谈 Slurm
munge 是 Slurm 的认证组件,节点之间所有 RPC 都靠它签名。它有两个硬性要求:所有节点共享同一个 key 文件,且各节点时间必须同步(差超过几分钟就会认证失败)。
apt-get install -y munge libmunge-dev # 在 node01 上生成 key,再 scp 到另外两台,注意权限 dd if=/dev/urandom bs=1 count=1024 of=/etc/munge/munge.key chown munge:munge /etc/munge/munge.key chmod 400 /etc/munge/munge.key systemctl enable --now munge systemctl status munge # 本地自测 munge -n | unmunge | grep STATUSSTATUS: Success (0)说明本地服务正常。跨节点验证更重要,在 node01 上执行ssh node02 'munge -n' | unmunge,如果这句能通,说明 key 和时间都没问题;如果不通,先systemctl status chrony看时间同步,再md5sum /etc/munge/munge.key对比三台机器的 key 是否一致。munge 不通的情况下,sinfo会一直报「Unable to contact slurm controller」,你会在 slurm.conf 上白折腾半天。
4.2 slurm.conf:真正需要改的只有十几行
先装包。Ubuntu 18.04 里slurm-wlm会把slurmctld、slurmd一起带上,单独装后面两个也行,但没必要。
apt-get update apt-get install -y slurm-wlm slurmd slurmctld这里有个判断要做:原文里还装了mariadb-server和libmysqlclient-dev,那是为了 Slurm 的数据库记账(slurmdbd)。如果你只想先把批量对接跑起来,完全不用碰数据库——把AccountingStorageType和JobCompType都设成none,作业照样调度,只是没有sacct历史记录。等真的需要统计资源使用量了再补 slurmdbd,能省掉一大段和数据库连接的纠缠。
配置文件按节点拓扑写,三个节点各一份,内容除节点名和地址外完全一致:
ClusterName=dockcluster ControlMachine=node01 ControlAddr=192.168.1.11 SlurmUser=root SlurmctldPort=6817 SlurmdPort=6818 SlurmctldPidFile=/var/run/slurm-llnl/slurmctld.pid SlurmdPidFile=/var/run/slurm-llnl/slurmd.pid StateSaveLocation=/var/lib/slurm-llnl/slurmctld SlurmdSpoolDir=/var/spool/slurm/d AuthType=auth/munge CryptoType=crypto/munge ProctrackType=proctrack/pgid TaskPlugin=task/none SwitchType=switch/none MpiDefault=none SelectType=select/linear SchedulerType=sched/backfill ReturnToService=1 SlurmctldTimeout=120 SlurmdTimeout=300 SlurmctldLogFile=/var/log/slurm-llnl/slurmctld.log SlurmdLogFile=/var/log/slurm-llnl/slurmd.log SlurmctldDebug=3 SlurmdDebug=3 NodeName=node01 NodeAddr=192.168.1.11 CPUs=1 CoresPerSocket=1 ThreadsPerCore=1 NodeName=node02 NodeAddr=192.168.1.12 CPUs=1 CoresPerSocket=1 ThreadsPerCore=1 NodeName=node03 NodeAddr=192.168.1.13 CPUs=1 CoresPerSocket=1 ThreadsPerCore=1 PartitionName=p1 Nodes=node01 Default=YES MaxTime=INFINITE State=UP PartitionName=p2 Nodes=node02 Default=YES MaxTime=INFINITE State=UP PartitionName=p3 Nodes=node03 Default=YES MaxTime=INFINITE State=UP| 字段 | 作用 | 踩坑点 |
|---|---|---|
| ControlMachine | 控制节点主机名 | Slurm 19.05 之后改成 SlurmctldHost,写错不生效 |
| SlurmUser | 运行守护进程的身份 | 设 root 省事,但 NFS 那边要配 no_root_squash |
| StateSaveLocation | 作业状态持久化目录 | 目录不存在,slurmctld 直接起不来 |
| SelectType | 资源选择插件 | linear 表示整机分配,是 17.11 时代的默认 |
| ProctrackType | 进程追踪方式 | pgid 最简单,不需要额外内核组件 |
| SchedulerType | 调度策略 | backfill 允许小作业插队,吞吐更好 |
| ReturnToService | 节点恢复策略 | 设 1 后节点重启会自动回到可用状态 |
版本差异这一条值得单独提醒。Ubuntu 18.04 仓库里的 Slurm 是 17.11 系列,用ControlMachine;如果你后面升级到 19.05 以上,这个字段被SlurmctldHost=node01取代,写成老字段会被忽略,表现是控制器监听在一个你没想到的地址上。判断依据很简单:slurmctld -V看版本,再对着版本改字段名。
CPUs=1也别照抄。这个值是节点实际可分配的 CPU 数,写小了浪费核,写大了调度器会把多个作业塞到一个核上,跑起来互相拖慢。用nproc看实际核数再填。
4.3 目录、日志与开机自启
Slurm 对目录很敏感,路径写进配置就必须真实存在,否则守护进程启动即退出。
mkdir -p /var/spool/slurm/d mkdir -p /var/lib/slurm-llnl/slurmctld mkdir -p /var/log/slurm-llnl systemctl enable slurmd slurmctld systemctl start slurmd slurmctld systemctl status slurmctld --no-pager起不来先看日志,不要靠猜:
journalctl -u slurmctld -n 50 --no-pager最常见的两条是Failed to initialize slurmctld(目录缺失或权限不对)和Unable to contact munge(4.1 那步没通)。还有一个容易自我怀疑的点:配置到底读的是哪个文件。Ubuntu 的 deb 包默认读/etc/slurm-llnl/slurm.conf,有些文档写的是/etc/slurm/slurm.conf,用systemctl cat slurmctld | grep ExecStart看一眼启动参数里的-f指向哪里,比自己纠结快得多。
4.4 三节点与 NFS:/vina 必须是同一份
三个节点各自装好工具之后,「工作目录」必须共享,否则作业被调度到 node02 时找不到配体文件。NFS 是最省事的做法。
# node01 上装服务端 apt-get install -y nfs-kernel-server mkdir -p /vina echo "/vina 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash)" >> /etc/exports exportfs -ra # node02 / node03 上挂载 apt-get install -y nfs-common mount -t nfs 192.168.1.11:/vina /vina df -h /vinano_root_squash这个选项是配合SlurmUser=root用的:不加的话,客户端以 root 身份写文件会被映射成匿名用户,结果就是作业跑完了但output.sdf写不进去,报Permission denied。挂载顺序也要注意:先挂载再往/vina里放数据,否则挂载会被本地目录覆盖,你之前拷进去的文件「消失」,实际上是被盖在挂载点下面了。
4.5 第一个作业:先验证调度链路,再谈并行对接
别一上来就投对接任务。先用一个两行的作业确认 sbatch → slurmd → 输出文件这条链路是通的。
#!/bin/bash #SBATCH -o job.%j.out #SBATCH -p p1 #SBATCH -J myFirstJob #SBATCH --nodes=1 #SBATCH --ntasks-per-node=1 hostname sleep 10sbatch job1.sh提交后,用这几个命令观察:
squeue # 看作业状态 R 运行 / PD 排队 sinfo # 看分区和节点的 State 是否 UP scancel 12 # 取消作业号 12 cat job.12.out # 看输出,应该是 node01sinfo里节点如果是down或drain,用sinfo -R看原因——多数是 slurmd 没起来或者 munge 认证失败。这一步过了,才说明从那台机器到那个节点的作业投递是通的,后面并行的所有问题都可以排除掉调度层面的嫌疑。
把原来的串行脚本改造成作业脚本,只需要在顶部加 SBATCH 指令、把原来的循环体包进去:
#!/bin/bash #SBATCH -o job.%j.out #SBATCH -p p1 #SBATCH -J dock_p1 #SBATCH --nodes=1 #SBATCH --ntasks-per-node=1 # 作业环境不继承登录 shell 的 PATH,三行必须重写 export PATH=/opt/dock/autodock_vina_1_1_2_linux_x86/bin:$PATH export PATH=/opt/dock/mgltools_x86_64Linux2_1.5.6/bin:$PATH export PATH=/opt/dock/mgltools_x86_64Linux2_1.5.6/MGLToolsPckgs/AutoDockTools/Utilities24:$PATH cd /vina/work/p1/mols for mol2 in *.mol2 do prepare_ligand4.py -l "$mol2" -o ligand.pdbqt -A hydrogens vina --config ../conf.txt --cpu 1 --out out.pdbqt --log log.txt obabel -ipdbqt out.pdbqt -osd -O temp.sdf cat temp.sdf >> output.sdf rm -f log.txt ligand.pdbqt out.pdbqt temp.sdf done配体按三份拆到p1、p2、p3三个目录,三个作业各占一个分区同时跑,整体耗时理论上降到三分之一。拆分可以很粗糙:
i=0 for f in /vina/work/mols/*.mol2 do case $((i % 3)) in 0) mv "$f" /vina/work/p1/mols/ ;; 1) mv "$f" /vina/work/p2/mols/ ;; 2) mv "$f" /vina/work/p3/mols/ ;; esac i=$((i+1)) done5. 避坑排查:从装到跑,五个最耗时间的坑
5.1 安装期的两个坑
第一个:python install.py报SyntaxError: Missing parentheses in call to 'print'。
现象是解压、进入目录、执行安装脚本,第一屏就刷一堆语法错误,看起来像安装包损坏。原因不是包坏了,是python这个命令指向了 Python 3——MGLTools 1.5.6 的安装脚本和它的大部分工具都是 Python 2 写的,print "xxx"这种写法在 3 里是语法错误。解决方式是显式用 2.7:python2.7 install.py,同时检查which python和echo $PATH,如果前面挂着某个科学计算发行版的 bin 目录,要么临时从 PATH 里摘掉,要么用绝对路径调用/usr/bin/python2.7。装完之后所有Utilities24下的脚本也走pythonsh,不要再拿系统 python 去调。
第二个:prepare_receptor4.py报Could not find atomic number for ...。
现象是受体 PDB 里某个原子(常见的是金属离子、修饰残基、或者原子名带撇号的特殊原子)在 AutoDock 的原子类型表里查不到,脚本处理到那儿就停。原因有两类:一类是 PDB 文件里原子名不规范(比如CA在蛋白质里是 α 碳,在某些配体里是钙),另一类是残基本身不在标准列表里。解决方式是先加-e把非标准残基删掉试试,能过就说明是老残留物;不能过就把报错的那几行从 PDB 里手动抠出来,看原子名和残基名,必要时用obabel -ipdb x.pdb -opdb -O y.pdb重新规整一遍。
5.2 单机运行期的两个坑
第三个:循环跑到某个配体,prepare_ligand4.py直接退出,提示缺@<TRIPOS>ATOM或原子数为 0。
现象很突然,前面几十个都好好的,就某一个崩。原因是obabel -m拆出来的某个 mol2 本身就是坏的——原 SDF 里那个分子可能只有坐标没有键信息,或者带了个 obabel 无法识别的键类型,转换时静默产生了空分子块。解决方式是在拆分后加一步体检,把异常文件提前挑出来:
# mol2 里 ATOM 行数少于 3 的基本都是坏分子,先剔出去 for f in vs*.mol2 do n=$(grep -c "^ *[0-9]* .* " "$f") [ "$n" -lt 3 ] && echo "suspect: $f" done更省事的做法是在主循环里对prepare_ligand4.py的返回码做判断,失败就跳过并记
本文还有配套的精品资源,点击获取