├── CONTCAR.jpg ├── ENERGY.png ├── RDF_all.png ├── README.md ├── XDATCAR_toolkit.py ├── compare.jpg ├── image-convertion.png ├── index.html ├── rdf_1.png ├── rmsd_1.jpg ├── rmsd_1.png ├── rmsd_2.jpg ├── rmsd_md_analysis.png ├── rmsd_noPBC.jpg └── 第一性原理动力学(AIMD)结果分析.md /CONTCAR.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/CONTCAR.jpg -------------------------------------------------------------------------------- /ENERGY.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/ENERGY.png -------------------------------------------------------------------------------- /RDF_all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/RDF_all.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # XDATCAR_toolkit 2 | XDATCAR_toolkit- A tool for convert XDATCAR to PDB 3 | -------------------------------------------------------------------------------- /XDATCAR_toolkit.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | # -*- coding:utf-8 -*- 3 | #Convert XDATCAR to PDB and extract energy & temperature profile for AIMD simulations 4 | # -h for help;-b for frame started;-e for frame ended;;-p for PDB conversion 5 | #by nxu tamas@zju.edu.cn 6 | #version 1.2 7 | #date 2019.4.9 8 | 9 | import os 10 | import shutil 11 | import numpy as np 12 | import matplotlib as mpl 13 | import math 14 | mpl.use('Agg') #silent mode 15 | from matplotlib import pyplot as plt 16 | from optparse import OptionParser 17 | import sys 18 | from copy import deepcopy 19 | 20 | class debug(object): 21 | def __init__(self,info='debug'): 22 | self.info=info 23 | def __call__(self,func): 24 | def wrapper(*args,**kwargs): 25 | print('[{info}] Now entering function {function}.....' \ 26 | .format(info=self.info,function=getattr(func,"__name__"))) 27 | return func(*args,**kwargs) 28 | return wrapper 29 | 30 | class Energy_Temp(object): 31 | def __init__(self): 32 | 'Read vasp MD energies and temperature.' 33 | self.temp=[] 34 | self.energy=[] 35 | self.energy_extract() 36 | 37 | def energy_extract(self): 38 | print('Now reading vasp MD energies and temperature.') 39 | if os.path.exists("OSZICAR"): 40 | oszicar=open("OSZICAR",'r') 41 | for index,line in enumerate(oszicar): 42 | if "E0=" in line: 43 | self.temp.append(float(line.split()[2])) 44 | self.energy.append(float(line.split()[4])) 45 | oszicar.close() 46 | #return (self.temp,self.energy) 47 | else: 48 | raise IOError('OSZICAR does not exist!') 49 | 50 | class XDATCAR(Energy_Temp): 51 | def __init__(self): 52 | 'Read lattice information and atomic coordinate.' 53 | super(XDATCAR,self).__init__() 54 | print('Now reading vasp XDATCAR.') 55 | self.lattice=np.zeros((3,3)) 56 | self.NPT=False 57 | self.frames=0 58 | self._timestep=1 # timestep 1fs 59 | self.alpha=0.0 60 | self.beta=0.0 61 | self.gamma=0.0 62 | self.current_frame=0 63 | 64 | self._lattice1=0.0 65 | self._lattice2=0.0 66 | self._lattice3=0.0 67 | self._lattice=np.zeros(3) 68 | self.format_trans=False 69 | 70 | @property 71 | def timestep(self): 72 | return self._timestep 73 | @timestep.setter 74 | def timestep(self,new_time_step): 75 | self._timestep=new_time_step 76 | 77 | if os.path.exists("XDATCAR"): 78 | self.XDATCAR=open("XDATCAR",'r') 79 | else: 80 | raise IOError('XDATCAR does not exist!') 81 | title=self.XDATCAR.readline().strip() 82 | for index,line in enumerate(self.XDATCAR): 83 | if "Direct" in line: 84 | self.frames+=1 85 | # elif title in line: 86 | # self.NPT=True 87 | self.XDATCAR.seek(0) 88 | self.lattice_read() 89 | self.XDATCAR.readline() 90 | for i in range(self.total_atom): 91 | self.XDATCAR.readline() 92 | 93 | if "Direct configuration" not in self.XDATCAR.readline(): 94 | self.NPT=True 95 | #self.frames=len(['Direct' for line in self.XDATCAR if "Direct" in line]) 96 | print('Total frames {0}, NpT is {1}'.format(self.frames,self.NPT)) 97 | # assert len(self.energy)==self.frames, \ 98 | # 'Number of XDATCAR frames does not equal to that of Energy terms in OSZICAR' 99 | self.XDATCAR.seek(0) 100 | self.lowrange=0;self.uprange=self.frames-1 101 | if self.NPT == False: self.lattice_read() 102 | 103 | def step_select(self,selected_step): # 't>100ps and t < 1000ps' 104 | 'eg. t > 100 and t < 1000' 105 | assert isinstance(selected_step,str),'Selected timestep must be in a "string"' 106 | if 'and' in selected_step: 107 | conditions=selected_step.split("and") 108 | else: 109 | conditions=[selected_step] 110 | for condition in conditions: 111 | try: 112 | if '>=' in condition: 113 | ranges=int(condition.split('>=')[1].strip()) 114 | self.lowrange=ranges-1 115 | elif '<=' in condition: 116 | ranges=int(condition.split('<=')[1].strip()) 117 | self.uprange=ranges-1 118 | elif '>' in condition: 119 | ranges=int(condition.split('>')[1].strip()) 120 | self.lowrange=ranges 121 | elif '<' in condition: 122 | ranges=int(condition.split('<')[1].strip()) 123 | self.uprange=ranges-2 124 | else: 125 | print('Selected timestep is invaid!');continue 126 | except ValueError: 127 | print('Selected timestep is invaid!') 128 | sys.exit(0) 129 | if (self.lowrange >= self.frames-1) or (self.uprange < 0): 130 | raise ValueError('Selected timestep is wrong!') 131 | if self.lowrange < 0: self.lowrange= 0 132 | if self.uprange > self.frames-1: self.uprange= self.frames-1 133 | 134 | def lattice_read(self): 135 | self.title=self.XDATCAR.readline().rstrip('\r\n').rstrip('\n') 136 | self.scaling_factor=float(self.XDATCAR.readline()) 137 | for i in range(3): 138 | self.lattice[i]=np.array([float(j) for j in self.XDATCAR.readline().split()]) 139 | self.lattice*=self.scaling_factor 140 | self._lattice1=np.sqrt(np.sum(np.square(self.lattice[0]))) 141 | self._lattice2=np.sqrt(np.sum(np.square(self.lattice[1]))) 142 | self._lattice3=np.sqrt(np.sum(np.square(self.lattice[2]))) 143 | self._lattice[0]=self._lattice1 144 | self._lattice[1]=self._lattice2 145 | self._lattice[2]=self._lattice3 146 | self.alpha=math.acos(np.dot(self.lattice[1],self.lattice[2]) \ 147 | /float((self._lattice2*self._lattice3)))/np.pi*180.0 148 | self.beta=math.acos(np.dot(self.lattice[0],self.lattice[2]) \ 149 | /float((self._lattice1*self._lattice3)))/np.pi*180.0 150 | self.gamma=math.acos(np.dot(self.lattice[0],self.lattice[1]) \ 151 | /float((self._lattice1*self._lattice2)))/np.pi*180.0 152 | self.element_list=[j for j in self.XDATCAR.readline().split()] 153 | try: 154 | self.element_amount=[int(j) for j in self.XDATCAR.readline().split()] 155 | except ValueError: 156 | raise ValueError('VASP 5.x XDATCAR is needed!') 157 | self.total_elements=[] 158 | for i in range(len(self.element_amount)): 159 | self.total_elements.extend([self.element_list[i]]*self.element_amount[i]) 160 | #print(self.total_elements) 161 | self.total_atom=sum(self.element_amount) 162 | self.atomic_position=np.zeros((self.total_atom,3)) 163 | 164 | def __iter__(self): 165 | return self 166 | 167 | def __next__(self): 168 | self.next() 169 | 170 | def skiplines_(self): 171 | if self.NPT == True: self.lattice_read() 172 | self.XDATCAR.readline() 173 | for i in range(self.total_atom): 174 | self.XDATCAR.readline() 175 | 176 | def next(self): 177 | if self.NPT == True: self.lattice_read() 178 | self.XDATCAR.readline() 179 | for i in range(self.total_atom): 180 | line_tmp=self.XDATCAR.readline() 181 | self.atomic_position[i]=np.array([float(j) for j in line_tmp.split()[0:3]]) 182 | self.cartesian_position=np.dot(self.atomic_position,self.lattice) 183 | self.current_frame+=1 184 | # 185 | #self.cartesian_position*=self.scaling_factor 186 | return self.cartesian_position 187 | 188 | def writepdb(self,pdb_frame): 189 | tobewriten=[] 190 | tobewriten.append("MODEL %r" %(pdb_frame)) 191 | tobewriten.append("REMARK Converted from XDATCAR file") 192 | tobewriten.append("REMARK Converted using VASPKIT") 193 | tobewriten.append('CRYST1{0:9.3f}{1:9.3f}{2:9.3f}{3:7.2f}{4:7.2f}{5:7.2f}' .format(self._lattice1,\ 194 | self._lattice2,self._lattice3,self.alpha,self.beta,self.gamma)) 195 | #print(len(self.total_elements)) 196 | for i in range(len(self.total_elements)): 197 | tobewriten.append('%4s%7d%4s%5s%6d%4s%8.3f%8.3f%8.3f%6.2f%6.2f%12s' \ 198 | %("ATOM",i+1,self.total_elements[i],"MOL",1,' ',self.cartesian_position[i][0],\ 199 | self.cartesian_position[i][1],self.cartesian_position[i][2],1.0,0.0,self.total_elements[i])) 200 | tobewriten.append('TER') 201 | tobewriten.append('ENDMDL') 202 | with open('XDATCAR.pdb','a+') as pdbwriter: 203 | tobewriten=[i+'\n' for i in tobewriten] 204 | pdbwriter.writelines(tobewriten) 205 | 206 | def unswrapPBC(self,prev_atomic_cartesian): 207 | diff= self.cartesian_position-prev_atomic_cartesian 208 | prev_atomic_cartesian=deepcopy(self.cartesian_position) 209 | xx=np.where(diff[:,0]>(self._lattice1/2),diff[:,0]-self._lattice1\ 210 | ,np.where(diff[:,0]<-(self._lattice1/2),diff[:,0]+self._lattice1\ 211 | ,diff[:,0])) 212 | yy=np.where(diff[:,1]>(self._lattice2/2),diff[:,1]-self._lattice2\ 213 | ,np.where(diff[:,1]<-(self._lattice2/2),diff[:,1]+self._lattice2\ 214 | ,diff[:,1])) 215 | zz=np.where(diff[:,2]>(self._lattice3/2),diff[:,2]-self._lattice3\ 216 | ,np.where(diff[:,2]<-(self._lattice3/2),diff[:,2]+self._lattice3\ 217 | ,diff[:,2])) 218 | xx=xx.reshape(-1,1);yy=yy.reshape(-1,1);zz=zz.reshape(-1,1) 219 | return (prev_atomic_cartesian,np.concatenate([xx,yy,zz],axis=1)) 220 | 221 | def reset_cartesian(self,real_atomic_cartesian,center_atom): 222 | if center_atom > len(real_atomic_cartesian)-1: 223 | raise SystemError("Selected atom does not exist!") 224 | for i in range(0,len(real_atomic_cartesian)): 225 | for j in range(3): 226 | if (real_atomic_cartesian[i][j]-real_atomic_cartesian[center_atom][j])>self._lattice[j]/2: 227 | real_atomic_cartesian[i][j]-=self._lattice[j] 228 | if (real_atomic_cartesian[i][j]-real_atomic_cartesian[center_atom][j])<-self._lattice[j]/2: 229 | real_atomic_cartesian[i][j]+=self._lattice[j] 230 | return real_atomic_cartesian 231 | 232 | def __call__(self,selected_step): 233 | self.step_select(selected_step) 234 | def __str__(self): 235 | return ('Read lattice information and atomic coordinate.') 236 | __repr__=__str__ 237 | 238 | class plot(object): 239 | 'Plot MD temperature and energy profile' 240 | def __init__(self,lwd,font,dpi,figsize,XDATCAR_inst=None): 241 | self.XDATCAR_inst=XDATCAR_inst;self.timestep=self.XDATCAR_inst.timestep 242 | self.time_range=(self.XDATCAR_inst.lowrange,self.XDATCAR_inst.uprange+1) 243 | self.lwd=lwd;self.font=font;self.dpi=dpi;self.figsize=figsize 244 | 245 | #@debug(info='debug') 246 | def plotfigure(self,title='MD temperature and energy profile'): 247 | from matplotlib import pyplot as plt 248 | self.newtemp=self.XDATCAR_inst.temp;self.newenergy=self.XDATCAR_inst.energy 249 | xdata=np.arange(self.time_range[0],self.time_range[1])*self.timestep 250 | axe = plt.subplot(121) 251 | self.newtemp=self.newtemp[self.time_range[0]:self.time_range[1]] 252 | axe.plot(xdata,self.newtemp, \ 253 | color='black', lw=self.lwd, linestyle='-', alpha=1) 254 | with open("Temperature.dat",'w') as writer: 255 | writer.write("Time(fs) Temperature(K)\n") 256 | for line in range(len(xdata)): 257 | writer.write("{0:f} {1:f}\n" .format(xdata[line],self.newtemp[line])) 258 | axe.set_xlabel(r'$Time$ (fs)',fontdict=self.font) 259 | axe.set_ylabel(r'$Temperature$ (K)',fontdict=self.font) 260 | axe.set_xlim((self.time_range[0]*self.timestep, self.time_range[1]*self.timestep)) 261 | axe.set_title('MD temperature profile') 262 | 263 | axe1 = plt.subplot(122) 264 | self.newenergy=self.newenergy[self.time_range[0]:self.time_range[1]] 265 | axe1.plot(xdata,self.newenergy, \ 266 | color='black', lw=self.lwd, linestyle='-', alpha=1) 267 | with open("Energy.dat",'w') as writer: 268 | writer.write("Time(fs) Energy(ev)\n") 269 | for line in range(len(xdata)): 270 | writer.write("{0:f} {1:f}\n" .format(xdata[line],self.newenergy[line])) 271 | axe1.set_xlabel(r'$Time$ (fs)',fontdict=self.font) 272 | axe1.set_ylabel(r'$Energy$ (ev)',fontdict=self.font) 273 | axe1.set_xlim((self.time_range[0]*self.timestep, self.time_range[1]*self.timestep)) 274 | axe1.set_title('MD energy profile') 275 | #plt.suptitle(title) 276 | fig = plt.gcf() 277 | fig.set_size_inches(self.figsize) 278 | plt.tight_layout() 279 | plt.savefig('ENERGY.png',bbox_inches='tight',pad_inches=0.1,dpi=self.dpi) 280 | 281 | 282 | if __name__ == "__main__": 283 | parser = OptionParser() 284 | parser.add_option("-b", "--begin", 285 | dest="begin", default=1, 286 | help="frames begin") 287 | 288 | parser.add_option("-e", "--end", 289 | dest="end", default='false', 290 | help="frames end") 291 | 292 | parser.add_option("-t", "--timestep", 293 | dest="timestep", default=1.0, 294 | help="timestep per frame") 295 | 296 | parser.add_option("-p", "--pdb", 297 | action="store_true", dest="format_trans", default=False, 298 | help="choose whether to convert XDATCAR to PDB!") 299 | 300 | parser.add_option("--pbc", 301 | action="store_true", dest="periodic", default=False, 302 | help="choose whether to swrap PBC images!") 303 | 304 | parser.add_option("-i", "--index", 305 | dest="index", default=-1, 306 | help="choose which atom to center whole molecule!") 307 | 308 | parser.add_option("--interval", 309 | dest="interval", default=1, 310 | help="extract frames interval!") 311 | 312 | (options,args) = parser.parse_args() 313 | try: 314 | float(options.timestep) 315 | int(options.index) 316 | _interval=int(options.interval) 317 | int(options.begin) 318 | if options.end != 'false': int(options.end) 319 | except: 320 | raise ValueError('wrong arguments') 321 | XDATCAR_inst=XDATCAR() 322 | XDATCAR_iter=iter(XDATCAR_inst) 323 | if options.format_trans: 324 | XDATCAR_inst.format_trans=True 325 | if os.path.exists("XDATCAR.pdb"): 326 | shutil.copyfile("XDATCAR.pdb","XDATCAR-bak.pdb") 327 | os.remove("XDATCAR.pdb") 328 | else: 329 | XDATCAR_inst.format_trans=False 330 | XDATCAR_inst.timestep=float(options.timestep) #timestep 1fs 331 | if options.end == 'false': 332 | XDATCAR_inst('t>= %r' %(int(options.begin))) 333 | else: 334 | XDATCAR_inst('t>= %r and t <= %r' %(int(options.begin),int(options.end))) # frame 10~300 corresponding to 20~600fs 335 | if _interval >(XDATCAR_inst.uprange-XDATCAR_inst.lowrange): 336 | raise SystemError('Trajectory interval exceed selected time range!') 337 | elif _interval<=1: 338 | _interval=1 339 | count=0 340 | current_pdb=1 341 | for i in range(XDATCAR_inst.uprange+1): 342 | if (i>=XDATCAR_inst.lowrange): 343 | cartesian_position=XDATCAR_iter.next() 344 | if count % _interval == 0: 345 | if options.format_trans == True: 346 | if options.periodic == True: 347 | if i == XDATCAR_inst.lowrange: 348 | real_atomic_cartesian=deepcopy(cartesian_position) 349 | if int(options.index) != -1: 350 | real_atomic_cartesian=XDATCAR_inst.reset_cartesian(real_atomic_cartesian,int(options.index)-1) 351 | XDATCAR_inst.cartesian_position=real_atomic_cartesian 352 | prev_atomic_cartesian=deepcopy(cartesian_position) 353 | else: 354 | prev_atomic_cartesian,diffs=XDATCAR_inst.unswrapPBC(prev_atomic_cartesian) 355 | real_atomic_cartesian+=diffs 356 | XDATCAR_inst.cartesian_position=real_atomic_cartesian 357 | XDATCAR_inst.writepdb(current_pdb) 358 | current_pdb+=1 359 | count+=1 360 | else: 361 | XDATCAR_iter.skiplines_() 362 | 363 | newtemp=XDATCAR_inst.temp 364 | newenergy=XDATCAR_inst.energy 365 | print('Finish reading XDATCAR.') 366 | 367 | timestep=XDATCAR_inst.timestep 368 | print('Selected time-range:{0}~{1}fs'.format((XDATCAR_inst.lowrange)*timestep,\ 369 | (XDATCAR_inst.uprange)*timestep)) 370 | XDATCAR_inst.XDATCAR.close() 371 | print('Timestep for new PDB trajectory is :{0}fs'.format(timestep*_interval)) 372 | lwd = 0.2 # Control width of line 373 | dpi=300 # figure_resolution 374 | figsize=(5,4) #figure_inches 375 | font = {'family' : 'arial', 376 | 'color' : 'black', 377 | 'weight' : 'normal', 378 | 'size' : 13.0, 379 | } #FONT_setup 380 | plots=plot(lwd,font,dpi,figsize,XDATCAR_inst) 381 | plots.plotfigure() 382 | 383 | -------------------------------------------------------------------------------- /compare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/compare.jpg -------------------------------------------------------------------------------- /image-convertion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/image-convertion.png -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 第一性原理动力学(AIMD)结果分析 406 | 407 | 408 |

