PageRenderTime 67ms CodeModel.GetById 35ms RepoModel.GetById 0ms app.codeStats 0ms

/ase/io/vasp.py

https://github.com/qsnake/ase
Python | 422 lines | 338 code | 32 blank | 52 comment | 106 complexity | 606960165167cf1fff6f5db5059b535c MD5 | raw file
  1. """
  2. This module contains functionality for reading and writing an ASE
  3. Atoms object in VASP POSCAR format.
  4. """
  5. import os
  6. def get_atomtypes(fname):
  7. """Given a file name, get the atomic symbols.
  8. The function can get this information from OUTCAR and POTCAR
  9. format files. The files can also be compressed with gzip or
  10. bzip2.
  11. """
  12. atomtypes=[]
  13. if fname.find('.gz') != -1:
  14. import gzip
  15. f = gzip.open(fname)
  16. elif fname.find('.bz2') != -1:
  17. import bz2
  18. f = bz2.BZ2File(fname)
  19. else:
  20. f = open(fname)
  21. for line in f:
  22. if line.find('TITEL') != -1:
  23. atomtypes.append(line.split()[3].split('_')[0].split('.')[0])
  24. return atomtypes
  25. def atomtypes_outpot(posfname, numsyms):
  26. """Try to retreive chemical symbols from OUTCAR or POTCAR
  27. If getting atomtypes from the first line in POSCAR/CONTCAR fails, it might
  28. be possible to find the data in OUTCAR or POTCAR, if these files exist.
  29. posfname -- The filename of the POSCAR/CONTCAR file we're trying to read
  30. numsyms -- The number of symbols we must find
  31. """
  32. import os.path as op
  33. import glob
  34. # First check files with exactly same name except POTCAR/OUTCAR instead
  35. # of POSCAR/CONTCAR.
  36. fnames = [posfname.replace('POSCAR', 'POTCAR').replace('CONTCAR',
  37. 'POTCAR')]
  38. fnames.append(posfname.replace('POSCAR', 'OUTCAR').replace('CONTCAR',
  39. 'OUTCAR'))
  40. # Try the same but with compressed files
  41. fsc = []
  42. for fn in fnames:
  43. fsc.append(fn + '.gz')
  44. fsc.append(fn + '.bz2')
  45. for f in fsc:
  46. fnames.append(f)
  47. # Finally try anything with POTCAR or OUTCAR in the name
  48. vaspdir = op.dirname(posfname)
  49. fs = glob.glob(vaspdir + '*POTCAR*')
  50. for f in fs:
  51. fnames.append(f)
  52. fs = glob.glob(vaspdir + '*OUTCAR*')
  53. for f in fs:
  54. fnames.append(f)
  55. tried = []
  56. files_in_dir = os.listdir('.')
  57. for fn in fnames:
  58. if fn in files_in_dir:
  59. tried.append(fn)
  60. at = get_atomtypes(fn)
  61. if len(at) == numsyms:
  62. return at
  63. raise IOError('Could not determine chemical symbols. Tried files '
  64. + str(tried))
  65. def get_atomtypes_from_formula(formula):
  66. """Return atom types from chemical formula (optionally prepended
  67. with and underscore).
  68. """
  69. from ase.atoms import string2symbols
  70. symbols = string2symbols(formula.split('_')[0])
  71. atomtypes = [symbols[0]]
  72. for s in symbols[1:]:
  73. if s != atomtypes[-1]: atomtypes.append(s)
  74. return atomtypes
  75. def read_vasp(filename='CONTCAR'):
  76. """Import POSCAR/CONTCAR type file.
  77. Reads unitcell, atom positions and constraints from the POSCAR/CONTCAR
  78. file and tries to read atom types from POSCAR/CONTCAR header, if this fails
  79. the atom types are read from OUTCAR or POTCAR file.
  80. """
  81. from ase import Atoms, Atom
  82. from ase.constraints import FixAtoms, FixScaled
  83. from ase.data import chemical_symbols
  84. import numpy as np
  85. if isinstance(filename, str):
  86. f = open(filename)
  87. else: # Assume it's a file-like object
  88. f = filename
  89. # First line should contain the atom symbols , eg. "Ag Ge" in
  90. # the same order
  91. # as later in the file (and POTCAR for the full vasp run)
  92. atomtypes = f.readline().split()
  93. # Sometimes the first line in POSCAR/CONTCAR is of the form
  94. # "CoP3_In-3.pos". Check for this case and extract atom types
  95. if len(atomtypes) == 1 and '_' in atomtypes[0]:
  96. atomtypes = get_atomtypes_from_formula(atomtypes[0])
  97. lattice_constant = float(f.readline().split()[0])
  98. # Now the lattice vectors
  99. a = []
  100. for ii in range(3):
  101. s = f.readline().split()
  102. floatvect = float(s[0]), float(s[1]), float(s[2])
  103. a.append(floatvect)
  104. basis_vectors = np.array(a) * lattice_constant
  105. # Number of atoms. Again this must be in the same order as
  106. # in the first line
  107. # or in the POTCAR or OUTCAR file
  108. atom_symbols = []
  109. numofatoms = f.readline().split()
  110. #vasp5.1 has an additional line which gives the atom types
  111. #the following try statement skips this line
  112. try:
  113. int(numofatoms[0])
  114. except ValueError:
  115. numofatoms = f.readline().split()
  116. # check for comments in numofatoms line and get rid of them if necessary
  117. commentcheck = np.array(['!' in s for s in numofatoms])
  118. if commentcheck.any():
  119. # only keep the elements up to the first including a '!':
  120. numofatoms = numofatoms[:np.arange(len(numofatoms))[commentcheck][0]]
  121. numsyms = len(numofatoms)
  122. if len(atomtypes) < numsyms:
  123. # First line in POSCAR/CONTCAR didn't contain enough symbols.
  124. atomtypes = atomtypes_outpot(f.name, numsyms)
  125. else:
  126. try:
  127. for atype in atomtypes[:numsyms]:
  128. if not atype in chemical_symbols:
  129. raise KeyError
  130. except KeyError:
  131. atomtypes = atomtypes_outpot(f.name, numsyms)
  132. for i, num in enumerate(numofatoms):
  133. numofatoms[i] = int(num)
  134. [atom_symbols.append(atomtypes[i]) for na in xrange(numofatoms[i])]
  135. # Check if Selective dynamics is switched on
  136. sdyn = f.readline()
  137. selective_dynamics = sdyn[0].lower() == "s"
  138. # Check if atom coordinates are cartesian or direct
  139. if selective_dynamics:
  140. ac_type = f.readline()
  141. else:
  142. ac_type = sdyn
  143. cartesian = ac_type[0].lower() == "c" or ac_type[0].lower() == "k"
  144. tot_natoms = sum(numofatoms)
  145. atoms_pos = np.empty((tot_natoms, 3))
  146. if selective_dynamics:
  147. selective_flags = np.empty((tot_natoms, 3), dtype=bool)
  148. for atom in xrange(tot_natoms):
  149. ac = f.readline().split()
  150. atoms_pos[atom] = (float(ac[0]), float(ac[1]), float(ac[2]))
  151. if selective_dynamics:
  152. curflag = []
  153. for flag in ac[3:6]:
  154. curflag.append(flag == 'F')
  155. selective_flags[atom] = curflag
  156. # Done with all reading
  157. if type(filename) == str:
  158. f.close()
  159. if cartesian:
  160. atoms_pos *= lattice_constant
  161. atoms = Atoms(symbols = atom_symbols, cell = basis_vectors, pbc = True)
  162. if cartesian:
  163. atoms.set_positions(atoms_pos)
  164. else:
  165. atoms.set_scaled_positions(atoms_pos)
  166. if selective_dynamics:
  167. constraints = []
  168. indices = []
  169. for ind, sflags in enumerate(selective_flags):
  170. if sflags.any() and not sflags.all():
  171. constraints.append(FixScaled(atoms.get_cell(), ind, sflags))
  172. elif sflags.all():
  173. indices.append(ind)
  174. if indices:
  175. constraints.append(FixAtoms(indices))
  176. if constraints:
  177. atoms.set_constraint(constraints)
  178. return atoms
  179. def read_vasp_out(filename='OUTCAR',index = -1):
  180. """Import OUTCAR type file.
  181. Reads unitcell, atom positions, energies, and forces from the OUTCAR file
  182. and attempts to read constraints (if any) from CONTCAR/POSCAR, if present.
  183. """
  184. import os
  185. import numpy as np
  186. from ase.calculators.singlepoint import SinglePointCalculator
  187. from ase import Atoms, Atom
  188. try: # try to read constraints, first from CONTCAR, then from POSCAR
  189. constr = read_vasp('CONTCAR').constraints
  190. except:
  191. try:
  192. constr = read_vasp('POSCAR').constraints
  193. except:
  194. constr = None
  195. if isinstance(filename, str):
  196. f = open(filename)
  197. else: # Assume it's a file-like object
  198. f = filename
  199. data = f.readlines()
  200. natoms = 0
  201. images = []
  202. atoms = Atoms(pbc = True, constraint = constr)
  203. energy = 0
  204. species = []
  205. species_num = []
  206. symbols = []
  207. ecount = 0
  208. poscount = 0
  209. for n,line in enumerate(data):
  210. if 'POTCAR:' in line:
  211. temp = line.split()[2]
  212. for c in ['.','_','1']:
  213. if c in temp:
  214. temp = temp[0:temp.find(c)]
  215. species += [temp]
  216. if 'ions per type' in line:
  217. species = species[:len(species)/2]
  218. temp = line.split()
  219. for ispecies in range(len(species)):
  220. species_num += [int(temp[ispecies+4])]
  221. natoms += species_num[-1]
  222. for iatom in range(species_num[-1]): symbols += [species[ispecies]]
  223. if 'direct lattice vectors' in line:
  224. cell = []
  225. for i in range(3):
  226. temp = data[n+1+i].split()
  227. cell += [[float(temp[0]), float(temp[1]), float(temp[2])]]
  228. atoms.set_cell(cell)
  229. if 'FREE ENERGIE OF THE ION-ELECTRON SYSTEM' in line:
  230. energy = float(data[n+2].split()[4])
  231. if ecount < poscount:
  232. # reset energy for LAST set of atoms, not current one - VASP 5.11? and up
  233. images[-1].calc.energy = energy
  234. ecount += 1
  235. if 'POSITION ' in line:
  236. forces = []
  237. for iatom in range(natoms):
  238. temp = data[n+2+iatom].split()
  239. atoms += Atom(symbols[iatom],[float(temp[0]),float(temp[1]),float(temp[2])])
  240. forces += [[float(temp[3]),float(temp[4]),float(temp[5])]]
  241. atoms.set_calculator(SinglePointCalculator(energy,forces,None,None,atoms))
  242. images += [atoms]
  243. atoms = Atoms(pbc = True, constraint = constr)
  244. poscount += 1
  245. # return requested images, code borrowed from ase/io/trajectory.py
  246. if isinstance(index, int):
  247. return images[index]
  248. else:
  249. step = index.step or 1
  250. if step > 0:
  251. start = index.start or 0
  252. if start < 0:
  253. start += len(images)
  254. stop = index.stop or len(images)
  255. if stop < 0:
  256. stop += len(images)
  257. else:
  258. if index.start is None:
  259. start = len(images) - 1
  260. else:
  261. start = index.start
  262. if start < 0:
  263. start += len(images)
  264. if index.stop is None:
  265. stop = -1
  266. else:
  267. stop = index.stop
  268. if stop < 0:
  269. stop += len(images)
  270. return [images[i] for i in range(start, stop, step)]
  271. def write_vasp(filename, atoms, label='', direct=False, sort=None, symbol_count = None, long_format=True):
  272. """Method to write VASP position (POSCAR/CONTCAR) files.
  273. Writes label, scalefactor, unitcell, # of various kinds of atoms,
  274. positions in cartesian or scaled coordinates (Direct), and constraints
  275. to file. Cartesian coordiantes is default and default label is the
  276. atomic species, e.g. 'C N H Cu'.
  277. """
  278. import numpy as np
  279. from ase.constraints import FixAtoms, FixScaled
  280. if isinstance(filename, str):
  281. f = open(filename, 'w')
  282. else: # Assume it's a 'file-like object'
  283. f = filename
  284. if isinstance(atoms, (list, tuple)):
  285. if len(atoms) > 1:
  286. raise RuntimeError("Don't know how to save more than "+
  287. "one image to VASP input")
  288. else:
  289. atoms = atoms[0]
  290. # Write atom positions in scaled or cartesian coordinates
  291. if direct:
  292. coord = atoms.get_scaled_positions()
  293. else:
  294. coord = atoms.get_positions()
  295. if atoms.constraints:
  296. sflags = np.zeros((len(atoms), 3), dtype=bool)
  297. for constr in atoms.constraints:
  298. if isinstance(constr, FixScaled):
  299. sflags[constr.a] = constr.mask
  300. elif isinstance(constr, FixAtoms):
  301. sflags[constr.index] = [True, True, True]
  302. if sort:
  303. ind = np.argsort(atoms.get_chemical_symbols())
  304. symbols = np.array(atoms.get_chemical_symbols())[ind]
  305. coord = coord[ind]
  306. if atoms.constraints:
  307. sflags = sflags[ind]
  308. else:
  309. symbols = atoms.get_chemical_symbols()
  310. # Create a list sc of (symbol, count) pairs
  311. if symbol_count:
  312. sc = symbol_count
  313. else:
  314. sc = []
  315. psym = symbols[0]
  316. count = 0
  317. for sym in symbols:
  318. if sym != psym:
  319. sc.append((psym, count))
  320. psym = sym
  321. count = 1
  322. else:
  323. count += 1
  324. sc.append((psym, count))
  325. # Create the label
  326. if label == '':
  327. for sym, c in sc:
  328. label += '%2s ' % sym
  329. f.write(label + '\n')
  330. # Write unitcell in real coordinates and adapt to VASP convention
  331. # for unit cell
  332. # ase Atoms doesn't store the lattice constant separately, so always
  333. # write 1.0.
  334. f.write('%19.16f\n' % 1.0)
  335. if long_format:
  336. latt_form = ' %21.16f'
  337. else:
  338. latt_form = ' %11.6f'
  339. for vec in atoms.get_cell():
  340. f.write(' ')
  341. for el in vec:
  342. f.write(latt_form % el)
  343. f.write('\n')
  344. # Numbers of each atom
  345. for sym, count in sc:
  346. f.write(' %3i' % count)
  347. f.write('\n')
  348. if atoms.constraints:
  349. f.write('Selective dynamics\n')
  350. if direct:
  351. f.write('Direct\n')
  352. else:
  353. f.write('Cartesian\n')
  354. if long_format:
  355. cform = ' %19.16f'
  356. else:
  357. cform = ' %9.6f'
  358. for iatom, atom in enumerate(coord):
  359. for dcoord in atom:
  360. f.write(cform % dcoord)
  361. if atoms.constraints:
  362. for flag in sflags[iatom]:
  363. if flag:
  364. s = 'F'
  365. else:
  366. s = 'T'
  367. f.write('%4s' % s)
  368. f.write('\n')
  369. if type(filename) == str:
  370. f.close()