Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1""" 

2This module contains functionality for reading and writing an ASE 

3Atoms object in VASP POSCAR format. 

4 

5""" 

6 

7import re 

8 

9import numpy as np 

10 

11from ase import Atoms 

12from ase.utils import reader, writer 

13from ase.io.utils import ImageIterator 

14from ase.io import ParseError 

15from .vasp_parsers import vasp_outcar_parsers as vop 

16from pathlib import Path 

17 

18__all__ = [ 

19 'read_vasp', 'read_vasp_out', 'iread_vasp_out', 'read_vasp_xdatcar', 

20 'read_vasp_xml', 'write_vasp', 'write_vasp_xdatcar' 

21] 

22 

23 

24def get_atomtypes(fname): 

25 """Given a file name, get the atomic symbols. 

26 

27 The function can get this information from OUTCAR and POTCAR 

28 format files. The files can also be compressed with gzip or 

29 bzip2. 

30 

31 """ 

32 fpath = Path(fname) 

33 

34 atomtypes = [] 

35 atomtypes_alt = [] 

36 if fpath.suffix == '.gz': 

37 import gzip 

38 opener = gzip.open 

39 elif fpath.suffix == '.bz2': 

40 import bz2 

41 opener = bz2.BZ2File 

42 else: 

43 opener = open 

44 with opener(fpath) as fd: 

45 for line in fd: 

46 if 'TITEL' in line: 

47 atomtypes.append(line.split()[3].split('_')[0].split('.')[0]) 

48 elif 'POTCAR:' in line: 

49 atomtypes_alt.append(line.split()[2].split('_')[0].split('.')[0]) 

50 

51 if len(atomtypes) == 0 and len(atomtypes_alt) > 0: 

52 # old VASP doesn't echo TITEL, but all versions print out species lines 

53 # preceded by "POTCAR:", twice 

54 if len(atomtypes_alt) % 2 != 0: 

55 raise ParseError(f'Tried to get atom types from {len(atomtypes_alt)} "POTCAR": ' 

56 'lines in OUTCAR, but expected an even number') 

