Coverage for /builds/hweiske/ase/ase/io/espresso.py: 77.44%

860 statements  

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

1"""Reads Quantum ESPRESSO files. 

2 

3Read multiple structures and results from pw.x output files. Read 

4structures from pw.x input files. 

5 

6Built for PWSCF v.5.3.0 but should work with earlier and later versions. 

7Can deal with most major functionality, with the notable exception of ibrav, 

8for which we only support ibrav == 0 and force CELL_PARAMETERS to be provided 

9explicitly. 

10 

11Units are converted using CODATA 2006, as used internally by Quantum 

12ESPRESSO. 

13""" 

14 

15import operator as op 

16import re 

17import warnings 

18from collections import defaultdict 

19from copy import deepcopy 

20from pathlib import Path 

21 

22import numpy as np 

23 

24from ase.atoms import Atoms 

25from ase.calculators.calculator import kpts2ndarray, kpts2sizeandoffsets 

26from ase.calculators.singlepoint import (SinglePointDFTCalculator, 

27 SinglePointKPoint) 

28from ase.constraints import FixAtoms, FixCartesian 

29from ase.data import chemical_symbols 

30from ase.dft.kpoints import kpoint_convert 

31from ase.io.espresso_namelist.keys import pw_keys 

32from ase.io.espresso_namelist.namelist import Namelist 

33from ase.units import create_units 

34from ase.utils import deprecated, reader, writer 

35 

36# Quantum ESPRESSO uses CODATA 2006 internally 

37units = create_units('2006') 

38 

39# Section identifiers 

40_PW_START = 'Program PWSCF' 

41_PW_END = 'End of self-consistent calculation' 

42_PW_CELL = 'CELL_PARAMETERS' 

43_PW_POS = 'ATOMIC_POSITIONS' 

44_PW_MAGMOM = 'Magnetic moment per site' 

45_PW_FORCE = 'Forces acting on atoms' 

46_PW_TOTEN = '! total energy' 

47_PW_STRESS = 'total stress' 

48_PW_FERMI = 'the Fermi energy is' 

49_PW_HIGHEST_OCCUPIED = 'highest occupied level' 

50_PW_HIGHEST_OCCUPIED_LOWEST_FREE = 'highest occupied, lowest unoccupied level' 

51_PW_KPTS = 'number of k points=' 

52_PW_BANDS = _PW_END 

53_PW_BANDSTRUCTURE = 'End of band structure calculation' 

54_PW_DIPOLE = "Debye" 

55_PW_DIPOLE_DIRECTION = "Computed dipole along edir" 

56 

57# ibrav error message 

58ibrav_error_message = ( 

59 'ASE does not support ibrav != 0. Note that with ibrav ' 

60 '== 0, Quantum ESPRESSO will still detect the symmetries ' 

61 'of your system because the CELL_PARAMETERS are defined ' 

62 'to a high level of precision.') 

63 

64 

65@reader 

66def read_espresso_out(fileobj, index=slice(None), results_required=True): 

67 """Reads Quantum ESPRESSO output files. 

68 

69 The atomistic configurations as well as results (energy, force, stress, 

70 magnetic moments) of the calculation are read for all configurations 

71 within the output file. 

72 

73 Will probably raise errors for broken or incomplete files. 

74 

75 Parameters 

76 ---------- 

77 fileobj : file|str 

78 A file like object or filename 

79 index : slice 

80 The index of configurations to extract. 

81 results_required : bool 

82 If True, atomistic configurations that do not have any 

83 associated results will not be included. This prevents double 

84 printed configurations and incomplete calculations from being 

85 returned as the final configuration with no results data. 

86 

87 Yields 

88 ------ 

89 structure : Atoms 

90 The next structure from the index slice. The Atoms has a 

91 SinglePointCalculator attached with any results parsed from 

92 the file. 

93 

94 

95 """ 

96 # work with a copy in memory for faster random access 

97 pwo_lines = fileobj.readlines() 

98 

99 # TODO: index -1 special case? 

100 # Index all the interesting points 

101 indexes = { 

102 _PW_START: [], 

103 _PW_END: [], 

104 _PW_CELL: [], 

105 _PW_POS: [], 

106 _PW_MAGMOM: [], 

107 _PW_FORCE: [], 

108 _PW_TOTEN: [], 

109 _PW_STRESS: [], 

110 _PW_FERMI: [], 

111 _PW_HIGHEST_OCCUPIED: [], 

112 _PW_HIGHEST_OCCUPIED_LOWEST_FREE: [], 

113 _PW_KPTS: [], 

114 _PW_BANDS: [], 

115 _PW_BANDSTRUCTURE: [], 

116 _PW_DIPOLE: [], 

117 _PW_DIPOLE_DIRECTION: [], 

118 } 

119 

120 for idx, line in enumerate(pwo_lines): 

121 for identifier in indexes: 

122 if identifier in line: 

123 indexes[identifier].append(idx) 

124 

125 # Configurations are either at the start, or defined in ATOMIC_POSITIONS 

126 # in a subsequent step. Can deal with concatenated output files. 

127 all_config_indexes = sorted(indexes[_PW_START] + 

128 indexes[_PW_POS]) 

129 

130 # Slice only requested indexes 

131 # setting results_required argument stops configuration-only 

132 # structures from being returned. This ensures the [-1] structure 

133 # is one that has results. Two cases: 

134 # - SCF of last configuration is not converged, job terminated 

135 # abnormally. 

136 # - 'relax' and 'vc-relax' re-prints the final configuration but 

137 # only 'vc-relax' recalculates. 

138 if results_required: 

139 results_indexes = sorted(indexes[_PW_TOTEN] + indexes[_PW_FORCE] + 

140 indexes[_PW_STRESS] + indexes[_PW_MAGMOM] + 

141 indexes[_PW_BANDS] + 

142 indexes[_PW_BANDSTRUCTURE]) 

143 

144 # Prune to only configurations with results data before the next 

145 # configuration 

146 results_config_indexes = [] 

147 for config_index, config_index_next in zip( 

148 all_config_indexes, 

149 all_config_indexes[1:] + [len(pwo_lines)]): 

150 if any(config_index < results_index < config_index_next 

151 for results_index in results_indexes): 

152 results_config_indexes.append(config_index) 

153 

154 # slice from the subset 

155 image_indexes = results_config_indexes[index] 

156 else: 

157 image_indexes = all_config_indexes[index] 

158 

159 # Extract initialisation information each time PWSCF starts 

160 # to add to subsequent configurations. Use None so slices know 

161 # when to fill in the blanks. 

162 pwscf_start_info = {idx: None for idx in indexes[_PW_START]} 

163 

164 for image_index in image_indexes: 

165 # Find the nearest calculation start to parse info. Needed in, 

166 # for example, relaxation where cell is only printed at the 

167 # start. 

168 if image_index in indexes[_PW_START]: 

169 prev_start_index = image_index 

170 else: 

171 # The greatest start index before this structure 

172 prev_start_index = [idx for idx in indexes[_PW_START] 

173 if idx < image_index][-1] 

174 

175 # add structure to reference if not there 

176 if pwscf_start_info[prev_start_index] is None: 

177 pwscf_start_info[prev_start_index] = parse_pwo_start( 

178 pwo_lines, prev_start_index) 

179 

180 # Get the bounds for information for this structure. Any associated 

181 # values will be between the image_index and the following one, 

182 # EXCEPT for cell, which will be 4 lines before if it exists. 

183 for next_index in all_config_indexes: 

184 if next_index > image_index: 

185 break 

186 else: 

187 # right to the end of the file 

188 next_index = len(pwo_lines) 

189 

190 # Get the structure 

191 # Use this for any missing data 

192 prev_structure = pwscf_start_info[prev_start_index]['atoms'] 

193 if image_index in indexes[_PW_START]: 

194 structure = prev_structure.copy() # parsed from start info 

195 else: 

196 if _PW_CELL in pwo_lines[image_index - 5]: 

197 # CELL_PARAMETERS would be just before positions if present 

198 cell, cell_alat = get_cell_parameters( 

199 pwo_lines[image_index - 5:image_index]) 

200 else: 

201 cell = prev_structure.cell 

202 cell_alat = pwscf_start_info[prev_start_index]['alat'] 

203 

204 # give at least enough lines to parse the positions 

205 # should be same format as input card 

206 n_atoms = len(prev_structure) 

207 positions_card = get_atomic_positions( 

208 pwo_lines[image_index:image_index + n_atoms + 1], 

209 n_atoms=n_atoms, cell=cell, alat=cell_alat) 

210 

211 # convert to Atoms object 

212 symbols = [label_to_symbol(position[0]) for position in 

213 positions_card] 

214 positions = [position[1] for position in positions_card] 

215 structure = Atoms(symbols=symbols, positions=positions, cell=cell, 

216 pbc=True) 

217 

218 # Extract calculation results 

219 # Energy 

220 energy = None 

221 for energy_index in indexes[_PW_TOTEN]: 

222 if image_index < energy_index < next_index: 

223 energy = float( 

224 pwo_lines[energy_index].split()[-2]) * units['Ry'] 

225 

226 # Forces 

227 forces = None 

228 for force_index in indexes[_PW_FORCE]: 

229 if image_index < force_index < next_index: 

230 # Before QE 5.3 'negative rho' added 2 lines before forces 

231 # Use exact lines to stop before 'non-local' forces 

232 # in high verbosity 

233 if not pwo_lines[force_index + 2].strip(): 

234 force_index += 4 

235 else: 

236 force_index += 2 

237 # assume contiguous 

238 forces = [ 

239 [float(x) for x in force_line.split()[-3:]] for force_line 

240 in pwo_lines[force_index:force_index + len(structure)]] 

241 forces = np.array(forces) * units['Ry'] / units['Bohr'] 

242 

243 # Stress 

244 stress = None 

245 for stress_index in indexes[_PW_STRESS]: 

246 if image_index < stress_index < next_index: 

247 sxx, sxy, sxz = pwo_lines[stress_index + 1].split()[:3] 

248 _, syy, syz = pwo_lines[stress_index + 2].split()[:3] 

249 _, _, szz = pwo_lines[stress_index + 3].split()[:3] 

250 stress = np.array([sxx, syy, szz, syz, sxz, sxy], dtype=float) 

251 # sign convention is opposite of ase 

252 stress *= -1 * units['Ry'] / (units['Bohr'] ** 3) 

253 

254 # Magmoms 

255 magmoms = None 

256 for magmoms_index in indexes[_PW_MAGMOM]: 

