Coverage for /builds/debichem-team/python-ase/ase/io/espresso.py: 77.53%

868 statements  

« prev     ^ index     » next       coverage.py v7.5.3, created at 2025-03-06 04:00 +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 ( 

27 SinglePointDFTCalculator, 

28 SinglePointKPoint, 

29) 

30from ase.constraints import FixAtoms, FixCartesian 

31from ase.data import chemical_symbols 

32from ase.dft.kpoints import kpoint_convert 

33from ase.io.espresso_namelist.keys import pw_keys 

34from ase.io.espresso_namelist.namelist import Namelist 

35from ase.units import create_units 

36from ase.utils import deprecated, reader, writer 

37 

38# Quantum ESPRESSO uses CODATA 2006 internally 

39units = create_units('2006') 

40 

41# Section identifiers 

42_PW_START = 'Program PWSCF' 

43_PW_END = 'End of self-consistent calculation' 

44_PW_CELL = 'CELL_PARAMETERS' 

45_PW_POS = 'ATOMIC_POSITIONS' 

46_PW_MAGMOM = 'Magnetic moment per site' 

47_PW_FORCE = 'Forces acting on atoms' 

48_PW_TOTEN = '! total energy' 

49_PW_STRESS = 'total stress' 

50_PW_FERMI = 'the Fermi energy is' 

51_PW_HIGHEST_OCCUPIED = 'highest occupied level' 

52_PW_HIGHEST_OCCUPIED_LOWEST_FREE = 'highest occupied, lowest unoccupied level' 

53_PW_KPTS = 'number of k points=' 

54_PW_BANDS = _PW_END 

55_PW_BANDSTRUCTURE = 'End of band structure calculation' 

56_PW_DIPOLE = "Debye" 

57_PW_DIPOLE_DIRECTION = "Computed dipole along edir" 

58 

59# ibrav error message 

60ibrav_error_message = ( 

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

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

63 'of your system because the CELL_PARAMETERS are defined ' 

64 'to a high level of precision.') 

65 

66 

67@reader 

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

69 """Reads Quantum ESPRESSO output files. 

70 

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

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

73 within the output file. 

74 

75 Will probably raise errors for broken or incomplete files. 

76 

77 Parameters 

78 ---------- 

79 fileobj : file|str 

80 A file like object or filename 

81 index : slice 

82 The index of configurations to extract. 

83 results_required : bool 

84 If True, atomistic configurations that do not have any 

85 associated results will not be included. This prevents double 

86 printed configurations and incomplete calculations from being 

87 returned as the final configuration with no results data. 

88 

89 Yields 

90 ------ 

91 structure : Atoms 

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

93 SinglePointCalculator attached with any results parsed from 

94 the file. 

95 

96 

97 """ 

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

99 pwo_lines = fileobj.readlines() 

100 

101 # TODO: index -1 special case? 

102 # Index all the interesting points 

103 indexes = { 

104 _PW_START: [], 

105 _PW_END: [], 

106 _PW_CELL: [], 

107 _PW_POS: [], 

108 _PW_MAGMOM: [], 

109 _PW_FORCE: [], 

110 _PW_TOTEN: [], 

111 _PW_STRESS: [], 

112 _PW_FERMI: [], 

113 _PW_HIGHEST_OCCUPIED: [], 

114 _PW_HIGHEST_OCCUPIED_LOWEST_FREE: [], 

115 _PW_KPTS: [], 

116 _PW_BANDS: [], 

117 _PW_BANDSTRUCTURE: [], 

118 _PW_DIPOLE: [], 

119 _PW_DIPOLE_DIRECTION: [], 

120 } 

121 

122 for idx, line in enumerate(pwo_lines): 

123 for identifier in indexes: 

124 if identifier in line: 

125 indexes[identifier].append(idx) 

126 

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

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

129 all_config_indexes = sorted(indexes[_PW_START] + 

130 indexes[_PW_POS]) 

131 

132 # Slice only requested indexes 

133 # setting results_required argument stops configuration-only 

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

135 # is one that has results. Two cases: 

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

137 # abnormally. 

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

139 # only 'vc-relax' recalculates. 

140 if results_required: 

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