57 atomtypes = atomtypes_alt[0:len(atomtypes_alt)//2] 

58 

59 return atomtypes 

60 

61 

62def atomtypes_outpot(posfname, numsyms): 

63 """Try to retrieve chemical symbols from OUTCAR or POTCAR 

64 

65 If getting atomtypes from the first line in POSCAR/CONTCAR fails, it might 

66 be possible to find the data in OUTCAR or POTCAR, if these files exist. 

67 

68 posfname -- The filename of the POSCAR/CONTCAR file we're trying to read 

69 

70 numsyms -- The number of symbols we must find 

71 

72 """ 

73 posfpath = Path(posfname) 

74 

75 # Check files with exactly same path except POTCAR/OUTCAR instead 

76 # of POSCAR/CONTCAR. 

77 fnames = [posfpath.with_name('POTCAR'), 

78 posfpath.with_name('OUTCAR')] 

79 # Try the same but with compressed files 

80 fsc = [] 

81 for fnpath in fnames: 

82 fsc.append(fnpath.parent / (fnpath.name + '.gz')) 

83 fsc.append(fnpath.parent / (fnpath.name + '.bz2')) 

84 for f in fsc: 

85 fnames.append(f) 

86 # Code used to try anything with POTCAR or OUTCAR in the name 

87 # but this is no longer supported 

88 

89 tried = [] 

90 for fn in fnames: 

91 if fn in posfpath.parent.iterdir(): 

92 tried.append(fn) 

93 at = get_atomtypes(fn) 

94 if len(at) == numsyms: 

95 return at 

96 

97 raise ParseError('Could not determine chemical symbols. Tried files ' + 

98 str(tried)) 

99 

100 

101def get_atomtypes_from_formula(formula): 

102 """Return atom types from chemical formula (optionally prepended 

103 with and underscore). 

104 """ 

105 from ase.symbols import string2symbols 

106 symbols = string2symbols(formula.split('_')[0]) 

107 atomtypes = [symbols[0]] 

108 for s in symbols[1:]: 

109 if s != atomtypes[-1]: 

110 atomtypes.append(s) 

111 return atomtypes 

112 

113 

114@reader 

115def read_vasp(filename='CONTCAR'): 

116 """Import POSCAR/CONTCAR type file. 

117 

118 Reads unitcell, atom positions and constraints from the POSCAR/CONTCAR 

119 file and tries to read atom types from POSCAR/CONTCAR header, if this fails 

120 the atom types are read from OUTCAR or POTCAR file. 

121 """ 

122 

123 from ase.constraints import FixAtoms, FixScaled 

124 from ase.data import chemical_symbols 

125 

126 fd = filename 

127 # The first line is in principle a comment line, however in VASP 

128 # 4.x a common convention is to have it contain the atom symbols, 

129 # eg. "Ag Ge" in the same order as later in the file (and POTCAR 

130 # for the full vasp run). In the VASP 5.x format this information 

131 # is found on the fifth line. Thus we save the first line and use 

132 # it in case we later detect that we're reading a VASP 4.x format 

133 # file. 

134 line1 = fd.readline() 

135 

136 lattice_constant = float(fd.readline().split()[0]) 

137 

138 # Now the lattice vectors 

139 a = [] 

140 for ii in range(3): 

141 s = fd.readline().split() 

142 floatvect = float(s[0]), float(s[1]), float(s[2]) 

143 a.append(floatvect) 

144 

145 basis_vectors = np.array(a) * lattice_constant 

146 

147 # Number of atoms. Again this must be in the same order as 

148 # in the first line 

149 # or in the POTCAR or OUTCAR file 

150 atom_symbols = [] 

151 numofatoms = fd.readline().split() 

152 # Check whether we have a VASP 4.x or 5.x format file. If the 

153 # format is 5.x, use the fifth line to provide information about 

154 # the atomic symbols. 

155 vasp5 = False 

156 try: 

157 int(numofatoms[0]) 

158 except ValueError: 

159 vasp5 = True 

160 atomtypes = numofatoms 

161 numofatoms = fd.readline().split() 

162 

163 # check for comments in numofatoms line and get rid of them if necessary 

164 commentcheck = np.array(['!' in s for s in numofatoms]) 

165 if commentcheck.any(): 

166 # only keep the elements up to the first including a '!': 

167 numofatoms = numofatoms[:np.arange(len(numofatoms))[commentcheck][0]] 

168 

169 if not vasp5: 

170 # Split the comment line (first in the file) into words and 

171 # try to compose a list of chemical symbols 

172 from ase.formula import Formula 

173 atomtypes = [] 

174 for word in line1.split(): 

175 word_without_delims = re.sub(r"-|_|,|\.|=|[0-9]|^", "", word) 

176 if len(word_without_delims) < 1: 

177 continue 

178 try: 

179 atomtypes.extend(list(Formula(word_without_delims))) 

180 except ValueError: 

181 # print(atomtype, e, 'is comment') 

182 pass 

183 # Now the list of chemical symbols atomtypes must be formed. 

184 # For example: atomtypes = ['Pd', 'C', 'O'] 

185 

186 numsyms = len(numofatoms) 

187 if len(atomtypes) < numsyms: 

188 # First line in POSCAR/CONTCAR didn't contain enough symbols. 

189 

190 # Sometimes the first line in POSCAR/CONTCAR is of the form 

191 # "CoP3_In-3.pos". Check for this case and extract atom types 

192 if len(atomtypes) == 1 and '_' in atomtypes[0]: 

193 atomtypes = get_atomtypes_from_formula(atomtypes[0]) 

194 else: 

195 atomtypes = atomtypes_outpot(fd.name, numsyms) 

196 else: 

197 try: 

198 for atype in atomtypes[:numsyms]: 

199 if atype not in chemical_symbols: 

200 raise KeyError 

201 except KeyError: 

202 atomtypes = atomtypes_outpot(fd.name, numsyms) 

203 

204 for i, num in enumerate(numofatoms): 

205 numofatoms[i] = int(num) 

206 [atom_symbols.append(atomtypes[i]) for na in range(numofatoms[i])] 

207 

208 # Check if Selective dynamics is switched on 

209 sdyn = fd.readline() 

210 selective_dynamics = sdyn[0].lower() == 's' 

211 

212 # Check if atom coordinates are cartesian or direct 

213 if selective_dynamics: 

214 ac_type = fd.readline() 

215 else: 

216 ac_type = sdyn 

217 cartesian = ac_type[0].lower() == 'c' or ac_type[0].lower() == 'k' 

218 tot_natoms = sum(numofatoms) 

219 atoms_pos = np.empty((tot_natoms, 3)) 

220 if selective_dynamics: 

221 selective_flags = np.empty((tot_natoms, 3), dtype=bool) 

222 for atom in range(tot_natoms): 

223 ac = fd.readline().split() 

224 atoms_pos[atom] = (float(ac[0]), float(ac[1]), float(ac[2])) 

225 if selective_dynamics: 

226 curflag = [] 

227 for flag in ac[3:6]: 

228 curflag.append(flag == 'F') 

229 selective_flags[atom] = curflag 

230 if cartesian: 

231 atoms_pos *= lattice_constant 

232 atoms = Atoms(symbols=atom_symbols, cell=basis_vectors, pbc=True) 

233 if cartesian: 

234 atoms.set_positions(atoms_pos) 

235 else: 

236 atoms.set_scaled_positions(atoms_pos) 

237 if selective_dynamics: 

238 constraints = [] 

239 indices = [] 

240 for ind, sflags in enumerate(selective_flags): 

241 if sflags.any() and not sflags.all(): 

242 constraints.append(FixScaled(atoms.get_cell(), ind, sflags)) 

243 elif sflags.all(): 

244 indices.append(ind) 

245 if indices: 

246 constraints.append(FixAtoms(indices)) 

247 if constraints: 

248 atoms.set_constraint(constraints) 

249 return atoms 

250 

251 

252def iread_vasp_out(filename, index=-1): 

253 """Import OUTCAR type file, as a generator.""" 

254 it = ImageIterator(vop.outcarchunks) 

255 return it(filename, index=index) 

256 

257 

258@reader 

259def read_vasp_out(filename='OUTCAR', index=-1): 

260 """Import OUTCAR type file. 

261 

262 Reads unitcell, atom positions, energies, and forces from the OUTCAR file 

263 and attempts to read constraints (if any) from CONTCAR/POSCAR, if present. 

264 """ 

265 # "filename" is actually a file-descriptor thanks to @reader 

266 g = iread_vasp_out(filename, index=index) 

267 # Code borrowed from formats.py:read 

268 if isinstance(index, (slice, str)): 

269 # Return list of atoms 

270 return list(g) 

271 else: 

272 # Return single atoms object 

273 return next(g) 

274 

275 

276@reader 

277def read_vasp_xdatcar(filename='XDATCAR', index=-1): 

278 """Import XDATCAR file 

279 

280 Reads all positions from the XDATCAR and returns a list of 

281 Atoms objects. Useful for viewing optimizations runs 

282 from VASP5.x 

283 

284 Constraints ARE NOT stored in the XDATCAR, and as such, Atoms 

285 objects retrieved from the XDATCAR will not have constraints set. 

286 """ 

287 fd = filename # @reader decorator ensures this is a file descriptor 

288 images = list() 

289 

290 cell = np.eye(3) 

291 atomic_formula = str() 

292 

293 while True: 

294 comment_line = fd.readline() 

295 if "Direct configuration=" not in comment_line: 

296 try: 

297 lattice_constant = float(fd.readline()) 

298 except Exception: 

299 # XXX: When would this happen? 

300 break 

301 

302 xx = [float(x) for x in fd.readline().split()] 

303 yy = [float(y) for y in fd.readline().split()] 

304 zz = [float(z) for z in fd.readline().split()] 

305 cell = np.array([xx, yy, zz]) * lattice_constant 

306 

307 symbols = fd.readline().split() 

308 numbers = [int(n) for n in fd.readline().split()] 

309 total = sum(numbers) 

310 

311 atomic_formula = ''.join('{:s}{:d}'.format(sym, numbers[n]) 

312 for n, sym in enumerate(symbols)) 

313 

314 fd.readline() 

315 

316 coords = [ 

317 np.array(fd.readline().split(), float) for ii in range(total) 

318 ] 

319 

320 image = Atoms(atomic_formula, cell=cell, pbc=True) 

321 image.set_scaled_positions(np.array(coords)) 

322 images.append(image) 

323 

324 if not index: 

325 return images 

326 else: 

327 return images[index] 

328 

329 

330def __get_xml_parameter(par): 

331 """An auxiliary function that enables convenient extraction of 

332 parameter values from a vasprun.xml file with proper type 

333 handling. 

334 

335 """ 

336 def to_bool(b): 

337 if b == 'T': 

338 return True 

339 else: 

340 return False 

341 

342 to_type = {'int': int, 'logical': to_bool, 'string': str, 'float': float} 

343 

344 text = par.text 

345 if text is None: 

346 text = '' 

347 

348 # Float parameters do not have a 'type' attrib 

349 var_type = to_type[par.attrib.get('type', 'float')] 

350 

351 try: 

352 if par.tag == 'v': 

353 return list(map(var_type, text.split())) 

354 else: 

355 return var_type(text.strip()) 

356 except ValueError: 

357 # Vasp can sometimes write "*****" due to overflow 

358 return None 

359 

360 

361def read_vasp_xml(filename='vasprun.xml', index=-1): 

362 """Parse vasprun.xml file. 

363 

364 Reads unit cell, atom positions, energies, forces, and constraints 

365 from vasprun.xml file 

366 """ 

367 

368 import xml.etree.ElementTree as ET 

369 from ase.constraints import FixAtoms, FixScaled 

370 from ase.calculators.singlepoint import (SinglePointDFTCalculator, 

371 SinglePointKPoint) 

372 from ase.units import GPa 

373 from collections import OrderedDict 

374 

375 tree = ET.iterparse(filename, events=['start', 'end']) 

376 

377 atoms_init = None 

378 calculation = [] 

379 ibz_kpts = None 

380 kpt_weights = None 

381 parameters = OrderedDict() 

382 

383 try: 

384 for event, elem in tree: 

385 

386 if event == 'end': 

387 if elem.tag == 'kpoints': 

388 for subelem in elem.iter(tag='generation'): 

389 kpts_params = OrderedDict() 

390 parameters['kpoints_generation'] = kpts_params 

391 for par in subelem.iter(): 

392 if par.tag in ['v', 'i']: 

393 parname = par.attrib['name'].lower() 

394 kpts_params[parname] = __get_xml_parameter(par) 

395 

396 kpts = elem.findall("varray[@name='kpointlist']/v") 

397 ibz_kpts = np.zeros((len(kpts), 3)) 

398 

399 for i, kpt in enumerate(kpts): 

400 ibz_kpts[i] = [float(val) for val in kpt.text.split()] 

401 

402 kpt_weights = elem.findall('varray[@name="weights"]/v') 

403 kpt_weights = [float(val.text) for val in kpt_weights] 

404 

405 elif elem.tag == 'parameters': 

406 for par in elem.iter(): 

407 if par.tag in ['v', 'i']: 

408 parname = par.attrib['name'].lower() 

409 parameters[parname] = __get_xml_parameter(par) 

410 

411 elif elem.tag == 'atominfo': 

412 species = [] 

413 

414 for entry in elem.find("array[@name='atoms']/set"): 

415 species.append(entry[0].text.strip()) 

416 

417 natoms = len(species) 

418 

419 elif (elem.tag == 'structure' 

420 and elem.attrib.get('name') == 'initialpos'): 

421 cell_init = np.zeros((3, 3), dtype=float) 

422 

423 for i, v in enumerate( 

424 elem.find("crystal/varray[@name='basis']")): 

425 cell_init[i] = np.array( 

426 [float(val) for val in v.text.split()]) 

427 

428 scpos_init = np.zeros((natoms, 3), dtype=float) 

429 

430 for i, v in enumerate( 

431 elem.find("varray[@name='positions']")): 

432 scpos_init[i] = np.array( 

433 [float(val) for val in v.text.split()]) 

434 

435 constraints = [] 

436 fixed_indices = [] 

437 

438 for i, entry in enumerate( 

439 elem.findall("varray[@name='selective']/v")): 

440 flags = (np.array( 

441 entry.text.split() == np.array(['F', 'F', 'F']))) 

442 if flags.all(): 

443 fixed_indices.append(i) 

444 elif flags.any(): 

445 constraints.append(FixScaled(cell_init, i, flags)) 

446 

447 if fixed_indices: 

448 constraints.append(FixAtoms(fixed_indices)) 

449 

450 atoms_init = Atoms(species, 

451 cell=cell_init, 

452 scaled_positions=scpos_init, 

453 constraint=constraints, 

454 pbc=True) 

455 

456 elif elem.tag == 'dipole': 

457 dblock = elem.find('v[@name="dipole"]') 

458 if dblock is not None: 

459 dipole = np.array( 

460 [float(val) for val in dblock.text.split()]) 

461 

462 elif event == 'start' and elem.tag == 'calculation': 

463 calculation.append(elem) 

464 

465 except ET.ParseError as parse_error: 

466 if atoms_init is None: 

467 raise parse_error 

468 if calculation and calculation[-1].find("energy") is None: 

469 calculation = calculation[:-1] 

470 if not calculation: 

471 yield atoms_init 

472 

473 if calculation: 

474 if isinstance(index, int): 

475 steps = [calculation[index]] 

476 else: 

477 steps = calculation[index] 

478 else: 

479 steps = [] 

480 

481 for step in steps: 

482 # Workaround for VASP bug, e_0_energy contains the wrong value 

483 # in calculation/energy, but calculation/scstep/energy does not 

484 # include classical VDW corrections. So, first calculate 

485 # e_0_energy - e_fr_energy from calculation/scstep/energy, then 

486 # apply that correction to e_fr_energy from calculation/energy. 

487 lastscf = step.findall('scstep/energy')[-1] 

488 dipoles = step.findall('scstep/dipole') 

489 if dipoles: 

490 lastdipole = dipoles[-1] 

491 else: 

492 lastdipole = None 

493 

494 de = (float(lastscf.find('i[@name="e_0_energy"]').text) - 

495 float(lastscf.find('i[@name="e_fr_energy"]').text)) 

496 

497 free_energy = float(step.find('energy/i[@name="e_fr_energy"]').text) 

498 energy = free_energy + de 

499 

500 cell = np.zeros((3, 3), dtype=float) 

501 for i, vector in enumerate( 

502 step.find('structure/crystal/varray[@name="basis"]')): 

503 cell[i] = np.array([float(val) for val in vector.text.split()]) 

504 

505 scpos = np.zeros((natoms, 3), dtype=float) 

506 for i, vector in enumerate( 

507 step.find('structure/varray[@name="positions"]')): 

508 scpos[i] = np.array([float(val) for val in vector.text.split()]) 

509 

510 forces = None 

511 fblocks = step.find('varray[@name="forces"]') 

512 if fblocks is not None: 

513 forces = np.zeros((natoms, 3), dtype=float) 

514 for i, vector in enumerate(fblocks): 

515 forces[i] = np.array( 

516 [float(val) for val in vector.text.split()]) 

517 

518 stress = None 

519 sblocks = step.find('varray[@name="stress"]') 

520 if sblocks is not None: 

521 stress = np.zeros((3, 3), dtype=float) 

522 for i, vector in enumerate(sblocks): 

523 stress[i] = np.array( 

524 [float(val) for val in vector.text.split()]) 

525 stress *= -0.1 * GPa 

526 stress = stress.reshape(9)[[0, 4, 8, 5, 2, 1]] 

527 

528 dipole = None 

529 if lastdipole is not None: 

530 dblock = lastdipole.find('v[@name="dipole"]') 

531 if dblock is not None: 

532 dipole = np.zeros((1, 3), dtype=float) 

533 dipole = np.array([float(val) for val in dblock.text.split()]) 

534 

535 dblock = step.find('dipole/v[@name="dipole"]') 

536 if dblock is not None: 

537 dipole = np.zeros((1, 3), dtype=float) 

538 dipole = np.array([float(val) for val in dblock.text.split()]) 

539 

540 efermi = step.find('dos/i[@name="efermi"]') 

541 if efermi is not None: 

542 efermi = float(efermi.text) 

543 

544 kpoints = [] 

545 for ikpt in range(1, len(ibz_kpts) + 1): 

546 kblocks = step.findall( 

547 'eigenvalues/array/set/set/set[@comment="kpoint %d"]' % ikpt) 

548 if kblocks is not None: 

549 for spin, kpoint in enumerate(kblocks): 

550 eigenvals = kpoint.findall('r') 

551 eps_n = np.zeros(len(eigenvals)) 

552 f_n = np.zeros(len(eigenvals)) 

553 for j, val in enumerate(eigenvals): 

554 val = val.text.split() 

555 eps_n[j] = float(val[0]) 

556 f_n[j] = float(val[1]) 

557 if len(kblocks) == 1: 

558 f_n *= 2 

559 kpoints.append( 

560 SinglePointKPoint(kpt_weights[ikpt - 1], spin, ikpt, 

561 eps_n, f_n)) 

562 if len(kpoints) == 0: 

563 kpoints = None 

564 

565 atoms = atoms_init.copy() 

566 atoms.set_cell(cell) 

567 atoms.set_scaled_positions(scpos) 

568 atoms.calc = SinglePointDFTCalculator(atoms, 

569 energy=energy, 

570 forces=forces, 

571 stress=stress, 

572 free_energy=free_energy, 

573 ibzkpts=ibz_kpts, 

574 efermi=efermi, 

575 dipole=dipole) 

576 atoms.calc.name = 'vasp' 

577 atoms.calc.kpts = kpoints 

578 atoms.calc.parameters = parameters 

579 yield atoms 

580 

581 

582@writer 

583def write_vasp_xdatcar(fd, images, label=None): 

584 """Write VASP MD trajectory (XDATCAR) file 

585 

586 Only Vasp 5 format is supported (for consistency with read_vasp_xdatcar) 

587 

588 Args: 

589 fd (str, fp): Output file 

590 images (iterable of Atoms): Atoms images to write. These must have 

591 consistent atom order and lattice vectors - this will not be 

592 checked. 

593 label (str): Text for first line of file. If empty, default to list of 

594 elements. 

595 

596 """ 

597 

598 images = iter(images) 

599 image = next(images) 

600 

601 if not isinstance(image, Atoms): 

602 raise TypeError("images should be a sequence of Atoms objects.") 

603 

604 symbol_count = _symbol_count_from_symbols(image.get_chemical_symbols()) 

605 

606 if label is None: 

607 label = ' '.join([s for s, _ in symbol_count]) 

608 fd.write(label + '\n') 

609 

610 # Not using lattice constants, set it to 1 

611 fd.write(' 1\n') 

612 

613 # Lattice vectors; use first image 

614 float_string = '{:11.6f}' 

615 for row_i in range(3): 

616 fd.write(' ') 

617 fd.write(' '.join(float_string.format(x) for x in image.cell[row_i])) 

618 fd.write('\n') 

619 

620 _write_symbol_count(fd, symbol_count) 

621 _write_xdatcar_config(fd, image, index=1) 

622 for i, image in enumerate(images): 

623 # Index is off by 2: 1-indexed file vs 0-indexed Python; 

624 # and we already wrote the first block. 

625 _write_xdatcar_config(fd, image, i + 2) 

626 

627 

628def _write_xdatcar_config(fd, atoms, index): 

629 """Write a block of positions for XDATCAR file 

630 

631 Args: 

632 fd (fd): writeable Python file descriptor 

633 atoms (ase.Atoms): Atoms to write 

634 index (int): configuration number written to block header 

635 

636 """ 

637 fd.write("Direct configuration={:6d}\n".format(index)) 

638 float_string = '{:11.8f}' 

639 scaled_positions = atoms.get_scaled_positions() 

640 for row in scaled_positions: 

641 fd.write(' ') 

642 fd.write(' '.join([float_string.format(x) for x in row])) 

643 fd.write('\n') 

644 

645 

646def _symbol_count_from_symbols(symbols): 

647 """Reduce list of chemical symbols into compact VASP notation 

648 

649 args: 

650 symbols (iterable of str) 

651 

652 returns: 

653 list of pairs [(el1, c1), (el2, c2), ...] 

654 """ 

655 sc = [] 

656 psym = symbols[0] 

657 count = 0 

658 for sym in symbols: 

659 if sym != psym: 

660 sc.append((psym, count)) 

661 psym = sym 

662 count = 1 

663 else: 

664 count += 1 

665 sc.append((psym, count)) 

666 return sc 

667 

668 

669def _write_symbol_count(fd, sc, vasp5=True): 

670 """Write the symbols and numbers block for POSCAR or XDATCAR 

671 

672 Args: 

673 f (fd): Descriptor for writable file 

674 sc (list of 2-tuple): list of paired elements and counts 

675 vasp5 (bool): if False, omit symbols and only write counts 

676 

677 e.g. if sc is [(Sn, 4), (S, 6)] then write:: 

678 

679 Sn S 

680 4 6 

681 

682 """ 

683 if vasp5: 

684 for sym, _ in sc: 

685 fd.write(' {:3s}'.format(sym)) 

686 fd.write('\n') 

687 

688 for _, count in sc: 

689 fd.write(' {:3d}'.format(count)) 

690 fd.write('\n') 

691 

692 

693@writer 

694def write_vasp(filename, 

695 atoms, 

696 label=None, 

697 direct=False, 

698 sort=None, 

699 symbol_count=None, 

700 long_format=True, 

701 vasp5=True, 

702 ignore_constraints=False, 

703 wrap=False): 

704 """Method to write VASP position (POSCAR/CONTCAR) files. 

705 

706 Writes label, scalefactor, unitcell, # of various kinds of atoms, 

707 positions in cartesian or scaled coordinates (Direct), and constraints 

708 to file. Cartesian coordinates is default and default label is the 

709 atomic species, e.g. 'C N H Cu'. 

710 """ 

711 

712 from ase.constraints import FixAtoms, FixScaled, FixedPlane, FixedLine 

713 

714 fd = filename # @writer decorator ensures this arg is a file descriptor 

715 

716 if isinstance(atoms, (list, tuple)): 

717 if len(atoms) > 1: 

718 raise RuntimeError('Don\'t know how to save more than ' + 

719 'one image to VASP input') 

720 else: 

721 atoms = atoms[0] 

722 

723 # Check lattice vectors are finite 

724 if np.any(atoms.cell.cellpar() == 0.): 

725 raise RuntimeError( 

726 'Lattice vectors must be finite and not coincident. ' 

727 'At least one lattice length or angle is zero.') 

728 

729 # Write atom positions in scaled or cartesian coordinates 

730 if direct: 

731 coord = atoms.get_scaled_positions(wrap=wrap) 

732 else: 

733 coord = atoms.get_positions(wrap=wrap) 

734 

735 constraints = atoms.constraints and not ignore_constraints 

736 

737 if constraints: 

738 sflags = np.zeros((len(atoms), 3), dtype=bool) 

739 for constr in atoms.constraints: 

740 if isinstance(constr, FixScaled): 

741 sflags[constr.a] = constr.mask 

742 elif isinstance(constr, FixAtoms): 

743 sflags[constr.index] = [True, True, True] 

744 elif isinstance(constr, FixedPlane): 

745 mask = np.all(np.abs(np.cross(constr.dir, atoms.cell)) < 1e-5, 

746 axis=1) 

747 if sum(mask) != 1: 

748 raise RuntimeError( 

749 'VASP requires that the direction of FixedPlane ' 

750 'constraints is parallel with one of the cell axis') 

751 sflags[constr.a] = mask 

752 elif isinstance(constr, FixedLine): 

753 mask = np.all(np.abs(np.cross(constr.dir, atoms.cell)) < 1e-5, 

754 axis=1) 

755 if sum(mask) != 1: 

756 raise RuntimeError( 

757 'VASP requires that the direction of FixedLine ' 

758 'constraints is parallel with one of the cell axis') 

759 sflags[constr.a] = ~mask 

760 

761 if sort: 

762 ind = np.argsort(atoms.get_chemical_symbols()) 

763 symbols = np.array(atoms.get_chemical_symbols())[ind] 

764 coord = coord[ind] 

765 if constraints: 

766 sflags = sflags[ind] 

767 else: 

768 symbols = atoms.get_chemical_symbols() 

769 

770 # Create a list sc of (symbol, count) pairs 

771 if symbol_count: 

772 sc = symbol_count 

773 else: 

774 sc = _symbol_count_from_symbols(symbols) 

775 

776 # Create the label 

777 if label is None: 

778 label = '' 

779 for sym, c in sc: 

780 label += '%2s ' % sym 

781 fd.write(label + '\n') 

782 

783 # Write unitcell in real coordinates and adapt to VASP convention 

784 # for unit cell 

785 # ase Atoms doesn't store the lattice constant separately, so always 

786 # write 1.0. 

787 fd.write('%19.16f\n' % 1.0) 

788 if long_format: 

789 latt_form = ' %21.16f' 

790 else: 

791 latt_form = ' %11.6f' 

792 for vec in atoms.get_cell(): 

793 fd.write(' ') 

794 for el in vec: 

795 fd.write(latt_form % el) 

796 fd.write('\n') 

797 

798 # Write out symbols (if VASP 5.x) and counts of atoms 

799 _write_symbol_count(fd, sc, vasp5=vasp5) 

800 

801 if constraints: 

802 fd.write('Selective dynamics\n') 

803 

804 if direct: 

805 fd.write('Direct\n') 

806 else: 

807 fd.write('Cartesian\n') 

808 

809 if long_format: 

810 cform = ' %19.16f' 

811 else: 

812 cform = ' %9.6f' 

813 for iatom, atom in enumerate(coord): 

814 for dcoord in atom: 

815 fd.write(cform % dcoord) 

816 if constraints: 

817 for flag in sflags[iatom]: 

818 if flag: 

819 s = 'F' 

820 else: 

821 s = 'T' 

822 fd.write('%4s' % s) 

823 fd.write('\n')