257 if image_index < magmoms_index < next_index: 

258 magmoms = [ 

259 float(mag_line.split()[-1]) for mag_line 

260 in pwo_lines[magmoms_index + 1: 

261 magmoms_index + 1 + len(structure)]] 

262 

263 # Dipole moment 

264 dipole = None 

265 if indexes[_PW_DIPOLE]: 

266 for dipole_index in indexes[_PW_DIPOLE]: 

267 if image_index < dipole_index < next_index: 

268 _dipole = float(pwo_lines[dipole_index].split()[-2]) 

269 

270 for dipole_index in indexes[_PW_DIPOLE_DIRECTION]: 

271 if image_index < dipole_index < next_index: 

272 _direction = pwo_lines[dipole_index].strip() 

273 prefix = 'Computed dipole along edir(' 

274 _direction = _direction[len(prefix):] 

275 _direction = int(_direction[0]) 

276 

277 dipole = np.eye(3)[_direction - 1] * _dipole * units['Debye'] 

278 

279 # Fermi level / highest occupied level 

280 efermi = None 

281 for fermi_index in indexes[_PW_FERMI]: 

282 if image_index < fermi_index < next_index: 

283 efermi = float(pwo_lines[fermi_index].split()[-2]) 

284 

285 if efermi is None: 

286 for ho_index in indexes[_PW_HIGHEST_OCCUPIED]: 

287 if image_index < ho_index < next_index: 

288 efermi = float(pwo_lines[ho_index].split()[-1]) 

289 

290 if efermi is None: 

291 for holf_index in indexes[_PW_HIGHEST_OCCUPIED_LOWEST_FREE]: 

292 if image_index < holf_index < next_index: 

293 efermi = float(pwo_lines[holf_index].split()[-2]) 

294 

295 # K-points 

296 ibzkpts = None 

297 weights = None 

298 kpoints_warning = "Number of k-points >= 100: " + \ 

299 "set verbosity='high' to print them." 

300 

301 for kpts_index in indexes[_PW_KPTS]: 

302 nkpts = int(re.findall(r'\b\d+\b', pwo_lines[kpts_index])[0]) 

303 kpts_index += 2 

304 

305 if pwo_lines[kpts_index].strip() == kpoints_warning: 

306 continue 

307 

308 # QE prints the k-points in units of 2*pi/alat 

309 # with alat defined as the length of the first 

310 # cell vector 

311 cell = structure.get_cell() 

312 alat = np.linalg.norm(cell[0]) 

313 ibzkpts = [] 

314 weights = [] 

315 for i in range(nkpts): 

316 L = pwo_lines[kpts_index + i].split() 

317 weights.append(float(L[-1])) 

318 coord = np.array([L[-6], L[-5], L[-4].strip('),')], 

319 dtype=float) 

320 coord *= 2 * np.pi / alat 

321 coord = kpoint_convert(cell, ckpts_kv=coord) 

322 ibzkpts.append(coord) 

323 ibzkpts = np.array(ibzkpts) 

324 weights = np.array(weights) 

325 

326 # Bands 

327 kpts = None 

328 kpoints_warning = "Number of k-points >= 100: " + \ 

329 "set verbosity='high' to print the bands." 

330 

331 for bands_index in indexes[_PW_BANDS] + indexes[_PW_BANDSTRUCTURE]: 

332 if image_index < bands_index < next_index: 

333 bands_index += 1 

334 # skip over the lines with DFT+U occupation matrices 

335 if 'enter write_ns' in pwo_lines[bands_index]: 

336 while 'exit write_ns' not in pwo_lines[bands_index]: 

337 bands_index += 1 

338 bands_index += 1 

339 

340 if pwo_lines[bands_index].strip() == kpoints_warning: 

341 continue 

342 

343 assert ibzkpts is not None 

344 spin, bands, eigenvalues = 0, [], [[], []] 

345 

346 while True: 

347 L = pwo_lines[bands_index].replace('-', ' -').split() 

348 if len(L) == 0: 

349 if len(bands) > 0: 

350 eigenvalues[spin].append(bands) 

351 bands = [] 

352 elif L == ['occupation', 'numbers']: 

353 # Skip the lines with the occupation numbers 

354 bands_index += len(eigenvalues[spin][0]) // 8 + 1 

355 elif L[0] == 'k' and L[1].startswith('='): 

356 pass 

357 elif 'SPIN' in L: 

358 if 'DOWN' in L: 

359 spin += 1 

360 else: 

361 try: 

362 bands.extend(map(float, L)) 

363 except ValueError: 

364 break 

365 bands_index += 1 

366 

367 if spin == 1: 

368 assert len(eigenvalues[0]) == len(eigenvalues[1]) 

369 assert len(eigenvalues[0]) == len(ibzkpts), \ 

370 (np.shape(eigenvalues), len(ibzkpts)) 

371 

372 kpts = [] 

373 for s in range(spin + 1): 

374 for w, k, e in zip(weights, ibzkpts, eigenvalues[s]): 

375 kpt = SinglePointKPoint(w, s, k, eps_n=e) 

376 kpts.append(kpt) 

377 

378 # Put everything together 

379 # 

380 # In PW the forces are consistent with the "total energy"; that's why 

381 # its value must be assigned to free_energy. 

382 # PW doesn't compute the extrapolation of the energy to 0K smearing 

383 # the closer thing to this is again the total energy that contains 

384 # the correct (i.e. variational) form of the band energy is 

385 # Eband = \int e N(e) de for e<Ef , where N(e) is the DOS 

386 # This differs by the term (-TS) from the sum of KS eigenvalues: 

387 # Eks = \sum wg(n,k) et(n,k) 

388 # which is non variational. When a Fermi-Dirac function is used 

389 # for a given T, the variational energy is REALLY the free energy F, 

390 # and F = E - TS , with E = non variational energy. 

391 # 

392 calc = SinglePointDFTCalculator(structure, energy=energy, 

393 free_energy=energy, 

394 forces=forces, stress=stress, 

395 magmoms=magmoms, efermi=efermi, 

396 ibzkpts=ibzkpts, dipole=dipole) 

397 calc.kpts = kpts 

398 structure.calc = calc 

399 

400 yield structure 

401 

402 

403def parse_pwo_start(lines, index=0): 

404 """Parse Quantum ESPRESSO calculation info from lines, 

405 starting from index. Return a dictionary containing extracted 

406 information. 

407 

408 - `celldm(1)`: lattice parameters (alat) 

409 - `cell`: unit cell in Angstrom 

410 - `symbols`: element symbols for the structure 

411 - `positions`: cartesian coordinates of atoms in Angstrom 

412 - `atoms`: an `ase.Atoms` object constructed from the extracted data 

413 

414 Parameters 

415 ---------- 

416 lines : list[str] 

417 Contents of PWSCF output file. 

418 index : int 

419 Line number to begin parsing. Only first calculation will 

420 be read. 

421 

422 Returns 

423 ------- 

424 info : dict 

425 Dictionary of calculation parameters, including `celldm(1)`, `cell`, 

426 `symbols`, `positions`, `atoms`. 

427 

428 Raises 

429 ------ 

430 KeyError 

431 If interdependent values cannot be found (especially celldm(1)) 

432 an error will be raised as other quantities cannot then be 

433 calculated (e.g. cell and positions). 

434 """ 

435 # TODO: extend with extra DFT info? 

436 

437 info = {} 

438 

439 for idx, line in enumerate(lines[index:], start=index): 

440 if 'celldm(1)' in line: 

441 # celldm(1) has more digits than alat!! 

442 info['celldm(1)'] = float(line.split()[1]) * units['Bohr'] 

443 info['alat'] = info['celldm(1)'] 

444 elif 'number of atoms/cell' in line: 

445 info['nat'] = int(line.split()[-1]) 

446 elif 'number of atomic types' in line: 

447 info['ntyp'] = int(line.split()[-1]) 

448 elif 'crystal axes:' in line: 

449 info['cell'] = info['celldm(1)'] * np.array([ 

450 [float(x) for x in lines[idx + 1].split()[3:6]], 

451 [float(x) for x in lines[idx + 2].split()[3:6]], 

452 [float(x) for x in lines[idx + 3].split()[3:6]]]) 

453 elif 'positions (alat units)' in line: 

454 info['symbols'], info['positions'] = [], [] 

455 

456 for at_line in lines[idx + 1:idx + 1 + info['nat']]: 

457 sym, x, y, z = parse_position_line(at_line) 

458 info['symbols'].append(label_to_symbol(sym)) 

459 info['positions'].append([x * info['celldm(1)'], 

460 y * info['celldm(1)'], 

461 z * info['celldm(1)']]) 

462 # This should be the end of interesting info. 

463 # Break here to avoid dealing with large lists of kpoints. 

464 # Will need to be extended for DFTCalculator info. 

465 break 

466 

467 # Make atoms for convenience 

468 info['atoms'] = Atoms(symbols=info['symbols'], 

469 positions=info['positions'], 

470 cell=info['cell'], pbc=True) 

471 

472 return info 

473 

474 

475def parse_position_line(line): 

476 """Parse a single line from a pw.x output file. 

477 

478 The line must contain information about the atomic symbol and the position, 

479 e.g. 

480 

481 995 Sb tau( 995) = ( 1.4212023 0.7037863 0.1242640 ) 

482 

483 Parameters 

484 ---------- 

485 line : str 

486 Line to be parsed. 

487 

488 Returns 

489 ------- 

490 sym : str 

491 Atomic symbol. 

492 x : float 

493 x-position. 

494 y : float 

495 y-position. 

496 z : float 

497 z-position. 

498 """ 

499 pat = re.compile(r'\s*\d+\s*(\S+)\s*tau\(\s*\d+\)\s*=' 

500 r'\s*\(\s*(\S+)\s+(\S+)\s+(\S+)\s*\)') 

501 match = pat.match(line) 

502 assert match is not None 

503 sym, x, y, z = match.group(1, 2, 3, 4) 

504 return sym, float(x), float(y), float(z) 

505 

506 

507@reader 

508def read_espresso_in(fileobj): 

509 """Parse a Quantum ESPRESSO input files, '.in', '.pwi'. 

510 

511 ESPRESSO inputs are generally a fortran-namelist format with custom 

512 blocks of data. The namelist is parsed as a dict and an atoms object 

513 is constructed from the included information. 

514 

515 Parameters 

516 ---------- 

517 fileobj : file | str 

518 A file-like object that supports line iteration with the contents 

519 of the input file, or a filename. 

520 

521 Returns 

522 ------- 

523 atoms : Atoms 

524 Structure defined in the input file. 

525 

526 Raises 

527 ------ 

528 KeyError 

529 Raised for missing keys that are required to process the file 

530 """ 