142 indexes[_PW_STRESS] + indexes[_PW_MAGMOM] + 

143 indexes[_PW_BANDS] + 

144 indexes[_PW_BANDSTRUCTURE]) 

145 

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

147 # configuration 

148 results_config_indexes = [] 

149 for config_index, config_index_next in zip( 

150 all_config_indexes, 

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

152 if any(config_index < results_index < config_index_next 

153 for results_index in results_indexes): 

154 results_config_indexes.append(config_index) 

155 

156 # slice from the subset 

157 image_indexes = results_config_indexes[index] 

158 else: 

159 image_indexes = all_config_indexes[index] 

160 

161 # Extract initialisation information each time PWSCF starts 

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

163 # when to fill in the blanks. 

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

165 

166 for image_index in image_indexes: 

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

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

169 # start. 

170 if image_index in indexes[_PW_START]: 

171 prev_start_index = image_index 

172 else: 

173 # The greatest start index before this structure 

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

175 if idx < image_index][-1] 

176 

177 # add structure to reference if not there 

178 if pwscf_start_info[prev_start_index] is None: 

179 pwscf_start_info[prev_start_index] = parse_pwo_start( 

180 pwo_lines, prev_start_index) 

181 

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

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

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

185 for next_index in all_config_indexes: 

186 if next_index > image_index: 

187 break 

188 else: 

189 # right to the end of the file 

190 next_index = len(pwo_lines) 

191 

192 # Get the structure 

193 # Use this for any missing data 

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

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

196 if image_index in indexes[_PW_START]: 

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

198 else: 

199 if _PW_CELL in pwo_lines[image_index - 5]: 

200 # CELL_PARAMETERS would be just before positions if present 

201 cell, cell_alat = get_cell_parameters( 

202 pwo_lines[image_index - 5:image_index]) 

203 else: 

204 cell = prev_structure.cell 

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

206 

207 # give at least enough lines to parse the positions 

208 # should be same format as input card 

209 n_atoms = len(prev_structure) 

210 positions_card = get_atomic_positions( 

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

212 n_atoms=n_atoms, cell=cell, alat=cell_alat) 

213 

214 # convert to Atoms object 

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

216 positions_card] 

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

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

219 pbc=True) 

220 

221 # Extract calculation results 

222 # Energy 

223 energy = None 

224 for energy_index in indexes[_PW_TOTEN]: 

225 if image_index < energy_index < next_index: 

