Coverage for /builds/ase/ase/ase/io/abinit.py : 84.60%

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
1import os
2from os.path import join
3import re
4from glob import glob
5from pathlib import Path
7import numpy as np
9from ase import Atoms
10from ase.data import chemical_symbols
11from ase.units import Hartree, Bohr, fs
14def read_abinit_in(fd):
15 """Import ABINIT input file.
17 Reads cell, atom positions, etc. from abinit input file
18 """
20 tokens = []
22 for line in fd:
23 meat = line.split('#', 1)[0]
24 tokens += meat.lower().split()
26 # note that the file can not be scanned sequentially
28 index = tokens.index("acell")
29 unit = 1.0
30 if tokens[index + 4].lower()[:3] != 'ang':
31 unit = Bohr
32 acell = [unit * float(tokens[index + 1]),
33 unit * float(tokens[index + 2]),
34 unit * float(tokens[index + 3])]
36 index = tokens.index("natom")
37 natom = int(tokens[index + 1])
39 index = tokens.index("ntypat")
40 ntypat = int(tokens[index + 1])
42 index = tokens.index("typat")
43 typat = []
44 while len(typat) < natom:
45 token = tokens[index + 1]
46 if '*' in token: # e.g. typat 4*1 3*2 ...
47 nrepeat, typenum = token.split('*')
48 typat += [int(typenum)] * int(nrepeat)
49 else:
50 typat.append(int(token))
51 index += 1
52 assert natom == len(typat)
54 index = tokens.index("znucl")
55 znucl = []
56 for i in range(ntypat):
57 znucl.append(int(tokens[index + 1 + i]))
59 index = tokens.index("rprim")
60 rprim = []
61 for i in range(3):
62 rprim.append([acell[i] * float(tokens[index + 3 * i + 1]),
63 acell[i] * float(tokens[index + 3 * i + 2]),
64 acell[i] * float(tokens[index + 3 * i + 3])])
66 # create a list with the atomic numbers
67 numbers = []
68 for i in range(natom):
69 ii = typat[i] - 1
70 numbers.append(znucl[ii])
72 # now the positions of the atoms
73 if "xred" in tokens:
74 index = tokens.index("xred")
75 xred = []
76 for i in range(natom):
77 xred.append([float(tokens[index + 3 * i + 1]),
78 float(tokens[index + 3 * i + 2]),
79 float(tokens[index + 3 * i + 3])])
80 atoms = Atoms(cell=rprim, scaled_positions=xred, numbers=numbers,
81 pbc=True)
82 else:
83 if "xcart" in tokens:
84 index = tokens.index("xcart")
85 unit = Bohr
86 elif "xangst" in tokens:
87 unit = 1.0
88 index = tokens.index("xangst")
89 else:
90 raise IOError(
91 "No xred, xcart, or xangs keyword in abinit input file")
93 xangs = []
94 for i in range(natom):
95 xangs.append([unit * float(tokens[index + 3 * i + 1]),
96 unit * float(tokens[index + 3 * i + 2]),
97 unit * float(tokens[index + 3 * i + 3])])
98 atoms = Atoms(cell=rprim, positions=xangs, numbers=numbers, pbc=True)
100 try:
101 ii = tokens.index('nsppol')
102 except ValueError:
103 nsppol = None
104 else:
105 nsppol = int(tokens[ii + 1])
107 if nsppol == 2:
108 index = tokens.index('spinat')
109 magmoms = [float(tokens[index + 3 * i + 3]) for i in range(natom)]
110 atoms.set_initial_magnetic_moments(magmoms)
112 assert len(atoms) == natom
113 return atoms
116keys_with_units = {
117 'toldfe': 'eV',
118 'tsmear': 'eV',
119 'paoenergyshift': 'eV',
120 'zmunitslength': 'Bohr',
121 'zmunitsangle': 'rad',
122 'zmforcetollength': 'eV/Ang',
123 'zmforcetolangle': 'eV/rad',
124 'zmmaxdispllength': 'Ang',
125 'zmmaxdisplangle': 'rad',
126 'ecut': 'eV',
127 'pawecutdg': 'eV',
128 'dmenergytolerance': 'eV',
129 'electronictemperature': 'eV',
130 'oneta': 'eV',
131 'onetaalpha': 'eV',
132 'onetabeta': 'eV',
133 'onrclwf': 'Ang',
134 'onchemicalpotentialrc': 'Ang',
135 'onchemicalpotentialtemperature': 'eV',
136 'mdmaxcgdispl': 'Ang',
137 'mdmaxforcetol': 'eV/Ang',
138 'mdmaxstresstol': 'eV/Ang**3',
139 'mdlengthtimestep': 'fs',
140 'mdinitialtemperature': 'eV',
141 'mdtargettemperature': 'eV',
142 'mdtargetpressure': 'eV/Ang**3',
143 'mdnosemass': 'eV*fs**2',
144 'mdparrinellorahmanmass': 'eV*fs**2',
145 'mdtaurelax': 'fs',
146 'mdbulkmodulus': 'eV/Ang**3',
147 'mdfcdispl': 'Ang',
148 'warningminimumatomicdistance': 'Ang',
149 'rcspatial': 'Ang',
150 'kgridcutoff': 'Ang',
151 'latticeconstant': 'Ang'}
154def write_abinit_in(fd, atoms, param=None, species=None, pseudos=None):
155 from ase.calculators.calculator import kpts2mp
157 if param is None:
158 param = {}
160 if species is None:
161 species = sorted(set(atoms.numbers))
163 inp = dict(param)
164 xc = inp.pop('xc', 'LDA')
165 for key in ['smearing', 'kpts', 'pps', 'raw']:
166 inp.pop(key, None)
168 smearing = param.get('smearing')
169 if 'tsmear' in param or 'occopt' in param:
170 assert smearing is None
172 if smearing is not None:
173 inp['occopt'] = {'fermi-dirac': 3,
174 'gaussian': 7}[smearing[0].lower()]
175 inp['tsmear'] = smearing[1]
177 inp['natom'] = len(atoms)
179 if 'nbands' in param:
180 inp['nband'] = param['nbands']
181 del inp['nbands']
183 # ixc is set from paw/xml file. Ignore 'xc' setting then.
184 if param.get('pps') not in ['pawxml']:
185 if 'ixc' not in param:
186 inp['ixc'] = {'LDA': 7,
187 'PBE': 11,
188 'revPBE': 14,
189 'RPBE': 15,
190 'WC': 23}[xc]
192 magmoms = atoms.get_initial_magnetic_moments()
193 if magmoms.any():
194 inp['nsppol'] = 2
195 fd.write('spinat\n')
196 for n, M in enumerate(magmoms):
197 fd.write('%.14f %.14f %.14f\n' % (0, 0, M))
198 else:
199 inp['nsppol'] = 1
201 if param.get('kpts') is not None:
202 mp = kpts2mp(atoms, param['kpts'])
203 fd.write('kptopt 1\n')
204 fd.write('ngkpt %d %d %d\n' % tuple(mp))
205 fd.write('nshiftk 1\n')
206 fd.write('shiftk\n')
207 fd.write('%.1f %.1f %.1f\n' % tuple((np.array(mp) + 1) % 2 * 0.5))
209 valid_lists = (list, np.ndarray)
210 for key in sorted(inp):
211 value = inp[key]
212 unit = keys_with_units.get(key)
213 if unit is not None:
214 if 'fs**2' in unit:
215 value /= fs**2
216 elif 'fs' in unit:
217 value /= fs
218 if isinstance(value, valid_lists):
219 if isinstance(value[0], valid_lists):
220 fd.write("{}\n".format(key))
221 for dim in value:
222 write_list(fd, dim, unit)
223 else:
224 fd.write("{}\n".format(key))
225 write_list(fd, value, unit)
226 else:
227 if unit is None:
228 fd.write("{} {}\n".format(key, value))
229 else:
230 fd.write("{} {} {}\n".format(key, value, unit))
232 if param.get('raw') is not None:
233 if isinstance(param['raw'], str):
234 raise TypeError('The raw parameter is a single string; expected '
235 'a sequence of lines')
236 for line in param['raw']:
237 if isinstance(line, tuple):
238 fd.write(' '.join(['%s' % x for x in line]) + '\n')
239 else:
240 fd.write('%s\n' % line)
242 fd.write('#Definition of the unit cell\n')
243 fd.write('acell\n')
244 fd.write('%.14f %.14f %.14f Angstrom\n' % (1.0, 1.0, 1.0))
245 fd.write('rprim\n')
246 if atoms.cell.rank != 3:
247 raise RuntimeError('Abinit requires a 3D cell, but cell is {}'
248 .format(atoms.cell))
249 for v in atoms.cell:
250 fd.write('%.14f %.14f %.14f\n' % tuple(v))
252 fd.write('chkprim 0 # Allow non-primitive cells\n')
254 fd.write('#Definition of the atom types\n')
255 fd.write('ntypat %d\n' % (len(species)))
256 fd.write('znucl {}\n'.format(' '.join(str(Z) for Z in species)))
257 fd.write('#Enumerate different atomic species\n')
258 fd.write('typat')
259 fd.write('\n')
261 types = []
262 for Z in atoms.numbers:
263 for n, Zs in enumerate(species):
264 if Z == Zs:
265 types.append(n + 1)
266 n_entries_int = 20 # integer entries per line
267 for n, type in enumerate(types):
268 fd.write(' %d' % (type))
269 if n > 1 and ((n % n_entries_int) == 1):
270 fd.write('\n')
271 fd.write('\n')
273 if pseudos is not None:
274 listing = ',\n'.join(pseudos)
275 line = f'pseudos "{listing}"\n'
276 fd.write(line)
278 fd.write('#Definition of the atoms\n')
279 fd.write('xcart\n')
280 for pos in atoms.positions / Bohr:
281 fd.write('%.14f %.14f %.14f\n' % tuple(pos))
283 fd.write('chkexit 1 # abinit.exit file in the running '
284 'directory terminates after the current SCF\n')
287def write_list(fd, value, unit):
288 for element in value:
289 fd.write("{} ".format(element))
290 if unit is not None:
291 fd.write("{}".format(unit))
292 fd.write("\n")
295def read_stress(fd):
296 # sigma(1 1)= 4.02063464E-04 sigma(3 2)= 0.00000000E+00
297 # sigma(2 2)= 4.02063464E-04 sigma(3 1)= 0.00000000E+00
298 # sigma(3 3)= 4.02063464E-04 sigma(2 1)= 0.00000000E+00
299 pat = re.compile(r'\s*sigma\(\d \d\)=\s*(\S+)\s*sigma\(\d \d\)=\s*(\S+)')
300 stress = np.empty(6)
301 for i in range(3):
302 line = next(fd)
303 m = pat.match(line)
304 if m is None:
305 # Not a real value error. What should we raise?
306 raise ValueError('Line {!r} does not match stress pattern {!r}'
307 .format(line, pat))
308 s1, s2 = m.group(1, 2)
309 stress[i] = float(m.group(1))
310 stress[i + 3] = float(m.group(2))
311 unit = Hartree / Bohr**3
312 return stress * unit
315def consume_multiline(fd, headerline, nvalues, dtype):
316 """Parse abinit-formatted "header + values" sections.
318 Example:
320 typat 1 1 1 1 1
321 1 1 1 1
322 """
323 tokens = headerline.split()
324 assert tokens[0].isalpha()
326 values = tokens[1:]
327 while len(values) < nvalues:
328 line = next(fd)
329 values.extend(line.split())
330 assert len(values) == nvalues
331 return np.array(values).astype(dtype)
334def read_abinit_out(fd):
335 results = {}
337 def skipto(string):
338 for line in fd:
339 if string in line:
340 return line
341 raise RuntimeError('Not found: {}'.format(string))
343 line = skipto('Version')
344 m = re.match(r'\.*?Version\s+(\S+)\s+of ABINIT', line)
345 assert m is not None
346 version = m.group(1)
347 results['version'] = version
349 use_v9_format = int(version.split('.', 1)[0]) >= 9
351 shape_vars = {}
353 skipto('echo values of preprocessed input variables')
355 for line in fd:
356 if '===============' in line:
357 break
359 tokens = line.split()
360 if not tokens:
361 continue
363 for key in ['natom', 'nkpt', 'nband', 'ntypat']:
364 if tokens[0] == key:
365 shape_vars[key] = int(tokens[1])
367 if line.lstrip().startswith('typat'): # Avoid matching ntypat
368 types = consume_multiline(fd, line, shape_vars['natom'], int)
370 if 'znucl' in line:
371 znucl = consume_multiline(fd, line, shape_vars['ntypat'], float)
373 if 'rprim' in line:
374 cell = consume_multiline(fd, line, 9, float)
375 cell = cell.reshape(3, 3)
377 natoms = shape_vars['natom']
379 # Skip ahead to results:
380 for line in fd:
381 if 'was not enough scf cycles to converge' in line:
382 raise RuntimeError(line)
383 if 'iterations are completed or convergence reached' in line:
384 break
385 else:
386 raise RuntimeError('Cannot find results section')
388 def read_array(fd, nlines):
389 arr = []
390 for i in range(nlines):
391 line = next(fd)
392 arr.append(line.split()[1:])
393 arr = np.array(arr).astype(float)
394 return arr
396 if use_v9_format:
397 energy_header = '--- !EnergyTerms'
398 total_energy_name = 'total_energy_eV'
400 def parse_energy(line):
401 return float(line.split(':')[1].strip())
402 else:
403 energy_header = 'Components of total free energy (in Hartree) :'
404 total_energy_name = '>>>>>>>>> Etotal'
406 def parse_energy(line):
407 return float(line.rsplit('=', 2)[1]) * Hartree
409 for line in fd:
410 if 'cartesian coordinates (angstrom) at end' in line:
411 positions = read_array(fd, natoms)
412 if 'cartesian forces (eV/Angstrom) at end' in line:
413 results['forces'] = read_array(fd, natoms)
414 if 'Cartesian components of stress tensor (hartree/bohr^3)' in line:
415 results['stress'] = read_stress(fd)
417 if line.strip() == energy_header:
418 # Header not to be confused with EnergyTermsDC,
419 # therefore we don't use .startswith()
420 energy = None
421 for line in fd:
422 # Which of the listed energies should we include?
423 if total_energy_name in line:
424 energy = parse_energy(line)
425 break
426 if energy is None:
427 raise RuntimeError('No energy found in output')
428 results['energy'] = results['free_energy'] = energy
430 if 'END DATASET(S)' in line:
431 break
433 znucl_int = znucl.astype(int)
434 znucl_int[znucl_int != znucl] = 0 # (Fractional Z)
435 numbers = znucl_int[types - 1]
437 atoms = Atoms(numbers=numbers,
438 positions=positions,
439 cell=cell,
440 pbc=True)
442 results['atoms'] = atoms
443 return results
446def match_kpt_header(line):
447 headerpattern = (r'\s*kpt#\s*\S+\s*'
448 r'nband=\s*(\d+),\s*'
449 r'wtk=\s*(\S+?),\s*'
450 r'kpt=\s*(\S+)+\s*(\S+)\s*(\S+)')
451 m = re.match(headerpattern, line)
452 assert m is not None, line
453 nbands = int(m.group(1))
454 weight = float(m.group(2))
455 kvector = np.array(m.group(3, 4, 5)).astype(float)
456 return nbands, weight, kvector
459def read_eigenvalues_for_one_spin(fd, nkpts):
461 kpoint_weights = []
462 kpoint_coords = []
464 eig_kn = []
465 for ikpt in range(nkpts):
466 header = next(fd)
467 nbands, weight, kvector = match_kpt_header(header)
468 kpoint_coords.append(kvector)
469 kpoint_weights.append(weight)
471 eig_n = []
472 while len(eig_n) < nbands:
473 line = next(fd)
474 tokens = line.split()
475 values = np.array(tokens).astype(float) * Hartree
476 eig_n.extend(values)
477 assert len(eig_n) == nbands
478 eig_kn.append(eig_n)
479 assert nbands == len(eig_kn[0])
481 kpoint_weights = np.array(kpoint_weights)
482 kpoint_coords = np.array(kpoint_coords)
483 eig_kn = np.array(eig_kn)
484 return kpoint_coords, kpoint_weights, eig_kn
487def read_eig(fd):
488 line = next(fd)
489 results = {}
490 m = re.match(r'\s*Fermi \(or HOMO\) energy \(hartree\)\s*=\s*(\S+)', line)
491 if m is not None:
492 results['fermilevel'] = float(m.group(1)) * Hartree
493 line = next(fd)
495 nspins = 1
497 m = re.match(r'\s*Magnetization \(Bohr magneton\)=\s*(\S+)', line)
498 if m is not None:
499 nspins = 2
500 magmom = float(m.group(1))
501 results['magmom'] = magmom
502 line = next(fd)
504 if 'Total spin up' in line:
505 assert nspins == 2
506 line = next(fd)
508 m = re.match(r'\s*Eigenvalues \(hartree\) for nkpt\s*='
509 r'\s*(\S+)\s*k\s*points', line)
510 if 'SPIN' in line or 'spin' in line:
511 # If using spinpol with fixed magmoms, we don't get the magmoms
512 # listed before now.
513 nspins = 2
514 assert m is not None
515 nkpts = int(m.group(1))
517 eig_skn = []
519 kpts, weights, eig_kn = read_eigenvalues_for_one_spin(fd, nkpts)
520 nbands = eig_kn.shape[1]
522 eig_skn.append(eig_kn)
523 if nspins == 2:
524 line = next(fd)
525 assert 'SPIN DOWN' in line
526 _, _, eig_kn = read_eigenvalues_for_one_spin(fd, nkpts)
527 assert eig_kn.shape == (nkpts, nbands)
528 eig_skn.append(eig_kn)
529 eig_skn = np.array(eig_skn)
531 eigshape = (nspins, nkpts, nbands)
532 assert eig_skn.shape == eigshape, (eig_skn.shape, eigshape)
534 results['ibz_kpoints'] = kpts
535 results['kpoint_weights'] = weights
536 results['eigenvalues'] = eig_skn
537 return results
540def get_default_abinit_pp_paths():
541 return os.environ.get('ABINIT_PP_PATH', '.').split(':')
544def prepare_abinit_input(directory, atoms, properties, parameters,
545 pp_paths=None,
546 raise_exception=True):
547 directory = Path(directory)
548 species = sorted(set(atoms.numbers))
549 if pp_paths is None:
550 pp_paths = get_default_abinit_pp_paths()
551 ppp = get_ppp_list(atoms, species,
552 raise_exception=raise_exception,
553 xc=parameters['xc'],
554 pps=parameters['pps'],
555 search_paths=pp_paths)
557 inputfile = directory / 'abinit.in'
559 # XXX inappropriate knowledge about choice of outputfile
560 outputfile = directory / 'abinit.abo'
562 # Abinit will write to label.txtA if label.txt already exists,
563 # so we remove it if it's there:
564 if outputfile.exists():
565 outputfile.unlink()
567 with open(inputfile, 'w') as fd:
568 write_abinit_in(fd, atoms, param=parameters, species=species,
569 pseudos=ppp)
572def read_abinit_outputs(directory, label):
573 directory = Path(directory)
574 textfilename = directory / f'{label}.abo'
575 results = {}
576 with open(textfilename) as fd:
577 dct = read_abinit_out(fd)
578 results.update(dct)
580 # The eigenvalues section in the main file is shortened to
581 # a limited number of kpoints. We read the complete one from
582 # the EIG file then:
583 with open(directory / f'{label}o_EIG') as fd:
584 dct = read_eig(fd)
585 results.update(dct)
586 return results
589def get_ppp_list(atoms, species, raise_exception, xc, pps,
590 search_paths):
591 ppp_list = []
593 xcname = 'GGA' if xc != 'LDA' else 'LDA'
594 for Z in species:
595 number = abs(Z)
596 symbol = chemical_symbols[number]
598 names = []
599 for s in [symbol, symbol.lower()]:
600 for xcn in [xcname, xcname.lower()]:
601 if pps in ['paw']:
602 hghtemplate = '%s-%s-%s.paw' # E.g. "H-GGA-hard-uspp.paw"
603 names.append(hghtemplate % (s, xcn, '*'))
604 names.append('%s[.-_]*.paw' % s)
605 elif pps in ['pawxml']:
606 hghtemplate = '%s.%s%s.xml' # E.g. "H.GGA_PBE-JTH.xml"
607 names.append(hghtemplate % (s, xcn, '*'))
608 names.append('%s[.-_]*.xml' % s)
609 elif pps in ['hgh.k']:
610 hghtemplate = '%s-q%s.hgh.k' # E.g. "Co-q17.hgh.k"
611 names.append(hghtemplate % (s, '*'))
612 names.append('%s[.-_]*.hgh.k' % s)
613 names.append('%s[.-_]*.hgh' % s)
614 elif pps in ['tm']:
615 hghtemplate = '%d%s%s.pspnc' # E.g. "44ru.pspnc"
616 names.append(hghtemplate % (number, s, '*'))
617 names.append('%s[.-_]*.pspnc' % s)
618 elif pps in ['hgh', 'hgh.sc']:
619 hghtemplate = '%d%s.%s.hgh' # E.g. "42mo.6.hgh"
620 # There might be multiple files with different valence
621 # electron counts, so we must choose between
622 # the ordinary and the semicore versions for some elements.
623 #
624 # Therefore we first use glob to get all relevant files,
625 # then pick the correct one afterwards.
626 names.append(hghtemplate % (number, s, '*'))
627 names.append('%d%s%s.hgh' % (number, s, '*'))
628 names.append('%s[.-_]*.hgh' % s)
629 else: # default extension
630 names.append('%02d-%s.%s.%s' % (number, s, xcn, pps))
631 names.append('%02d[.-_]%s*.%s' % (number, s, pps))
632 names.append('%02d%s*.%s' % (number, s, pps))
633 names.append('%s[.-_]*.%s' % (s, pps))
635 found = False
636 for name in names: # search for file names possibilities
637 for path in search_paths: # in all available directories
638 filenames = glob(join(path, name))
639 if not filenames:
640 continue
641 if pps == 'paw':
642 # warning: see download.sh in
643 # abinit-pseudopotentials*tar.gz for additional
644 # information!
645 #
646 # XXXX This is probably buggy, max(filenames) uses
647 # an lexicographic order so 14 < 8, and it's
648 # untested so if I change it I'm sure things will
649 # just be inconsistent. --askhl
650 filenames[0] = max(filenames) # Semicore or hard
651 elif pps == 'hgh':
652 # Lowest valence electron count
653 filenames[0] = min(filenames)
654 elif pps == 'hgh.k':
655 # Semicore - highest electron count
656 filenames[0] = max(filenames)
657 elif pps == 'tm':
658 # Semicore - highest electron count
659 filenames[0] = max(filenames)
660 elif pps == 'hgh.sc':
661 # Semicore - highest electron count
662 filenames[0] = max(filenames)
664 if filenames:
665 found = True
666 ppp_list.append(filenames[0])
667 break
668 if found:
669 break
671 if not found:
672 ppp_list.append("Provide {}.{}.{}?".format(symbol, '*', pps))
673 if raise_exception:
674 msg = ('Could not find {} pseudopotential {} for {} in {}'
675 .format(xcname.lower(), pps, symbol, search_paths))
676 raise RuntimeError(msg)
678 return ppp_list