531 # parse namelist section and extract remaining lines 

532 data, card_lines = read_fortran_namelist(fileobj) 

533 

534 # get the cell if ibrav=0 

535 if 'system' not in data: 

536 raise KeyError('Required section &SYSTEM not found.') 

537 elif 'ibrav' not in data['system']: 

538 raise KeyError('ibrav is required in &SYSTEM') 

539 elif data['system']['ibrav'] == 0: 

540 # celldm(1) is in Bohr, A is in angstrom. celldm(1) will be 

541 # used even if A is also specified. 

542 if 'celldm(1)' in data['system']: 

543 alat = data['system']['celldm(1)'] * units['Bohr'] 

544 elif 'A' in data['system']: 

545 alat = data['system']['A'] 

546 else: 

547 alat = None 

548 cell, _ = get_cell_parameters(card_lines, alat=alat) 

549 else: 

550 raise ValueError(ibrav_error_message) 

551 

552 # species_info holds some info for each element 

553 species_card = get_atomic_species( 

554 card_lines, n_species=data['system']['ntyp']) 

555 species_info = {} 

556 for ispec, (label, weight, pseudo) in enumerate(species_card): 

557 symbol = label_to_symbol(label) 

558 

559 # starting_magnetization is in fractions of valence electrons 

560 magnet_key = f"starting_magnetization({ispec + 1})" 

561 magmom = data["system"].get(magnet_key, 0.0) 

562 species_info[symbol] = {"weight": weight, "pseudo": pseudo, 

563 "magmom": magmom} 

564 

565 positions_card = get_atomic_positions( 

566 card_lines, n_atoms=data['system']['nat'], cell=cell, alat=alat) 

567 

568 symbols = [label_to_symbol(position[0]) for position in positions_card] 

569 positions = [position[1] for position in positions_card] 

570 constraint_flags = [position[2] for position in positions_card] 

571 magmoms = [species_info[symbol]["magmom"] for symbol in symbols] 

572 

573 # TODO: put more info into the atoms object 

574 # e.g magmom, forces. 

575 atoms = Atoms(symbols=symbols, positions=positions, cell=cell, pbc=True, 

576 magmoms=magmoms) 

577 atoms.set_constraint(convert_constraint_flags(constraint_flags)) 

578 

579 return atoms 

580 

581 

582def get_atomic_positions(lines, n_atoms, cell=None, alat=None): 

583 """Parse atom positions from ATOMIC_POSITIONS card. 

584 

585 Parameters 

586 ---------- 

587 lines : list[str] 

588 A list of lines containing the ATOMIC_POSITIONS card. 

589 n_atoms : int 

590 Expected number of atoms. Only this many lines will be parsed. 

591 cell : np.array 

592 Unit cell of the crystal. Only used with crystal coordinates. 

593 alat : float 

594 Lattice parameter for atomic coordinates. Only used for alat case. 

595 

596 Returns 

597 ------- 

598 positions : list[(str, (float, float, float), (int, int, int))] 

599 A list of the ordered atomic positions in the format: 

600 label, (x, y, z), (if_x, if_y, if_z) 

601 Force multipliers are set to None if not present. 

602 

603 Raises 

604 ------ 

605 ValueError 

606 Any problems parsing the data result in ValueError 

607 

608 """ 

609 

610 positions = None 

611 # no blanks or comment lines, can the consume n_atoms lines for positions 

612 trimmed_lines = (line for line in lines if line.strip() and line[0] != '#') 

613 

614 for line in trimmed_lines: 

615 if line.strip().startswith('ATOMIC_POSITIONS'): 

616 if positions is not None: 

617 raise ValueError('Multiple ATOMIC_POSITIONS specified') 

618 # Priority and behaviour tested with QE 5.3 

619 if 'crystal_sg' in line.lower(): 

620 raise NotImplementedError('CRYSTAL_SG not implemented') 

621 elif 'crystal' in line.lower(): 

622 cell = cell 

623 elif 'bohr' in line.lower(): 

624 cell = np.identity(3) * units['Bohr'] 

625 elif 'angstrom' in line.lower(): 

626 cell = np.identity(3) 

627 # elif 'alat' in line.lower(): 

628 # cell = np.identity(3) * alat 

629 else: 

630 if alat is None: 

631 raise ValueError('Set lattice parameter in &SYSTEM for ' 

632 'alat coordinates') 

633 # Always the default, will be DEPRECATED as mandatory 

634 # in future 

635 cell = np.identity(3) * alat 

636 

637 positions = [] 

638 for _ in range(n_atoms): 

639 split_line = next(trimmed_lines).split() 

640 # These can be fractions and other expressions 

641 position = np.dot((infix_float(split_line[1]), 

642 infix_float(split_line[2]), 

643 infix_float(split_line[3])), cell) 

644 if len(split_line) > 4: 

645 force_mult = tuple(int(split_line[i]) for i in (4, 5, 6)) 

646 else: 

647 force_mult = None 

648 

649 positions.append((split_line[0], position, force_mult)) 

650 

651 return positions 

652 

653 

654def get_atomic_species(lines, n_species): 

655 """Parse atomic species from ATOMIC_SPECIES card. 

656 

657 Parameters 

658 ---------- 

659 lines : list[str] 

660 A list of lines containing the ATOMIC_POSITIONS card. 

661 n_species : int 

662 Expected number of atom types. Only this many lines will be parsed. 

663 

664 Returns 

665 ------- 

666 species : list[(str, float, str)] 

667 

668 Raises 

669 ------ 

670 ValueError 

671 Any problems parsing the data result in ValueError 

672 """ 

673 

674 species = None 

675 # no blanks or comment lines, can the consume n_atoms lines for positions 

676 trimmed_lines = (line.strip() for line in lines 

677 if line.strip() and not line.startswith('#')) 

678 

679 for line in trimmed_lines: 

680 if line.startswith('ATOMIC_SPECIES'): 

681 if species is not None: 

682 raise ValueError('Multiple ATOMIC_SPECIES specified') 

683 

684 species = [] 

685 for _dummy in range(n_species): 

686 label_weight_pseudo = next(trimmed_lines).split() 

687 species.append((label_weight_pseudo[0], 

688 float(label_weight_pseudo[1]), 

689 label_weight_pseudo[2])) 

690 

691 return species 

692 

693 

694def get_cell_parameters(lines, alat=None): 

695 """Parse unit cell from CELL_PARAMETERS card. 

696 

697 Parameters 

698 ---------- 

699 lines : list[str] 

700 A list with lines containing the CELL_PARAMETERS card. 

701 alat : float | None 

702 Unit of lattice vectors in Angstrom. Only used if the card is 

703 given in units of alat. alat must be None if CELL_PARAMETERS card 

704 is in Bohr or Angstrom. For output files, alat will be parsed from 

705 the card header and used in preference to this value. 

706 

707 Returns 

708 ------- 

709 cell : np.array | None 

710 Cell parameters as a 3x3 array in Angstrom. If no cell is found 

711 None will be returned instead. 

712 cell_alat : float | None 

713 If a value for alat is given in the card header, this is also 

714 returned, otherwise this will be None. 

715 

716 Raises 

717 ------ 

718 ValueError 

719 If CELL_PARAMETERS are given in units of bohr or angstrom 

720 and alat is not 

721 """ 

722 

723 cell = None 

724 cell_alat = None 

725 # no blanks or comment lines, can take three lines for cell 

726 trimmed_lines = (line for line in lines if line.strip() and line[0] != '#') 

727 

728 for line in trimmed_lines: 

729 if line.strip().startswith('CELL_PARAMETERS'): 

730 if cell is not None: 

731 # multiple definitions 

732 raise ValueError('CELL_PARAMETERS specified multiple times') 

733 # Priority and behaviour tested with QE 5.3 

734 if 'bohr' in line.lower(): 

735 if alat is not None: 

736 raise ValueError('Lattice parameters given in ' 

737 '&SYSTEM celldm/A and CELL_PARAMETERS ' 

738 'bohr') 

739 cell_units = units['Bohr'] 

740 elif 'angstrom' in line.lower(): 

741 if alat is not None: 

742 raise ValueError('Lattice parameters given in ' 

743 '&SYSTEM celldm/A and CELL_PARAMETERS ' 

744 'angstrom') 

745 cell_units = 1.0 

746 elif 'alat' in line.lower(): 

747 # Output file has (alat = value) (in Bohrs) 

748 if '=' in line: 

749 alat = float(line.strip(') \n').split()[-1]) * units['Bohr'] 

750 cell_alat = alat 

751 elif alat is None: 

752 raise ValueError('Lattice parameters must be set in ' 

753 '&SYSTEM for alat units') 

754 cell_units = alat 

755 elif alat is None: 

756 # may be DEPRECATED in future 

757 cell_units = units['Bohr'] 

758 else: 

759 # may be DEPRECATED in future 

760 cell_units = alat 

761 # Grab the parameters; blank lines have been removed 

762 cell = [[ffloat(x) for x in next(trimmed_lines).split()[:3]], 

763 [ffloat(x) for x in next(trimmed_lines).split()[:3]], 

764 [ffloat(x) for x in next(trimmed_lines).split()[:3]]] 

765 cell = np.array(cell) * cell_units 

766 

767 return cell, cell_alat 

768 

769 

770def convert_constraint_flags(constraint_flags): 

771 """Convert Quantum ESPRESSO constraint flags to ASE Constraint objects. 

772 

773 Parameters 

774 ---------- 

775 constraint_flags : list[tuple[int, int, int]] 

776 List of constraint flags (0: fixed, 1: moved) for all the atoms. 

777 If the flag is None, there are no constraints on the atom. 

778 

779 Returns 

780 ------- 

781 constraints : list[FixAtoms | FixCartesian] 

782 List of ASE Constraint objects. 

783 """ 

784 constraints = [] 

785 for i, constraint in enumerate(constraint_flags): 

786 if constraint is None: 

787 continue 

788 # mask: False (0): moved, True (1): fixed 

789 mask = ~np.asarray(constraint, bool) 

790 constraints.append(FixCartesian(i, mask)) 

791 return canonicalize_constraints(constraints) 

792 

793 

794def canonicalize_constraints(constraints): 