226 energy = float( 

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

228 

229 # Forces 

230 forces = None 

231 for force_index in indexes[_PW_FORCE]: 

232 if image_index < force_index < next_index: 

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

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

235 # in high verbosity 

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

237 force_index += 4 

238 else: 

239 force_index += 2 

240 # assume contiguous 

241 forces = [ 

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

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

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

245 

246 # Stress 

247 stress = None 

248 for stress_index in indexes[_PW_STRESS]: 

249 if image_index < stress_index < next_index: 

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

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

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

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

254 # sign convention is opposite of ase 

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

256 

257 # Magmoms 

258 magmoms = None 

259 for magmoms_index in indexes[_PW_MAGMOM]: 

260 if image_index < magmoms_index < next_index: 

261 magmoms = [ 

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

263 in pwo_lines[magmoms_index + 1: 

264 magmoms_index + 1 + len(structure)]] 

265 

266 # Dipole moment 

267 dipole = None 

268 if indexes[_PW_DIPOLE]: 

269 for dipole_index in indexes[_PW_DIPOLE]: 

270 if image_index < dipole_index < next_index: 

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

272 

273 for dipole_index in indexes[_PW_DIPOLE_DIRECTION]: 

274 if image_index < dipole_index < next_index: 

275 _direction = pwo_lines[dipole_index].strip() 

276 prefix = 'Computed dipole along edir(' 

277 _direction = _direction[len(prefix):] 

278 _direction = int(_direction[0]) 

279 

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

281 

282 # Fermi level / highest occupied level 

283 efermi = None 

284 for fermi_index in indexes[_PW_FERMI]: 

285 if image_index < fermi_index < next_index: 

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

287 

288 if efermi is None: 

289 for ho_index in indexes[_PW_HIGHEST_OCCUPIED]: 

290 if image_index < ho_index < next_index: 

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

292 

293 if efermi is None: 

294 for holf_index in indexes[_PW_HIGHEST_OCCUPIED_LOWEST_FREE]: 

295 if image_index < holf_index < next_index: 

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

297 

298 # K-points 

299 ibzkpts = None 

300 weights = None 

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

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

303 

304 for kpts_index in indexes[_PW_KPTS]: 

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

306 kpts_index += 2 

307 

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

309 continue 

310 

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

312 cell = structure.get_cell() 

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 / cell_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 # Just an integer 

849 try: 

850 return int(string) 

851 except ValueError: 

852 pass 

853 # Standard float 

854 try: 

855 return float(string) 

856 except ValueError: 

857 pass 

858 # Fortran double 

859 try: 

860 return ffloat(string) 

861 except ValueError: 

862 pass 

863 

864 # possible bool, else just the raw string 

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

866 return True 

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

868 return False 

869 else: 

870 return string.strip("'") 

871 

872 

873def read_fortran_namelist(fileobj): 

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

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

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

877 Behaviour is taken from Quantum ESPRESSO 5.3. Parses fairly 

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

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

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

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

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

883 anywhere on a line. All of these are ignored if the value is in 'quotes'. 

884 

885 Parameters 

886 ---------- 

887 fileobj : file 

888 An open file-like object. 

889 

890 Returns 

891 ------- 

892 data : dict[str, dict] 

893 Dictionary for each section in the namelist with 

894 key = value pairs of data. 

895 additional_cards : list[str] 

896 Any lines not used to create the data, 

897 assumed to belong to 'cards' in the input file. 

898 """ 

899 

900 data = {} 

901 card_lines = [] 

902 in_namelist = False 

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

904 

905 for line in fileobj: 

906 # leading and trailing whitespace never needed 

907 line = line.strip() 

908 if line.startswith('&'): 

909 # inside a namelist 

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

911 if section in data: 

912 # Repeated sections are completely ignored. 

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

914 section = "_ignored" 

915 data[section] = {} 

916 in_namelist = True 

917 if not in_namelist and line: 

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

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

920 card_lines.append(line) 

921 if in_namelist: 

922 # parse k, v from line: 

923 key = [] 

924 value = None 

925 in_quotes = False 

926 for character in line: 

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

928 # finished value: 

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

930 ''.join(value).strip()) 

931 key = [] 

932 value = None 

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

934 # start writing value 

935 value = [] 

936 elif character == "'": 

937 # only found in value anyway 

938 in_quotes = not in_quotes 

939 value.append("'") 

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

941 break 

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

943 in_namelist = False 

944 break 

945 elif value is not None: 

946 value.append(character) 

947 else: 

948 key.append(character) 

949 if value is not None: 

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

951 ''.join(value).strip()) 

952 

953 return Namelist(data), card_lines 

954 

955 

956def ffloat(string): 

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

958 

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

960 double or quad precision numbers. Double precision numbers are 

961 converted to python floats and quad precision values are interpreted 

962 as numpy longdouble values (platform specific precision). 

963 

964 Parameters 

965 ---------- 

966 string : str 

967 A string containing a number in fortran real format 

968 

969 Returns 

970 ------- 

971 value : float | np.longdouble 

972 Parsed value of the string. 

973 

974 Raises 

975 ------ 

976 ValueError 

977 Unable to parse a float value. 

978 

979 """ 

980 

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

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

983 else: 

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

985 

986 

987def label_to_symbol(label): 

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

989 chemical symbol. 

990 

991 Parameters 

992 ---------- 

993 label : str 

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

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

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

997 max total length cannot exceed 3 characters). 

998 

999 Returns 

1000 ------- 

1001 symbol : str 

1002 The best matching species from ase.utils.chemical_symbols 

1003 

1004 Raises 

1005 ------ 

1006 KeyError 

1007 Couldn't find an appropriate species. 

1008 

1009 Notes 

1010 ----- 

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

1012 or hydrogen labelled 'e'. 

1013 """ 

1014 

1015 # possibly a two character species 

1016 # ase Atoms need proper case of chemical symbols. 

1017 if len(label) >= 2: 

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

1019 if test_symbol in chemical_symbols: 

1020 return test_symbol 

1021 # finally try with one character 

1022 test_symbol = label[0].upper() 

1023 if test_symbol in chemical_symbols: 

1024 return test_symbol 

1025 else: 

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

1027 ''.format(label)) 

1028 

1029 

1030def infix_float(text): 

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

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

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

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

1035 value properly, but slowly. 

1036 

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

1038 0.28867513459481287 

1039 

1040 Parameters 

1041 ---------- 

1042 text : str 

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

1044 

1045 Returns 

1046 ------- 

1047 value : float 

1048 Result of the mathematical expression. 

1049 

1050 """ 

1051 

1052 def middle_brackets(full_text): 

1053 """Extract text from innermost brackets.""" 

1054 start, end = 0, len(full_text) 

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

1056 if char == '(': 

1057 start = idx 

1058 if char == ')': 

1059 end = idx + 1 

1060 break 

1061 return full_text[start:end] 

1062 

1063 def eval_no_bracket_expr(full_text): 

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

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

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

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

1068 try: 

1069 return float(full_text) 

1070 except ValueError: 

1071 for symbol, func in exprs: 

1072 if symbol in full_text: 

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

1074 return func(eval_no_bracket_expr(left), 

1075 eval_no_bracket_expr(right)) 

1076 

1077 while '(' in text: 

1078 middle = middle_brackets(text) 

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

1080 

1081 return float(eval_no_bracket_expr(text)) 

1082 

1083 

1084# Number of valence electrons in the pseudopotentials recommended by 

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

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

1087# of valence electrons. 

1088SSSP_VALENCE = [ 

1089 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, 

1090 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, 

1091 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, 

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

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

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

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

1096 

1097 

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

1099 """ 

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

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

1102 dimension is rounded up (compatible with CASTEP). 

1103 

1104 Parameters 

1105 ---------- 

1106 atoms: ase.Atoms 

1107 A structure that can have get_reciprocal_cell called on it. 

1108 spacing: float 

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

1110 calculated_spacing : list 

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

1112 members will be replaced with the actual calculated spacing in 

1113 $A^{-1}$. 

1114 

1115 Returns 

1116 ------- 

1117 kpoint_grid : [int, int, int] 

1118 MP grid specification to give the required spacing. 

1119 

1120 """ 

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

1122 # reciprocal dimensions 

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

1124 

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

1126 int(r_y / spacing) + 1, 

1127 int(r_z / spacing) + 1] 

1128 

1129 for i, _ in enumerate(kpoint_grid): 

1130 if not atoms.pbc[i]: 

1131 kpoint_grid[i] = 1 

1132 

1133 if calculated_spacing is not None: 

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

1135 r_y / kpoint_grid[1], 

1136 r_z / kpoint_grid[2]] 

