Coverage for /builds/hweiske/ase/ase/calculators/vasp/vasp_auxiliary.py: 13.33%

195 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2024-04-22 11:22 +0000

1import os 

2import re 

3 

4import numpy as np 

5 

6 

7def get_vasp_version(string): 

8 """Extract version number from header of stdout. 

9 

10 Example:: 

11 

12 >>> get_vasp_version('potato vasp.6.1.2 bumblebee') 

13 '6.1.2' 

14 

15 """ 

16 match = re.search(r'vasp\.(\S+)', string, re.M) 

17 return match.group(1) 

18 

19 

20class VaspChargeDensity: 

21 """Class for representing VASP charge density. 

22 

23 Filename is normally CHG.""" 

24 # Can the filename be CHGCAR? There's a povray tutorial 

25 # in doc/tutorials where it's CHGCAR as of January 2021. --askhl 

26 

27 def __init__(self, filename): 

28 # Instance variables 

29 self.atoms = [] # List of Atoms objects 

30 self.chg = [] # Charge density 

31 self.chgdiff = [] # Charge density difference, if spin polarized 

32 self.aug = '' # Augmentation charges, not parsed just a big string 

33 self.augdiff = '' # Augmentation charge differece, is spin polarized 

34 

35 # Note that the augmentation charge is not a list, since they 

36 # are needed only for CHGCAR files which store only a single 

37 # image. 

38 if filename is not None: 

39 self.read(filename) 

40 

41 def is_spin_polarized(self): 

42 if len(self.chgdiff) > 0: 

43 return True 

44 return False 

45 

46 def _read_chg(self, fobj, chg, volume): 

47 """Read charge from file object 

48 

49 Utility method for reading the actual charge density (or 

50 charge density difference) from a file object. On input, the 

51 file object must be at the beginning of the charge block, on 

52 output the file position will be left at the end of the 

53 block. The chg array must be of the correct dimensions. 

54 

55 """ 

56 # VASP writes charge density as 

57 # WRITE(IU,FORM) (((C(NX,NY,NZ),NX=1,NGXC),NY=1,NGYZ),NZ=1,NGZC) 

58 # Fortran nested implied do loops; innermost index fastest 

59 # First, just read it in 

60 for zz in range(chg.shape[2]): 

61 for yy in range(chg.shape[1]): 

62 chg[:, yy, zz] = np.fromfile(fobj, count=chg.shape[0], sep=' ') 

63 chg /= volume 

64 

65 def read(self, filename): 

66 """Read CHG or CHGCAR file. 

67 

68 If CHG contains charge density from multiple steps all the 

69 steps are read and stored in the object. By default VASP 

70 writes out the charge density every 10 steps. 

71 

72 chgdiff is the difference between the spin up charge density 

73 and the spin down charge density and is thus only read for a 

74 spin-polarized calculation. 

75 

76 aug is the PAW augmentation charges found in CHGCAR. These are 

77 not parsed, they are just stored as a string so that they can 

78 be written again to a CHGCAR format file. 

79 

80 """ 

81 import ase.io.vasp as aiv 

82 with open(filename) as fd: 

83 self.atoms = [] 

84 self.chg = [] 

85 self.chgdiff = [] 

86 self.aug = '' 

87 self.augdiff = '' 

88 while True: 

89 try: 

90 atoms = aiv.read_vasp(fd) 

91 except (OSError, ValueError, IndexError): 

92 # Probably an empty line, or we tried to read the 

93 # augmentation occupancies in CHGCAR 

94 break 

95 fd.readline() 

96 ngr = fd.readline().split() 

97 ng = (int(ngr[0]), int(ngr[1]), int(ngr[2])) 

98 chg = np.empty(ng) 

99 self._read_chg(fd, chg, atoms.get_volume()) 

100 self.chg.append(chg) 

101 self.atoms.append(atoms) 

102 # Check if the file has a spin-polarized charge density part, 

103 # and if so, read it in. 

104 fl = fd.tell() 

105 # First check if the file has an augmentation charge part 

106 # (CHGCAR file.) 

107 line1 = fd.readline() 

108 if line1 == '': 

109 break 

110 elif line1.find('augmentation') != -1: 

111 augs = [line1] 

112 while True: 

113 line2 = fd.readline() 

114 if line2.split() == ngr: 

115 self.aug = ''.join(augs) 

116 augs = [] 

117 chgdiff = np.empty(ng) 

118 self._read_chg(fd, chgdiff, atoms.get_volume()) 

119 self.chgdiff.append(chgdiff) 

120 elif line2 == '': 

121 break 

122 else: 

123 augs.append(line2) 

124 if len(self.aug) == 0: 

125 self.aug = ''.join(augs) 

126 augs = [] 

127 else: 

128 self.augdiff = ''.join(augs) 

129 augs = [] 

130 elif line1.split() == ngr: 

