Coverage for gpaw/calculator.py: 88%

1223 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-20 00:19 +0000

1"""This module defines an ASE-calculator interface to GPAW. 

2 

3The central object that glues everything together. 

4""" 

5 

6import warnings 

7from typing import Any, Dict 

8 

9import gpaw 

10import gpaw.mpi as mpi 

11import numpy as np 

12from ase import Atoms 

13from ase.calculators.calculator import Calculator, kpts2ndarray 

14from ase.units import Bohr, Ha 

15from ase.utils import plural 

16from ase.utils.timing import Timer 

17from gpaw.band_descriptor import BandDescriptor 

18from gpaw.convergence_criteria import dict2criterion 

19from gpaw.density import RealSpaceDensity 

20from gpaw.dos import DOSCalculator 

21from gpaw.eigensolvers import get_eigensolver 

22from gpaw.external import PointChargePotential 

23from gpaw.forces import calculate_forces 

24from gpaw.grid_descriptor import GridDescriptor 

25from gpaw.hamiltonian import RealSpaceHamiltonian 

26from gpaw.hybrids import HybridXC 

27from gpaw.io import Reader, Writer 

28from gpaw.io.logger import GPAWLogger 

29from gpaw.jellium import create_background_charge 

30from gpaw.kohnsham_layouts import get_KohnSham_layouts 

31from gpaw.kpt_descriptor import KPointDescriptor 

32from gpaw.kpt_refine import create_kpoint_descriptor_with_refinement 

33from gpaw.matrix import suggest_blocking 

34from gpaw.occupations import ParallelLayout, create_occ_calc 

35from gpaw.output import (print_cell, print_parallelization_details, 

36 print_positions) 

37from gpaw.pw.density import ReciprocalSpaceDensity 

38from gpaw.pw.hamiltonian import ReciprocalSpaceHamiltonian 

39from gpaw.scf import SCFLoop, SCFEvent 

40from gpaw.setup import Setups 

41from gpaw.stress import calculate_stress 

42from gpaw.symmetry import Symmetry 

43from gpaw.typing import Array1D 

44from gpaw.utilities import check_atoms_too_close, compiled_with_sl 

45from gpaw.utilities.gpts import get_number_of_grid_points 

46from gpaw.utilities.grid import GridRedistributor 

47from gpaw.utilities.memory import MemNode, maxrss 

48from gpaw.utilities.partition import AtomPartition 

49from gpaw.wavefunctions.mode import create_wave_function_mode 

50from gpaw.xc import XC 

51from gpaw.xc.kernel import XCKernel 

52from gpaw.xc.sic import SIC 

53 

54 

55class GPAW(Calculator): 

56 """This is the ASE-calculator frontend for doing a GPAW calculation.""" 

57 

58 implemented_properties = ['energy', 'free_energy', 

59 'forces', 'stress', 

60 'dipole', 'magmom', 'magmoms'] 

61 

62 default_parameters: Dict[str, Any] = { 

63 'mode': None, # issue #897: start deprecating reliance on default mode 

64 'xc': 'LDA', 

65 'occupations': None, 

66 'poissonsolver': None, 

67 'h': None, # Angstrom 

68 'gpts': None, 

69 'kpts': [(0.0, 0.0, 0.0)], 

70 'nbands': None, 

71 'charge': 0, 

72 'setups': {}, 

73 'basis': {}, 

74 'spinpol': None, 

75 'filter': None, 

76 'mixer': None, 

77 'eigensolver': None, 

78 'background_charge': None, 

79 'experimental': {'reuse_wfs_method': 'paw', 

80 'niter_fixdensity': 0, 

81 'magmoms': None, 

82 'soc': None, 

83 'kpt_refine': None}, 

84 'external': None, 

85 'random': False, 

86 'hund': False, 

87 'maxiter': 333, 

88 'symmetry': {'point_group': True, 

89 'time_reversal': True, 

90 'symmorphic': True, 

91 'tolerance': 1e-7, 

92 'do_not_symmetrize_the_density': None}, # deprecated 

93 'convergence': {'energy': 0.0005, # eV / electron 

94 'density': 1.0e-4, # electrons / electron 

95 'eigenstates': 4.0e-8, # eV^2 / electron 

96 'bands': 'occupied'}, 

97 'verbose': 0, 

98 'fixdensity': False, # deprecated 

99 'dtype': None} # deprecated 

100 

101 default_parallel: Dict[str, Any] = { 

102 'kpt': None, 

103 'domain': None, 

104 'band': None, 

105 'order': 'kdb', 

106 'stridebands': False, 

107 'augment_grids': False, 

108 'sl_auto': False, 

109 'sl_default': None, 

110 'sl_diagonalize': None, 

111 'sl_inverse_cholesky': None, 

112 'sl_lcao': None, 

113 'sl_lrtddft': None, 

114 'use_elpa': False, 

115 'elpasolver': '2stage', 

116 'buffer_size': None} 

117 

118 old = True 

119 

120 def __init__(self, 

121 restart=None, 

122 *, 

123 label=None, 

124 timer=None, 

125 communicator=None, 

126 txt='?', 

127 parallel=None, 

128 **kwargs): 

129 

130 if txt == '?': 

131 txt = '-' if restart is None else None 

132 

133 self.parallel = dict(self.default_parallel) 

134 if parallel: 

135 for key in parallel: 

136 if key not in self.default_parallel: 

137 allowed = ', '.join(list(self.default_parallel.keys())) 

138 raise TypeError('Unexpected keyword "{}" in "parallel" ' 

139 'dictionary. Must be one of: {}' 

140 .format(key, allowed)) 

141 self.parallel.update(parallel) 

142 

143 if timer is None: 

144 self.timer = Timer() 

145 else: 

146 self.timer = timer 

147 

148 self.scf = None 

149 self.wfs = None 

150 self.density = None 

151 self.hamiltonian = None 

152 self.spos_ac = None # XXX store this in some better way. 

153 

154 self.observers = [] # XXX move to self.scf 

155 self.initialized = False 

156 

157 self.world = communicator 

158 if self.world is None: 

159 self.world = mpi.world 

160 elif not hasattr(self.world, 'new_communicator'): 

161 self.world = mpi.world.new_communicator(np.asarray(self.world)) 

162 

163 self.log = GPAWLogger(world=self.world) 

164 self.log.fd = txt 

165 

166 self.reader = None 

167 

168 Calculator.__init__(self, restart, label=label, _set_ok=True, **kwargs) 

169 

170 def new(self, 

171 timer=None, 

172 communicator=None, 

173 txt='-', 

174 parallel=None, 

175 **kwargs): 

176 """Create a new calculator, inheriting input parameters. 

177 

178 The ``txt`` file and timer are the only input parameters to 

179 be created anew. Internal variables, such as the density 

180 or the wave functions, are not reused either. 

181 

182 For example, to perform an identical calculation with a 

183 parameter changed (e.g. changing XC functional to PBE):: 

184 

185 new_calc = calc.new(xc='PBE') 

186 atoms.calc = new_calc 

187 """ 

188 assert 'atoms' not in kwargs 

189 assert 'restart' not in kwargs 

190 assert 'ignore_bad_restart_file' not in kwargs 

191 assert 'label' not in kwargs 

192 

193 # Let the communicator fall back to world 

194 if communicator is None: 

195 communicator = self.world 

196 

197 if parallel is not None: 

198 new_parallel = dict(self.parallel) 

199 new_parallel.update(parallel) 

200 else: 

201 new_parallel = None 

202 

203 new_kwargs = dict(self.parameters) 

204 new_kwargs.update(kwargs) 

205 

206 return GPAW(timer=timer, communicator=communicator, 

207 txt=txt, parallel=new_parallel, **new_kwargs) 

208 

209 def fixed_density(self, *, 

210 update_fermi_level: bool = False, 

211 communicator=None, 

212 txt='-', 

213 parallel: Dict[str, Any] = None, 

214 **kwargs) -> 'GPAW': 

215 """Create new calculator and do SCF calculation with fixed density. 

216 

217 Returns a new GPAW object fully converged. 

218 

219 Useful for band-structure calculations. Given a ground-state 

220 calculation, ``gs_calc``, one can do:: 

221 

222 bs_calc = gs_calc.fixed_density(kpts=<path>, 

223 symmetry='off') 

224 bs = bs_calc.get_band_structure() 

225 

226 Parameters 

227 ========== 

228 update_fermi_level: 

229 Update or keep the old Fermi-level. 

230 """ 

231 

232 for key in kwargs: 

233 if key not in {'nbands', 'occupations', 'poissonsolver', 'kpts', 

234 'eigensolver', 'random', 'maxiter', 'basis', 

235 'symmetry', 'convergence', 'verbose'}: 

236 raise TypeError(f'Cannot change {key!r} in ' 

237 'fixed_density calculation!') 

238 

239 params = self.parameters.copy() 

240 params.update(kwargs) 

241 

242 if params['h'] is None: 

243 # Backwards compatibility 

244 params['gpts'] = self.density.gd.N_c 

245 

246 calc = GPAW(communicator=communicator, 

247 txt=txt, 

248 parallel=parallel, 

249 **params) 

250 calc.initialize(self.atoms) 

251 calc.density.initialize_from_other_density(self.density, 

252 calc.wfs.kptband_comm) 

253 calc.density.fixed = True 

254 calc.wfs.fermi_levels = self.wfs.fermi_levels 

255 calc.scf.fix_fermi_level = not update_fermi_level 

256 if calc.hamiltonian.xc.type == 'GLLB': 

257 new_response = calc.hamiltonian.xc.response 

258 old_response = self.hamiltonian.xc.response 

259 new_response.initialize_from_other_response(old_response) 

260 new_response.fix_potential = True 

261 elif calc.hamiltonian.xc.type == 'MGGA': 

262 for kpt in self.wfs.kpt_u: 

263 if kpt.psit is None: 

264 raise ValueError("Needs wave functions for " 

265 "MGGA fixed density.\n" 

266 "To run from a restart file, it must " 

267 "be written with mode='all'") 

268 self.wfs.initialize_wave_functions_from_restart_file() 

269 taut_sG = self.wfs.calculate_kinetic_energy_density() 

270 wgd = self.wfs.gd.new_descriptor(comm=self.world, 

271 allow_empty_domains=True) 

272 redist = GridRedistributor(self.world, self.wfs.kptband_comm, 

273 self.wfs.gd, wgd) 

274 taut_sG = redist.distribute(taut_sG) 

275 redist = GridRedistributor(self.world, calc.wfs.kptband_comm, 

276 calc.wfs.gd, wgd) 

277 taut_sG = redist.collect(taut_sG) 

278 calc.hamiltonian.xc.fix_kinetic_energy_density(taut_sG) 

279 calc.calculate(system_changes=[]) 

280 return calc 

281 

282 def __enter__(self): 

283 return self 

284 

285 def __exit__(self, *args): 

286 self.close() 

287 

288 def __del__(self): 

289 self.close() 

290 

291 def close(self): 

292 # Write timings and close reader if necessary. 

293 # If we crashed in the constructor (e.g. a bad keyword), we may not 

294 # have the normally expected attributes: 

295 if hasattr(self, 'timer') and not self.log.fd.closed: 

296 self.timer.write(self.log.fd) 

297 

298 if hasattr(self, 'reader') and self.reader is not None: 

299 self.reader.close() 

300 

301 def write(self, filename, mode=''): 