1137 

1138 return kpoint_grid 

1139 

1140 

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

1142 """Format one line of atomic positions in 

1143 Quantum ESPRESSO ATOMIC_POSITIONS card. 

1144 

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

1146 >>> format_atom_position(atom, True) 

1147 Li 0.0000000000 0.0000000000 0.0000000000 

1148 Li 0.5000000000 0.5000000000 0.5000000000 

1149 

1150 Parameters 

1151 ---------- 

1152 atom : Atom 

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

1154 crystal_coordinates: bool 

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

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

1157 mask, optional : str 

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

1159 tidx, optional : int 

1160 Magnetic type index. 

1161 

1162 Returns 

1163 ------- 

1164 atom_line : str 

1165 Input line for atom position 

1166 """ 

1167 if crystal_coordinates: 

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

1169 else: 

1170 coords = atom.position 

1171 line_fmt = '{atom.symbol}' 

1172 inps = dict(atom=atom) 

1173 if tidx is not None: 

1174 line_fmt += '{tidx}' 

1175 inps["tidx"] = tidx 

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

1177 inps["coords"] = coords 

1178 line_fmt += ' ' + mask + '\n' 

1179 astr = line_fmt.format(**inps) 

1180 return astr 

1181 

1182 

1183@writer 

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

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

1186 crystal_coordinates=False, additional_cards=None, 