第一性原理分子动力学(AIMD)结果分析

与经典分子动力学不同,第一性原理分子动力学不需要提供力场参数,只需要提供原子初始结构,就能根据电子波函数正交化产生的虚拟力,求解牛顿运动方程。在运行优化任务时,VASP生成的XDATCAR记录的是优化步骤的离子构型;在运行AIMD任务时,记录的就是运动轨迹。而现阶段读取XDATCAR轨迹分析性质的后处理软件并不多,能读取的兼容性也并不好。VASPKIT0.72版本之后支持了将XDATCAR转换成通用的多帧PDB文件的功能(504)以便可视化并进行后处理分析。但是并没有提供后处理分析接口,因此我们开发了一个Python脚本XDATCAR_toolkit.py,除了实现了选择一定范围内的帧数转换成PDB文件的功能,还可以提取分子动力学模拟过程中的能量,温度并做出变化趋势图。这对判断动力学是否平衡很有帮助。另外本脚本预留了接口,可以调用读取每一帧的晶格信息和原子坐标,以便进行后续扩展编程。此脚本需要安装了numpy包的python环境,以及matplotlib包以便于画图。

在得到通用轨迹PDB文件后,就可以利用现用的分子动力学后处理软件进行处理分析,比如VMDMDtrajMD AnalysisPymol等。本教程将演示通过VMDMD Analysis软件包分析RDF(径向分布函数)和RMSD(均方根偏差),前者可以用来分析结构性质,后者对判断结构是否稳定以及模拟是否平衡很有帮助。