302 """Write calculator object to a file. 

303 

304 Parameters 

305 ---------- 

306 filename 

307 File to be written 

308 mode 

309 Write mode. Use ``mode='all'`` 

310 to include wave functions in the file. 

311 """ 

312 self.log(f'Writing to {filename} (mode={mode!r})\n') 

313 writer = Writer(filename, self.world) 

314 self._write(writer, mode) 

315 writer.close() 

316 self.world.barrier() 

317 

318 def _write(self, writer, mode): 

319 from ase.io.trajectory import write_atoms 

320 writer.write(version=3, gpaw_version=gpaw.__version__, 

321 ha=Ha, bohr=Bohr) 

322 

323 write_atoms(writer.child('atoms'), self.atoms) 

324 writer.child('results').write(**self.results) 

325 writer.child('parameters').write(**self.todict()) 

326 

327 self.density.write(writer.child('density')) 

328 self.hamiltonian.write(writer.child('hamiltonian')) 

329 # self.occupations.write(writer.child('occupations')) 

330 self.scf.write(writer.child('scf')) 

331 self.wfs.write(writer.child('wave_functions'), mode == 'all') 

332 

333 return writer 

334 

335 def _set_atoms(self, atoms): 

336 check_atoms_too_close(atoms) 

337 self.atoms = atoms 

338 mpi.synchronize_atoms(self.atoms, self.world) 

339 

340 # GPAW works in terms of the scaled positions. We want to 

341 # extract the scaled positions in only one place, and that is 

342 # here. No other place may recalculate them, or we might end up 

343 # with rounding errors and inconsistencies. 

344 self.spos_ac = np.ascontiguousarray(atoms.get_scaled_positions() % 1.0) 

345 self.world.broadcast(self.spos_ac, 0) 

346 

347 def read(self, filename): 

348 from ase.io.trajectory import read_atoms 

349 self.log(f'Reading from {filename}') 

350 

351 self.reader = reader = Reader(filename) 

352 # assert reader.version <= 3, 'Can\'t read new GPW-files' 

353 

354 atoms = read_atoms(reader.atoms) 

355 self._set_atoms(atoms) 

356 

357 res = reader.results 

358 self.results = {key: res.get(key) for key in res.keys()} 

359 if self.results: 

360 self.log('Read {}'.format(', '.join(sorted(self.results)))) 

361 

362 self.log('Reading input parameters:') 

363 # XXX param 

364 self.parameters = self.get_default_parameters() 

365 dct = {} 

366 for key, value in reader.parameters.asdict().items(): 

367 if key in {'txt', 'fixdensity'}: 

368 continue # old gpw-files may have these 

369 if (isinstance(value, dict) and 

370 isinstance(self.parameters[key], dict)): 

371 self.parameters[key].update(value) 

372 else: 

373 self.parameters[key] = value 

374 dct[key] = self.parameters[key] 

375 

376 self.log.print_dict(dct) 

377 self.log() 

378 

379 self.initialize(reading=True) 

380 

381 self.density.read(reader) 

382 self.hamiltonian.read(reader) 

383 self.scf.read(reader) 

384 self.wfs.read(reader) 

385 

386 # We need to do this in a better way: XXX 

387 from gpaw.utilities.partition import AtomPartition 

388 atom_partition = AtomPartition(self.wfs.gd.comm, 

389 np.zeros(len(self.atoms), dtype=int)) 

390 self.density.atom_partition = atom_partition 

391 self.hamiltonian.atom_partition = atom_partition 

392 rank_a = self.density.gd.get_ranks_from_positions(self.spos_ac) 

393 new_atom_partition = AtomPartition(self.density.gd.comm, rank_a) 

394 for obj in [self.density, self.hamiltonian]: 

395 obj.set_positions_without_ruining_everything(self.spos_ac, 

396 new_atom_partition) 

397 if new_atom_partition != atom_partition: 

398 for kpt in self.wfs.kpt_u: 

399 kpt.projections = kpt.projections.redist(new_atom_partition) 

400 self.wfs.atom_partition = new_atom_partition 

401 

402 self.hamiltonian.xc.read(reader) 

403 

404 return reader 

405 

406 def check_state(self, atoms, tol=1e-12): 

407 system_changes = Calculator.check_state(self, atoms, tol) 

408 if 'positions' not in system_changes: 

409 if self.hamiltonian: 

410 if self.hamiltonian.vext: 

411 if self.hamiltonian.vext.vext_g is None: 

412 # QMMM atoms have moved: 

413 system_changes.append('positions') 

414 return system_changes 

415 

416 def calculate(self, atoms=None, properties=['energy'], 

417 system_changes=['cell']): 

418 for _ in self.icalculate(atoms, properties, system_changes): 

419 pass 

420 

421 def icalculate(self, atoms=None, properties=['energy'], 

422 system_changes=['cell']): 

423 """Calculate things.""" 

424 

425 Calculator.calculate(self, atoms) 

426 atoms = self.atoms 

427 

428 if system_changes: 

429 self.log('System changes:', ', '.join(system_changes), '\n') 

430 if self.density is not None and system_changes == ['positions']: 

431 # Only positions have changed: 

432 self.density.reset() 

433 else: 

434 # Drastic changes: 

435 self.wfs = None 

436 self.density = None 

437 self.hamiltonian = None 

438 self.scf = None 

439 self.initialize(atoms) 

440 

441 self.set_positions(atoms) 

442 

443 if not self.initialized: 

444 self.initialize(atoms) 

445 self.set_positions(atoms) 

446 

447 if not (self.wfs.positions_set and self.hamiltonian.positions_set): 

448 self.set_positions(atoms) 

449 

450 yield SCFEvent(self.density, self.hamiltonian, self.wfs, 0, self.log) 

451 

452 if not self.scf.converged: 

453 print_cell(self.wfs.gd, self.atoms.pbc, self.log) 

454 

455 with self.timer('SCF-cycle'): 

456 yield from self.scf.irun( 

457 self.wfs, self.hamiltonian, 

458 self.density, 

459 self.log, self.call_observers) 

460 

461 self.log('\nConverged after {} iterations.\n' 

462 .format(self.scf.niter)) 

463 

464 e_free = self.hamiltonian.e_total_free 

465 e_extrapolated = self.hamiltonian.e_total_extrapolated 

466 self.results['energy'] = e_extrapolated * Ha 

467 self.results['free_energy'] = e_free * Ha 

468 

469 dipole_v = self.density.calculate_dipole_moment() * Bohr 

470 self.log('Dipole moment: ({:.6f}, {:.6f}, {:.6f}) |e|*Ang\n' 

471 .format(*dipole_v)) 

472 self.results['dipole'] = dipole_v 

473 

474 if self.wfs.nspins == 2 or not self.density.collinear: 

475 totmom_v, magmom_av = self.density.estimate_magnetic_moments() 

476 self.log('Total magnetic moment: ({:.6f}, {:.6f}, {:.6f})' 

477 .format(*totmom_v)) 

478 self.log('Local magnetic moments:') 

479 symbols = self.atoms.get_chemical_symbols() 

480 for a, mom_v in enumerate(magmom_av): 

481 self.log('{:4} {:2} ({:9.6f}, {:9.6f}, {:9.6f})' 

482 .format(a, symbols[a], *mom_v)) 

483 self.log() 

484 self.results['magmom'] = totmom_v[2] 

485 self.results['magmoms'] = magmom_av[:, 2].copy() 

486 else: 

487 self.results['magmom'] = 0.0 

488 self.results['magmoms'] = np.zeros(len(self.atoms)) 

489 

490 occ_name = getattr(self.wfs.occupations, "name", None) 

491 if occ_name == 'mom' and self.wfs.occupations.update_numbers: 

492 if isinstance(self.parameters.occupations, dict): 

493 for s, numbers in enumerate(self.wfs.occupations.numbers): 

494 self.parameters['occupations']['numbers'][s] = numbers 

495 

496 self.summary() 

497 

498 self.call_observers(self.scf.niter, final=True) 

499 

500 if 'forces' in properties: 

501 with self.timer('Forces'): 

502 F_av = calculate_forces(self.wfs, self.density, 

503 self.hamiltonian, self.log) 

504 self.results['forces'] = F_av * (Ha / Bohr) 

505 

506 if 'stress' in properties: 

507 with self.timer('Stress'): 

508 try: 

509 stress = calculate_stress(self).flat[[0, 4, 8, 5, 2, 1]] 

510 except NotImplementedError: 

511 # Our ASE Calculator base class will raise 

512 # PropertyNotImplementedError for us. 

513 pass 

514 else: 

515 self.results['stress'] = stress * (Ha / Bohr**3) 

516 

517 def _print_gapinfo(self): 

518 try: 

519 from ase.dft.bandgap import GapInfo 

520 except ImportError: 

521 print('No gapinfo -- requires new ASE', file=self.log.fd) 

522 return 

523 

524 if len(self.wfs.fermi_levels) == 1: 

525 try: 

526 gaptext = GapInfo.fromcalc(self).description( 

527 ibz_kpoints=self.get_ibz_k_points()) 

528 except ValueError: 

529 gaptext = 'Could not find a gap' 

530 

531 print(gaptext, file=self.log.fd) 

532 

533 def summary(self): 

534 self.hamiltonian.summary(self.wfs, self.log) 

535 self.density.summary(self.atoms, self.results.get('magmom', 0.0), 

536 self.log) 

537 self.wfs.summary(self.log) 

538 self._print_gapinfo() 

539 

540 self.log.fd.flush() 

541 

542 def set(self, _set_ok=False, **kwargs): 

543 """Change parameters for calculator. 

544 

545 Example:: 

546 

547 calc.set(eigensolver=...) 

548 """ 

549 if not _set_ok: 

550 # We want to get rid of cal.set(...), but these are still in use, 

551 # so we allow them for now 

552 if not kwargs.keys() <= {'eigensolver', 'external', 

553 'convergence', 'txt', 

554 'xc', 'occupations'}: 

555 raise ValueError( 

556 'Please use new(...) instead of set(...)') 

557 

558 # Verify that keys are consistent with default ones. 

559 for key in kwargs: 

560 if key != 'txt' and key not in self.default_parameters: 

561 raise TypeError(f'Unknown GPAW parameter: {key}') 

562 

563 if key in ['symmetry', 

564 'experimental'] and isinstance(kwargs[key], dict): 

565 # For values that are dictionaries, verify subkeys, too. 

566 default_dict = self.default_parameters[key] 

567 for subkey in kwargs[key]: 

568 if subkey not in default_dict: 

569 allowed = ', '.join(list(default_dict.keys())) 

570 raise TypeError('Unknown subkeyword "{}" of keyword ' 

571 '"{}". Must be one of: {}' 

572 .format(subkey, key, allowed)) 

573 

574 # We need to handle txt early in order to get logging up and running: 

575 if 'txt' in kwargs: 

576 self.log.fd = kwargs.pop('txt') 

577 

578 if 'idiotproof' in kwargs: 

579 del kwargs['idiotproof'] 

580 warnings.warn('Ignoring deprecated keyword "idiotproof"', 

581 DeprecatedParameterWarning) 

582 

583 changed_parameters = Calculator.set(self, **kwargs) 

584 

585 for key in ['setups', 'basis']: 

586 if key in changed_parameters: 

587 dct = changed_parameters[key] 

588 if isinstance(dct, dict) and None in dct: 