1187 **kwargs): 

1188 """ 

1189 Create an input file for pw.x. 

1190 

1191 Use set_initial_magnetic_moments to turn on spin, if nspin is set to 2 

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

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

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

1195 valence electrons. 

1196 

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

1198 units (Usually Ry or atomic units). 

1199 

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

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

1202 

1203 Implemented features: 

1204 

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

1206 :class:`ase.constraints.FixCartesian`. 

1207 - ``starting_magnetization`` derived from the ``magmoms`` and 

1208 pseudopotentials (searches default paths for pseudo files.) 

1209 - Automatic assignment of options to their correct sections. 

1210 

1211 Not implemented: 

1212 

1213 - Non-zero values of ibrav 

1214 - Lists of k-points 

1215 - Other constraints 

1216 - Hubbard parameters 

1217 - Validation of the argument types for input 

1218 - Validation of required options 

1219 

1220 Parameters 

1221 ---------- 

1222 fd: file | str 

1223 A file to which the input is written. 

1224 atoms: Atoms 

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

1226 input_data: dict 

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

1228 pseudopotentials: dict 

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

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

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

1232 kspacing: float 

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

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

1235 will be used instead. 

1236 kpts: (int, int, int), dict or np.ndarray 

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

1238 as the dimensions of a Monkhorst-Pack grid. 

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

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

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

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

1243 are typically reduced by half. 

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

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

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

1247 If ``kpts`` is a NumPy array, the raw k-points will be passed to 

1248 Quantum Espresso as given in the array (in crystal coordinates). 

1249 Must be of shape (n_kpts, 4). The fourth column contains the 

1250 k-point weights. 

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 elif isinstance(kgrid, np.ndarray): 

1411 if np.shape(kgrid)[1] != 4: 

1412 raise ValueError('Only Nx4 kgrids are supported right now.') 

1413 pwi.append('K_POINTS crystal\n') 

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

1415 for k in kgrid: 

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

1417 pwi.append('\n') 

1418 else: 

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

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

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

1422 pwi.append('\n') 

1423 

1424 # CELL block, if required 

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

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

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

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

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

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

1431 pwi.append('\n') 

1432 

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

1434 if crystal_coordinates: 

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

1436 else: 

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

1438 pwi.extend(atomic_positions_str) 

1439 pwi.append('\n') 

1440 

1441 # DONE! 

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

1443 

1444 if additional_cards: 

1445 if isinstance(additional_cards, list): 

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

1447 additional_cards += "\n" 

1448 

1449 fd.write(additional_cards) 

1450 

1451 

1452def write_espresso_ph( 

1453 fd, 

1454 input_data=None, 

1455 qpts=None, 

1456 nat_todo_indices=None, 

1457 **kwargs) -> None: 

1458 """ 

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

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

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

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

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

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

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

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

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

1468 

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

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

1471 dictionary. 

1472 

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

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

1475 

1476 Parameters 

1477 ---------- 

1478 fd 

1479 The file descriptor of the input file. 

1480 

1481 kwargs 

1482 kwargs dictionary possibly containing the following keys: 

1483 

1484 - input_data: dict 

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

1486 - nat_todo_indices: list[int] 

1487 

1488 Returns 

1489 ------- 

1490 None 

1491 """ 

1492 

1493 input_data = Namelist(input_data) 

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

1495 

1496 input_ph = input_data["inputph"] 

1497 

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

1499 qpts = qpts or (0, 0, 0) 

1500 

1501 pwi = input_data.to_string() 

1502 

1503 fd.write(pwi) 

1504 

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

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

1507 

1508 if qplot: 

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

1510 for qpt in qpts: 

1511 fd.write( 

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

1513 ) 

1514 elif not (qplot or ldisp): 

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

1516 if inp_nat_todo: 

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

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

1519 fd.write("\n") 

1520 

1521 

1522def read_espresso_ph(fileobj): 

1523 """ 

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

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

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

1527 

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

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

1530 - dieltensor: The dielectric tensor. 

1531 - borneffcharge: The effective Born charges. 

1532 - borneffcharge_dfpt: The effective Born charges from DFPT. 