795 """Canonicalize ASE FixCartesian constraints. 

796 

797 If the given FixCartesian constraints share the same `mask`, they can be 

798 merged into one. Further, if `mask == (True, True, True)`, they can be 

799 converted as `FixAtoms`. This method "canonicalizes" FixCartesian objects 

800 in such a way. 

801 

802 Parameters 

803 ---------- 

804 constraints : List[FixCartesian] 

805 List of ASE FixCartesian constraints. 

806 

807 Returns 

808 ------- 

809 constrants_canonicalized : List[FixAtoms | FixCartesian] 

810 List of ASE Constraint objects. 

811 """ 

812 # https://docs.python.org/3/library/collections.html#defaultdict-examples 

813 indices_for_masks = defaultdict(list) 

814 for constraint in constraints: 

815 key = tuple((constraint.mask).tolist()) 

816 indices_for_masks[key].extend(constraint.index.tolist()) 

817 

818 constraints_canonicalized = [] 

819 for mask, indices in indices_for_masks.items(): 

820 if mask == (False, False, False): # no directions are fixed 

821 continue 

822 if mask == (True, True, True): # all three directions are fixed 

823 constraints_canonicalized.append(FixAtoms(indices)) 

824 else: 

825 constraints_canonicalized.append(FixCartesian(indices, mask)) 

826 

827 return constraints_canonicalized 

828 

829 

830def str_to_value(string): 

831 """Attempt to convert string into int, float (including fortran double), 

832 or bool, in that order, otherwise return the string. 

833 Valid (case-insensitive) bool values are: '.true.', '.t.', 'true' 

834 and 't' (or false equivalents). 

835 

836 Parameters 

837 ---------- 

838 string : str 

839 Test to parse for a datatype 

840 

841 Returns 

842 ------- 

843 value : any 

844 Parsed string as the most appropriate datatype of int, float, 

845 bool or string. 

846 

847 """ 

848 

849 # Just an integer 

850 try: 

851 return int(string) 

852 except ValueError: 

853 pass 

854 # Standard float 

855 try: 

856 return float(string) 

857 except ValueError: 

858 pass 

859 # Fortran double 

860 try: 

861 return ffloat(string) 

862 except ValueError: 

863 pass 

864 

865 # possible bool, else just the raw string 

866 if string.lower() in ('.true.', '.t.', 'true', 't'): 

867 return True 

868 elif string.lower() in ('.false.', '.f.', 'false', 'f'): 

869 return False 

870 else: 

871 return string.strip("'") 

872 

873 

874def read_fortran_namelist(fileobj): 

875 """Takes a fortran-namelist formatted file and returns nested 

876 dictionaries of sections and key-value data, followed by a list 

877 of lines of text that do not fit the specifications. 

878 

879 Behaviour is taken from Quantum ESPRESSO 5.3. Parses fairly 

880 convoluted files the same way that QE should, but may not get 

881 all the MANDATORY rules and edge cases for very non-standard files: 

882 Ignores anything after '!' in a namelist, split pairs on ',' 

883 to include multiple key=values on a line, read values on section 

884 start and end lines, section terminating character, '/', can appear 

885 anywhere on a line. 

886 All of these are ignored if the value is in 'quotes'. 

887 

888 Parameters 

889 ---------- 

890 fileobj : file 

891 An open file-like object. 

892 

893 Returns 

894 ------- 

895 data : dict of dict 

896 Dictionary for each section in the namelist with key = value 

897 pairs of data. 

898 card_lines : list of str 

899 Any lines not used to create the data, assumed to belong to 'cards' 

900 in the input file. 

901 

902 """ 

903 

904 data = {} 

905 card_lines = [] 

906 in_namelist = False 

907 section = 'none' # can't be in a section without changing this 

908 

909 for line in fileobj: 

910 # leading and trailing whitespace never needed 

911 line = line.strip() 

912 if line.startswith('&'): 

913 # inside a namelist 

914 section = line.split()[0][1:].lower() # case insensitive 

915 if section in data: 

916 # Repeated sections are completely ignored. 

917 # (Note that repeated keys overwrite within a section) 

918 section = "_ignored" 

919 data[section] = {} 

920 in_namelist = True 

921 if not in_namelist and line: 

922 # Stripped line is Truthy, so safe to index first character 

923 if line[0] not in ('!', '#'): 

924 card_lines.append(line) 

925 if in_namelist: 

926 # parse k, v from line: 

927 key = [] 

928 value = None 

929 in_quotes = False 

930 for character in line: 

931 if character == ',' and value is not None and not in_quotes: 

932 # finished value: 

933 data[section][''.join(key).strip()] = str_to_value( 

934 ''.join(value).strip()) 

935 key = [] 

936 value = None 

937 elif character == '=' and value is None and not in_quotes: 

938 # start writing value 

939 value = [] 

940 elif character == "'": 

941 # only found in value anyway 

942 in_quotes = not in_quotes 

943 value.append("'") 

944 elif character == '!' and not in_quotes: 

945 break 

946 elif character == '/' and not in_quotes: 

947 in_namelist = False 

948 break 

949 elif value is not None: 

950 value.append(character) 

951 else: 

952 key.append(character) 

953 if value is not None: 

954 data[section][''.join(key).strip()] = str_to_value( 

955 ''.join(value).strip()) 

956 

957 return Namelist(data), card_lines 

958 

959 

960def ffloat(string): 

961 """Parse float from fortran compatible float definitions. 

962 

963 In fortran exponents can be defined with 'd' or 'q' to symbolise 

964 double or quad precision numbers. Double precision numbers are 

965 converted to python floats and quad precision values are interpreted 

966 as numpy longdouble values (platform specific precision). 

967 

968 Parameters 

969 ---------- 

970 string : str 

971 A string containing a number in fortran real format 

972 

973 Returns 

974 ------- 

975 value : float | np.longdouble 

976 Parsed value of the string. 

977 

978 Raises 

979 ------ 

980 ValueError 

981 Unable to parse a float value. 

982 

983 """ 

984 

985 if 'q' in string.lower(): 

986 return np.longdouble(string.lower().replace('q', 'e')) 

987 else: 

988 return float(string.lower().replace('d', 'e')) 

989 

990 

991def label_to_symbol(label): 

992 """Convert a valid espresso ATOMIC_SPECIES label to a 

993 chemical symbol. 

994 

995 Parameters 

996 ---------- 

997 label : str 

998 chemical symbol X (1 or 2 characters, case-insensitive) 

999 or chemical symbol plus a number or a letter, as in 

1000 "Xn" (e.g. Fe1) or "X_*" or "X-*" (e.g. C1, C_h; 

1001 max total length cannot exceed 3 characters). 

1002 

1003 Returns 

1004 ------- 

1005 symbol : str 

1006 The best matching species from ase.utils.chemical_symbols 

1007 

1008 Raises 

1009 ------ 

1010 KeyError 

1011 Couldn't find an appropriate species. 

1012 

1013 Notes 

1014 ----- 

1015 It's impossible to tell whether e.g. He is helium 

1016 or hydrogen labelled 'e'. 

1017 """ 

1018 

1019 # possibly a two character species 

1020 # ase Atoms need proper case of chemical symbols. 

1021 if len(label) >= 2: 

1022 test_symbol = label[0].upper() + label[1].lower() 

1023 if test_symbol in chemical_symbols: 

1024 return test_symbol 

1025 # finally try with one character 

1026 test_symbol = label[0].upper() 

1027 if test_symbol in chemical_symbols: 

1028 return test_symbol 

1029 else: 

1030 raise KeyError('Could not parse species from label {}.' 

1031 ''.format(label)) 

1032 

1033 

1034def infix_float(text): 

1035 """Parse simple infix maths into a float for compatibility with 

1036 Quantum ESPRESSO ATOMIC_POSITIONS cards. Note: this works with the 

1037 example, and most simple expressions, but the capabilities of 

1038 the two parsers are not identical. Will also parse a normal float 

1039 value properly, but slowly. 

1040 

1041 >>> infix_float('1/2*3^(-1/2)') 

1042 0.28867513459481287 

1043 

1044 Parameters 

1045 ---------- 

1046 text : str 

1047 An arithmetic expression using +, -, *, / and ^, including brackets. 

1048 

1049 Returns 

1050 ------- 

1051 value : float 

1052 Result of the mathematical expression. 

1053 

1054 """ 

1055 

1056 def middle_brackets(full_text): 

1057 """Extract text from innermost brackets.""" 

1058 start, end = 0, len(full_text) 

1059 for (idx, char) in enumerate(full_text): 

1060 if char == '(': 

1061 start = idx 

1062 if char == ')': 

1063 end = idx + 1 

1064 break 

1065 return full_text[start:end] 

1066 

1067 def eval_no_bracket_expr(full_text): 

1068 """Calculate value of a mathematical expression, no brackets.""" 

1069 exprs = [('+', op.add), ('*', op.mul), 

1070 ('/', op.truediv), ('^', op.pow)] 

1071 full_text = full_text.lstrip('(').rstrip(')') 

1072 try: 

1073 return float(full_text) 

1074 except ValueError: 

1075 for symbol, func in exprs: 

1076 if symbol in full_text: 

1077 left, right = full_text.split(symbol, 1) # single split 

1078 return func(eval_no_bracket_expr(left), 

1079 eval_no_bracket_expr(right)) 

1080 

1081 while '(' in text: 

1082 middle = middle_brackets(text) 

1083 text = text.replace(middle, f'{eval_no_bracket_expr(middle)}') 

1084 

1085 return float(eval_no_bracket_expr(text)) 

1086 

1087 

1088# Number of valence electrons in the pseudopotentials recommended by 

1089# http://materialscloud.org/sssp/. These are just used as a fallback for 

1090# calculating initial magetization values which are given as a fraction 

1091# of valence electrons. 

1092SSSP_VALENCE = [ 

1093 0, 1.0, 2.0, 3.0, 4.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 3.0, 4.0, 

1094 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 

1095 18.0, 19.0, 20.0, 13.0, 14.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 

1096 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0, 12.0, 13.0, 14.0, 15.0, 6.0, 

1097 7.0, 18.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 

1098 19.0, 20.0, 21.0, 22.0, 23.0, 24.0, 25.0, 36.0, 27.0, 14.0, 15.0, 30.0, 

1099 15.0, 32.0, 19.0, 12.0, 13.0, 14.0, 15.0, 16.0, 18.0] 

1100 

1101 

1102def kspacing_to_grid(atoms, spacing, calculated_spacing=None): 