131 chgdiff = np.empty(ng) 

132 self._read_chg(fd, chgdiff, atoms.get_volume()) 

133 self.chgdiff.append(chgdiff) 

134 else: 

135 fd.seek(fl) 

136 

137 def _write_chg(self, fobj, chg, volume, format='chg'): 

138 """Write charge density 

139 

140 Utility function similar to _read_chg but for writing. 

141 

142 """ 

143 # Make a 1D copy of chg, must take transpose to get ordering right 

144 chgtmp = chg.T.ravel() 

145 # Multiply by volume 

146 chgtmp = chgtmp * volume 

147 # Must be a tuple to pass to string conversion 

148 chgtmp = tuple(chgtmp) 

149 # CHG format - 10 columns 

150 if format.lower() == 'chg': 

151 # Write all but the last row 

152 for ii in range((len(chgtmp) - 1) // 10): 

153 fobj.write(' %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G\ 

154 %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G\n' % chgtmp[ii * 10:(ii + 1) * 10]) 

155 # If the last row contains 10 values then write them without a 

156 # newline 

157 if len(chgtmp) % 10 == 0: 

158 fobj.write(' %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G' 

159 ' %#11.5G %#11.5G %#11.5G %#11.5G %#11.5G' % 

160 chgtmp[len(chgtmp) - 10:len(chgtmp)]) 

161 # Otherwise write fewer columns without a newline 

162 else: 

163 for ii in range(len(chgtmp) % 10): 

164 fobj.write((' %#11.5G') % 

165 chgtmp[len(chgtmp) - len(chgtmp) % 10 + ii]) 

166 # Other formats - 5 columns 

167 else: 

168 # Write all but the last row 

169 for ii in range((len(chgtmp) - 1) // 5): 

170 fobj.write(' %17.10E %17.10E %17.10E %17.10E %17.10E\n' % 

171 chgtmp[ii * 5:(ii + 1) * 5]) 

172 # If the last row contains 5 values then write them without a 

173 # newline 

174 if len(chgtmp) % 5 == 0: 

175 fobj.write(' %17.10E %17.10E %17.10E %17.10E %17.10E' % 

176 chgtmp[len(chgtmp) - 5:len(chgtmp)]) 

177 # Otherwise write fewer columns without a newline 

178 else: 

179 for ii in range(len(chgtmp) % 5): 

180 fobj.write((' %17.10E') % 

181 chgtmp[len(chgtmp) - len(chgtmp) % 5 + ii]) 

182 # Write a newline whatever format it is 

183 fobj.write('\n') 

184 

185 def write(self, filename, format=None): 

186 """Write VASP charge density in CHG format. 

187 

188 filename: str 

189 Name of file to write to. 

190 format: str 

191 String specifying whether to write in CHGCAR or CHG 

192 format. 

193 

194 """ 

195 import ase.io.vasp as aiv 

196 if format is None: 

197 if filename.lower().find('chgcar') != -1: 

198 format = 'chgcar' 

199 elif filename.lower().find('chg') != -1: 

200 format = 'chg' 

201 elif len(self.chg) == 1: 

202 format = 'chgcar' 

203 else: 

204 format = 'chg' 

205 with open(filename, 'w') as fd: 

206 for ii, chg in enumerate(self.chg): 

207 if format == 'chgcar' and ii != len(self.chg) - 1: 

208 continue # Write only the last image for CHGCAR 

209 aiv.write_vasp(fd, 

210 self.atoms[ii], 

211 direct=True, 

212 long_format=False) 

213 fd.write('\n') 

214 for dim in chg.shape: 

215 fd.write(' %4i' % dim) 

216 fd.write('\n') 

217 vol = self.atoms[ii].get_volume() 

218 self._write_chg(fd, chg, vol, format) 

219 if format == 'chgcar': 

220 fd.write(self.aug) 

221 if self.is_spin_polarized(): 

222 if format == 'chg': 

223 fd.write('\n') 

224 for dim in chg.shape: 

225 fd.write(' %4i' % dim) 

226 fd.write('\n') # a new line after dim is required 

227 self._write_chg(fd, self.chgdiff[ii], vol, format) 

228 if format == 'chgcar': 

229 # a new line is always provided self._write_chg 

230 fd.write(self.augdiff) 

231 if format == 'chg' and len(self.chg) > 1: 

232 fd.write('\n') 

233 

234 

235class VaspDos: 

236 """Class for representing density-of-states produced by VASP 

237 

238 The energies are in property self.energy 

239 

240 Site-projected DOS is accesible via the self.site_dos method. 

241 

242 Total and integrated DOS is accessible as numpy.ndarray's in the 

243 properties self.dos and self.integrated_dos. If the calculation is 

244 spin polarized, the arrays will be of shape (2, NDOS), else (1, 

245 NDOS). 

246 

247 The self.efermi property contains the currently set Fermi 

248 level. Changing this value shifts the energies. 

249 

250 """ 

251 

252 def __init__(self, doscar='DOSCAR', efermi=0.0): 

253 """Initialize""" 

254 self._efermi = 0.0 

255 self.read_doscar(doscar) 

256 self.efermi = efermi 

257 

258 # we have determine the resort to correctly map ase atom index to the 

259 # POSCAR. 

260 self.sort = [] 

261 self.resort = [] 

262 if os.path.isfile('ase-sort.dat'): 

263 with open('ase-sort.dat') as file: 

264 lines = file.readlines() 

265 for line in lines: 

266 data = line.split() 

267 self.sort.append(int(data[0])) 

268 self.resort.append(int(data[1])) 

269 

270 def _set_efermi(self, efermi): 

271 """Set the Fermi level.""" 

272 ef = efermi - self._efermi 

273 self._efermi = efermi 

274 self._total_dos[0, :] = self._total_dos[0, :] - ef 

275 try: 

276 self._site_dos[:, 0, :] = self._site_dos[:, 0, :] - ef 

277 except IndexError: 

278 pass 

279 

280 def _get_efermi(self): 

281 return self._efermi 

282 

283 efermi = property(_get_efermi, _set_efermi, None, "Fermi energy.") 

284 

285 def _get_energy(self): 

286 """Return the array with the energies.""" 

287 return self._total_dos[0, :] 

288 

289 energy = property(_get_energy, None, None, "Array of energies") 

290 

291 def site_dos(self, atom, orbital): 

292 """Return an NDOSx1 array with dos for the chosen atom and orbital. 

293 

294 atom: int 

295 Atom index 

296 orbital: int or str 

297 Which orbital to plot 

298 

299 If the orbital is given as an integer: 

300 If spin-unpolarized calculation, no phase factors: 

301 s = 0, p = 1, d = 2 

302 Spin-polarized, no phase factors: 

303 s-up = 0, s-down = 1, p-up = 2, p-down = 3, d-up = 4, d-down = 5 

304 If phase factors have been calculated, orbitals are 

305 s, py, pz, px, dxy, dyz, dz2, dxz, dx2 

306 double in the above fashion if spin polarized. 

307 

308 """ 

309 # Correct atom index for resorting if we need to. This happens when the 

310 # ase-sort.dat file exists, and self.resort is not empty. 

311 if self.resort: 

312 atom = self.resort[atom] 

313 

314 # Integer indexing for orbitals starts from 1 in the _site_dos array 

315 # since the 0th column contains the energies 

316 if isinstance(orbital, int): 

317 return self._site_dos[atom, orbital + 1, :] 

318 n = self._site_dos.shape[1] 

319 

320 from .vasp_data import PDOS_orbital_names_and_DOSCAR_column 

321 norb = PDOS_orbital_names_and_DOSCAR_column[n] 

322 

323 return self._site_dos[atom, norb[orbital.lower()], :] 

324 

325 def _get_dos(self): 

326 if self._total_dos.shape[0] == 3: 

327 return self._total_dos[1, :] 

328 elif self._total_dos.shape[0] == 5: 

329 return self._total_dos[1:3, :] 

330 

331 dos = property(_get_dos, None, None, 'Average DOS in cell') 

332 

333 def _get_integrated_dos(self): 

334 if self._total_dos.shape[0] == 3: 

335 return self._total_dos[2, :] 

336 elif self._total_dos.shape[0] == 5: 

337 return self._total_dos[3:5, :] 

338 

339 integrated_dos = property(_get_integrated_dos, None, None, 

340 'Integrated average DOS in cell') 

341 

342 def read_doscar(self, fname="DOSCAR"): 

343 """Read a VASP DOSCAR file""" 

344 with open(fname) as fd: 

345 natoms = int(fd.readline().split()[0]) 

346 [fd.readline() for _ in range(4)] 

347 # First we have a block with total and total integrated DOS 

348 ndos = int(fd.readline().split()[2]) 

349 dos = [] 

350 for _ in range(ndos): 

351 dos.append(np.array([float(x) for x in fd.readline().split()])) 

352 self._total_dos = np.array(dos).T 

353 # Next we have one block per atom, if INCAR contains the stuff 

354 # necessary for generating site-projected DOS 

355 dos = [] 

356 for _ in range(natoms): 

357 line = fd.readline() 

358 if line == '': 

359 # No site-projected DOS 

360 break 

361 ndos = int(line.split()[2]) 

362 line = fd.readline().split() 

363 cdos = np.empty((ndos, len(line))) 

364 cdos[0] = np.array(line) 

365 for nd in range(1, ndos): 

366 line = fd.readline().split() 

367 cdos[nd] = np.array([float(x) for x in line]) 

368 dos.append(cdos.T) 

369 self._site_dos = np.array(dos)