Coverage for ase / io / gpaw_out.py: 83.73%
209 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
1# fmt: off
3import re
5import numpy as np
7from ase.atoms import Atoms
8from ase.calculators.singlepoint import (
9 SinglePointDFTCalculator,
10 SinglePointKPoint,
11)
14def index_startswith(lines: list[str], string: str) -> int:
15 for i, line in enumerate(lines):
16 if line.strip().startswith(string):
17 return i
18 raise ValueError
21def index_pattern(lines: list[str], pattern: str) -> int:
22 repat = re.compile(pattern)
23 for i, line in enumerate(lines):
24 if repat.match(line):
25 return i
26 raise ValueError
29def read_forces(lines: list[str],
30 ii: int,
31 atoms: Atoms) -> tuple[list[tuple[float, float, float]], int]:
32 f = []
33 for i in range(ii + 1, ii + 1 + len(atoms)):
34 try:
35 x, y, z = lines[i].split()[-3:]
36 f.append((float(x), float(y), float(z)))
37 except (ValueError, IndexError) as m:
38 raise OSError(f'Malformed GPAW log file: {m}')
39 return f, i
42def read_stresses(lines: list[str],
43 ii: int,) -> tuple[list[tuple[float, float, float]], int]:
44 s = []
45 for i in range(ii + 1, ii + 4):
46 try:
47 x, y, z = lines[i].split()[-3:]
48 s.append((float(x), float(y), float(z)))
49 except (ValueError, IndexError) as m:
50 raise OSError(f'Malformed GPAW log file: {m}')
51 return s, i
54def read_gpaw_out(fileobj, index): # -> Union[Atoms, List[Atoms]]:
55 """Read text output from GPAW calculation."""
56 lines = [line.lower() for line in fileobj.readlines()]
58 # read charge
59 try:
60 ii = index_startswith(lines, 'total charge:')
61 except ValueError:
62 q = None
63 else:
64 q = float(lines[ii].split()[2])
66 blocks = []
67 i1 = 0
68 for i2, line in enumerate(lines):
69 if line == 'positions:\n':
70 if i1 > 0:
71 blocks.append(lines[i1:i2])
72 i1 = i2
73 blocks.append(lines[i1:])
75 images: list[Atoms] = []
76 for lines in blocks:
77 try:
78 i = lines.index('unit cell:\n')
79 except ValueError:
80 pass
81 else:
82 if lines[i + 2].startswith(' -'):
83 del lines[i + 2] # old format
84 cell: list[float | list[float]] = []
85 pbc = []
86 for line in lines[i + 2:i + 5]:
87 words = line.split()
88 if len(words) == 5: # old format
89 cell.append(float(words[2]))
90 pbc.append(words[1] == 'yes')
91 else: # new format with GUC
92 cell.append([float(word) for word in words[3:6]])
93 pbc.append(words[2] == 'yes')
95 symbols = []
96 positions = []
97 magmoms = []
98 for line in lines[1:]:
99 words = line.split()
100 if len(words) < 5:
101 break
102 n, symbol, x, y, z = words[:5]
103 symbols.append(symbol.split('.')[0].title())
104 positions.append([float(x), float(y), float(z)])
105 if len(words) > 5:
106 mom = float(words[-1].rstrip(')'))
107 magmoms.append(mom)
108 if len(symbols):
109 atoms = Atoms(symbols=symbols, positions=positions,
110 cell=cell, pbc=pbc)
111 else:
112 atoms = Atoms(cell=cell, pbc=pbc)
114 try:
115 ii = index_pattern(lines, '\\d+ k-point')
116 word = lines[ii].split()
117 kx = int(word[2])
118 ky = int(word[4])
119 kz = int(word[6])
120 bz_kpts = (kx, ky, kz)
121 ibz_kpts = int(lines[ii + 1].split()[0])
122 except (ValueError, TypeError, IndexError):
123 bz_kpts = None
124 ibz_kpts = None
126 try:
127 i = index_startswith(lines, 'energy contributions relative to') + 2
128 except ValueError:
129 e = energy_contributions = None
130 else:
131 energy_contributions = {}
132 while i < len(lines):
133 fields = lines[i].split(':')
134 if len(fields) == 2:
135 name = fields[0]
136 energy = float(fields[1])
137 energy_contributions[name] = energy
138 if name in ['zero kelvin', 'extrapolated']:
139 e = energy
140 break
141 i += 1
142 else: # no break
143 raise ValueError
145 try:
146 ii = index_pattern(lines, '(fixed )?fermi level(s)?:')
147 except ValueError:
148 eFermi = None
149 else:
150 fields = lines[ii].split()
151 try:
152 def strip(string):
153 for rubbish in '[],':
154 string = string.replace(rubbish, '')
155 return string
156 eFermi = [float(strip(fields[-2])),
157 float(strip(fields[-1]))]
158 except ValueError:
159 eFermi = float(fields[-1])
161 # read Eigenvalues and occupations
162 ii1 = ii2 = 1e32
163 try:
164 ii1 = index_startswith(lines, 'band eigenvalues occupancy')
165 except ValueError:
166 pass
167 try:
168 ii2 = index_startswith(lines, 'band eigenvalues occupancy')
169 except ValueError:
170 pass
171 ii = min(ii1, ii2)
172 if ii == 1e32:
173 kpts = None
174 else:
175 ii += 1
176 words = lines[ii].split()
177 vals = []
178 while len(words) > 2:
179 vals.append([float(w) for w in words])
180 ii += 1
181 words = lines[ii].split()
182 vals = np.array(vals).transpose()
183 kpts = [SinglePointKPoint(1, 0, 0)]
184 kpts[0].eps_n = vals[1]
185 kpts[0].f_n = vals[2]
186 if vals.shape[0] > 3:
187 kpts.append(SinglePointKPoint(1, 1, 0))
188 kpts[1].eps_n = vals[3]
189 kpts[1].f_n = vals[4]
190 # read dipole moment
191 try:
192 ii = index_startswith(lines, 'dipole moment:')
193 except ValueError:
194 dipole = None
195 else:
196 line = lines[ii]
197 for x in '()[],':
198 line = line.replace(x, '')
199 dipole = np.array([float(c) for c in line.split()[2:5]])
201 try:
202 ii = index_startswith(lines, 'local magnetic moments')
203 except ValueError:
204 pass
205 else:
206 magmoms = []
207 for j in range(ii + 1, ii + 1 + len(atoms)):
208 line = lines[j]
209 if '#' in line: # new GPAW format
210 magmom = line.split()[-4].split(']')[0]
211 else:
212 magmom = line.split()[-1].rstrip(')')
213 magmoms.append(float(magmom))
215 try:
216 ii = lines.index('forces in ev/ang:\n')
217 except ValueError:
218 f = None
219 else:
220 f, i = read_forces(lines, ii, atoms)
222 try:
223 ii = lines.index('stress tensor:\n')
224 except ValueError:
225 stress_tensor = None
226 else:
227 stress_tensor, i = read_stresses(lines, ii)
229 try:
230 parameters = {}
231 ii = index_startswith(lines, 'vdw correction:')
232 except ValueError:
233 name = 'gpaw'
234 else:
235 name = lines[ii - 1].strip()
236 # save uncorrected values
237 parameters.update({
238 'calculator': 'gpaw',
239 'uncorrected_energy': e,
240 })
241 line = lines[ii + 1]
242 assert line.startswith('energy:')
243 e = float(line.split()[-1])
244 f, i = read_forces(lines, ii + 3, atoms)
246 if len(images) > 0 and e is None:
247 break
249 if q is not None and len(atoms) > 0:
250 n = len(atoms)
251 atoms.set_initial_charges([q / n] * n)
253 if magmoms:
254 atoms.set_initial_magnetic_moments(magmoms)
255 else:
256 magmoms = None
257 if e is not None or f is not None:
258 calc = SinglePointDFTCalculator(atoms, energy=e, forces=f,
259 dipole=dipole, magmoms=magmoms,
260 efermi=eFermi,
261 bzkpts=bz_kpts, ibzkpts=ibz_kpts,
262 stress=stress_tensor)
263 calc.name = name
264 calc.parameters = parameters
265 if energy_contributions is not None:
266 calc.energy_contributions = energy_contributions
267 if kpts is not None:
268 calc.kpts = kpts
269 atoms.calc = calc
271 images.append(atoms)
273 if len(images) == 0:
274 raise OSError('Corrupted GPAW-text file!')
276 return images[index]