589 dct['default'] = dct.pop(None) 

590 warnings.warn( 

591 f'Please use {key}={dct}', 

592 DeprecatedParameterWarning) 

593 

594 if not changed_parameters: 

595 return {} 

596 

597 self.initialized = False 

598 self.scf = None 

599 self.results = {} 

600 

601 self.log('Input parameters:') 

602 self.log.print_dict(changed_parameters) 

603 self.log() 

604 

605 for key in changed_parameters: 

606 if key in ['eigensolver', 'convergence'] and self.wfs: 

607 self.wfs.set_eigensolver(None) 

608 

609 if key in ['mixer', 'verbose', 'txt', 'hund', 'random', 

610 'eigensolver']: 

611 continue 

612 

613 if key in ['convergence', 'fixdensity', 'maxiter']: 

614 continue 

615 

616 # Check nested arguments 

617 if key in ['experimental']: 

618 changed_parameters2 = changed_parameters[key] 

619 for key2 in changed_parameters2: 

620 if key2 in ['kpt_refine', 'magmoms', 'soc']: 

621 self.wfs = None 

622 elif key2 in ['reuse_wfs_method', 'niter_fixdensity']: 

623 continue 

624 else: 

625 raise TypeError('Unknown keyword argument:', key2) 

626 continue 

627 

628 # More drastic changes: 

629 if self.wfs: 

630 self.wfs.set_orthonormalized(False) 

631 if key in ['xc', 'poissonsolver']: 

632 self.hamiltonian = None 

633 elif key in ['occupations', 'width']: 

634 pass 

635 elif key in ['external', 'charge', 'background_charge']: 

636 self.hamiltonian = None 

637 self.density = None 

638 self.wfs = None 

639 elif key in ['kpts', 'nbands', 'symmetry']: 

640 self.wfs = None 

641 elif key in ['h', 'gpts', 'setups', 'spinpol', 'dtype', 'mode']: 

642 self.density = None 

643 self.hamiltonian = None 

644 self.wfs = None 

645 elif key in ['basis']: 

646 self.wfs = None 

647 else: 

648 raise TypeError('Unknown keyword argument: "%s"' % key) 

649 

650 def initialize_positions(self, atoms=None): 

651 """Update the positions of the atoms.""" 

652 self.log('Initializing position-dependent things.\n') 

653 if atoms is None: 

654 atoms = self.atoms 

655 else: 

656 atoms = atoms.copy() 

657 self._set_atoms(atoms) 

658 

659 rank_a = self.wfs.gd.get_ranks_from_positions(self.spos_ac) 

660 atom_partition = AtomPartition(self.wfs.gd.comm, rank_a, name='gd') 

661 self.wfs.set_positions(self.spos_ac, atom_partition) 

662 self.density.set_positions(self.spos_ac, atom_partition) 

663 self.hamiltonian.set_positions(self.spos_ac, atom_partition) 

664 

665 def set_positions(self, atoms=None): 

666 """Update the positions of the atoms and initialize wave functions.""" 

667 self.initialize_positions(atoms) 

668 

669 nlcao, nrand = self.wfs.initialize(self.density, self.hamiltonian, 

670 self.spos_ac) 

671 if nlcao + nrand: 

672 self.log('Creating initial wave functions:') 

673 if nlcao: 

674 self.log(' ', plural(nlcao, 'band'), 'from LCAO basis set') 

675 if nrand: 

676 self.log(' ', plural(nrand, 'band'), 'from random numbers') 

677 self.log() 

678 

679 self.wfs.eigensolver.reset() 

680 self.scf.reset() 

681 occ_name = getattr(self.wfs.occupations, "name", None) 

682 if occ_name == 'mom': 

683 # Initialize MOM reference orbitals 

684 self.wfs.occupations.initialize_reference_orbitals() 

685 print_positions(self.atoms, self.log, self.density.magmom_av) 

686 

687 def initialize(self, atoms=None, reading=False): 

688 """Inexpensive initialization.""" 

689 

690 self.log('Initialize ...\n') 

691 

692 if atoms is None: 

693 atoms = self.atoms 

694 else: 

695 atoms = atoms.copy() 

696 self._set_atoms(atoms) 

697 

698 par = self.parameters 

699 

700 natoms = len(atoms) 

701 

702 cell_cv = atoms.get_cell() / Bohr 

703 number_of_lattice_vectors = cell_cv.any(axis=1).sum() 

704 if number_of_lattice_vectors < 3: 

705 raise ValueError( 

706 'GPAW requires 3 lattice vectors. Your system has {}.' 

707 .format(number_of_lattice_vectors)) 

708 

709 pbc_c = atoms.get_pbc() 

710 assert len(pbc_c) == 3 

711 magmom_a = atoms.get_initial_magnetic_moments() 

712 

713 if par.experimental.get('magmoms') is not None: 

714 magmom_av = np.array(par.experimental['magmoms'], float) 

715 collinear = False 

716 else: 

717 magmom_av = np.zeros((natoms, 3)) 

718 magmom_av[:, 2] = magmom_a 

719 collinear = True 

720 

721 mpi.synchronize_atoms(atoms, self.world) 

722 

723 # Generate new xc functional only when it is reset by set 

724 # XXX sounds like this should use the _changed_keywords dictionary. 

725 if self.hamiltonian is None or self.hamiltonian.xc is None: 

726 if isinstance(par.xc, (str, dict, XCKernel)): 

727 xc = XC(par.xc, collinear=collinear, atoms=atoms) 

728 else: 

729 xc = par.xc 

730 else: 

731 xc = self.hamiltonian.xc 

732 

733 if not collinear and xc.type != 'LDA': 

734 raise ValueError('Only LDA supported for ' 

735 'SC Non-collinear calculations') 

736 

737 if par.fixdensity: 

738 warnings.warn( 

739 ('The fixdensity keyword has been deprecated. ' 

740 'Please use the GPAW.fixed_density() method instead.'), 

741 DeprecatedParameterWarning) 

742 if self.hamiltonian.xc.type == 'MGGA': 

743 raise ValueError('MGGA does not support deprecated ' 

744 'fixdensity option.') 

745 

746 mode = par.mode 

747 if mode is None: 

748 warnings.warn( 

749 ('Finite-difference mode implicitly chosen; ' 

750 'it will be an error to not specify a mode in the future'), 

751 DeprecatedParameterWarning) 

752 mode = 'fd' 

753 par.mode = 'fd' 

754 if isinstance(mode, str): 

755 mode = {'name': mode} 

756 if isinstance(mode, dict): 

757 mode = create_wave_function_mode(**mode) 

758 

759 if par.dtype == complex: 

760 warnings.warn( 

761 ('Use mode={}(..., force_complex_dtype=True) ' 

762 'instead of dtype=complex').format(mode.name.upper()), 

763 DeprecatedParameterWarning) 

764 mode.force_complex_dtype = True 

765 del par['dtype'] 

766 par.mode = mode 

767 

768 if xc.orbital_dependent and mode.name == 'lcao': 

769 raise ValueError('LCAO mode does not support ' 

770 'orbital-dependent XC functionals.') 

771 

772 realspace = mode.interpolation != 'fft' 

773 

774 self.create_setups(mode, xc) 

775 

776 if not realspace: 

777 pbc_c = np.ones(3, bool) 

778 

779 magnetic = magmom_av.any() 

780 

781 if par.hund: 

782 spinpol = True 

783 magnetic = True 

784 c = par.charge / natoms 

785 for a, setup in enumerate(self.setups): 

786 magmom_av[a, 2] = setup.get_hunds_rule_moment(c) 

787 

788 if collinear: 

789 spinpol = par.spinpol 

790 if spinpol is None: 

791 spinpol = magnetic 

792 elif magnetic and not spinpol: 

793 raise ValueError('Non-zero initial magnetic moment for a ' + 

794 'spin-paired calculation!') 

795 nspins = 1 + int(spinpol) 

796 

797 if spinpol: 

798 self.log('Spin-polarized calculation.') 

799 self.log(f'Initial magnetic moment: {magmom_av.sum():.6f}\n') 

800 else: 

801 self.log('Spin-paired calculation\n') 

802 else: 

803 nspins = 1 

804 self.log('Non-collinear calculation.') 

805 self.log('Initial magnetic moment: ({:.6f}, {:.6f}, {:.6f})\n' 

806 .format(*magmom_av.sum(0))) 

807 

808 self.create_symmetry(magmom_av, cell_cv, reading) 

809 

810 if par.gpts is not None: 

811 if par.h is not None: 

812 raise ValueError("""You can't use both "gpts" and "h"!""") 

813 N_c = np.array(par.gpts) 

814 h = None 

815 else: 

816 h = par.h 

817 if h is not None: 

818 h /= Bohr 

819 if h is None and reading: 

820 shape = self.reader.density.proxy('density').shape[-3:] 

821 N_c = 1 - pbc_c + shape 

822 elif h is None and self.density is not None: 

823 N_c = self.density.gd.N_c 

824 else: 

825 N_c = get_number_of_grid_points(cell_cv, h, mode, realspace, 

826 self.symmetry) 

827 

828 self.setups.set_symmetry(self.symmetry) 

829 

830 if not collinear and len(self.symmetry.op_scc) > 1: 

831 raise ValueError('Can''t use symmetries with non-collinear ' 

832 'calculations') 

833 

834 if isinstance(par.background_charge, dict): 

835 background = create_background_charge(**par.background_charge) 

836 else: 

837 background = par.background_charge 

838 

839 nao = self.setups.nao 

840 nvalence = self.setups.nvalence - par.charge 

841 if par.background_charge is not None: 

842 nvalence += background.charge 

843 

844 M = np.linalg.norm(magmom_av.sum(0)) 

845 

846 nbands = par.nbands 

847 

848 orbital_free = any(setup.orbital_free for setup in self.setups) 

849 if orbital_free: 

850 nbands = 1 

851 

852 if isinstance(nbands, str): 

853 if nbands == 'nao': 

854 nbands = nao 

855 elif nbands[-1] == '%': 

856 basebands = (nvalence + M) / 2 

857 nbands = int(np.ceil(float(nbands[:-1]) / 100 * basebands)) 

858 else: 

859 raise ValueError('Integer expected: Only use a string ' 

860 'if giving a percentage of occupied bands') 

861 

862 if nbands is None: 

863 # Number of bound partial waves: 

864 nbandsmax = sum(setup.get_default_nbands() 

865 for setup in self.setups) 

866 nbands = int(np.ceil(1.2 * (nvalence + M) / 2)) + 4 

867 if nbands > nbandsmax: 

868 nbands = nbandsmax 

869 if mode.name == 'lcao' and nbands > nao: 

870 nbands = nao 

871 elif nbands <= 0: 