1533 - polarizability: The polarizability tensor. 

1534 - modes: The phonon modes. 

1535 - eqpoints: The symmetrically equivalent q-points. 

1536 - freqs: The phonon frequencies. 

1537 - mode_symmetries: The symmetries of the modes. 

1538 - atoms: The atoms object. 

1539 

1540 Some notes: 

1541 

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

1543 precision in ph.x outputs. Be careful when using the atoms object 

1544 retrieved from this function. 

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

1546 if the calculation couldn't diagonalize the dynamical matrix 

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

1548 still be returned. 

1549 

1550 Parameters 

1551 ---------- 

1552 fileobj 

1553 The file descriptor of the output file. 

1554 

1555 Returns 

1556 ------- 

1557 dict 

1558 The results dictionnary as described above. 

1559 """ 

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

1561 

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

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

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

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

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

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

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

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

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

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

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

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

1574 CELL = ( 

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

1576 ) 

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

1578 

1579 output = { 

1580 QPOINTS: [], 

1581 NKPTS: [], 

1582 DIEL: [], 

1583 BORN: [], 

1584 BORN_DFPT: [], 

1585 POLA: [], 

1586 REPR: [], 

1587 EQPOINTS: [], 

1588 DIAG: [], 

1589 MODE_SYM: [], 

1590 POSITIONS: [], 

1591 ALAT: [], 

1592 CELL: [], 

1593 ELECTRON_PHONON: [], 

1594 } 

1595 

1596 names = { 

1597 QPOINTS: "qpoints", 

1598 NKPTS: "kpoints", 

1599 DIEL: "dieltensor", 

1600 BORN: "borneffcharge", 

1601 BORN_DFPT: "borneffcharge_dfpt", 

1602 POLA: "polarizability", 

1603 REPR: "representations", 

1604 EQPOINTS: "eqpoints", 

1605 DIAG: "freqs", 

1606 MODE_SYM: "mode_symmetries", 

1607 POSITIONS: "positions", 

1608 ALAT: "alat", 

1609 CELL: "cell", 

1610 ELECTRON_PHONON: "ep_data", 

1611 } 

1612 

1613 unique = { 

1614 QPOINTS: True, 

1615 NKPTS: False, 

1616 DIEL: True, 

1617 BORN: True, 

1618 BORN_DFPT: True, 

1619 POLA: True, 

1620 REPR: True, 

1621 EQPOINTS: True, 

1622 DIAG: True, 

1623 MODE_SYM: True, 

1624 POSITIONS: True, 

1625 ALAT: True, 

1626 CELL: True, 

1627 ELECTRON_PHONON: True, 

1628 } 

1629 

1630 results = {} 

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

1632 n_lines = len(fdo_lines) 

1633 

1634 for idx, line in enumerate(fdo_lines): 

1635 for key in output: 

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

1637 output[key].append(idx) 

1638 

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

1640 

1641 def _read_qpoints(idx): 

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

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

1644 

1645 def _read_kpoints(idx): 

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

1647 kpts = [] 

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

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

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

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

1652 return np.array(kpts) 

1653 

1654 def _read_repr(idx): 

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

1656 representations = {} 

1657 while idx + n < n_lines: 

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

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

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

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

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

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

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

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

1666 if curr == n_repr: 

1667 break 

1668 n += 1 

1669 return representations 

1670 

1671 def _read_modes(idx): 

1672 n = 1 

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

1674 modes = [] 

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

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

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

1678 n += 1 

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

1680 

1681 def _read_eqpoints(idx): 

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

1683 return np.loadtxt( 

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

1685 ).reshape(-1, 3) 

1686 

1687 def _read_freqs(idx): 

1688 n = 0 

1689 freqs = [] 

1690 stop = 0 

1691 while not freqs or stop < 2: 

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

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

1694 freqs.append(float(tmp)) 

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

1696 stop += 1 

1697 n += 1 

1698 return np.array(freqs) 

1699 

1700 def _read_sym(idx): 

1701 n = 1 

1702 sym = {} 

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

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

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

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

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

1708 n += 1 

1709 return sym 

1710 

1711 def _read_epsil(idx): 

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

1713 for n in range(1, 4): 

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

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

1716 return epsil 

1717 

1718 def _read_born(idx): 

1719 n = 1 

1720 born = [] 

1721 while idx + n < n_lines: 

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

1723 pass 

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

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

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

1727 else: 

1728 break 

1729 n += 1 

1730 born = np.array(born) 

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

1732 

1733 def _read_born_dfpt(idx): 

1734 n = 1 

1735 born = [] 

1736 while idx + n < n_lines: 

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

1738 pass 

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

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

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

1742 else: 

1743 break 

1744 n += 1 

1745 born = np.array(born) 

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

1747 

1748 def _read_pola(idx): 

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

1750 for n in range(1, 4): 

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

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

1753 return pola 

1754 

1755 def _read_positions(idx): 

1756 positions = [] 

1757 symbols = [] 

1758 n = 1 

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

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

1761 positions.append( 

1762 [round(float(x), 5) 

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

1764 ) 

1765 n += 1 

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

1767 atoms.pbc = True 

1768 return atoms 

1769 

1770 def _read_alat(idx): 

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

1772 

1773 def _read_cell(idx): 

1774 cell = [] 

1775 n = 1 

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

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

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

1779 n += 1 

1780 return np.array(cell) 

1781 

1782 def _read_electron_phonon(idx): 

1783 results = {} 

1784 

1785 broad_re = ( 

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

1787 ) 

1788 dos_re = ( 

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

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

1791 ) 

1792 lg_re = ( 

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

1794 ) 

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

1796 

1797 lambdas = [] 

1798 gammas = [] 

1799 

1800 current = None 

1801 

1802 n = 1 

1803 while idx + n < n_lines: 

1804 line = fdo_lines[idx + n] 

1805 

1806 broad_match = re.match(broad_re, line) 

1807 dos_match = re.match(dos_re, line) 

1808 lg_match = re.match(lg_re, line) 

1809 end_match = re.match(end_re, line) 

1810 

1811 if broad_match: 

1812 if lambdas: 

1813 results[current]["lambdas"] = lambdas 

1814 results[current]["gammas"] = gammas 

1815 lambdas = [] 

1816 gammas = [] 

1817 current = float(broad_match[1]) 

1818 results[current] = {} 

1819 elif dos_match: 

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

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

1822 elif lg_match: 

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

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

1825 

1826 if end_match: 

1827 results[current]["lambdas"] = lambdas 

1828 results[current]["gammas"] = gammas 

1829 break 

1830 

1831 n += 1 

1832 

1833 return results 

1834 

1835 properties = { 

1836 NKPTS: _read_kpoints, 

1837 DIEL: _read_epsil, 

1838 BORN: _read_born, 

1839 BORN_DFPT: _read_born_dfpt, 

1840 POLA: _read_pola, 

1841 REPR: _read_repr, 

1842 EQPOINTS: _read_eqpoints, 

1843 DIAG: _read_freqs, 

1844 MODE_SYM: _read_sym, 

1845 POSITIONS: _read_positions, 

1846 ALAT: _read_alat, 

1847 CELL: _read_cell, 

1848 ELECTRON_PHONON: _read_electron_phonon, 

1849 } 

1850 

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

1852 

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

1854 qpoint = _read_qpoints(past) 

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

1856 for prop in properties: 

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

1858 selected = output[prop][p] 

1859 if len(selected) == 0: 

1860 continue 

1861 if unique[prop]: 

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

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

1864 else: 

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

1866 for k, idx in enumerate(selected): 

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

1868 curr_result[names[prop]] = tmp 

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

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

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

1872 if atoms: 

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

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

1875 atoms.wrap() 

1876 curr_result["atoms"] = atoms 

1877 

1878 return results 

1879 

1880 

1881def write_fortran_namelist( 

1882 fd, 

1883 input_data=None, 

1884 binary=None, 

1885 additional_cards=None, 

1886 **kwargs) -> None: 

1887 """ 