1103 """ 

1104 Calculate the kpoint mesh that is equivalent to the given spacing 

1105 in reciprocal space (units Angstrom^-1). The number of kpoints is each 

1106 dimension is rounded up (compatible with CASTEP). 

1107 

1108 Parameters 

1109 ---------- 

1110 atoms: ase.Atoms 

1111 A structure that can have get_reciprocal_cell called on it. 

1112 spacing: float 

1113 Minimum K-Point spacing in $A^{-1}$. 

1114 calculated_spacing : list 

1115 If a three item list (or similar mutable sequence) is given the 

1116 members will be replaced with the actual calculated spacing in 

1117 $A^{-1}$. 

1118 

1119 Returns 

1120 ------- 

1121 kpoint_grid : [int, int, int] 

1122 MP grid specification to give the required spacing. 

1123 

1124 """ 

1125 # No factor of 2pi in ase, everything in A^-1 

1126 # reciprocal dimensions 

1127 r_x, r_y, r_z = np.linalg.norm(atoms.cell.reciprocal(), axis=1) 

1128 

1129 kpoint_grid = [int(r_x / spacing) + 1, 

1130 int(r_y / spacing) + 1, 

1131 int(r_z / spacing) + 1] 

1132 

1133 for i, _ in enumerate(kpoint_grid): 

1134 if not atoms.pbc[i]: 

1135 kpoint_grid[i] = 1 

1136 

1137 if calculated_spacing is not None: 

1138 calculated_spacing[:] = [r_x / kpoint_grid[0], 

1139 r_y / kpoint_grid[1], 

1140 r_z / kpoint_grid[2]] 

1141 

1142 return kpoint_grid 

1143 

1144 

1145def format_atom_position(atom, crystal_coordinates, mask='', tidx=None): 

1146 """Format one line of atomic positions in 

1147 Quantum ESPRESSO ATOMIC_POSITIONS card. 

1148 

1149 >>> for atom in make_supercell(bulk('Li', 'bcc'), np.ones(3)-np.eye(3)): 

1150 >>> format_atom_position(atom, True) 

1151 Li 0.0000000000 0.0000000000 0.0000000000 

1152 Li 0.5000000000 0.5000000000 0.5000000000 

1153 

1154 Parameters 

1155 ---------- 

1156 atom : Atom 

1157 A structure that has symbol and [position | (a, b, c)]. 

1158 crystal_coordinates: bool 

1159 Whether the atomic positions should be written to the QE input file in 

1160 absolute (False, default) or relative (crystal) coordinates (True). 

1161 mask, optional : str 

1162 String of ndim=3 0 or 1 for constraining atomic positions. 

1163 tidx, optional : int 

1164 Magnetic type index. 

1165 

1166 Returns 

1167 ------- 

1168 atom_line : str 

1169 Input line for atom position 

1170 """ 

1171 if crystal_coordinates: 

1172 coords = [atom.a, atom.b, atom.c] 

1173 else: 

1174 coords = atom.position 

1175 line_fmt = '{atom.symbol}' 

1176 inps = dict(atom=atom) 

1177 if tidx is not None: 

1178 line_fmt += '{tidx}' 

1179 inps["tidx"] = tidx 

1180 line_fmt += ' {coords[0]:.10f} {coords[1]:.10f} {coords[2]:.10f} ' 

1181 inps["coords"] = coords 

1182 line_fmt += ' ' + mask + '\n' 

1183 astr = line_fmt.format(**inps) 

1184 return astr 

1185 

1186 

1187@writer 

1188def write_espresso_in(fd, atoms, input_data=None, pseudopotentials=None, 

1189 kspacing=None, kpts=None, koffset=(0, 0, 0), 

1190 crystal_coordinates=False, additional_cards=None, 

1191 **kwargs): 

1192 """ 

1193 Create an input file for pw.x. 

1194 

1195 Use set_initial_magnetic_moments to turn on spin, if ispin is set to 2 

1196 with no magnetic moments, they will all be set to 0.0. Magnetic moments 

1197 will be converted to the QE units (fraction of valence electrons) using 

1198 any pseudopotential files found, or a best guess for the number of 

1199 valence electrons. 

1200 

1201 Units are not converted for any other input data, so use Quantum ESPRESSO 

1202 units (Usually Ry or atomic units). 

1203 

1204 Keys with a dimension (e.g. Hubbard_U(1)) will be incorporated as-is 

1205 so the `i` should be made to match the output. 

1206 

1207 Implemented features: 

1208 

1209 - Conversion of :class:`ase.constraints.FixAtoms` and 

1210 :class:`ase.constraints.FixCartesian`. 

1211 - `starting_magnetization` derived from the `mgmoms` and pseudopotentials 

1212 (searches default paths for pseudo files.) 

1213 - Automatic assignment of options to their correct sections. 

1214 

1215 Not implemented: 

1216 

1217 - Non-zero values of ibrav 

1218 - Lists of k-points 

1219 - Other constraints 

1220 - Hubbard parameters 

1221 - Validation of the argument types for input 

1222 - Validation of required options 

1223 

1224 Parameters 

1225 ---------- 

1226 fd: file | str 

1227 A file to which the input is written. 

1228 atoms: Atoms 

1229 A single atomistic configuration to write to `fd`. 

1230 input_data: dict 

1231 A flat or nested dictionary with input parameters for pw.x 

1232 pseudopotentials: dict 

1233 A filename for each atomic species, e.g. 

1234 {'O': 'O.pbe-rrkjus.UPF', 'H': 'H.pbe-rrkjus.UPF'}. 

1235 A dummy name will be used if none are given. 

1236 kspacing: float 

1237 Generate a grid of k-points with this as the minimum distance, 

1238 in A^-1 between them in reciprocal space. If set to None, kpts 

1239 will be used instead. 

1240 kpts: (int, int, int) or dict 

1241 If kpts is a tuple (or list) of 3 integers, it is interpreted 

1242 as the dimensions of a Monkhorst-Pack grid. 

1243 If ``kpts`` is set to ``None``, only the Γ-point will be included 

1244 and QE will use routines optimized for Γ-point-only calculations. 

1245 Compared to Γ-point-only calculations without this optimization 

1246 (i.e. with ``kpts=(1, 1, 1)``), the memory and CPU requirements 

1247 are typically reduced by half. 

1248 If kpts is a dict, it will either be interpreted as a path 

1249 in the Brillouin zone (*) if it contains the 'path' keyword, 

1250 otherwise it is converted to a Monkhorst-Pack grid (**). 

1251 (*) see ase.dft.kpoints.bandpath 

1252 (**) see ase.calculators.calculator.kpts2sizeandoffsets 

1253 koffset: (int, int, int) 

1254 Offset of kpoints in each direction. Must be 0 (no offset) or 

1255 1 (half grid offset). Setting to True is equivalent to (1, 1, 1). 

1256 crystal_coordinates: bool 

1257 Whether the atomic positions should be written to the QE input file in 

1258 absolute (False, default) or relative (crystal) coordinates (True). 

1259 

1260 """ 

1261 

1262 # Convert to a namelist to make working with parameters much easier 

1263 # Note that the name ``input_data`` is chosen to prevent clash with 

1264 # ``parameters`` in Calculator objects 

1265 input_parameters = Namelist(input_data) 

1266 input_parameters.to_nested('pw', **kwargs) 

1267 

1268 # Convert ase constraints to QE constraints 

1269 # Nx3 array of force multipliers matches what QE uses 

1270 # Do this early so it is available when constructing the atoms card 

1271 moved = np.ones((len(atoms), 3), dtype=bool) 

1272 for constraint in atoms.constraints: 

1273 if isinstance(constraint, FixAtoms): 

1274 moved[constraint.index] = False 

1275 elif isinstance(constraint, FixCartesian): 

1276 moved[constraint.index] = ~constraint.mask 

1277 else: 

1278 warnings.warn(f'Ignored unknown constraint {constraint}') 

1279 masks = [] 

1280 for atom in atoms: 

1281 # only inclued mask if something is fixed 

1282 if not all(moved[atom.index]): 

1283 mask = ' {:d} {:d} {:d}'.format(*moved[atom.index]) 

1284 else: 

1285 mask = '' 

1286 masks.append(mask) 

1287 

1288 # Species info holds the information on the pseudopotential and 

1289 # associated for each element 

1290 if pseudopotentials is None: 

1291 pseudopotentials = {} 

1292 species_info = {} 

1293 for species in set(atoms.get_chemical_symbols()): 

1294 # Look in all possible locations for the pseudos and try to figure 

1295 # out the number of valence electrons 

1296 pseudo = pseudopotentials[species] 

1297 species_info[species] = {'pseudo': pseudo} 

1298 

1299 # Convert atoms into species. 

1300 # Each different magnetic moment needs to be a separate type even with 

1301 # the same pseudopotential (e.g. an up and a down for AFM). 

1302 # if any magmom are > 0 or nspin == 2 then use species labels. 

1303 # Rememeber: magnetisation uses 1 based indexes 

1304 atomic_species = {} 

1305 atomic_species_str = [] 

1306 atomic_positions_str = [] 

1307 

1308 nspin = input_parameters['system'].get('nspin', 1) # 1 is the default 

1309 noncolin = input_parameters['system'].get('noncolin', False) 

1310 rescale_magmom_fac = kwargs.get('rescale_magmom_fac', 1.0) 

1311 if any(atoms.get_initial_magnetic_moments()): 

1312 if nspin == 1 and not noncolin: 

1313 # Force spin on 

1314 input_parameters['system']['nspin'] = 2 

1315 nspin = 2 

1316 

1317 if nspin == 2 or noncolin: 

1318 # Magnetic calculation on 

1319 for atom, mask, magmom in zip( 

1320 atoms, masks, atoms.get_initial_magnetic_moments()): 

1321 if (atom.symbol, magmom) not in atomic_species: 

1322 # for qe version 7.2 or older magmon must be rescale by 

1323 # about a factor 10 to assume sensible values 

1324 # since qe-v7.3 magmom values will be provided unscaled 

1325 fspin = float(magmom) / rescale_magmom_fac 

1326 # Index in the atomic species list 

1327 sidx = len(atomic_species) + 1 

1328 # Index for that atom type; no index for first one 

1329 tidx = sum(atom.symbol == x[0] for x in atomic_species) or ' ' 

1330 atomic_species[(atom.symbol, magmom)] = (sidx, tidx) 

1331 # Add magnetization to the input file 

1332 mag_str = f"starting_magnetization({sidx})" 

1333 input_parameters['system'][mag_str] = fspin 