将XDATCAR转换成PDB文件

以VASP官网中单个水分子的AIMD模拟为例。模拟的输入文件如下,模拟的步长是0.5fs,模拟步数1000步,模拟时间500fs。脚本和测试例子可以在我的Github仓库(https://github.com/tamaswells/VASP_script/tree/master/XDATCAR_tookit)下载。

图1. 模拟的盒子

INCAR

POSCAR

KPOINTS

模拟完成后将XDATCAR_toolkit.py上传到文件夹中(或者置于环境变量的路径文件夹中并赋予可执行权限即可直接调用命令XDATCAR_toolkit.py运行脚本),在shell环境中运行以下命令:

即可将XDATCAR的全部帧也就是0~499.5fs的轨迹转化成PDB格式。其中-p用于开启PDB转换功能,-t 0.5用于指定时间步为0.5fs,--pbc用于获取基于第一帧演变的连续轨迹。

运行完成后,将会在文件夹内生成Temperature.datEnergy.datENERGY.pngXDATCAR.pdb四个文件,前面两个分别为温度和能量随着模拟时间的的变化数据,第三个是使用matplotlib绘制的趋势图(如下图),最后一个是转换得到的轨迹PDB文件,可以用于可视化轨迹,亦可用于后处理分析。-b参数用于指定转换从哪一帧开始,-e参数用于指定转换到哪一帧结束。经刘锦程博士建议,增加一个--pbc的选项,用于处理周期性获取连续的轨迹。当分子穿过盒子边界时,记录真实的位置坐标(尽管它出了边界)而不是从盒子另一边穿入的ghost原子的坐标。这对于分析与时间相关性的量(比如RMSD)很有帮助。所谓连续指的是后面的轨迹都是从第一帧演变得到的真实坐标,但是并不能保证第一帧的分子是完整的,由于周期性的缘故,第一帧内摆放的分子可能分处于盒子两侧。李继存老师有篇博文(http://jerkwin.github.io/2016/05/31/GROMACS%E8%BD%A8%E8%BF%B9%E5%91%A8%E6%9C%9F%E6%80%A7%E8%BE%B9%E7%95%8C%E6%9D%A1%E4%BB%B6%E7%9A%84%E5%A4%84%E7%90%86/)讲的很明白,可以参考。如果发现第一帧内分子不完整,可以通过添加-i 1参数将分子向第一个原子靠近平移以获得完整的分子。如果发现不理想,可以通过调整-i的参数获得完整的分子。

图2. 温度和系统能量的变化趋势图

RDF径向分布函数分析

得到PDB文件后,可以使用VMDMD Analysis等分子动力学后处理软件进行分析。

使用VMD分析工具分析

打开VMD,将PDB文件拖入显示窗口,在主菜单VMD Main中选择Extensions-Analysis-Radial Pair Distribution Function g(r),选择分析H(type H)在O(type O)周围的概率分布。值得注意的是分析RDF时,横坐标也就是max r不能超过盒子最小边长的一半,也就是得满足最小映像约定。如图4所示,在计算RDF时,如果max r的取值大于盒子最小边长的一半,就有可能重复算到一个粒子和它的映像粒子,这使得程序的周期性判断失准。将生成的dat文件的第一列和第二列作图即可得到RDF图。

图3. VMD中计算RDF
图4. 最小映像约定示意图

使用 MD Analysis分析 RDF

MD Analysis是一个成熟的分子动力学后处理软件,使用Python编写,开源。其教程不仅步骤详细还会给出背景理论知识。可以通过conda或者pip工具在线安装。

RDF分析的介绍和使用方法在网页(https://www.mdanalysis.org/docs/documentation_pages/analysis/rdf.html#radial-distribution-functions-mdanalysis-analysis-rdf)上查看。使用以下的脚本得到在O原子周围找到H原子的概率,并调用matplotlib绘制RDF图。在1.0 Å 处出现一个尖峰,也就是对应了O-H键的平衡键长(0.96Å)。

图5. RDF_O_H

RMSD均方根偏差分析

VMD分析RMSD

确保使用了-pbc参数以获取连续的轨迹,将生成的XDATCAR.pdb文件拖入显示窗口。如图6右所示,第一帧内水的三个原子不在同一个镜像内,分子不完整。在进行RMSD分析时,尽管轨迹是连续的,但是在对齐分子时就会出现问题。因此在本例中需要选择第一个原子作为中心将分子平移完整,在图6左中,分子已经在同一个镜像中了。

 

图6. 完整和不完整的水分子

将重新生成的PDB文件拖入显示窗口,在主菜单VMD Main中选择Extensions-Analysis-Analysis-RMSD Trajectory Tool,在计算RMSD前必须先做Align(对齐),这会使得每一帧结构进行平移、旋转来与参考帧的结构尽可能贴近,从而使得RMSD最小化。刘锦程提到研究生物法分子的RMSD时需要对齐操作,而研究小分子时不需要对齐分子。

图7. VMD中计算RMSD

把左上角文本框里的默认的Protein改成all(代表所有原子都纳入考虑),然后把noh复选框的勾去掉(否则将忽略氢原子)。然后点右上角的ALIGN按钮,此时所有帧的结构就已经对齐了。本例中演示以模拟的第一帧为参考,分析氧原子位置的均方根偏差。因此在Reference mol那里选top作为参考结构,左上角文本框由all改为type O(代表计算O原子的RMSD),然后勾上Plot复选框,最后点击RMSD按钮即可得到O原子的RMSD图。在File菜单栏可以选择导出dat数据。

图8. VMD中未对齐轨迹计算的RMSD
图9. VMD中对齐了轨迹后计算的RMSD

 

使用 MD Analysis分析 RMSD

RMSD分析的介绍和使用方法在网页(https://www.mdanalysis.org/docs/documentation_pages/analysis/rms.html?highlight=average上查看。使用以下的脚本可以分别得到所有原子,氢原子,氧原子的RMSD,并调用matplotlib绘制RMSD图。网页中有一段话(Note If you use trajectory data from simulations performed under periodic boundary conditions then you must make your molecules whole before performing RMSD calculations so that the centers of mass of the selected and reference structure are properly superimposed.)也就是在计算RMSD的时候选择的分子必须是完整的,不能分处于盒子的两边。这与我们之前的描述是一致的。MD Analysis默认对齐了分子。

使用以下脚本可以绘制对齐了轨迹后所有原子,氧原子和氢原子的RMSD。

 

图10. MD Analysis计算的对齐了轨迹后计算的所有原子,H原子和O原子的RMSD

 

 

 

 

409 | 410 | 411 | -------------------------------------------------------------------------------- /rdf_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/rdf_1.png -------------------------------------------------------------------------------- /rmsd_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/rmsd_1.jpg -------------------------------------------------------------------------------- /rmsd_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/rmsd_1.png -------------------------------------------------------------------------------- /rmsd_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/rmsd_2.jpg -------------------------------------------------------------------------------- /rmsd_md_analysis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/rmsd_md_analysis.png -------------------------------------------------------------------------------- /rmsd_noPBC.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/tamaswells/XDATCAR_toolkit/30c3d25ab904713cac0c720ffbfcc8a1effa2fd0/rmsd_noPBC.jpg -------------------------------------------------------------------------------- /第一性原理动力学(AIMD)结果分析.md: -------------------------------------------------------------------------------- 1 | ## 第一性原理分子动力学(AIMD)结果分析 2 | 3 | 与经典分子动力学不同,第一性原理分子动力学不需要提供力场参数,只需要提供原子初始结构,就能根据电子波函数正交化产生的虚拟力,求解牛顿运动方程。在运行优化任务时,VASP生成的XDATCAR记录的是优化步骤的离子构型;在运行AIMD任务时,记录的就是运动轨迹。而现阶段读取XDATCAR轨迹分析性质的后处理软件并不多,能读取的兼容性也并不好。VASPKIT0.72版本之后支持了将XDATCAR转换成通用的多帧PDB文件的功能(504)以便可视化并进行后处理分析。但是并没有提供后处理分析接口,因此我们开发了一个Python脚本`XDATCAR_toolkit.py`,除了实现了选择一定范围内的帧数转换成PDB文件的功能,还可以提取分子动力学模拟过程中的能量,温度并做出变化趋势图。这对判断动力学是否平衡很有帮助。另外本脚本预留了接口,可以调用读取每一帧的晶格信息和原子坐标,以便进行后续扩展编程。此脚本需要安装了`numpy`包的python环境,以及`matplotlib`包以便于画图。 4 | 5 | 在得到通用轨迹PDB文件后,就可以利用现用的分子动力学后处理软件进行处理分析,比如`VMD`,`MDtraj`,`MD Analysis`, `Pymol`等。本教程将演示通过`VMD`和`MD Analysis`软件包分析`RDF`(径向分布函数)和`RMSD`(均方根偏差),前者可以用来分析结构性质,后者对判断结构是否稳定以及模拟是否平衡很有帮助。 6 | 7 | ### 将XDATCAR转换成PDB文件 8 | 以VASP官网中单个水分子的AIMD模拟为例。模拟的输入文件如下,模拟的步长是0.5fs,模拟步数1000步,模拟时间500fs。脚本和测试例子可以在我的Github仓库(https://github.com/tamaswells/VASP_script/tree/master/XDATCAR_tookit)下载。 9 | 10 | ![](./CONTCAR.jpg) 11 | 12 |
图1. 模拟的盒子
13 | 14 | #### INCAR 15 | 16 | ```bash 17 | PREC = Normal ! standard precision 18 | ENMAX = 400 ! cutoff should be set manually 19 | ISMEAR = 0 ; SIGMA = 0.1 20 | 21 | ISYM = 0 ! strongly recommened for MD 22 | IBRION = 0 ! molecular dynamics 23 | NSW = 1000 ! 1000 steps 24 | POTIM = 0.5 ! timestep 0.5 fs 25 | 26 | SMASS = -3 ! Nose Hoover thermostat 27 | TEBEG = 2000 ; TEEND = 2000 ! temperature 28 | 29 | NBANDS = 8 30 | ``` 31 | 32 | #### POSCAR 33 | 34 | ```bash 35 | H2O _2 36 | 0.52918 ! scaling parameter 37 | 12 0 0 38 | 0 12 0 39 | 0 0 12 40 | 1 2 41 | select 42 | cart 43 | 0.00 0.00 0.00 T T F 44 | 1.10 -1.43 0.00 T T F 45 | 1.10 1.43 0.00 T T F 46 | ``` 47 | 48 | #### KPOINTS 49 | 50 | ```bash 51 | Gamma-point only 52 | 1 ! one k-point 53 | rec ! in units of the reciprocal lattice vector 54 | 0 0 0 1 ! 3 coordinates and weight 55 | ``` 56 | 57 | 模拟完成后将`XDATCAR_toolkit.py`上传到文件夹中(或者置于环境变量的路径文件夹中并赋予可执行权限即可直接调用命令`XDATCAR_toolkit.py`运行脚本),在shell环境中运行以下命令: 58 | 59 | ```bash 60 | python XDATCAR_toolkit.py -p -t 0.5 --pbc 61 | ``` 62 | 63 | 即可将XDATCAR的全部帧也就是0~499.5fs的轨迹转化成PDB格式。其中`-p`用于开启PDB转换功能,`-t 0.5`用于指定时间步为0.5fs,`--pbc`用于获取基于第一帧演变的连续轨迹。 64 | 65 | ```bash 66 | Now reading vasp MD energies and temperature. 67 | Now reading vasp XDATCAR. 68 | Total frames 1000, NpT is False 69 | Finish reading XDATCAR. 70 | Selected time-range:0.0~499.5fs 71 | [debug] Now entering function plotfigure..... 72 | ``` 73 | 74 | 运行完成后,将会在文件夹内生成`Temperature.dat`,`Energy.dat`,`ENERGY.png`和`XDATCAR.pdb`四个文件,前面两个分别为温度和能量随着模拟时间的的变化数据,第三个是使用`matplotlib`绘制的趋势图(如下图),最后一个是转换得到的轨迹PDB文件,可以用于可视化轨迹,亦可用于后处理分析。`-b`参数用于指定转换从哪一帧开始,`-e`参数用于指定转换到哪一帧结束。经刘锦程博士建议,增加一个`--pbc`的选项,用于处理周期性获取连续的轨迹。当分子穿过盒子边界时,记录真实的位置坐标(尽管它出了边界)而不是从盒子另一边穿入的`ghost`原子的坐标。这对于分析与时间相关性的量(比如RMSD)很有帮助。所谓连续指的是后面的轨迹都是从第一帧演变得到的真实坐标,但是并不能保证第一帧的分子是完整的,由于周期性的缘故,第一帧内摆放的分子可能分处于盒子两侧。李继存老师有篇博文(http://jerkwin.github.io/2016/05/31/GROMACS%E8%BD%A8%E8%BF%B9%E5%91%A8%E6%9C%9F%E6%80%A7%E8%BE%B9%E7%95%8C%E6%9D%A1%E4%BB%B6%E7%9A%84%E5%A4%84%E7%90%86/)讲的很明白,可以参考。如果发现第一帧内分子不完整,可以通过添加`-i 1`参数将分子向第一个原子靠近平移以获得完整的分子。如果发现不理想,可以通过调整`-i`的参数获得完整的分子。 75 |
76 | 77 |
图2. 温度和系统能量的变化趋势图
78 | 79 | ### RDF径向分布函数分析 80 | 81 | 得到PDB文件后,可以使用`VMD`,`MD Analysis`等分子动力学后处理软件进行分析。 82 | 83 | #### 使用VMD分析工具分析 84 | 85 | 打开VMD,将PDB文件拖入显示窗口,在主菜单`VMD Main`中选择`Extensions-Analysis-Radial Pair Distribution Function g(r)`,选择分析H(type H)在O(type O)周围的概率分布。值得注意的是分析RDF时,横坐标也就是max r不能超过盒子最小边长的一半,也就是得满足最小映像约定。如图4所示,在计算RDF时,如果max r的取值大于盒子最小边长的一半,就有可能重复算到一个粒子和它的映像粒子,这使得程序的周期性判断失准。将生成的dat文件的第一列和第二列作图即可得到RDF图。 86 | 87 | ![](./rdf_1.png) 88 | 89 |
图3. VMD中计算RDF
90 | 91 |
92 | 93 | 94 |
图4. 最小映像约定示意图
95 | 96 | #### 使用 MD Analysis分析 RDF 97 | 98 | [MD Analysis](https://www.mdanalysis.org/)是一个成熟的分子动力学后处理软件,使用Python编写,开源。其教程不仅步骤详细还会给出背景理论知识。可以通过conda或者pip工具在线安装。 99 | 100 | ```bash 101 | conda config --add channels conda-forge 102 | conda install mdanalysis 103 | #or 104 | pip install --upgrade MDAnalysis 105 | ``` 106 | 107 | RDF分析的介绍和使用方法在网页(https://www.mdanalysis.org/docs/documentation_pages/analysis/rdf.html#radial-distribution-functions-mdanalysis-analysis-rdf)上查看。使用以下的脚本得到在O原子周围找到H原子的概率,并调用`matplotlib`绘制RDF图。在1.0 $Å$ 处出现一个尖峰,也就是对应了O-H键的平衡键长(0.96$Å$)。 108 | 109 | ```python 110 | import MDAnalysis 111 | import MDAnalysis.analysis.rdf 112 | import matplotlib.pyplot as plt 113 | 114 | u = MDAnalysis.Universe('XDATCAR.pdb', permissive=True) 115 | g1= u.select_atoms('type O') 116 | g2= u.select_atoms('type H') 117 | rdf = MDAnalysis.analysis.rdf.InterRDF(g1,g2,nbins=75, range=(0.0, min(u.dimensions[:3])/2.0)) 118 | 119 | rdf.run() 120 | 121 | fig = plt.figure(figsize=(5,4)) 122 | ax = fig.add_subplot(111) 123 | ax.plot(rdf.bins, rdf.rdf, 'k-', label="rdf") 124 | 125 | ax.legend(loc="best") 126 | ax.set_xlabel(r"Distance ($\AA$)") 127 | ax.set_ylabel(r"RDF") 128 | fig.savefig("RDF_all.png") 129 | #plt.show() 130 | ``` 131 | 132 | ![](./RDF_all.png) 133 | 134 |
图5. RDF_O_H
135 | 136 | ### RMSD均方根偏差分析 137 | 138 | #### VMD分析RMSD 139 | 140 | 确保使用了`-pbc`参数以获取**连续的轨迹**,将生成的`XDATCAR.pdb`文件拖入显示窗口。如图6右所示,第一帧内水的三个原子不在同一个镜像内,分子不完整。在进行RMSD分析时,尽管轨迹是连续的,但是在对齐分子时就会出现问题。因此在本例中需要选择第一个原子作为中心将分子平移完整,在图6左中,分子已经在同一个镜像中了。 141 | 142 | ```bash 143 | python XDATCAR_toolkit.py -p -t 0.5 --pbc -i 1 144 | ``` 145 | 146 | 147 | 148 | ![](./compare.jpg) 149 | 150 |
图6. 完整和不完整的水分子
151 | 152 | 将重新生成的PDB文件拖入显示窗口,在主菜单`VMD Main`中选择`Extensions-Analysis-Analysis-RMSD Trajectory Tool`,在计算RMSD前必须先做`Align`(对齐),这会使得每一帧结构进行平移、旋转来与参考帧的结构尽可能贴近,从而使得RMSD最小化。**刘锦程提到研究生物法分子的RMSD时需要对齐操作,而研究小分子时不需要对齐分子。** 153 | 154 | ![](./rmsd_1.png) 155 | 156 |
图7. VMD中计算RMSD
157 | 158 | 把左上角文本框里的默认的`Protein`改成`all`(代表所有原子都纳入考虑),然后把`noh`复选框的勾去掉(否则将忽略氢原子)。然后点右上角的`ALIGN按钮`,此时所有帧的结构就已经对齐了。本例中演示以模拟的第一帧为参考,分析氧原子位置的均方根偏差。因此在`Reference mol`那里选`top`作为参考结构,左上角文本框由`all`改为`type O`(代表计算O原子的RMSD),然后勾上`Plot`复选框,最后点击`RMSD`按钮即可得到O原子的RMSD图。在`File`菜单栏可以选择导出dat数据。 159 | 160 |
161 | 162 |
图8. VMD中未对齐轨迹计算的RMSD
163 | 164 |
165 | 166 |
图9. VMD中对齐了轨迹后计算的RMSD
167 | 168 | 169 | 170 | #### 使用 MD Analysis分析 RMSD 171 | 172 | RMSD分析的介绍和使用方法在网页(https://www.mdanalysis.org/docs/documentation_pages/analysis/rms.html?highlight=average上查看。使用以下的脚本可以分别得到所有原子,氢原子,氧原子的RMSD,并调用`matplotlib`绘制RMSD图。网页中有一段话(Note If you use trajectory data from simulations performed under **periodic boundary conditions** then you *must make your molecules whole* before performing RMSD calculations so that the centers of mass of the selected and reference structure are properly superimposed.)也就是在计算RMSD的时候选择的分子必须是**完整的**,不能分处于盒子的两边。这与我们之前的描述是一致的。`MD Analysis`默认对齐了分子。 173 | 174 | 使用以下脚本可以绘制对齐了轨迹后所有原子,氧原子和氢原子的RMSD。 175 | 176 | ```bash 177 | import MDAnalysis 178 | import MDAnalysis.analysis.rms 179 | import matplotlib.pyplot as plt 180 | 181 | u = MDAnalysis.Universe('XDATCAR.pdb', permissive=True) 182 | ref = MDAnalysis.Universe('XDATCAR.pdb', permissive=True) # reference (with the default ref_frame=0) 183 | ref.trajectory[0] #use first frame as reference 184 | R = MDAnalysis.analysis.rms.RMSD(u, ref, 185 | select="all", # superimpose on whole backbone of all atoms # align based on all atoms 186 | groupselections=["type H","type O"], 187 | filename="rmsd_all.dat",center=True)#, # CORE 188 | timestep=0.0005 #0.5fs from fs to ps as Reader has no dt information, set to 1.0 ps 189 | R.run() 190 | rmsd = R.rmsd.T # transpose makes it easier for plotting 191 | time = rmsd[1]*timestep 192 | 193 | fig = plt.figure(figsize=(5,4)) 194 | ax = fig.add_subplot(111) 195 | ax.plot(time, rmsd[2], 'k-', label="all") 196 | ax.plot(time, rmsd[3], 'r--', label="type H") 197 | ax.plot(time, rmsd[4], 'b--', label="type O") 198 | ax.legend(loc="best") 199 | ax.set_xlabel("time (ps)") 200 | ax.set_ylabel(r"RMSD ($\AA$)") 201 | fig.savefig("rmsd_md_analysis.png") 202 | ``` 203 | 204 | 205 | 206 | ![](./rmsd_md_analysis.png) 207 | 208 |
图10. MD Analysis计算的对齐了轨迹后计算的所有原子,H原子和O原子的RMSD
209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | --------------------------------------------------------------------------------