872 nbands = max(1, int(nvalence + M + 0.5) // 2 + (-nbands)) 

873 

874 if nbands > nao and mode.name == 'lcao': 

875 raise ValueError('Too many bands for LCAO calculation: ' 

876 '%d bands and only %d atomic orbitals!' % 

877 (nbands, nao)) 

878 

879 if nvalence < 0: 

880 raise ValueError( 

881 'Charge %f is not possible - not enough valence electrons' % 

882 par.charge) 

883 

884 if nvalence > 2 * nbands and not orbital_free: 

885 raise ValueError('Too few bands! Electrons: %f, bands: %d' 

886 % (nvalence, nbands)) 

887 

888 # Gather convergence criteria for SCF loop. 

889 criteria = self.default_parameters['convergence'].copy() # keep order 

890 criteria.update(par.convergence) 

891 custom = criteria.pop('custom', []) 

892 del criteria['bands'] 

893 for name, criterion in criteria.items(): 

894 if hasattr(criterion, 'todict'): 

895 # 'Copy' so no two calculators share an instance. 

896 criteria[name] = dict2criterion(criterion.todict()) 

897 else: 

898 criteria[name] = dict2criterion({name: criterion}) 

899 

900 if not isinstance(custom, (list, tuple)): 

901 custom = [custom] 

902 for criterion in custom: 

903 if isinstance(criterion, dict): # from .gpw file 

904 msg = ('Custom convergence criterion "{:s}" encountered, ' 

905 'which GPAW does not know how to load. This ' 

906 'criterion is NOT enabled; you may want to manually' 

907 ' set it.'.format(criterion['name'])) 

908 warnings.warn(msg) 

909 continue 

910 

911 criteria[criterion.name] = criterion 

912 msg = ('Custom convergence criterion {:s} encountered. ' 

913 'Please be sure that each calculator is fed a ' 

914 'unique instance of this criterion. ' 

915 'Note that if you save the calculator instance to ' 

916 'a .gpw file you may not be able to re-open it. ' 

917 .format(criterion.name)) 

918 warnings.warn(msg) 

919 

920 if self.scf is None: 

921 self.create_scf(criteria, mode) 

922 

923 if not collinear: 

924 nbands *= 2 

925 

926 if not self.wfs: 

927 self.create_wave_functions(mode, realspace, 

928 nspins, collinear, nbands, nao, 

929 nvalence, self.setups, 

930 cell_cv, pbc_c, N_c, 

931 xc) 

932 else: 

933 self.wfs.set_setups(self.setups) 

934 

935 occ = self.create_occupations(cell_cv, magmom_av[:, 2].sum(), 

936 orbital_free, nvalence) 

937 self.wfs.occupations = occ 

938 

939 if not self.wfs.eigensolver: 

940 self.create_eigensolver(xc, nbands, mode) 

941 

942 if self.density is None and not reading: 

943 assert not par.fixdensity, 'No density to fix!' 

944 

945 olddens = None 

946 if (self.density is not None and 

947 (self.density.gd.parsize_c != self.wfs.gd.parsize_c).any()): 

948 # Domain decomposition has changed, so we need to 

949 # reinitialize density and hamiltonian: 

950 if par.fixdensity: 

951 olddens = self.density 

952 

953 self.density = None 

954 self.hamiltonian = None 

955 

956 if self.density is None: 

957 self.create_density(realspace, mode, background, h) 

958 

959 # XXXXXXXXXX if setups change, then setups.core_charge may change. 

960 # But that parameter was supplied in Density constructor! 

961 # This surely is a bug! 

962 self.density.initialize(self.setups, self.timer, 

963 magmom_av, par.hund) 

964 self.density.set_mixer(par.mixer) 

965 if self.density.mixer.driver.name == 'dummy' or par.fixdensity: 

966 self.log('No density mixing\n') 

967 else: 

968 self.log(self.density.mixer, '\n') 

969 self.density.fixed = par.fixdensity 

970 self.density.log = self.log 

971 

972 if olddens is not None: 

973 self.density.initialize_from_other_density(olddens, 

974 self.wfs.kptband_comm) 

975 

976 if self.hamiltonian is None: 

977 self.create_hamiltonian(realspace, mode, xc) 

978 

979 xc.initialize(self.density, self.hamiltonian, self.wfs) 

980 

981 description = xc.get_description() 

982 if description is not None: 

983 self.log('XC parameters: {}\n' 

984 .format('\n '.join(description.splitlines()))) 

985 

986 if xc.type == 'GLLB' and olddens is not None: 

987 xc.heeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeelp(olddens) 

988 

989 self.print_memory_estimate(maxdepth=3) 

990 

991 print_parallelization_details(self.wfs, self.hamiltonian, self.log) 

992 

993 self.log('Number of atoms:', natoms) 

994 self.log('Number of atomic orbitals:', self.wfs.setups.nao) 

995 self.log('Number of bands in calculation:', self.wfs.bd.nbands) 

996 self.log('Number of valence electrons:', self.wfs.nvalence) 

997 

998 n = par.convergence.get('bands', 'occupied') 

999 if isinstance(n, int) and n < 0: 

1000 n += self.wfs.bd.nbands 

1001 

1002 solver_name = getattr(self.wfs.eigensolver, "name", None) 

1003 if solver_name == 'etdm-fdpw': 

1004 if not self.wfs.eigensolver.converge_unocc: 

1005 if n == 'all' or (isinstance(n, int) 

1006 and n > self.wfs.nvalence / 2): 

1007 warnings.warn( 

1008 'Please, use eigensolver=FDPWETDM(..., ' 

1009 'converge_unocc=True) to converge unoccupied bands') 

1010 n = 'occupied' 

1011 else: 

1012 n = 'all' 

1013 

1014 self.log('Bands to converge:', n) 

1015 

1016 self.log(flush=True) 

1017 

1018 self.timer.print_info(self) 

1019 

1020 if gpaw.dry_run: 

1021 self.dry_run() 

1022 

1023 if (realspace and 

1024 self.hamiltonian.poisson.get_description() == 'FDTD+TDDFT'): 

1025 self.hamiltonian.poisson.set_density(self.density) 

1026 self.hamiltonian.poisson.print_messages(self.log) 

1027 self.log.fd.flush() 

1028 

1029 self.initialized = True 

1030 self.log('... initialized\n') 

1031 

1032 def create_setups(self, mode, xc): 

1033 if self.parameters.filter is None and mode.name != 'pw': 

1034 gamma = 1.6 

1035 N_c = self.parameters.get('gpts') 

1036 if N_c is None: 

1037 h = (self.parameters.h or 0.2) / Bohr 

1038 else: 

1039 icell_vc = np.linalg.inv(self.atoms.cell) 

1040 h = ((icell_vc**2).sum(0)**-0.5 / N_c).max() / Bohr 

1041 

1042 def filter(rgd, rcut, f_r, l=0): 

1043 gcut = np.pi / h - 2 / rcut / gamma 

1044 ftmp = rgd.filter(f_r, rcut * gamma, gcut, l) 

1045 f_r[:] = ftmp[:len(f_r)] 

1046 else: 

1047 filter = self.parameters.filter 

1048 

1049 Z_a = self.atoms.get_atomic_numbers() 

1050 self.setups = Setups(Z_a, 

1051 self.parameters.setups, self.parameters.basis, 

1052 xc, filter=filter, world=self.world) 

1053 self.log(self.setups) 

1054 

1055 def create_grid_descriptor(self, N_c, cell_cv, pbc_c, 

1056 domain_comm, parsize_domain): 

1057 return GridDescriptor(N_c, cell_cv, pbc_c, domain_comm, 

1058 parsize_domain) 

1059 

1060 def create_occupations(self, cell_cv, magmom, orbital_free, nvalence): 

1061 dct = self.parameters.occupations 

1062 

1063 if dct is None: 

1064 if orbital_free: 

1065 dct = {'name': 'orbital-free'} 

1066 else: 

1067 if self.atoms.pbc.any(): 

1068 dct = {'name': 'fermi-dirac', 

1069 'width': 0.1} # eV 

1070 else: 

1071 dct = {'width': 0.0} 

1072 elif not isinstance(dct, dict): 

1073 return dct 

1074 

1075 if self.wfs.nspins == 1: 

1076 dct.pop('fixmagmom', None) 

1077 

1078 kwargs = dct.copy() 

1079 name = kwargs.pop('name', '') 

1080 if name == 'mom': 

1081 from gpaw.mom import OccupationsMOM 

1082 occ = OccupationsMOM(self.wfs, **kwargs) 

1083 

1084 self.log(occ) 

1085 return occ 

1086 

1087 occ = create_occ_calc( 

1088 dct, 

1089 parallel_layout=ParallelLayout(self.wfs.bd, 

1090 self.wfs.kd.comm, 

1091 self.wfs.gd.comm), 

1092 fixed_magmom_value=magmom, 

1093 rcell=np.linalg.inv(cell_cv).T, 

1094 monkhorst_pack_size=self.wfs.kd.N_c, 

1095 bz2ibzmap=self.wfs.kd.bz2ibz_k, 

1096 nspins=self.wfs.nspins, 

1097 nelectrons=nvalence, 

1098 nkpts=self.wfs.kd.nibzkpts, 

1099 nbands=self.wfs.bd.nbands 

1100 ) 

1101 

1102 self.log('Occupation numbers:', occ, '\n') 

1103 return occ 

1104 

1105 def create_scf(self, criteria, mode): 

1106 # if mode.name == 'lcao': 

1107 # niter_fixdensity = 0 

1108 # else: 

1109 # niter_fixdensity = 2 

1110 

1111 self.scf = SCFLoop( 

1112 criteria, 

1113 self.parameters.maxiter, 

1114 # XXX make sure niter_fixdensity value is *always* set from default 

1115 # Subdictionary defaults seem to not be set when user provides 

1116 # e.g. {}. We should change that so it works like the ordinary 

1117 # parameters. 

1118 self.parameters.experimental.get('niter_fixdensity', 0)) 

1119 self.log(self.scf) 

1120 

1121 def create_symmetry(self, magmom_av, cell_cv, reading): 

1122 symm = self.parameters.symmetry 

1123 if symm == 'off': 

1124 symm = {'point_group': False, 'time_reversal': False} 

1125 

1126 if 'do_not_symmetrize_the_density' in symm: 

1127 symm = symm.copy() 

1128 dnstd = symm.pop('do_not_symmetrize_the_density') 

1129 if dnstd is not None: 

1130 info = 'Ignoring your "do_not_symmetrize_the_density" keyword!' 

1131 if reading: 

1132 self.log(info) 

1133 else: 

1134 warnings.warn(info + ' Please remove it.', 

1135 DeprecatedParameterWarning) 

1136 

1137 if self.parameters.external is not None: 

1138 symm = symm.copy() 

1139 symm['point_group'] = False 

1140 

1141 if reading and self.reader.version <= 1: 

1142 symm['allow_invert_aperiodic_axes'] = False 

1143 

1144 m_av = magmom_av.round(decimals=3) # round off 

1145 id_a = [id + tuple(m_v) for id, m_v in zip(self.setups.id_a, m_av)] 

1146 self.symmetry = Symmetry(id_a, cell_cv, self.atoms.pbc, **symm) 

1147 self.symmetry.analyze(self.spos_ac) 

1148 

1149 def create_eigensolver(self, xc, nbands, mode): 

1150 # Number of bands to converge: 

1151 nbands_converge = self.parameters.convergence.get('bands', 'occupied') 

1152 if nbands_converge == 'all': 

1153 nbands_converge = nbands 

1154 elif isinstance(nbands_converge, int): 

1155 if nbands_converge < 0: 

1156 nbands_converge += nbands 

1157 eigensolver = get_eigensolver(self.parameters.eigensolver, mode, 

1158 self.parameters.convergence) 

1159 eigensolver.nbands_converge = nbands_converge 

1160 # XXX Eigensolver class doesn't define an nbands_converge property 

1161 

1162 if isinstance(xc, SIC): 

1163 eigensolver.blocksize = 1 

1164 

1165 self.wfs.set_eigensolver(eigensolver) 

1166 

1167 self.log('Eigensolver\n ', self.wfs.eigensolver, '\n') 

1168 

1169 def create_density(self, realspace, mode, background, h): 

1170 gd = self.wfs.gd 

1171 

1172 big_gd = gd.new_descriptor(comm=self.world) 

1173 # Check whether grid is too small. 8 is smallest admissible. 

1174 # (we decide this by how difficult it is to make the tests pass) 

1175 # (Actually it depends on stencils! But let the user deal with it) 

1176 N_c = big_gd.get_size_of_global_array(pad=True) 

1177 too_small = np.any(N_c / big_gd.parsize_c < 8) 

1178 if (self.parallel['augment_grids'] and not too_small and 

1179 mode.name != 'pw'): 

1180 aux_gd = big_gd 

1181 else: 

1182 aux_gd = gd 

1183 

1184 redistributor = GridRedistributor(self.world, 

1185 self.wfs.kptband_comm, 

1186 gd, aux_gd) 

1187 

1188 # Construct grid descriptor for fine grids for densities 

1189 # and potentials: 

1190 finegd = aux_gd.refine() 

1191 

1192 kwargs = dict( 

1193 gd=gd, finegd=finegd, 

1194 nspins=self.wfs.nspins, 

1195 collinear=self.wfs.collinear, 

1196 charge=self.parameters.charge + self.wfs.setups.core_charge, 

1197 redistributor=redistributor, 

1198 background_charge=background) 

1199 

1200 if realspace: 

1201 self.density = RealSpaceDensity(stencil=mode.interpolation, 

1202 **kwargs) 

1203 else: 

1204 if h is None: 

1205 ecut = 2 * self.wfs.pd.ecut 

1206 else: 

1207 ecut = 0.5 * (np.pi / h)**2 

1208 self.density = ReciprocalSpaceDensity(ecut=ecut, **kwargs) 

1209 

1210 self.log(self.density, '\n') 

1211 

1212 def create_hamiltonian(self, realspace, mode, xc): 

1213 dens = self.density 

1214 kwargs = dict( 

1215 gd=dens.gd, finegd=dens.finegd, 

1216 nspins=dens.nspins, 

1217 collinear=dens.collinear, 

1218 setups=dens.setups, 

1219 timer=self.timer, 

1220 xc=xc, 

1221 world=self.world, 

1222 redistributor=dens.redistributor, 

1223 vext=self.parameters.external, 

1224 psolver=self.parameters.poissonsolver, 

1225 charge=dens.charge) 

1226 if realspace: 

1227 self.hamiltonian = RealSpaceHamiltonian(stencil=mode.interpolation, 

1228 **kwargs) 

1229 xc.set_grid_descriptor(self.hamiltonian.finegd) 

1230 else: 

1231 # This code will work if dens.redistributor uses 

1232 # ordinary density.gd as aux_gd 

1233 gd = dens.finegd 

1234 

1235 xc_redist = None 

1236 if self.parallel['augment_grids']: 

1237 from gpaw.grid_descriptor import BadGridError 

1238 try: 

1239 aux_gd = gd.new_descriptor(comm=self.world) 

1240 except BadGridError as err: 

1241 import warnings 

1242 warnings.warn('Ignoring augment_grids: {}' 

1243 .format(err)) 

1244 else: 

1245 bcast_comm = dens.redistributor.broadcast_comm 

1246 xc_redist = GridRedistributor(self.world, bcast_comm, 

1247 gd, aux_gd) 

1248 

1249 self.hamiltonian = ReciprocalSpaceHamiltonian( 

1250 pd2=dens.pd2, pd3=dens.pd3, realpbc_c=self.atoms.pbc, 

1251 xc_redistributor=xc_redist, 

1252 **kwargs) 

1253 xc.set_grid_descriptor(self.hamiltonian.xc_gd) 

1254 

1255 self.hamiltonian.soc = self.parameters.experimental.get('soc') 

1256 self.log(self.hamiltonian, '\n') 

1257 

1258 def create_kpoint_descriptor(self, nspins): 

1259 par = self.parameters 

1260 

1261 # Zero cell vectors that are not periodic so that ASE's 

1262 # kpts2ndarray can handle 1-d and 2-d correctly: 

1263 atoms = Atoms(cell=self.atoms.cell * self.atoms.pbc[:, np.newaxis], 

1264 pbc=self.atoms.pbc) 

1265 bzkpts_kc = kpts2ndarray(par.kpts, atoms) 

1266 

1267 kpt_refine = par.experimental.get('kpt_refine') 

1268 if kpt_refine is None: 

1269 kd = KPointDescriptor(bzkpts_kc, nspins) 

1270 

1271 self.timer.start('Set symmetry') 

1272 kd.set_symmetry(self.atoms, self.symmetry, comm=self.world) 

1273 self.timer.stop('Set symmetry') 

1274 

1275 else: 

1276 self.timer.start('Set k-point refinement') 

1277 kd = create_kpoint_descriptor_with_refinement( 

1278 kpt_refine, 

1279 bzkpts_kc, nspins, self.atoms, 

1280 self.symmetry, comm=self.world, 

1281 timer=self.timer) 

1282 self.timer.stop('Set k-point refinement') 

1283 # Update quantities which might have changed, if symmetry 

1284 # was changed 

1285 self.symmetry = kd.symmetry 

1286 self.setups.set_symmetry(kd.symmetry) 

1287 

1288 self.log(kd) 

1289 

1290 return kd 

1291 

1292 def create_wave_functions(self, mode, realspace, 

1293 nspins, collinear, nbands, nao, nvalence, 

1294 setups, cell_cv, pbc_c, N_c, xc): 

1295 par = self.parameters 

1296 

1297 kd = self.create_kpoint_descriptor(nspins) 

1298 

1299 parallelization = mpi.Parallelization(self.world, 

1300 kd.nibzkpts) 

1301 

1302 parsize_kpt = self.parallel['kpt'] 

1303 parsize_domain = self.parallel['domain'] 

1304 parsize_bands = self.parallel['band'] 

1305 

1306 if isinstance(xc, HybridXC): 

1307 parsize_kpt = 1 

1308 parsize_domain = self.world.size 

1309 parsize_bands = 1 

1310 

1311 ndomains = None 

1312 if parsize_domain is not None: 

1313 ndomains = np.prod(parsize_domain) 

1314 parallelization.set(kpt=parsize_kpt, 

1315 domain=ndomains, 

1316 band=parsize_bands) 

1317 comms = parallelization.build_communicators() 

1318 domain_comm = comms['d'] 

1319 kpt_comm = comms['k'] 

1320 band_comm = comms['b'] 

1321 kptband_comm = comms['D'] 

1322 domainband_comm = comms['K'] 

1323 

1324 self.comms = comms 

1325 

1326 kd.set_communicator(kpt_comm) 

1327 

1328 parstride_bands = self.parallel['stridebands'] 

1329 if parstride_bands: 

1330 raise RuntimeError('stridebands is unreliable') 

1331 

1332 bd = BandDescriptor(nbands, band_comm, parstride_bands) 

1333 

1334 # Construct grid descriptor for coarse grids for wave functions: 

1335 gd = self.create_grid_descriptor(N_c, cell_cv, pbc_c, 

1336 domain_comm, parsize_domain) 

1337 

1338 if hasattr(self, 'time') or mode.force_complex_dtype or not collinear: 

1339 dtype = complex 

1340 else: 

1341 if kd.gamma: 

1342 dtype = float 

1343 else: 

1344 dtype = complex 

1345 

1346 wfs_kwargs = dict(gd=gd, nvalence=nvalence, setups=setups, 

1347 bd=bd, dtype=dtype, world=self.world, kd=kd, 

1348 kptband_comm=kptband_comm, timer=self.timer) 

1349 

1350 if self.parallel['sl_auto'] and compiled_with_sl(): 

1351 # Choose scalapack parallelization automatically 

1352 

1353 for key, val in self.parallel.items(): 

1354 if (key.startswith('sl_') and key != 'sl_auto' and 

1355 val is not None): 

1356 raise ValueError("Cannot use 'sl_auto' together " 

1357 "with '%s'" % key) 

1358 

1359 max_scalapack_cpus = bd.comm.size * gd.comm.size 

1360 sl_default = suggest_blocking(nbands, max_scalapack_cpus) 

1361 else: 

1362 sl_default = self.parallel['sl_default'] 

1363 

1364 if mode.name == 'lcao': 

1365 assert collinear 

1366 # Layouts used for general diagonalizer 

1367 sl_lcao = self.parallel['sl_lcao'] 

1368 if sl_lcao is None: 

1369 sl_lcao = sl_default 

1370 

1371 elpasolver = None 

1372 if self.parallel['use_elpa']: 

1373 elpasolver = self.parallel['elpasolver'] 

1374 lcaoksl = get_KohnSham_layouts(sl_lcao, 'lcao', 

1375 gd, bd, domainband_comm, dtype, 

1376 nao=nao, timer=self.timer, 

1377 elpasolver=elpasolver) 

1378 

1379 self.wfs = mode(lcaoksl, **wfs_kwargs) 

1380 

1381 elif mode.name == 'fd' or mode.name == 'pw': 

1382 # Use (at most) all available LCAO for initialization 

1383 lcaonbands = min(nbands, nao) 

1384 

1385 try: 

1386 lcaobd = BandDescriptor(lcaonbands, band_comm, 

1387 parstride_bands) 

1388 except RuntimeError: 

1389 initksl = None 

1390 else: 

1391 # Layouts used for general diagonalizer 

1392 # (LCAO initialization) 

1393 sl_lcao = self.parallel['sl_lcao'] 

1394 if sl_lcao is None: 

1395 sl_lcao = sl_default 

1396 initksl = get_KohnSham_layouts(sl_lcao, 'lcao', 

1397 gd, lcaobd, domainband_comm, 

1398 dtype, nao=nao, 

1399 timer=self.timer) 

1400 

1401 reuse_wfs_method = par.experimental.get('reuse_wfs_method', 'paw') 

1402 sl = (domainband_comm,) + (self.parallel['sl_diagonalize'] or 

1403 sl_default or 

1404 (1, 1, None)) 

1405 self.wfs = mode(sl, initksl, 

1406 reuse_wfs_method=reuse_wfs_method, 

1407 collinear=collinear, 

1408 **wfs_kwargs) 

1409 else: 

1410 self.wfs = mode(self, collinear=collinear, **wfs_kwargs) 

1411 

1412 self.log(self.wfs, '\n') 

1413 

1414 def dry_run(self): 

1415 # Can be overridden like in gpaw.atom.atompaw 

1416 print_cell(self.wfs.gd, self.atoms.pbc, self.log) 

1417 print_positions(self.atoms, self.log, self.density.magmom_av) 

1418 self.log.fd.flush() 

1419 

1420 # Write timing info now before the interpreter shuts down: 

1421 self.close() 

1422 

1423 # Disable timing output during shut-down: 

1424 del self.timer 

1425 

1426 raise SystemExit 

1427 

1428 def get_atomic_electrostatic_potentials(self) -> Array1D: 

1429 r"""Return the electrostatic potential at the atomic sites. 

1430 

1431 Return list of energies in eV, one for each atom: 

1432 

1433 ::: 

1434 

1435 / _ ~ _ ^a _ _a 

1436 Y |dr v (r) g (r-R ) 

1437 00 / H 00 

1438 

1439 """ 

1440 ham = self.hamiltonian 

1441 dens = self.density 

1442 self.initialize_positions() 

1443 dens.interpolate_pseudo_density() 

1444 dens.calculate_pseudo_charge() 

1445 ham.update(dens) 

1446 W_aL = ham.calculate_atomic_hamiltonians(dens) 

1447 W_a = np.zeros(len(self.atoms)) 

1448 for a, W_L in W_aL.items(): 

1449 W_a[a] = W_L[0] / (4 * np.pi)**0.5 * Ha 

1450 W_aL.partition.comm.sum(W_a) 

1451 return W_a 

1452 

1453 def linearize_to_xc(self, newxc): 

1454 """Linearize Hamiltonian to difference XC functional. 

1455 

1456 Used in real time TDDFT to perform calculations with various kernels. 

1457 """ 

1458 if isinstance(newxc, str): 

1459 newxc = XC(newxc) 

1460 self.log('Linearizing xc-hamiltonian to ' + str(newxc)) 

1461 newxc.initialize(self.density, self.hamiltonian, self.wfs) 

1462 self.hamiltonian.linearize_to_xc(newxc, self.density) 

1463 

1464 def attach(self, function, n=1, *args, **kwargs): 

1465 """Register observer function to run during the SCF cycle. 

1466 

1467 Call *function* using *args* and 

1468 *kwargs* as arguments. 

1469 

1470 If *n* is positive, then 

1471 *function* will be called every *n* SCF iterations + the 

1472 final iteration if it would not be otherwise 

1473 

1474 If *n* is negative, then *function* will only be 

1475 called on iteration *abs(n)*. 

1476 

1477 If *n* is 0, then *function* will only be called 

1478 on convergence""" 

1479 

1480 try: 

1481 slf = function.__self__ 

1482 except AttributeError: 

1483 pass 

1484 else: 

1485 if slf is self: 

1486 # function is a bound method of self. Store the name 

1487 # of the method and avoid circular reference: 

1488 function = function.__func__.__name__ 

1489 

1490 # Replace self in args with another unique reference 

1491 # to avoid circular reference 

1492 if not hasattr(self, 'self_ref'): 

1493 self.self_ref = object() 

1494 self_ = self.self_ref 

1495 args = tuple([self_ if arg is self else arg for arg in args]) 

1496 

1497 self.observers.append((function, n, args, kwargs)) 

1498 

1499 def call_observers(self, iter, final=False): 

1500 """Call all registered callback functions.""" 

1501 for function, n, args, kwargs in self.observers: 

1502 call = False 

1503 # Call every n iterations, including the last 

1504 if n > 0: 

1505 if ((iter % n) == 0) != final: 

1506 call = True 

1507 # Call only on iteration n 

1508 elif n < 0 and not final: 

1509 if iter == abs(n): 

1510 call = True 

1511 # Call only on convergence 

1512 elif n == 0 and final: 

1513 call = True 

1514 if call: 

1515 if isinstance(function, str): 

1516 function = getattr(self, function) 

1517 # Replace self reference with self 

1518 self_ = self.self_ref 

1519 args = tuple([self if arg is self_ else arg for arg in args]) 

1520 function(*args, **kwargs) 

1521 

1522 def get_reference_energy(self): 

1523 return self.wfs.setups.Eref * Ha 

1524 

1525 def get_homo_lumo(self, spin=None): 

1526 """Return HOMO and LUMO eigenvalues. 

1527 

1528 By default, return the true HOMO-LUMO eigenvalues (spin=None). 

1529 

1530 If spin is 0 or 1, return HOMO-LUMO eigenvalues taken among 

1531 only those states with the given spin.""" 

1532 return self.wfs.get_homo_lumo(spin) * Ha 

1533 

1534 def estimate_memory(self, mem): 

1535 """Estimate memory use of this object.""" 

1536 for name, obj in [('Density', self.density), 

1537 ('Hamiltonian', self.hamiltonian), 

1538 ('Wavefunctions', self.wfs)]: 

1539 obj.estimate_memory(mem.subnode(name)) 

1540 

1541 def print_memory_estimate(self, log=None, maxdepth=-1): 

1542 """Print estimated memory usage for PAW object and components. 

1543 

1544 maxdepth is the maximum nesting level of displayed components. 

1545 

1546 The PAW object must be initialize()'d, but needs not have large 

1547 arrays allocated.""" 

1548 # NOTE. This should work with "--dry-run=N" 

1549 # 

1550 # However, the initial overhead estimate is wrong if this method 

1551 # is called within a real mpirun/gpaw-python context. 

1552 if log is None: 

1553 log = self.log 

1554 log('Memory estimate:') 

1555 

1556 mem_init = maxrss() # initial overhead includes part of Hamiltonian! 

1557 log(' Process memory now: %.2f MiB' % (mem_init / 1024.0**2)) 

1558 

1559 mem = MemNode('Calculator', 0) 

1560 mem.indent = ' ' 

1561 try: 

1562 self.estimate_memory(mem) 

1563 except AttributeError as m: 

1564 log('Attribute error: %r' % m) 

1565 log('Some object probably lacks estimate_memory() method') 

1566 log('Memory breakdown may be incomplete') 

1567 mem.calculate_size() 

1568 mem.write(log.fd, maxdepth=maxdepth, depth=1) 

1569 log() 

1570 

1571 def converge_wave_functions(self): 

1572 """Converge the wave-functions if not present.""" 

1573 

1574 if self.scf and self.scf.converged: 

1575 if isinstance(self.wfs.kpt_u[0].psit_nG, np.ndarray): 

1576 return 

1577 if self.wfs.kpt_u[0].psit_nG is not None: 

1578 self.wfs.initialize_wave_functions_from_restart_file() 

1579 return 

1580 

1581 if not self.initialized: 

1582 self.initialize() 

1583 

1584 self.set_positions() 

1585 

1586 self.scf.converged = False 

1587 fixed = self.density.fixed 

1588 self.density.fixed = True 

1589 self.calculate(system_changes=[]) 

1590 self.density.fixed = fixed 

1591 

1592 def diagonalize_full_hamiltonian(self, nbands=None, ecut=None, 

1593 scalapack=None, 

1594 expert=False): 

1595 if not self.initialized: 

1596 self.initialize() 

1597 nbands = self.wfs.diagonalize_full_hamiltonian( 

1598 self.hamiltonian, self.atoms, self.log, 

1599 nbands, ecut, scalapack, expert) 

1600 self.parameters.nbands = nbands 

1601 

1602 def get_number_of_bands(self) -> int: 

1603 """Return the number of bands.""" 

1604 return self.wfs.bd.nbands 

1605 

1606 def get_xc_functional(self) -> str: 

1607 """Return the XC-functional identifier. 

1608 

1609 'LDA', 'PBE', ...""" 

1610 

1611 xc = self.parameters.get('xc', 'LDA') 

1612 if isinstance(xc, dict): 

1613 xc = xc['name'] 

1614 return xc 

1615 

1616 def get_number_of_spins(self): 

1617 return self.wfs.nspins 

1618 

1619 def get_spin_polarized(self): 

1620 """Is it a spin-polarized calculation?""" 

1621 return self.wfs.nspins == 2 

1622 

1623 def get_bz_k_points(self): 

1624 """Return the k-points.""" 

1625 return self.wfs.kd.bzk_kc.copy() 

1626 

1627 def get_ibz_k_points(self): 

1628 """Return k-points in the irreducible part of the Brillouin zone.""" 

1629 return self.wfs.kd.ibzk_kc.copy() 

1630 

1631 def get_bz_to_ibz_map(self): 

1632 """Return indices from BZ to IBZ.""" 

1633 return self.wfs.kd.bz2ibz_k.copy() 

1634 

1635 def get_k_point_weights(self): 

1636 """Weights of the k-points. 

1637 

1638 The sum of all weights is one.""" 

1639 

1640 return self.wfs.kd.weight_k 

1641 

1642 def get_pseudo_density(self, spin=None, gridrefinement=1, 

1643 pad=True, broadcast=True): 

1644 """Return pseudo-density array. 

1645 

1646 If *spin* is not given, then the total density is returned. 

1647 Otherwise, the spin up or down density is returned (spin=0 or 

1648 1).""" 

1649 

1650 if gridrefinement == 1: 

1651 nt_sG = self.density.nt_sG 

1652 gd = self.density.gd 

1653 elif gridrefinement == 2: 

1654 if self.density.nt_sg is None: 

1655 self.density.interpolate_pseudo_density() 

1656 nt_sG = self.density.nt_sg 

1657 gd = self.density.finegd 

1658 else: 

1659 raise NotImplementedError 

1660 

1661 if spin is None: 

1662 if self.density.nspins == 1: 

1663 nt_G = nt_sG[0] 

1664 else: 

1665 nt_G = nt_sG.sum(axis=0) 

1666 else: 

1667 if self.density.nspins == 1: 

1668 nt_G = 0.5 * nt_sG[0] 

1669 else: 

1670 nt_G = nt_sG[spin] 

1671 

1672 nt_G = gd.collect(nt_G, broadcast=broadcast) 

1673 

1674 if nt_G is None: 

1675 return None 

1676 

1677 if pad: 

1678 nt_G = gd.zero_pad(nt_G) 

1679 

1680 return nt_G / Bohr**3 

1681 

1682 get_pseudo_valence_density = get_pseudo_density # Don't use this one! 

1683 

1684 def get_effective_potential(self, spin=0, pad=True, broadcast=True): 

1685 """Return pseudo effective-potential.""" 

1686 vt_G = self.hamiltonian.gd.collect(self.hamiltonian.vt_sG[spin], 

1687 broadcast=broadcast) 

1688 if vt_G is None: 

1689 return None 

1690 

1691 if pad: 

1692 vt_G = self.hamiltonian.gd.zero_pad(vt_G) 

1693 return vt_G * Ha 

1694 

1695 def get_electrostatic_potential(self): 

1696 """Return the electrostatic potential. 

1697 

1698 This is the potential from the pseudo electron density and the 

1699 PAW-compensation charges. So, the electrostatic potential will 

1700 only be correct outside the PAW augmentation spheres. 

1701 """ 

1702 

1703 ham = self.hamiltonian 

1704 dens = self.density 

1705 self.initialize_positions() 

1706 dens.interpolate_pseudo_density() 

1707 dens.calculate_pseudo_charge() 

1708 return ham.get_electrostatic_potential(dens) * Ha 

1709 

1710 def get_pseudo_density_corrections(self): 

1711 """Integrated density corrections. 

1712 

1713 Returns the integrated value of the difference between the pseudo- 

1714 and the all-electron densities at each atom. These are the numbers 

1715 you should add to the result of doing e.g. Bader analysis on the 

1716 pseudo density.""" 

1717 if self.wfs.nspins == 1: 

1718 return np.array([self.density.get_correction(a, 0) 

1719 for a in range(len(self.atoms))]) 

1720 else: 

1721 return np.array([[self.density.get_correction(a, spin) 

1722 for a in range(len(self.atoms))] 

1723 for spin in range(2)]) 

1724 

1725 def get_all_electron_density(self, spin=None, gridrefinement=2, 

1726 pad=True, broadcast=True, collect=True, 

1727 skip_core=False): 

1728 """Return reconstructed all-electron density array.""" 

1729 n_sG, gd = self.density.get_all_electron_density( 

1730 self.atoms, gridrefinement=gridrefinement, skip_core=skip_core) 

1731 if spin is None: 

1732 if self.density.nspins == 1: 

1733 n_G = n_sG[0] 

1734 else: 

1735 n_G = n_sG.sum(axis=0) 

1736 else: 

1737 if self.density.nspins == 1: 

1738 n_G = 0.5 * n_sG[0] 

1739 else: 

1740 n_G = n_sG[spin] 

1741 

1742 if collect: 

1743 n_G = gd.collect(n_G, broadcast=broadcast) 

1744 

1745 if n_G is None: 

1746 return None 

1747 

1748 if pad: 

1749 n_G = gd.zero_pad(n_G) 

1750 

1751 return n_G / Bohr**3 

1752 

1753 def get_fermi_level(self): 

1754 """Return the Fermi-level.""" 

1755 assert self.wfs.fermi_levels is not None 

1756 if len(self.wfs.fermi_levels) != 1: 

1757 raise ValueError('There are two Fermi-levels!') 

1758 return self.wfs.fermi_levels[0] * Ha 

1759 

1760 def get_fermi_levels(self): 

1761 """Return the Fermi-levels in case of fixed-magmom.""" 

1762 assert self.wfs.fermi_levels is not None 

1763 if len(self.wfs.fermi_levels) != 2: 

1764 raise ValueError('There is only one Fermi-level!') 

1765 return self.wfs.fermi_levels * Ha 

1766 

1767 def get_wigner_seitz_densities(self, spin): 

1768 """Get the weight of the spin-density in Wigner-Seitz cells 

1769 around each atom. 

1770 

1771 The density assigned to each atom is relative to the neutral atom, 

1772 i.e. the density sums to zero. 

1773 """ 

1774 from gpaw.analyse.wignerseitz import wignerseitz 

1775 atom_index = wignerseitz(self.wfs.gd, self.atoms) 

1776 

1777 nt_G = self.density.nt_sG[spin] 

1778 weight_a = np.empty(len(self.atoms)) 

1779 for a in range(len(self.atoms)): 

1780 # XXX Optimize! No need to integrate in zero-region 

1781 smooth = self.wfs.gd.integrate(np.where(atom_index == a, 

1782 nt_G, 0.0)) 

1783 correction = self.density.get_correction(a, spin) 

1784 weight_a[a] = smooth + correction 

1785 

1786 return weight_a 

1787 

1788 def get_dos(self, spin=0, npts=201, width=None): 

1789 """The total DOS. 

1790 

1791 Fold eigenvalues with Gaussians, and put on an energy grid. 

1792 

1793 returns an (energies, dos) tuple, where energies are relative to the 

1794 vacuum level for non-periodic systems, and the average potential for 

1795 periodic systems. 

1796 """ 

1797 if width is None: 

1798 width = 0.1 

1799 

1800 w_k = self.wfs.kd.weight_k 

1801 Nb = self.wfs.bd.nbands 

1802 energies = np.empty(len(w_k) * Nb) 

1803 weights = np.empty(len(w_k) * Nb) 

1804 x = 0 

1805 for k, w in enumerate(w_k): 

1806 energies[x:x + Nb] = self.get_eigenvalues(k, spin) 

1807 weights[x:x + Nb] = w 

1808 x += Nb 

1809 

1810 from gpaw.utilities.dos import fold 

1811 return fold(energies, weights, npts, width) 

1812 

1813 def get_wigner_seitz_ldos(self, a, spin=0, npts=201, width=None): 

1814 """The Local Density of States, using a Wigner-Seitz basis function. 

1815 

1816 Project wave functions onto a Wigner-Seitz box at atom ``a``, and 

1817 use this as weight when summing the eigenvalues.""" 

1818 if width is None: 

1819 width = 0.1 

1820 

1821 from gpaw.utilities.dos import fold, raw_wignerseitz_LDOS 

1822 energies, weights = raw_wignerseitz_LDOS(self, a, spin) 

1823 return fold(energies * Ha, weights, npts, width) 

1824 

1825 def get_orbital_ldos(self, a, 

1826 spin=0, angular='spdf', npts=201, width=None, 

1827 nbands=None, spinorbit=False): 

1828 """The Local Density of States, using atomic orbital basis functions. 

1829 

1830 Project wave functions onto an atom orbital at atom ``a``, and 

1831 use this as weight when summing the eigenvalues. 

1832 

1833 The atomic orbital has angular momentum ``angular``, which can be 

1834 's', 'p', 'd', 'f', or any combination (e.g. 'sdf'). 

1835 

1836 An integer value for ``angular`` can also be used to specify a specific 

1837 projector function to project onto. 

1838 

1839 Setting nbands limits the number of bands included. This speeds up the 

1840 calculation if one has many bands in the calculator but is only 

1841 interested in the DOS at low energies. 

1842 """ 

1843 from gpaw.utilities.dos import fold, raw_orbital_LDOS 

1844 if width is None: 

1845 width = 0.1 

1846 

1847 if not spinorbit: 

1848 energies, weights = raw_orbital_LDOS(self, a, spin, angular, 

1849 nbands) 

1850 else: 

1851 raise DeprecationWarning( 

1852 'Please use GPAW.dos(soc=True, ...).raw_pdos(...)') 

1853 

1854 return fold(energies * Ha, weights, npts, width) 

1855 

1856 def get_lcao_dos(self, atom_indices=None, basis_indices=None, 

1857 npts=201, width=None): 

1858 """Get density of states projected onto orbitals in LCAO mode. 

1859 

1860 basis_indices is a list of indices of basis functions on which 

1861 to project. To specify all basis functions on a set of atoms, 

1862 you can supply atom_indices instead. Both cannot be given 

1863 simultaneously.""" 

1864 

1865 both_none = atom_indices is None and basis_indices is None 

1866 neither_none = atom_indices is not None and basis_indices is not None 

1867 if both_none or neither_none: 

1868 raise ValueError('Please give either atom_indices or ' 

1869 'basis_indices but not both') 

1870 

1871 if width is None: 

1872 width = 0.1 

1873 

1874 if self.wfs.S_qMM is None: 

1875 from gpaw.utilities.dos import RestartLCAODOS 

1876 lcaodos = RestartLCAODOS(self) 

1877 else: 

1878 from gpaw.utilities.dos import LCAODOS 

1879 lcaodos = LCAODOS(self) 

1880 

1881 if atom_indices is not None: 

1882 basis_indices = lcaodos.get_atom_indices(atom_indices) 

1883 

1884 eps_n, w_n = lcaodos.get_subspace_pdos(basis_indices) 

1885 from gpaw.utilities.dos import fold 

1886 return fold(eps_n * Ha, w_n, npts, width) 

1887 

1888 def get_all_electron_ldos(self, mol, spin=0, npts=201, width=None, 

1889 wf_k=None, P_aui=None, lc=None, raw=False): 

1890 """The Projected Density of States, using all-electron wavefunctions. 

1891 

1892 Projects onto a pseudo_wavefunctions (wf_k) corresponding to some band 

1893 n and uses P_aui ([paw.nuclei[a].P_uni[:,n,:] for a in atoms]) to 

1894 obtain the all-electron overlaps. 

1895 Instead of projecting onto a wavefunction, a molecular orbital can 

1896 be specified by a linear combination of weights (lc) 

1897 """ 

1898 from gpaw.utilities.dos import all_electron_LDOS, fold 

1899 

1900 if raw: 

1901 return all_electron_LDOS(self, mol, spin, lc=lc, 

1902 wf_k=wf_k, P_aui=P_aui) 

1903 if width is None: 

1904 width = 0.1 

1905 

1906 energies, weights = all_electron_LDOS(self, mol, spin, 

1907 lc=lc, wf_k=wf_k, P_aui=P_aui) 

1908 return fold(energies * Ha, weights, npts, width) 

1909 

1910 def get_pseudo_wave_function(self, band=0, kpt=0, spin=0, broadcast=True, 

1911 pad=True, periodic=False): 

1912 """Return pseudo-wave-function array. 

1913 

1914 Units: 1/Angstrom^(3/2) 

1915 """ 

1916 if self.wfs.mode == 'lcao' and not self.wfs.positions_set: 

1917 self.initialize_positions() 

1918 

1919 if pad: 

1920 psit_G = self.get_pseudo_wave_function(band, kpt, spin, broadcast, 

1921 pad=False, 

1922 periodic=periodic) 

1923 if psit_G is None: 

1924 return 

1925 else: 

1926 return self.wfs.gd.zero_pad(psit_G) 

1927 

1928 psit_G = self.wfs.get_wave_function_array(band, kpt, spin, 

1929 periodic=periodic) 

1930 if broadcast: 

1931 if self.wfs.world.rank != 0: 

1932 psit_G = self.wfs.gd.empty(dtype=self.wfs.dtype, 

1933 global_array=True) 

1934 psit_G = np.ascontiguousarray(psit_G) 

1935 self.wfs.world.broadcast(psit_G, 0) 

1936 return psit_G / Bohr**1.5 

1937 elif self.wfs.world.rank == 0: 

1938 return psit_G / Bohr**1.5 

1939 

1940 def get_eigenvalues(self, kpt=0, spin=0, broadcast=True): 

1941 """Return eigenvalue array.""" 

1942 assert 0 <= kpt < self.wfs.kd.nibzkpts, kpt 

1943 eps_n = self.wfs.collect_eigenvalues(kpt, spin) 

1944 if broadcast: 

1945 if self.wfs.world.rank != 0: 

1946 eps_n = np.empty(self.wfs.bd.nbands) 

1947 self.wfs.world.broadcast(eps_n, 0) 

1948 return eps_n * Ha 

1949 

1950 def get_occupation_numbers(self, 

1951 kpt: int = 0, 

1952 spin: int = 0, 

1953 broadcast: bool = True, 

1954 raw: bool = False) -> np.ndarray: 

1955 """Return occupation array. 

1956 

1957 Parameters 

1958 ========== 

1959 kpt: 

1960 Index of IBZ k-point. 

1961 spin: 

1962 Spin-channel index. 

1963 broadcast: 

1964 Broadcast result to all MPI-ranks. 

1965 raw: 

1966 Return numbers in the [0,1] range without spin-degeneracy 

1967 or k-point weights. 

1968 """ 

1969 f_n = self.wfs.collect_occupations(kpt, spin) 

1970 if raw: 

1971 weight = self.wfs.kd.weight_k[kpt] * 2 / self.wfs.nspins 

1972 f_n /= weight 

1973 if broadcast: 

1974 if self.wfs.world.rank != 0: 

1975 f_n = np.empty(self.wfs.bd.nbands) 

1976 self.wfs.world.broadcast(f_n, 0) 

1977 return f_n 

1978 

1979 def get_xc_difference(self, xc): 

1980 if isinstance(xc, (str, dict)): 

1981 xc = XC(xc) 

1982 xc.set_grid_descriptor(self.density.finegd) 

1983 xc.initialize(self.density, self.hamiltonian, self.wfs) 

1984 xc.set_positions(self.spos_ac) 

1985 if xc.orbital_dependent: 

1986 self.converge_wave_functions() 

1987 return self.hamiltonian.get_xc_difference(xc, self.density) * Ha 

1988 

1989 def initial_wannier(self, initialwannier, kpointgrid, fixedstates, 

1990 edf, spin, nbands): 

1991 """Initial guess for the shape of wannier functions. 

1992 

1993 Use initial guess for wannier orbitals to determine rotation 

1994 matrices U and C. 

1995 """ 

1996 

1997 from ase.dft.wannier import rotation_from_projection 

1998 proj_knw = self.get_projections(initialwannier, spin) 

1999 U_kww = [] 

2000 C_kul = [] 

2001 for fixed, proj_nw in zip(fixedstates, proj_knw): 

2002 U_ww, C_ul = rotation_from_projection(proj_nw[:nbands], 

2003 fixed, 

2004 ortho=True) 

2005 U_kww.append(U_ww) 

2006 C_kul.append(C_ul) 

2007 

2008 U_kww = np.asarray(U_kww) 

2009 return C_kul, U_kww 

2010 

2011 def get_wannier_localization_matrix(self, nbands, dirG, kpoint, 

2012 nextkpoint, G_I, spin): 

2013 """Calculate integrals for maximally localized Wannier functions.""" 

2014 

2015 # Due to orthorhombic cells, only one component of dirG is non-zero. 

2016 k_kc = self.wfs.kd.bzk_kc 

2017 G_c = k_kc[nextkpoint] - k_kc[kpoint] - G_I 

2018 

2019 return self.get_wannier_integrals(spin, kpoint, 

2020 nextkpoint, G_c, nbands) 

2021 

2022 def get_wannier_integrals(self, s, k, k1, G_c, nbands=None): 

2023 """Calculate integrals for maximally localized Wannier functions.""" 

2024 

2025 assert s <= self.wfs.nspins 

2026 kpt_rank, u = divmod(k + len(self.wfs.kd.ibzk_kc) * s, 

2027 len(self.wfs.kpt_u)) 

2028 kpt_rank1, u1 = divmod(k1 + len(self.wfs.kd.ibzk_kc) * s, 

2029 len(self.wfs.kpt_u)) 

2030 

2031 # XXX not for the kpoint/spin parallel case 

2032 assert self.wfs.kd.comm.size == 1 

2033 

2034 # If calc is a save file, read in tar references to memory 

2035 # For lcao mode just initialize the wavefunctions from the 

2036 # calculated lcao coefficients 

2037 if self.wfs.mode == 'lcao': 

2038 self.wfs.initialize_wave_functions_from_lcao() 

2039 else: 

2040 self.wfs.initialize_wave_functions_from_restart_file() 

2041 

2042 # Get pseudo part 

2043 psit_nR = self.get_realspace_wfs(u) 

2044 psit1_nR = self.get_realspace_wfs(u1) 

2045 Z_nn = self.wfs.gd.wannier_matrix(psit_nR, psit1_nR, G_c, nbands) 

2046 # Add corrections 

2047 self.add_wannier_correction(Z_nn, G_c, u, u1, nbands) 

2048 

2049 self.wfs.gd.comm.sum(Z_nn) 

2050 

2051 return Z_nn 

2052 

2053 def add_wannier_correction(self, Z_nn, G_c, u, u1, nbands=None): 

2054 r"""Calculate the correction to the wannier integrals. 

2055 

2056 See: (Eq. 27 ref1):: 

2057 

2058 -i G.r 

2059 Z = <psi | e |psi > 

2060 nm n m 

2061 

2062 __ __ 

2063 ~ \ a \ a* a a 

2064 Z = Z + ) exp[-i G . R ] ) P dO P 

2065 nmx nmx /__ x /__ ni ii' mi' 

2066 

2067 a ii' 

2068 

2069 Note that this correction is an approximation that assumes the 

2070 exponential varies slowly over the extent of the augmentation sphere. 

2071 

2072 ref1: Thygesen et al, Phys. Rev. B 72, 125119 (2005) 

2073 """ 

2074 

2075 if nbands is None: 

2076 nbands = self.wfs.bd.nbands 

2077 

2078 P_ani = self.wfs.kpt_u[u].P_ani 

2079 P1_ani = self.wfs.kpt_u[u1].P_ani 

2080 for a, P_ni in P_ani.items(): 

2081 P_ni = P_ani[a][:nbands] 

2082 P1_ni = P1_ani[a][:nbands] 

2083 dO_ii = self.wfs.setups[a].dO_ii 

2084 e = np.exp(-2.j * np.pi * np.dot(G_c, self.spos_ac[a])) 

2085 Z_nn += e * np.dot(np.dot(P_ni.conj(), dO_ii), P1_ni.T) 

2086 

2087 def get_projections(self, locfun, spin=0): 

2088 """Project wave functions onto localized functions 

2089 

2090 Determine the projections of the Kohn-Sham eigenstates 

2091 onto specified localized functions of the format:: 

2092 

2093 locfun = [[spos_c, l, sigma], [...]] 

2094 

2095 spos_c can be an atom index, or a scaled position vector. l is 

2096 the angular momentum, and sigma is the (half-) width of the 

2097 radial gaussian. 

2098 

2099 Return format is:: 

2100 

2101 f_kni = <psi_kn | f_i> 

2102 

2103 where psi_kn are the wave functions, and f_i are the specified 

2104 localized functions. 

2105 

2106 As a special case, locfun can be the string 'projectors', in which 

2107 case the bound state projectors are used as localized functions. 

2108 """ 

2109 wfs = self.wfs 

2110 

2111 if locfun == 'projectors': 

2112 f_kin = [] 

2113 for kpt in wfs.kpt_u: 

2114 if kpt.s == spin: 

2115 f_in = [] 

2116 for a, P_ni in kpt.P_ani.items(): 

2117 i = 0 

2118 setup = wfs.setups[a] 

2119 for l, n in zip(setup.l_j, setup.n_j): 

2120 if n >= 0: 

2121 for j in range(i, i + 2 * l + 1): 

2122 f_in.append(P_ni[:, j]) 

2123 i += 2 * l + 1 

2124 f_kin.append(f_in) 

2125 f_kni = np.array(f_kin).transpose(0, 2, 1) 

2126 return f_kni.conj() 

2127 

2128 from math import factorial as fac 

2129 

2130 from gpaw.lfc import LocalizedFunctionsCollection as LFC 

2131 from gpaw.spline import Spline 

2132 

2133 nkpts = len(wfs.kd.ibzk_kc) 

2134 nbf = np.sum([2 * l + 1 for pos, l, a in locfun]) 

2135 f_kni = np.zeros((nkpts, wfs.bd.nbands, nbf), wfs.dtype) 

2136 

2137 spos_xc = [] 

2138 splines_x = [] 

2139 for spos_c, l, sigma in locfun: 

2140 if isinstance(spos_c, int): 

2141 spos_c = self.spos_ac[spos_c] 

2142 spos_xc.append(spos_c) 

2143 alpha = .5 * Bohr**2 / sigma**2 

2144 r = np.linspace(0, 10. * sigma, 500) 

2145 f_g = (fac(l) * (4 * alpha)**(l + 3 / 2.) * 

2146 np.exp(-alpha * r**2) / 

2147 (np.sqrt(4 * np.pi) * fac(2 * l + 1))) 

2148 splines_x.append([Spline.from_data(l, rmax=r[-1], f_g=f_g)]) 

2149 

2150 lf = LFC(wfs.gd, splines_x, wfs.kd, dtype=wfs.dtype, cut=True) 

2151 lf.set_positions(spos_xc) 

2152 assert wfs.gd.comm.size == 1 

2153 k = 0 

2154 f_ani = lf.dict(wfs.bd.nbands) 

2155 for u, kpt in enumerate(wfs.kpt_u): 

2156 if kpt.s != spin: 

2157 continue 

2158 psit_nR = self.get_realspace_wfs(u) 

2159 lf.integrate(psit_nR, f_ani, kpt.q) 

2160 i1 = 0 

2161 for x, f_ni in f_ani.items(): 

2162 i2 = i1 + f_ni.shape[1] 

2163 f_kni[k, :, i1:i2] = f_ni 

2164 i1 = i2 

2165 k += 1 

2166 return f_kni.conj() 

2167 

2168 def get_realspace_wfs(self, u): 

2169 if self.wfs.mode == 'pw': 

2170 nbands = self.wfs.bd.nbands 

2171 psit_nR = np.zeros(np.insert(self.wfs.gd.N_c, 0, nbands), 

2172 self.wfs.dtype) 

2173 for n in range(nbands): 

2174 psit_nR[n] = self.wfs._get_wave_function_array(u, n) 

2175 else: 

2176 psit_nR = self.wfs.kpt_u[u].psit_nG[:] 

2177 

2178 return psit_nR 

2179 

2180 def get_number_of_grid_points(self): 

2181 return self.wfs.gd.N_c 

2182 

2183 def get_number_of_iterations(self): 

2184 return self.scf.niter 

2185 

2186 def get_number_of_electrons(self): 

2187 return self.wfs.setups.nvalence - self.density.charge 

2188 

2189 def get_electrostatic_corrections(self): 

2190 """Calculate PAW correction to average electrostatic potential.""" 

2191 dEH_a = np.zeros(len(self.atoms)) 

2192 for a, D_sp in self.density.D_asp.items(): 

2193 setup = self.wfs.setups[a] 

2194 dEH_a[a] = setup.dEH0 + np.dot(setup.dEH_p, D_sp.sum(0)) 

2195 self.wfs.gd.comm.sum(dEH_a) 

2196 return dEH_a * Ha * Bohr**3 

2197 

2198 def get_nonselfconsistent_energies(self, type='beefvdw'): 

2199 from gpaw.xc.bee import BEEFEnsemble 

2200 if type not in ['beefvdw', 'mbeef', 'mbeefvdw']: 

2201 raise NotImplementedError('Not implemented for type = %s' % type) 

2202 assert self.scf.converged 

2203 bee = BEEFEnsemble(self) 

2204 x = bee.create_xc_contributions('exch') 

2205 c = bee.create_xc_contributions('corr') 

2206 if type == 'beefvdw': 

2207 return np.append(x, c) 

2208 elif type == 'mbeef': 

2209 return x.flatten() 

2210 elif type == 'mbeefvdw': 

2211 return np.append(x.flatten(), c) 

2212 

2213 def embed(self, q_p, rc=0.2, rc2=np.inf, width=1.0): 

2214 """Embed QM region in point-charges.""" 

2215 pc = PointChargePotential(q_p, rc=rc, rc2=rc2, width=width) 

2216 self.set(external=pc) 

2217 return pc 

2218 

2219 def dos(self, 

2220 soc: bool = False, 

2221 theta: float = 0.0, 

2222 phi: float = 0.0, 

2223 shift_fermi_level: bool = True) -> DOSCalculator: 

2224 """Create DOS-calculator. 

2225 

2226 Default is to shift_fermi_level to 0.0 eV. For soc=True, angles 

2227 can be given in degrees. 

2228 """ 

2229 return DOSCalculator.from_calculator( 

2230 self, soc=soc, 

2231 theta=theta, phi=phi, 

2232 shift_fermi_level=shift_fermi_level) 

2233 

2234 def gs_adapter(self): 

2235 # Temporary helper to convert response code and related parts 

2236 # so it does not depend directly on calc. 

2237 # 

2238 # This method can be removed once we finish that process. 

2239 from gpaw.response.groundstate import ResponseGroundStateAdapter 

2240 return ResponseGroundStateAdapter(self) 

2241 

2242 

2243class DeprecatedParameterWarning(FutureWarning): 

2244 """Warning class for when a parameter or its value is deprecated."""