1334 species_pseudo = species_info[atom.symbol]['pseudo'] 

1335 atomic_species_str.append( 

1336 f"{atom.symbol}{tidx} {atom.mass} {species_pseudo}\n") 

1337 # lookup tidx to append to name 

1338 sidx, tidx = atomic_species[(atom.symbol, magmom)] 

1339 # construct line for atomic positions 

1340 atomic_positions_str.append( 

1341 format_atom_position( 

1342 atom, crystal_coordinates, mask=mask, tidx=tidx) 

1343 ) 

1344 else: 

1345 # Do nothing about magnetisation 

1346 for atom, mask in zip(atoms, masks): 

1347 if atom.symbol not in atomic_species: 

1348 atomic_species[atom.symbol] = True # just a placeholder 

1349 species_pseudo = species_info[atom.symbol]['pseudo'] 

1350 atomic_species_str.append( 

1351 f"{atom.symbol} {atom.mass} {species_pseudo}\n") 

1352 # construct line for atomic positions 

1353 atomic_positions_str.append( 

1354 format_atom_position(atom, crystal_coordinates, mask=mask) 

1355 ) 

1356 

1357 # Add computed parameters 

1358 # different magnetisms means different types 

1359 input_parameters['system']['ntyp'] = len(atomic_species) 

1360 input_parameters['system']['nat'] = len(atoms) 

1361 

1362 # Use cell as given or fit to a specific ibrav 

1363 if 'ibrav' in input_parameters['system']: 

1364 ibrav = input_parameters['system']['ibrav'] 

1365 if ibrav != 0: 

1366 raise ValueError(ibrav_error_message) 

1367 else: 

1368 # Just use standard cell block 

1369 input_parameters['system']['ibrav'] = 0 

1370 

1371 # Construct input file into this 

1372 pwi = input_parameters.to_string(list_form=True) 

1373 

1374 # Pseudopotentials 

1375 pwi.append('ATOMIC_SPECIES\n') 

1376 pwi.extend(atomic_species_str) 

1377 pwi.append('\n') 

1378 

1379 # KPOINTS - add a MP grid as required 

1380 if kspacing is not None: 

1381 kgrid = kspacing_to_grid(atoms, kspacing) 

1382 elif kpts is not None: 

1383 if isinstance(kpts, dict) and 'path' not in kpts: 

1384 kgrid, shift = kpts2sizeandoffsets(atoms=atoms, **kpts) 

1385 koffset = [] 

1386 for i, x in enumerate(shift): 

1387 assert x == 0 or abs(x * kgrid[i] - 0.5) < 1e-14 

1388 koffset.append(0 if x == 0 else 1) 

1389 else: 

1390 kgrid = kpts 

1391 else: 

1392 kgrid = "gamma" 

1393 

1394 # True and False work here and will get converted by ':d' format 

1395 if isinstance(koffset, int): 

1396 koffset = (koffset, ) * 3 

1397 

1398 # BandPath object or bandpath-as-dictionary: 

1399 if isinstance(kgrid, dict) or hasattr(kgrid, 'kpts'): 

1400 pwi.append('K_POINTS crystal_b\n') 

1401 assert hasattr(kgrid, 'path') or 'path' in kgrid 

1402 kgrid = kpts2ndarray(kgrid, atoms=atoms) 

1403 pwi.append(f'{len(kgrid)}\n') 

1404 for k in kgrid: 

1405 pwi.append(f"{k[0]:.14f} {k[1]:.14f} {k[2]:.14f} 0\n") 

1406 pwi.append('\n') 

1407 elif isinstance(kgrid, str) and (kgrid == "gamma"): 

1408 pwi.append('K_POINTS gamma\n') 

1409 pwi.append('\n') 

1410 else: 

1411 pwi.append('K_POINTS automatic\n') 

1412 pwi.append(f"{kgrid[0]} {kgrid[1]} {kgrid[2]} " 

1413 f" {koffset[0]:d} {koffset[1]:d} {koffset[2]:d}\n") 

1414 pwi.append('\n') 

1415 

1416 # CELL block, if required 

1417 if input_parameters['SYSTEM']['ibrav'] == 0: 

1418 pwi.append('CELL_PARAMETERS angstrom\n') 

1419 pwi.append('{cell[0][0]:.14f} {cell[0][1]:.14f} {cell[0][2]:.14f}\n' 

1420 '{cell[1][0]:.14f} {cell[1][1]:.14f} {cell[1][2]:.14f}\n' 

1421 '{cell[2][0]:.14f} {cell[2][1]:.14f} {cell[2][2]:.14f}\n' 

1422 ''.format(cell=atoms.cell)) 

1423 pwi.append('\n') 

1424 

1425 # Positions - already constructed, but must appear after namelist 

1426 if crystal_coordinates: 

1427 pwi.append('ATOMIC_POSITIONS crystal\n') 

1428 else: 

1429 pwi.append('ATOMIC_POSITIONS angstrom\n') 

1430 pwi.extend(atomic_positions_str) 

1431 pwi.append('\n') 

1432 

1433 # DONE! 

1434 fd.write(''.join(pwi)) 

1435 

1436 if additional_cards: 

1437 if isinstance(additional_cards, list): 

1438 additional_cards = "\n".join(additional_cards) 

1439 additional_cards += "\n" 

1440 

1441 fd.write(additional_cards) 

1442 

1443 

1444def write_espresso_ph( 

1445 fd, 

1446 input_data=None, 

1447 qpts=None, 

1448 nat_todo_indices=None, 

1449 **kwargs) -> None: 

1450 """ 

1451 Function that write the input file for a ph.x calculation. Normal namelist 

1452 cards are passed in the input_data dictionary. Which can be either nested 

1453 or flat, ASE style. The q-points are passed in the qpts list. If qplot is 

1454 set to True then qpts is expected to be a list of list|tuple of length 4. 

1455 Where the first three elements are the coordinates of the q-point in units 

1456 of 2pi/alat and the last element is the weight of the q-point. if qplot is 

1457 set to False then qpts is expected to be a simple list of length 4 (single 

1458 q-point). Finally if ldisp is set to True, the above is discarded and the 

1459 q-points are read from the nq1, nq2, nq3 cards in the input_data dictionary. 

1460 

1461 Additionally, a nat_todo_indices kwargs (list[int]) can be specified in the 

1462 kwargs. It will be used if nat_todo is set to True in the input_data 

1463 dictionary. 

1464 

1465 Globally, this function follows the convention set in the ph.x documentation 

1466 (https://www.quantum-espresso.org/Doc/INPUT_PH.html) 

1467 

1468 Parameters 

1469 ---------- 

1470 fd 

1471 The file descriptor of the input file. 

1472 

1473 kwargs 

1474 kwargs dictionary possibly containing the following keys: 

1475 

1476 - input_data: dict 

1477 - qpts: list[list[float]] | list[tuple[float]] | list[float] 

1478 - nat_todo_indices: list[int] 

1479 

1480 Returns 

1481 ------- 

1482 None 

1483 """ 

1484 

1485 input_data = Namelist(input_data) 

1486 input_data.to_nested('ph', **kwargs) 

1487 

1488 input_ph = input_data["inputph"] 

1489 

1490 inp_nat_todo = input_ph.get("nat_todo", 0) 

1491 qpts = qpts or (0, 0, 0) 

1492 

1493 pwi = input_data.to_string() 

1494 

1495 fd.write(pwi) 

1496 

1497 qplot = input_ph.get("qplot", False) 

1498 ldisp = input_ph.get("ldisp", False) 

1499 

1500 if qplot: 

1501 fd.write(f"{len(qpts)}\n") 

1502 for qpt in qpts: 

1503 fd.write( 

1504 f"{qpt[0]:0.8f} {qpt[1]:0.8f} {qpt[2]:0.8f} {qpt[3]:1d}\n" 

1505 ) 

1506 elif not (qplot or ldisp): 

1507 fd.write(f"{qpts[0]:0.8f} {qpts[1]:0.8f} {qpts[2]:0.8f}\n") 

1508 if inp_nat_todo: 

1509 tmp = [str(i) for i in nat_todo_indices] 

1510 fd.write(" ".join(tmp)) 

1511 fd.write("\n") 

1512 

1513 

1514def read_espresso_ph(fileobj): 

1515 """ 

1516 Function that reads the output of a ph.x calculation. 

1517 It returns a dictionary where each q-point is a key and 

1518 the value is a dictionary with the following keys if available: 

1519 

1520 - qpoints: The q-point in cartesian coordinates. 

1521 - kpoints: The k-points in cartesian coordinates. 

1522 - dieltensor: The dielectric tensor. 

1523 - borneffcharge: The effective Born charges. 

1524 - borneffcharge_dfpt: The effective Born charges from DFPT. 

1525 - polarizability: The polarizability tensor. 

1526 - modes: The phonon modes. 

1527 - eqpoints: The symmetrically equivalent q-points. 

1528 - freqs: The phonon frequencies. 

1529 - mode_symmetries: The symmetries of the modes. 

1530 - atoms: The atoms object. 

1531 

1532 Some notes: 

1533 

1534 - For some reason, the cell is not defined to high level of 

1535 precision with ph.x. Be careful when using the atoms object 

1536 retrieved from this function. 

1537 - This function can be called on incomplete calculations i.e. 

1538 if the calculation couldn't diagonalize the dynamical matrix 

1539 for some q-points, the results for the other q-points will 

1540 still be returned. 

1541 

1542 Parameters 

1543 ---------- 

1544 fileobj 

1545 The file descriptor of the output file. 

1546 

1547 Returns 

1548 ------- 

1549 dict 

1550 The results dictionnary as described above. 

1551 """ 

1552 freg = re.compile(r"-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+\-]?\d+)?") 

1553 

1554 QPOINTS = r"(?i)^\s*Calculation\s*of\s*q" 

1555 NKPTS = r"(?i)^\s*number\s*of\s*k\s*points\s*" 

1556 DIEL = r"(?i)^\s*Dielectric\s*constant\s*in\s*cartesian\s*axis\s*$" 

1557 BORN = r"(?i)^\s*Effective\s*charges\s*\(d\s*Force\s*/\s*dE\)" 

1558 POLA = r"(?i)^\s*Polarizability\s*(a.u.)\^3" 

1559 REPR = r"(?i)^\s*There\s*are\s*\d+\s*irreducible\s*representations\s*$" 

1560 EQPOINTS = r"(?i)^\s*Number\s*of\s*q\s*in\s*the\s*star\s*=\s*" 

