├── README.md └── xdatcar.py /README.md: -------------------------------------------------------------------------------- 1 | # VASP_XDATCAR 2 | A python class for parsing VASP XDATCAR from molecular dynamics. 3 | 4 | The XDATCAR file contains the trajectory during a molecular dynamics run, i.e. 5 | the positions of all the atoms at each time step. From this information, we may 6 | calculate the following physical quantity 7 | 8 | 1. the time-dependent temperature of the system 9 | 2. Velocity Autocorrelation Function (VAF) and Phonon Density of States 10 | 3. Pair Correlation Function (PCF) 11 | 12 | NOTES 13 | 14 | Set NBLOCK = 1 in the INCAR so that all the configuration in the MD run is 15 | wrtten to XDATCAR. 16 | 17 | The element mass (POMASS) and the MD time step (POTIM) is read from OUTCAR. 18 | -------------------------------------------------------------------------------- /xdatcar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This script was created by zqj 3 | # 4 | 5 | ##################################### NOTES ##################################### 6 | # 1. Set NBLOCK = 1 in the INCAR, so that all the configuration is wrtten to 7 | # XDATCAR. 8 | ################################################################################# 9 | 10 | import os 11 | import warnings 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | from scipy.fftpack import fft, ifft, fftfreq, fftshift 15 | 16 | # Boltzmann Constant in [eV/K] 17 | kB = 8.617332478E-5 18 | # electron volt in [Joule] 19 | ev = 1.60217733E-19 20 | # Avogadro's Constant 21 | Navogadro = 6.0221412927E23 22 | 23 | ################################################################################ 24 | class xdatcar: 25 | """ Python Class for VASP XDATCAR """ 26 | 27 | def __init__(self, File=None): 28 | if File is None: 29 | self.xdatcar = 'XDATCAR' 30 | else: 31 | self.xdatcar = File 32 | 33 | # time step of MD 34 | self.potim = None 35 | # mass per type 36 | self.mtype = None 37 | self.readoutcar() 38 | 39 | self.TypeName = None 40 | self.ChemSymb = None 41 | self.Ntype = None 42 | self.Nions = None 43 | self.Nelem = None 44 | self.Niter = None 45 | 46 | # position in Direct Coordinate 47 | self.position = None 48 | # position in Cartesian Coordinate 49 | self.positionC = None 50 | # Velocity in Angstrom per Femtosecond 51 | self.velocity = None 52 | self.readxdat() 53 | 54 | self.mass_and_name_per_ion() 55 | # Temperature 56 | self.Temp = np.zeros(self.Niter-1) 57 | # Kinetic Energy 58 | self.Ken = np.zeros(self.Niter-1) 59 | # Time in femtosecond 60 | self.Time = np.arange(self.Niter-1) * self.potim 61 | self.getTemp() 62 | 63 | # Velocity Autocorrelation Function 64 | self.VAF = None 65 | self.VAF2= None 66 | # Pair Correlation Function 67 | # self.PCF = None 68 | 69 | def mass_and_name_per_ion(self): 70 | # mass per ion 71 | self.mions = [] 72 | self.ChemSymb = [] 73 | 74 | if self.TypeName is None: 75 | self.TypeName = [chr(i) for i in range(65,91)][:self.Ntype] 76 | 77 | for i in range(self.Ntype): 78 | self.mions += [np.tile(self.mtype[i], self.Nelem[i])] 79 | self.ChemSymb += [np.tile(self.TypeName[i], self.Nelem[i])] 80 | 81 | self.mions = np.concatenate(self.mions) 82 | self.ChemSymb = np.concatenate(self.ChemSymb) 83 | 84 | def readxdat(self): 85 | """ Read VASP XDATCAR """ 86 | 87 | # inp = [line for line in open(self.xdatcar) if line.strip()] 88 | inp = open(self.xdatcar).readlines() 89 | 90 | scale = float(inp[1]) 91 | self.cell = np.array([line.split() for line in inp[2:5]], dtype=float) 92 | self.cell *= scale 93 | 94 | ta = inp[5].split() 95 | tb = inp[6].split() 96 | 97 | if ta[0].isalpha(): 98 | self.TypeName = ta 99 | self.Ntype = len(ta) 100 | self.Nelem = np.array(tb, dtype=int) 101 | self.Nions = self.Nelem.sum() 102 | self._nhead = 8 103 | else: 104 | # Names of each elements not written in XDATCAR head 105 | self.Nelem = np.array(ta, type=int) 106 | self.Nions = self.Nelem.sum() 107 | self.Ntype = len(ta) 108 | self.TypeName = None 109 | self._nhead = 7 110 | 111 | # For ISIF >= 3, VASP stores cell shapes at each step 112 | if self.isif >= 3: 113 | # No. of iterations 114 | self.Niter = len(inp) // (self._nhead + self.Nions) 115 | if len(inp) % (self._nhead + self.Nions) != 0: 116 | raise ValueError("XDATCAR may have been corrupted!") 117 | 118 | self.position = np.array( 119 | [ 120 | [line.split() for line in inp[ 121 | self._nhead + ii * (self.Nions + self._nhead) 122 | : 123 | self._nhead + ii * (self.Nions + self._nhead) + self.Nions 124 | ] 125 | ] 126 | for ii in range(self.Niter) 127 | ], dtype=float 128 | ) 129 | 130 | self.scales = np.array([ 131 | inp[ii*(self.Nions + self._nhead)+1] for ii in range(self.Niter) 132 | ], 133 | dtype=float 134 | ) 135 | 136 | self.cells = np.array( 137 | [ 138 | [line.split() for line in inp[ 139 | 2 + ii * (self.Nions + self._nhead) 140 | : 141 | 2 + ii * (self.Nions + self._nhead) + 3 142 | ] 143 | ] 144 | for ii in range(self.Niter) 145 | ], dtype=float 146 | ) * self.scales[:,None,None] 147 | 148 | self.positionC = np.zeros_like(self.position) 149 | for ii in range(self.Niter): 150 | self.positionC[ii,:,:] = np.dot(self.position[ii,:,:], self.cells[ii]) 151 | 152 | else: 153 | # No. of iterations 154 | self.Niter = (len(inp) - self._nhead - 1) // (1 + self.Nions) 155 | if (len(inp) - self._nhead - 1) % (1 + self.Nions) != 0: 156 | raise ValueError("XDATCAR may have been corrupted!") 157 | 158 | self.position = np.array( 159 | [ 160 | [line.split() for line in inp[ 161 | self._nhead + ii * (self.Nions+1) 162 | : 163 | self._nhead + ii * (self.Nions+1) + self.Nions 164 | ] 165 | ] 166 | for ii in range(self.Niter) 167 | ], dtype=float 168 | ) 169 | self.positionC = np.tensordot(self.position, self.cell, axes=(2,0)) 170 | 171 | # Velocity is ill-defined for varied-shape cell 172 | dpos = np.diff(self.position, axis=0) 173 | # apply periodic boundary condition 174 | dpos[dpos > 0.5] -= 1.0 175 | dpos[dpos <-0.5] += 1.0 176 | # Velocity in Angstrom per femtosecond 177 | for i in range(self.Niter-1): 178 | dpos[i,:,:] = np.dot(dpos[i,:,:], self.cell) / self.potim 179 | 180 | self.velocity = dpos 181 | 182 | 183 | def readoutcar(self): 184 | """ read POTIM and POMASS from OUTCAR """ 185 | 186 | if os.path.isfile("OUTCAR"): 187 | # print "OUTCAR found!" 188 | # print "Reading POTIM & POMASS from OUTCAR..." 189 | 190 | outcar = [line.strip() for line in open('OUTCAR')] 191 | lm = 0; 192 | for ll, line in enumerate(outcar): 193 | if 'POTIM =' in line: 194 | # lp = ll 195 | self.potim = float(line.split()[2]) 196 | 197 | # For ISIF >= 3, VASP output CELLs for each step 198 | if 'ISIF =' in line: 199 | self.isif = int(line.split()[2]) 200 | 201 | if 'Mass of Ions in am' in line: 202 | lm = ll + 1 203 | 204 | if lm: 205 | break 206 | 207 | # In case Masses not written in OUTCAR 208 | if lm == 0: 209 | raise ValueError("Masses for atoms NOT found! Check OUTCAR to see if 'POMASS' for atoms are present!") 210 | 211 | # For heavy atoms, digits for atomic masses may stick together, 212 | # resulting in cases like: "POMASS = 95.94 32.07183.85" 213 | 214 | pomass_line = outcar[lm] 215 | pomass_tmp = pomass_line.split()[2:] 216 | # Count the number of decimal points, which should equal to the 217 | # number of types of elements 218 | if len(pomass_tmp) != pomass_line.count('.'): 219 | # Fortunately, VASP use fixed-format for printing the atomic 220 | # masses, i.e. the number of decimal digits for all the floats 221 | # are the same. Check the last float for this number. 222 | 223 | nd = pomass_line[::-1].index('.') 224 | # Find the positions for the decimal points, and add "nd" 225 | dpos = [ii+nd for ii,xx in enumerate(pomass_line) if xx == '.'] 226 | # Add extra space to the end the number and rejoin the string 227 | pomass_new_line = ''.join( 228 | [xx + ' ' if ii in dpos 229 | else xx 230 | for ii, xx in 231 | enumerate(pomass_line)] 232 | ) 233 | self.mtype = np.array(pomass_new_line.split()[2:], dtype=float) 234 | else: 235 | self.mtype = np.array(pomass_tmp, dtype=float) 236 | 237 | def getTemp(self, Nfree=None): 238 | """ Temp vs Time """ 239 | 240 | for i in range(self.Niter-1): 241 | ke = np.sum(np.sum(self.velocity[i,:,:]**2, axis=1) * self.mions / 2.) 242 | self.Ken[i] = ke * 1E7 / Navogadro / ev 243 | if Nfree is None: 244 | Nfree = 3 * (self.Nions - 1) 245 | self.Temp[i] = 2 * self.Ken[i] / (kB * Nfree) 246 | 247 | def getVAF(self): 248 | """ Velocity Autocorrelation Function """ 249 | 250 | # VAF definitions 251 | # VAF(t) = Natoms^-1 * \sum_i 252 | ############################################################ 253 | # Fast Fourier Transform Method to calculate VAF 254 | ############################################################ 255 | # The cross-correlation theorem for the two-sided correlation: 256 | # corr(a,b) = ifft(fft(a)*fft(b).conj() 257 | 258 | # If a == b, then this reduces to the special case of the 259 | # Wiener-Khinchin theorem (autocorrelation of a): 260 | 261 | # corr(a,a) = ifft(abs(fft(a))**2) 262 | # where the power spectrum of a is simply: 263 | # fft(corr(a,a)) == abs(fft(a))**2 264 | ############################################################ 265 | # in this function, numpy.correlate is used to calculate the VAF 266 | 267 | self.VAF2 = np.zeros((self.Niter-1)*2 - 1) 268 | for i in range(self.Nions): 269 | for j in range(3): 270 | self.VAF2 += np.correlate(self.velocity[:,i,j], 271 | self.velocity[:,i,j], 272 | 'full') 273 | # two-sided VAF 274 | self.VAF2 /= np.sum(self.velocity**2) 275 | self.VAF = self.VAF2[self.Niter-2:] 276 | 277 | def phononDos(self, unit='THz', sigma=5): 278 | """ Phonon DOS from VAF """ 279 | 280 | N = self.Niter - 1 281 | # Frequency in THz 282 | omega = fftfreq(2*N-1, self.potim) * 1E3 283 | # Frequency in cm^-1 284 | if unit.lower() == 'cm-1': 285 | omega *= 33.35640951981521 286 | if unit.lower() == 'mev': 287 | omega *= 4.13567 288 | # from scipy.ndimage.filters import gaussian_filter1d as gaussian 289 | # smVAF = gaussian(self.VAF2, sigma=sigma) 290 | # pdos = np.abs(fft(smVAF))**2 291 | if self.VAF2 is None: 292 | self.getVAF() 293 | pdos = np.abs(fft(self.VAF2 - np.average(self.VAF2)))**2 294 | 295 | return omega[:N], pdos[:N] 296 | 297 | def PCF(self, bins=50, Niter=10, A='', B=''): 298 | """ Pair Correlation Function """ 299 | 300 | if not A: 301 | A = self.TypeName[0] 302 | if not B: 303 | B = A 304 | 305 | whichA = self.ChemSymb == A 306 | whichB = self.ChemSymb == B 307 | indexA = np.arange(self.Nions)[whichA] 308 | indexB = np.arange(self.Nions)[whichB] 309 | posA = self.position[:,whichA,:] 310 | posB = self.position[:,whichB,:] 311 | 312 | steps = range(0, self.Niter, Niter) 313 | rABs = np.array([posA[i,k,:]-posB[i,j,:] 314 | for k in range(indexA.size) 315 | for j in range(indexB.size) 316 | for i in steps 317 | if indexA[k] != indexB[j]]) 318 | # periodic boundary condition 319 | rABs[rABs > 0.5] -= 1.0 320 | rABs[rABs <-0.5] += 1.0 321 | # from direct to cartesian coordinate 322 | rABs = np.linalg.norm(np.dot(self.cell, rABs.T), axis=0) 323 | # histogram of pair distances 324 | val, b = np.histogram(rABs, bins=bins) 325 | # density of the system 326 | rho = self.Nions / np.linalg.det(self.cell) 327 | # Number of A type atom 328 | Na = self.Nelem[self.TypeName.index(A)] 329 | # Number of B type atom 330 | Nb = self.Nelem[self.TypeName.index(B)] 331 | dr = b[1] - b[0] 332 | val = val * self.Nions / (4*np.pi*b[1:]**2 * dr) / (Na * Nb * rho) / len(steps) 333 | 334 | return val, b[1:] 335 | 336 | ################################################################################ 337 | 338 | # test code of the above class 339 | if __name__ == '__main__': 340 | inp = xdatcar() 341 | inp.getVAF() 342 | # plt.plot((np.abs(fft(inp.VAF[inp.Niter-2:]))**2)) 343 | # print inp.VAF.shape 344 | # plt.plot(inp.Time, inp.VAF, 'ko-', lw=1.0, ms=2, 345 | # markeredgecolor='r', markerfacecolor='red') 346 | # 347 | # plt.xlabel('Time [fs]') 348 | # plt.ylabel('Velocity Autocorrelation Function') 349 | 350 | # x, y = inp.phononDos('cm-1') 351 | # plt.plot(x, y, 'ko-') 352 | # plt.xlim(0, 5000) 353 | # # plt.ylim(-0.5, 1.0) 354 | 355 | val, b = inp.PCF(100, 1) 356 | plt.plot(b, val) 357 | plt.axhline(y=1, color='r') 358 | plt.xlim(0, 5) 359 | plt.show() 360 | --------------------------------------------------------------------------------