1888 Function which writes input for simple espresso binaries. 

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

1890 Non-exhaustive list (to complete) 

1891 

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

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

1894 

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

1896 to be a string or a list of strings. 

1897 

1898 Parameters 

1899 ---------- 

1900 fd 

1901 The file descriptor of the input file. 

1902 input_data: dict 

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

1904 binary: str 

1905 Name of the binary 

1906 additional_cards: str | list[str] 

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

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

1909 

1910 Returns 

1911 ------- 

1912 None 

1913 """ 

1914 input_data = Namelist(input_data) 

1915 

1916 if binary: 

1917 input_data.to_nested(binary, **kwargs) 

1918 

1919 pwi = input_data.to_string() 

1920 

1921 fd.write(pwi) 

1922 

1923 if additional_cards: 

1924 if isinstance(additional_cards, list): 

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

1926 additional_cards += "\n" 

1927 

1928 fd.write(additional_cards) 

1929 

1930 fd.write("EOF") 

1931 

1932 

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

1934 DeprecationWarning) 

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

1936 """ 

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

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

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

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

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

1942 Namelist object is a case-insensitive dict. 

1943 

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

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

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

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

1948 

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

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

1951 

1952 The priority of the keys is: 

1953 kwargs[key] > parameters[key] > parameters[section][key] 