1561 DIAG = r"(?i)^\s*Diagonalizing\s*the\s*dynamical\s*matrix\s*$" 

1562 MODE_SYM = r"(?i)^\s*Mode\s*symmetry,\s*" 

1563 BORN_DFPT = r"(?i)^\s*Effective\s*charges\s*\(d\s*P\s*/\s*du\)" 

1564 POSITIONS = r"(?i)^\s*site\s*n\..*\(alat\s*units\)" 

1565 ALAT = r"(?i)^\s*celldm\(1\)=" 

1566 CELL = ( 

1567 r"^\s*crystal\s*axes:\s*\(cart.\s*coord.\s*in\s*units\s*of\s*alat\)" 

1568 ) 

1569 ELECTRON_PHONON = r"(?i)^\s*electron-phonon\s*interaction\s*...\s*$" 

1570 

1571 output = { 

1572 QPOINTS: [], 

1573 NKPTS: [], 

1574 DIEL: [], 

1575 BORN: [], 

1576 BORN_DFPT: [], 

1577 POLA: [], 

1578 REPR: [], 

1579 EQPOINTS: [], 

1580 DIAG: [], 

1581 MODE_SYM: [], 

1582 POSITIONS: [], 

1583 ALAT: [], 

1584 CELL: [], 

1585 ELECTRON_PHONON: [], 

1586 } 

1587 

1588 names = { 

1589 QPOINTS: "qpoints", 

1590 NKPTS: "kpoints", 

1591 DIEL: "dieltensor", 

1592 BORN: "borneffcharge", 

1593 BORN_DFPT: "borneffcharge_dfpt", 

1594 POLA: "polarizability", 

1595 REPR: "representations", 

1596 EQPOINTS: "eqpoints", 

1597 DIAG: "freqs", 

1598 MODE_SYM: "mode_symmetries", 

1599 POSITIONS: "positions", 

1600 ALAT: "alat", 

1601 CELL: "cell", 

1602 ELECTRON_PHONON: "ep_data", 

1603 } 

1604 

1605 unique = { 

1606 QPOINTS: True, 

1607 NKPTS: False, 

1608 DIEL: True, 

1609 BORN: True, 

1610 BORN_DFPT: True, 

1611 POLA: True, 

1612 REPR: True, 

1613 EQPOINTS: True, 

1614 DIAG: True, 

1615 MODE_SYM: True, 

1616 POSITIONS: True, 

1617 ALAT: True, 

1618 CELL: True, 

1619 ELECTRON_PHONON: True, 

1620 } 

1621 

1622 results = {} 

1623 fdo_lines = [i for i in fileobj.read().splitlines() if i] 

1624 n_lines = len(fdo_lines) 

1625 

1626 for idx, line in enumerate(fdo_lines): 

1627 for key in output: 

1628 if bool(re.match(key, line)): 

1629 output[key].append(idx) 

1630 

1631 output = {key: np.array(value) for key, value in output.items()} 

1632 

1633 def _read_qpoints(idx): 

1634 match = re.findall(freg, fdo_lines[idx]) 

1635 return tuple(round(float(x), 7) for x in match) 

1636 

1637 def _read_kpoints(idx): 

1638 n_kpts = int(re.findall(freg, fdo_lines[idx])[0]) 

1639 kpts = [] 

1640 for line in fdo_lines[idx + 2: idx + 2 + n_kpts]: 

1641 if bool(re.search(r"^\s*k\(.*wk", line)): 

1642 kpts.append([round(float(x), 7) 

1643 for x in re.findall(freg, line)[1:]]) 

1644 return np.array(kpts) 

1645 

1646 def _read_repr(idx): 

1647 n_repr, curr, n = int(re.findall(freg, fdo_lines[idx])[0]), 0, 0 

1648 representations = {} 

1649 while idx + n < n_lines: 

1650 if re.search(r"^\s*Representation.*modes", fdo_lines[idx + n]): 

1651 curr = int(re.findall(freg, fdo_lines[idx + n])[0]) 

1652 representations[curr] = {"done": False, "modes": []} 

1653 if re.search(r"Calculated\s*using\s*symmetry", fdo_lines[idx + n]) \ 

1654 or re.search(r"-\s*Done\s*$", fdo_lines[idx + n]): 

1655 representations[curr]["done"] = True 

1656 if re.search(r"(?i)^\s*(mode\s*#\s*\d\s*)+", fdo_lines[idx + n]): 

1657 representations[curr]["modes"] = _read_modes(idx + n) 

1658 if curr == n_repr: 

1659 break 

1660 n += 1 

1661 return representations 

1662 

1663 def _read_modes(idx): 

1664 n = 1 

1665 n_modes = len(re.findall(r"mode", fdo_lines[idx])) 

1666 modes = [] 

1667 while not modes or bool(re.match(r"^\s*\(", fdo_lines[idx + n])): 

1668 tmp = re.findall(freg, fdo_lines[idx + n]) 

1669 modes.append([round(float(x), 5) for x in tmp]) 

1670 n += 1 

1671 return np.hsplit(np.array(modes), n_modes) 

1672 

1673 def _read_eqpoints(idx): 

1674 n_star = int(re.findall(freg, fdo_lines[idx])[0]) 

1675 return np.loadtxt( 

1676 fdo_lines[idx + 2: idx + 2 + n_star], usecols=(1, 2, 3) 

1677 ).reshape(-1, 3) 

1678 

1679 def _read_freqs(idx): 

1680 n = 0 

1681 freqs = [] 

1682 stop = 0 

1683 while not freqs or stop < 2: 

1684 if bool(re.search(r"^\s*freq", fdo_lines[idx + n])): 

1685 tmp = re.findall(freg, fdo_lines[idx + n])[1] 

1686 freqs.append(float(tmp)) 

1687 if bool(re.search(r"\*{5,}", fdo_lines[idx + n])): 

1688 stop += 1 

1689 n += 1 

1690 return np.array(freqs) 

1691 

1692 def _read_sym(idx): 

1693 n = 1 

1694 sym = {} 

1695 while bool(re.match(r"^\s*freq", fdo_lines[idx + n])): 

1696 r = re.findall("\\d+", fdo_lines[idx + n]) 

1697 r = tuple(range(int(r[0]), int(r[1]) + 1)) 

1698 sym[r] = fdo_lines[idx + n].split("-->")[1].strip() 

1699 sym[r] = re.sub(r"\s+", " ", sym[r]) 

1700 n += 1 

1701 return sym 

1702 

1703 def _read_epsil(idx): 

1704 epsil = np.zeros((3, 3)) 

1705 for n in range(1, 4): 

1706 tmp = re.findall(freg, fdo_lines[idx + n]) 

1707 epsil[n - 1] = [round(float(x), 9) for x in tmp] 

1708 return epsil 

1709 

1710 def _read_born(idx): 

1711 n = 1 

1712 born = [] 

1713 while idx + n < n_lines: 

1714 if re.search(r"^\s*atom\s*\d\s*\S", fdo_lines[idx + n]): 

1715 pass 

1716 elif re.search(r"^\s*E\*?(x|y|z)\s*\(", fdo_lines[idx + n]): 

1717 tmp = re.findall(freg, fdo_lines[idx + n]) 

1718 born.append([round(float(x), 5) for x in tmp]) 

1719 else: 

1720 break 

1721 n += 1 

1722 born = np.array(born) 

1723 return np.vsplit(born, len(born) // 3) 

1724 

1725 def _read_born_dfpt(idx): 

1726 n = 1 

1727 born = [] 

1728 while idx + n < n_lines: 

1729 if re.search(r"^\s*atom\s*\d\s*\S", fdo_lines[idx + n]): 

1730 pass 

1731 elif re.search(r"^\s*P(x|y|z)\s*\(", fdo_lines[idx + n]): 

1732 tmp = re.findall(freg, fdo_lines[idx + n]) 

1733 born.append([round(float(x), 5) for x in tmp]) 

1734 else: 

1735 break 

1736 n += 1 

1737 born = np.array(born) 

1738 return np.vsplit(born, len(born) // 3) 

1739 

1740 def _read_pola(idx): 

1741 pola = np.zeros((3, 3)) 

1742 for n in range(1, 4): 

1743 tmp = re.findall(freg, fdo_lines[idx + n])[:3] 

1744 pola[n - 1] = [round(float(x), 2) for x in tmp] 

1745 return pola 

1746 

1747 def _read_positions(idx): 

1748 positions = [] 

1749 symbols = [] 

1750 n = 1 

1751 while re.findall(r"^\s*\d+", fdo_lines[idx + n]): 

1752 symbols.append(fdo_lines[idx + n].split()[1]) 

1753 positions.append( 

1754 [round(float(x), 5) 

1755 for x in re.findall(freg, fdo_lines[idx + n])[-3:]] 

1756 ) 

1757 n += 1 

1758 atoms = Atoms(positions=positions, symbols=symbols) 

1759 atoms.pbc = True 

1760 return atoms 

1761 

1762 def _read_alat(idx): 

1763 return round(float(re.findall(freg, fdo_lines[idx])[1]), 5) 

1764 

1765 def _read_cell(idx): 

1766 cell = [] 

1767 n = 1 

1768 while re.findall(r"^\s*a\(\d\)", fdo_lines[idx + n]): 

1769 cell.append([round(float(x), 4) 

1770 for x in re.findall(freg, fdo_lines[idx + n])[-3:]]) 

1771 n += 1 

1772 return np.array(cell) 

1773 

1774 def _read_electron_phonon(idx): 

1775 results = {} 

1776 

1777 broad_re = ( 

1778 r"^\s*Gaussian\s*Broadening:\s+([\d.]+)\s+Ry, ngauss=\s+\d+" 

1779 ) 

1780 dos_re = ( 

1781 r"^\s*DOS\s*=\s*([\d.]+)\s*states/" 

1782 r"spin/Ry/Unit\s*Cell\s*at\s*Ef=\s+([\d.]+)\s+eV" 

1783 ) 

1784 lg_re = ( 

1785 r"^\s*lambda\(\s+(\d+)\)=\s+([\d.]+)\s+gamma=\s+([\d.]+)\s+GHz" 

1786 ) 

1787 end_re = r"^\s*Number\s*of\s*q\s*in\s*the\s*star\s*=\s+(\d+)$" 

1788 

1789 lambdas = [] 

1790 gammas = [] 

1791 

1792 current = None 

1793 

1794 n = 1 

1795 while idx + n < n_lines: 

1796 line = fdo_lines[idx + n] 

1797 

1798 broad_match = re.match(broad_re, line) 

1799 dos_match = re.match(dos_re, line) 

1800 lg_match = re.match(lg_re, line) 

1801 end_match = re.match(end_re, line) 

1802 

1803 if broad_match: 

1804 if lambdas: 

1805 results[current]["lambdas"] = lambdas 

1806 results[current]["gammas"] = gammas 

1807 lambdas = [] 

1808 gammas = [] 

1809 current = float(broad_match[1]) 

1810 results[current] = {} 

1811 elif dos_match: 

1812 results[current]["dos"] = float(dos_match[1]) 

1813 results[current]["fermi"] = float(dos_match[2]) 

1814 elif lg_match: 

1815 lambdas.append(float(lg_match[2])) 

1816 gammas.append(float(lg_match[3])) 

1817 

1818 if end_match: 

1819 results[current]["lambdas"] = lambdas 

1820 results[current]["gammas"] = gammas 

1821 break 

1822 

1823 n += 1 

1824 

1825 return results 

1826 

1827 properties = { 

1828 NKPTS: _read_kpoints, 

1829 DIEL: _read_epsil, 

1830 BORN: _read_born, 

1831 BORN_DFPT: _read_born_dfpt, 

1832 POLA: _read_pola, 

1833 REPR: _read_repr, 

1834 EQPOINTS: _read_eqpoints, 

1835 DIAG: _read_freqs, 

1836 MODE_SYM: _read_sym, 

1837 POSITIONS: _read_positions, 

1838 ALAT: _read_alat, 

1839 CELL: _read_cell, 

1840 ELECTRON_PHONON: _read_electron_phonon, 

1841 } 

1842 

1843 iblocks = np.append(output[QPOINTS], n_lines) 

1844 

1845 for qnum, (past, future) in enumerate(zip(iblocks[:-1], iblocks[1:])): 

1846 qpoint = _read_qpoints(past) 

1847 results[qnum + 1] = curr_result = {"qpoint": qpoint} 

1848 for prop in properties: 

1849 p = (past < output[prop]) & (output[prop] < future) 

1850 selected = output[prop][p] 

1851 if len(selected) == 0: 

1852 continue 

1853 if unique[prop]: 

1854 idx = output[prop][p][-1] 

1855 curr_result[names[prop]] = properties[prop](idx) 

1856 else: 

1857 tmp = {k + 1: 0 for k in range(len(selected))} 

1858 for k, idx in enumerate(selected): 

1859 tmp[k + 1] = properties[prop](idx) 

1860 curr_result[names[prop]] = tmp 

1861 alat = curr_result.pop("alat", 1.0) 

1862 atoms = curr_result.pop("positions", None) 

1863 cell = curr_result.pop("cell", np.eye(3)) 

1864 if atoms: 

1865 atoms.positions *= alat * units["Bohr"] 

1866 atoms.cell = cell * alat * units["Bohr"] 

1867 atoms.wrap() 

1868 curr_result["atoms"] = atoms 

1869 

1870 return results 

1871 

1872 

1873def write_fortran_namelist( 

1874 fd, 

1875 input_data=None, 

1876 binary=None, 

1877 additional_cards=None, 

1878 **kwargs) -> None: 

1879 """ 

1880 Function which writes input for simple espresso binaries. 

1881 List of supported binaries are in the espresso_keys.py file. 

1882 Non-exhaustive list (to complete) 

1883 

1884 Note: "EOF" is appended at the end of the file. 

1885 (https://lists.quantum-espresso.org/pipermail/users/2020-November/046269.html) 

1886 

1887 Additional fields are written 'as is' in the input file. It is expected 

1888 to be a string or a list of strings. 

1889 

1890 Parameters 

1891 ---------- 

1892 fd 

1893 The file descriptor of the input file. 

1894 input_data: dict 

1895 A flat or nested dictionary with input parameters for the binary.x 

1896 binary: str 

1897 Name of the binary 

1898 additional_cards: str | list[str] 

1899 Additional fields to be written at the end of the input file, after 

1900 the namelist. It is expected to be a string or a list of strings. 

1901 

1902 Returns 

1903 ------- 

1904 None 

1905 """ 

1906 input_data = Namelist(input_data) 

1907 

1908 if binary: 

1909 input_data.to_nested(binary, **kwargs) 

1910 

1911 pwi = input_data.to_string() 

1912 

1913 fd.write(pwi) 

1914 

1915 if additional_cards: 

1916 if isinstance(additional_cards, list): 

1917 additional_cards = "\n".join(additional_cards) 

1918 additional_cards += "\n" 

1919 

1920 fd.write(additional_cards) 

1921 

1922 fd.write("EOF") 

1923 

1924 

1925@deprecated('Please use the ase.io.espresso.Namelist class', 

1926 DeprecationWarning) 

1927def construct_namelist(parameters=None, keys=None, warn=False, **kwargs): 

1928 """ 

1929 Construct an ordered Namelist containing all the parameters given (as 

1930 a dictionary or kwargs). Keys will be inserted into their appropriate 

1931 section in the namelist and the dictionary may contain flat and nested 

1932 structures. Any kwargs that match input keys will be incorporated into 

1933 their correct section. All matches are case-insensitive, and returned 

1934 Namelist object is a case-insensitive dict. 

1935 

1936 If a key is not known to ase, but in a section within `parameters`, 

1937 it will be assumed that it was put there on purpose and included 

1938 in the output namelist. Anything not in a section will be ignored (set 

1939 `warn` to True to see ignored keys). 

1940 

1941 Keys with a dimension (e.g. Hubbard_U(1)) will be incorporated as-is 

1942 so the `i` should be made to match the output. 

1943 

1944 The priority of the keys is: 

1945 kwargs[key] > parameters[key] > parameters[section][key] 

1946 Only the highest priority item will be included. 

1947 

1948 .. deprecated:: 3.23.0 

1949 Please use :class:`ase.io.espresso.Namelist` instead. 

1950 

1951 Parameters 

1952 ---------- 

1953 parameters: dict 

1954 Flat or nested set of input parameters. 

1955 keys: Namelist | dict 

1956 Namelist to use as a template for the output. 

1957 warn: bool 

1958 Enable warnings for unused keys. 

1959 

1960 Returns 

1961 ------- 

1962 input_namelist: Namelist 

1963 pw.x compatible namelist of input parameters. 

1964 

1965 """ 

1966 

1967 if keys is None: 

1968 keys = deepcopy(pw_keys) 

1969 # Convert everything to Namelist early to make case-insensitive 

1970 if parameters is None: 

1971 parameters = Namelist() 

1972 else: 

1973 # Maximum one level of nested dict 

1974 # Don't modify in place 

1975 parameters_namelist = Namelist() 

1976 for key, value in parameters.items(): 

1977 if isinstance(value, dict): 

1978 parameters_namelist[key] = Namelist(value) 

1979 else: 

1980 parameters_namelist[key] = value 

1981 parameters = parameters_namelist 

1982 

1983 # Just a dict 

1984 kwargs = Namelist(kwargs) 

1985 

1986 # Final parameter set 

1987 input_namelist = Namelist() 

1988 

1989 # Collect 

1990 for section in keys: 

1991 sec_list = Namelist() 

1992 for key in keys[section]: 

1993 # Check all three separately and pop them all so that 

1994 # we can check for missing values later 

1995 value = None 

1996 

1997 if key in parameters.get(section, {}): 

1998 value = parameters[section].pop(key) 

1999 if key in parameters: 

2000 value = parameters.pop(key) 

2001 if key in kwargs: 

2002 value = kwargs.pop(key) 

2003 

2004 if value is not None: 

2005 sec_list[key] = value 

2006 

2007 # Check if there is a key(i) version (no extra parsing) 

2008 for arg_key in list(parameters.get(section, {})): 

2009 if arg_key.split('(')[0].strip().lower() == key.lower(): 

2010 sec_list[arg_key] = parameters[section].pop(arg_key) 

2011 cp_parameters = parameters.copy() 

2012 for arg_key in cp_parameters: 

2013 if arg_key.split('(')[0].strip().lower() == key.lower(): 

2014 sec_list[arg_key] = parameters.pop(arg_key) 

2015 cp_kwargs = kwargs.copy() 

2016 for arg_key in cp_kwargs: 

2017 if arg_key.split('(')[0].strip().lower() == key.lower(): 

2018 sec_list[arg_key] = kwargs.pop(arg_key) 

2019 

2020 # Add to output 

2021 input_namelist[section] = sec_list 

2022 

2023 unused_keys = list(kwargs) 

2024 # pass anything else already in a section 

2025 for key, value in parameters.items(): 

2026 if key in keys and isinstance(value, dict): 

2027 input_namelist[key].update(value) 

2028 elif isinstance(value, dict): 

2029 unused_keys.extend(list(value)) 

2030 else: 

2031 unused_keys.append(key) 

2032 

2033 if warn and unused_keys: 

2034 warnings.warn('Unused keys: {}'.format(', '.join(unused_keys))) 

2035 

2036 return input_namelist 

2037 

2038 

2039@deprecated('Please use the .to_string() method of Namelist instead.', 

2040 DeprecationWarning) 

2041def namelist_to_string(input_parameters): 

2042 """Format a Namelist object as a string for writing to a file. 

2043 Assume sections are ordered (taken care of in namelist construction) 

2044 and that repr converts to a QE readable representation (except bools) 

2045 

2046 .. deprecated:: 3.23.0 

2047 Please use the :meth:`ase.io.espresso.Namelist.to_string` method 

2048 instead. 

2049 

2050 Parameters 

2051 ---------- 

2052 input_parameters : Namelist | dict 

2053 Expecting a nested dictionary of sections and key-value data. 

2054 

2055 Returns 

2056 ------- 

2057 pwi : List[str] 

2058 Input line for the namelist 

2059 """ 

2060 pwi = [] 

2061 for section in input_parameters: 

2062 pwi.append(f'&{section.upper()}\n') 

2063 for key, value in input_parameters[section].items(): 

2064 if value is True: 

2065 pwi.append(f' {key:16} = .true.\n') 

2066 elif value is False: 

2067 pwi.append(f' {key:16} = .false.\n') 

2068 elif isinstance(value, Path): 

2069 pwi.append(f' {key:16} = "{value}"\n') 

2070 else: 

2071 # repr format to get quotes around strings 

2072 pwi.append(f' {key:16} = {value!r}\n') 

2073 pwi.append('/\n') # terminate section 

2074 pwi.append('\n') 

2075 return pwi