1954 Only the highest priority item will be included. 

1955 

1956 .. deprecated:: 3.23.0 

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

1958 

1959 Parameters 

1960 ---------- 

1961 parameters: dict 

1962 Flat or nested set of input parameters. 

1963 keys: Namelist | dict 

1964 Namelist to use as a template for the output. 

1965 warn: bool 

1966 Enable warnings for unused keys. 

1967 

1968 Returns 

1969 ------- 

1970 input_namelist: Namelist 

1971 pw.x compatible namelist of input parameters. 

1972 

1973 """ 

1974 

1975 if keys is None: 

1976 keys = deepcopy(pw_keys) 

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

1978 if parameters is None: 

1979 parameters = Namelist() 

1980 else: 

1981 # Maximum one level of nested dict 

1982 # Don't modify in place 

1983 parameters_namelist = Namelist() 

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

1985 if isinstance(value, dict): 

1986 parameters_namelist[key] = Namelist(value) 

1987 else: 

1988 parameters_namelist[key] = value 

1989 parameters = parameters_namelist 

1990 

1991 # Just a dict 

1992 kwargs = Namelist(kwargs) 

1993 

1994 # Final parameter set 

1995 input_namelist = Namelist() 

1996 

1997 # Collect 

1998 for section in keys: 

1999 sec_list = Namelist() 

2000 for key in keys[section]: 

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

2002 # we can check for missing values later 

2003 value = None 

2004 

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

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

2007 if key in parameters: 

2008 value = parameters.pop(key) 

2009 if key in kwargs: 

2010 value = kwargs.pop(key) 

2011 

2012 if value is not None: 

2013 sec_list[key] = value 

2014 

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

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

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

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

2019 cp_parameters = parameters.copy() 

2020 for arg_key in cp_parameters: 

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

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

2023 cp_kwargs = kwargs.copy() 

2024 for arg_key in cp_kwargs: 

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

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

2027 

2028 # Add to output 

2029 input_namelist[section] = sec_list 

2030 

2031 unused_keys = list(kwargs) 

2032 # pass anything else already in a section 

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

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

2035 input_namelist[key].update(value) 

2036 elif isinstance(value, dict): 

2037 unused_keys.extend(list(value)) 

2038 else: 

2039 unused_keys.append(key) 

2040 

2041 if warn and unused_keys: 

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

2043 

2044 return input_namelist 

2045 

2046 

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

2048 DeprecationWarning) 

2049def namelist_to_string(input_parameters): 

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

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

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

2053 

2054 .. deprecated:: 3.23.0 

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

2056 instead. 

2057 

2058 Parameters 

2059 ---------- 

2060 input_parameters : Namelist | dict 

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

2062 

2063 Returns 

2064 ------- 

2065 pwi : List[str] 

2066 Input line for the namelist 

2067 """ 

2068 pwi = [] 

2069 for section in input_parameters: 

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

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

2072 if value is True: 

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

2074 elif value is False: 

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

2076 elif isinstance(value, Path): 

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

2078 else: 

2079 # repr format to get quotes around strings 

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

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

2082 pwi.append('\n') 

2083 return pwi