Coverage for gpaw/dft.py: 84%

435 statements  

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

1from __future__ import annotations 

2 

3import importlib 

4import warnings 

5from pathlib import Path 

6from typing import IO, TYPE_CHECKING, Any, Sequence, Union, Literal 

7 

8import numpy as np 

9from ase import Atoms 

10from ase.calculators.calculator import kpts2sizeandoffsets 

11from numpy.typing import DTypeLike 

12 

13from gpaw.mpi import MPIComm 

14from gpaw.new.calculation import DFTCalculation 

15from gpaw.new.logger import Logger 

16from gpaw.new.symmetry import Symmetries, create_symmetries_object 

17from gpaw.new.pwfd.davidson import Davidson as DavidsonEigensolver 

18from gpaw.new.pwfd.rmmdiis import RMMDIIS as RMMDIISEigensolver 

19 

20if TYPE_CHECKING: 

21 from gpaw.new.ase_interface import ASECalculator 

22 

23PARAMETER_NAMES = [ 

24 'mode', 'basis', 'charge', 'convergence', 'eigensolver', 'environment', 

25 'experimental', 'extensions', 'gpts', 'h', 'hund', 

26 'interpolation', 'kpts', 'magmoms', 'maxiter', 'mixer', 'nbands', 

27 'occupations', 'parallel', 'poissonsolver', 'random', 'setups', 'soc', 

28 'spinpol', 'symmetry', 'xc'] 

29 

30 

31class DeprecatedParameterWarning(FutureWarning): 

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

33 

34 

35class Parameter: 

36 def __repr__(self): 

37 args = ', '.join(f'{k}={v!r}' for k, v in self.todict().items()) 

38 return f'{self.__class__.__name__}({args})' 

39 

40 def _not_none(self, *keys: str) -> dict: 

41 dct = {} 

42 for key in keys: 

43 value = self.__dict__[key] 

44 if value is not None: 

45 dct[key] = value 

46 return dct 

47 

48 

49class Mode(Parameter): 

50 qspiral = None 

51 

52 def __init__(self, 

53 *, 

54 dtype: DTypeLike | None = None, 

55 force_complex_dtype: bool = False): 

56 self.dtype = dtype 

57 self.force_complex_dtype = force_complex_dtype 

58 self.name = self.__class__.__name__.lower() 

59 

60 def todict(self) -> dict: 

61 dct = self._not_none('dtype') 

62 if self.force_complex_dtype: 

63 dct['force_complex_dtype'] = True 

64 return dct 

65 

66 @classmethod 

67 def from_param(cls, mode) -> Mode: 

68 if isinstance(mode, str): 

69 mode = {'name': mode} 

70 if isinstance(mode, dict): 

71 mode = mode.copy() 

72 return {'pw': PW, 

73 'lcao': LCAO, 

74 'fd': FD, 

75 'tb': TB}[mode.pop('name')](**mode) 

76 return mode 

77 

78 def dft_components_builder(self, atoms, params, *, log=None, comm=None): 

79 module = importlib.import_module(f'gpaw.new.{self.name}.builder') 

80 return getattr(module, f'{self.name.upper()}DFTComponentsBuilder')( 

81 atoms, params, log=log, comm=comm) 

82 

83 

84class PW(Mode): 

85 def __init__(self, 

86 ecut: float = 340, 

87 *, 

88 qspiral=None, 

89 dedecut=None, 

90 dtype: DTypeLike | None = None, 

91 force_complex_dtype: bool = False): 

92 """PW-mode. 

93 

94 Parameters 

95 ========== 

96 ecut: 

97 Plane-wave cutoff energy in eV. 

98 """ 

99 self.ecut = ecut 

100 self.qspiral = qspiral 

101 self.dedecut = dedecut 

102 super().__init__(dtype=dtype, 

103 force_complex_dtype=force_complex_dtype) 

104 

105 def todict(self): 

106 dct = super().todict() 

107 dct |= self._not_none('ecut', 'qspiral', 'dedecut') 

108 return dct 

109 

110 

111class LCAO(Mode): 

112 distribution = '?' 

113 

114 def __init__(self, 

115 *, 

116 dtype: DTypeLike | None = None, 

117 force_complex_dtype: bool = False): 

118 super().__init__(dtype=dtype, 

119 force_complex_dtype=force_complex_dtype) 

120 

121 

122class FD(Mode): 

123 def __init__(self, 

124 *, 

125 nn=3, 

126 dtype: DTypeLike | None = None, 

127 force_complex_dtype: bool = False): 

128 self.nn = nn 

129 super().__init__(dtype=dtype, 

130 force_complex_dtype=force_complex_dtype) 

131 

132 def todict(self): 

133 dct = super().todict() 

134 if self.nn != 3: 

135 dct['nn'] = self.nn 

136 return dct 

137 

138 

139class TB(Mode): 

140 distribution = '?' 

141 

142 

143class Eigensolver(Parameter): 

144 @classmethod 

145 def from_param(cls, eigensolver): 

146 if isinstance(eigensolver, str): 

147 eigensolver = {'name': eigensolver} 

148 elif not isinstance(eigensolver, dict): 

149 return eigensolver 

150 if 'name' in eigensolver: 

151 eigensolver = eigensolver.copy() 

152 name = eigensolver.pop('name') 

153 if name == 'dav': 

154 name = 'davidson' 

155 warnings.warn('Please use "davidson" instead of "dav"') 

156 if name in eigensolvers: 

157 return eigensolvers[name](**eigensolver) 

158 raise ValueError(f'Unknown eigensolver: {name}') 

159 return DefaultEigensolver(eigensolver) 

160 

161 

162class DefaultEigensolver(Eigensolver): 

163 def __init__(self, params: dict): 

164 self.params = params 

165 

166 def todict(self): 

167 return self.params 

168 

169 

170class PWFDEigensolverParamater(Eigensolver): 

171 def __init__(self, 

172 niter: int = 2, 

173 max_buffer_mem: int = 200 * 1024**2): 

174 self.niter = niter 

175 self.max_buffer_mem = max_buffer_mem 

176 

177 def todict(self): 

178 return {'niter': self.niter} 

179 

180 def build(self, 

181 nbands, 

182 wf_desc, 

183 band_comm, 

184 hamiltonian, 

185 converge_bands, 

186 setups, 

187 atoms): 

188 return self.cls( 

189 nbands, 

190 wf_desc, 

191 band_comm, 

192 hamiltonian, 

193 converge_bands, 

194 niter=self.niter, 

195 max_buffer_mem=self.max_buffer_mem) 

196 

197 

198class Davidson(PWFDEigensolverParamater): 

199 name = 'davidson' 

200 cls = DavidsonEigensolver 

201 

202 

203class RMMDIIS(PWFDEigensolverParamater): 

204 name = 'rmm-diis' 

205 cls = RMMDIISEigensolver 

206 

207 def __init__(self, 

208 niter: int = 1, 

209 max_buffer_mem: int = 200 * 1024**2, 

210 trial_step: float | None = None): 

211 self.niter = niter 

212 self.max_buffer_mem = max_buffer_mem 

213 self.trial_step = trial_step 

214 

215 def todict(self): 

216 return {'niter': self.niter, 

217 'max_buffer_mem': self.max_buffer_mem, 

218 'trial_step': self.trial_step} 

219 

220 def build(self, 

221 nbands, 

222 wf_desc, 

223 band_comm, 

224 create_preconditioner, 

225 converge_bands, 

226 setups, 

227 atoms): 

228 return self.cls( 

229 nbands, 

230 wf_desc, 

231 band_comm, 

232 create_preconditioner, 

233 converge_bands, 

234 niter=self.niter, 

235 max_buffer_mem=self.max_buffer_mem, 

236 trial_step=self.trial_step) 

237 

238 

239class LCAOEigensolver(Eigensolver): 

240 name = 'lcao' 

241 

242 def build_lcao(self, basis, relpos_ac, cell_cv, symmetries): 

243 from gpaw.new.lcao.eigensolver import LCAOEigensolver as LCAOES 

244 return LCAOES(basis) 

245 

246 

247class HybridLCAOEigensolver(LCAOEigensolver): 

248 def build_lcao(self, basis, relpos_ac, cell_cv, symmetries): 

249 from gpaw.new.lcao.hybrids import HybridLCAOEigensolver as HLCAOES 

250 return HLCAOES(basis, relpos_ac, cell_cv) 

251 

252 

253class Scissors(LCAOEigensolver): 

254 name = 'scissors' 

255 

256 def __init__(self, shifts: list): 

257 self.shifts = shifts 

258 

259 def todict(self): 

260 return {'shifts': self.shifts} 

261 

262 def build_lcao(self, basis, relpos_ac, cell_cv, symmetries): 

263 from gpaw.lcao.scissors import ScissorsLCAOEigensolver 

264 return ScissorsLCAOEigensolver(basis, 

265 self.shifts, 

266 symmetries) 

267 

268 

269eigensolvers = { 

270 'davidson': Davidson, 

271 'rmm-diis': RMMDIIS, 

272 'lcao': LCAOEigensolver, 

273 'hybrid-lcao': HybridLCAOEigensolver, 

274 'scissors': Scissors} 

275 

276 

277class Extension(Parameter): 

278 @classmethod 

279 def from_param(self, extension): 

280 if isinstance(extension, dict): 

281 dct = extension.copy() 

282 name = dct.pop('name') 

283 if name == 'd3': 

284 from gpaw.new.extensions import D3 

285 return D3(**dct) 

286 raise ValueError(name) 

287 return extension 

288 

289 

290class Environment(Parameter): 

291 @classmethod 

292 def from_param(self, env): 

293 if env is None: 

294 return Environment() 

295 if isinstance(env, dict): 

296 dct = env.copy() 

297 name = dct.pop('name') 

298 if name == 'sjm': 

299 from gpaw.new.sjm import SJM 

300 return SJM(**dct) 

301 if name == 'solvation': 

302 from gpaw.new.solvation import Solvation 

303 return Solvation(**dct) 

304 raise ValueError(f'Unknown environment: {name}') 

305 return env 

306 

307 def build(self, 

308 setups, 

309 grid, 

310 relpos_ac, 

311 log, 

312 comm): 

313 from gpaw.new.environment import Environment as Env 

314 return Env(len(setups)) 

315 

316 

317class Mixer(Parameter): 

318 def __init__(self, params: dict): 

319 self.params = params 

320 

321 def todict(self): 

322 return self.params 

323 

324 @classmethod 

325 def from_param(cls, mixer): 

326 if isinstance(mixer, Mixer): 

327 return mixer 

328 return Mixer(mixer) 

329 

330 

331class Occupations(Parameter): 

332 def __init__(self, params: dict): 

333 self.params = params 

334 

335 def todict(self): 

336 return self.params 

337 

338 @classmethod 

339 def from_param(cls, occupations): 

340 if isinstance(occupations, dict): 

341 return Occupations(occupations) 

342 return occupations 

343 

344 

345class PoissonSolver(Parameter): 

346 def __init__(self, params: dict): 

347 self.params = params 

348 

349 def todict(self): 

350 return self.params 

351 

352 @classmethod 

353 def from_param(cls, ps): 

354 if isinstance(ps, dict): 

355 return PoissonSolver(ps) 

356 return ps 

357 

358 def build(self, *, grid, xp=np): 

359 from gpaw.poisson import PoissonSolver as make_poisson_solver 

360 solver = make_poisson_solver(**self.params, xp=xp) 

361 return solver.build(grid, xp) 

362 

363 

364def array_or_none(a): 

365 if a is None: 

366 return None 

367 return np.array(a) 

368 

369 

370class Symmetry(Parameter): 

371 def __init__(self, 

372 *, 

373 rotations: np.ndarray | None = None, 

374 translations: np.ndarray | None = None, 

375 atommaps: np.ndarray | None = None, 

376 extra_ids: Sequence[int] | None = None, 

377 tolerance: float | None = None, # Å 

378 point_group: bool = True, 

379 symmorphic: bool = True, 

380 time_reversal: bool = True): 

381 self.rotations = array_or_none(rotations) 

382 self.translations = array_or_none(translations) 

383 self.atommaps = array_or_none(atommaps) 

384 self.extra_ids = array_or_none(extra_ids) 

385 self.tolerance = tolerance 

386 self.point_group = point_group 

387 self.symmorphic = symmorphic 

388 self.time_reversal = time_reversal 

389 

390 @classmethod 

391 def from_param(cls, s): 

392 if isinstance(s, Symmetry): 

393 return s 

394 if isinstance(s, str): 

395 if s == 'off': 

396 return Symmetry(point_group=False, time_reversal=False) 

397 if s == 'on': 

398 return Symmetry() 

399 raise ValueError() 

400 if 'name' in s: 

401 s = s.copy() 

402 del s['name'] 

403 return Symmetry(**(s or {})) 

404 

405 def todict(self): 

406 dct = self._not_none('rotations', 'translations', 'atommaps', 

407 'extra_ids', 'tolerance') 

408 for name in ['point_group', 'symmorphic', 'time_reversal']: 

409 if not getattr(self, name): 

410 dct[name] = False 

411 return dct 

412 

413 def build(self, 

414 atoms: Atoms, 

415 *, 

416 setup_ids: Sequence | None = None, 

417 magmoms: np.ndarray | None = None, 

418 _backwards_compatible=False) -> Symmetries: 

419 return create_symmetries_object( 

420 atoms, 

421 setup_ids=setup_ids, 

422 magmoms=magmoms, 

423 rotations=self.rotations, 

424 translations=self.translations, 

425 atommaps=self.atommaps, 

426 extra_ids=self.extra_ids, 

427 tolerance=self.tolerance, 

428 point_group=self.point_group, 

429 symmorphic=self.symmorphic, 

430 _backwards_compatible=_backwards_compatible) 

431 

432 

433class BZSampling(Parameter): 

434 @classmethod 

435 def from_param(cls, kpts): 

436 if isinstance(kpts, BZSampling): 

437 return kpts 

438 if hasattr(kpts, 'kpts'): 

439 return KPoints(kpts.kpts) 

440 if isinstance(kpts, dict): 

441 if 'kpts' in kpts: 

442 return KPoints(kpts['kpts']) 

443 if 'path' in kpts: 

444 return BandPath(**kpts) 

445 kpts = kpts.copy() 

446 kpts.pop('name', '') 

447 else: 

448 kpts = np.array(kpts) 

449 if kpts.ndim == 1: 

450 kpts = {'size': kpts} 

451 else: 

452 return KPoints(kpts) 

453 return MonkhorstPack(**kpts) 

454 

455 

456class KPoints(BZSampling): 

457 def __init__(self, 

458 kpts: Sequence[Sequence[float]]): 

459 self.kpts = kpts 

460 

461 def todict(self): 

462 return {'kpts': self.kpts} 

463 

464 def build(self, atoms): 

465 from gpaw.new.brillouin import BZPoints 

466 return BZPoints(self.kpts) 

467 

468 

469class MonkhorstPack(BZSampling): 

470 def __init__(self, 

471 size: Sequence[int] | None = None, 

472 density: float | None = None, 

473 gamma: bool | None = None): 

474 self.size = size 

475 self.density = density 

476 self.gamma = gamma 

477 

478 def todict(self): 

479 dct = {} 

480 if self.size is not None: 

481 dct['size'] = self.size 

482 if self.density is not None: 

483 dct['density'] = self.density 

484 if self.gamma is not None: 

485 dct['gamma'] = self.gamma 

486 return dct 

487 

488 def build(self, atoms): 

489 from gpaw.new.brillouin import MonkhorstPackKPoints 

490 size, offset = kpts2sizeandoffsets(**self.todict(), atoms=atoms) 

491 for n, periodic in zip(size, atoms.pbc): 

492 if not periodic and n != 1: 

493 raise ValueError('K-points can only be used with PBCs!') 

494 return MonkhorstPackKPoints(size, offset) 

495 

496 

497class BandPath(BZSampling): 

498 def __init__(self, 

499 path: str, 

500 npoints: int): 

501 self.path = path 

502 self.npoints = npoints 

503 

504 def todict(self): 

505 return {'path': self.path, 'npoints': self.npoints} 

506 

507 def build(self, atoms): 

508 from gpaw.new.brillouin import BZBandPath 

509 return BZBandPath(atoms.cell.bandpath(self.path, 

510 npoints=self.npoints, 

511 pbc=atoms.pbc)) 

512 

513 

514class XC(Parameter): 

515 def __init__(self, name, **kwargs): 

516 self.name = name 

517 self.kwargs = kwargs 

518 

519 def todict(self): 

520 return {'name': self.name, **self.kwargs} 

521 

522 def functional(self, collinear): 

523 from gpaw.xc import XC as xc 

524 return xc({'name': self.name, **self.kwargs}, 

525 collinear=collinear) 

526 

527 @classmethod 

528 def from_param(cls, xc): 

529 if isinstance(xc, XC): 

530 return xc 

531 if isinstance(xc, str): 

532 xc = {'name': xc} 

533 return XC(**xc) 

534 

535 

536KptsType = Union[Sequence[int], dict, Sequence[Sequence[float]]] 

537 

538PARALLEL_KEYS = { 

539 'kpt', 'domain', 'band', 'order', 'stridebands', 'augment_grids', 

540 'sl_auto', 'sl_default', 'sl_diagonalize', 'sl_inverse_cholesky', 

541 'sl_lcao', 'sl_lrtddft', 'use_elpa', 'elpasolver', 'buffer_size', 'gpu'} 

542 

543 

544class Parameters: 

545 def __init__( 

546 self, 

547 *, 

548 mode: str | dict | Mode, 

549 basis: str | dict[str | int | None, str] | None = None, 

550 charge: float | None = None, 

551 convergence: dict | None = None, 

552 eigensolver: str | dict | Eigensolver | None = None, 

553 environment=None, 

554 experimental: dict | None = None, 

555 extensions: Sequence[Extension] | None = None, 

556 gpts: Sequence[int] | None = None, 

557 h: float | None = None, 

558 hund: bool | None = None, 

559 interpolation: int | Literal['fft'] | None = None, 

560 kpts: KptsType | MonkhorstPack | None = None, 

561 magmoms: Sequence[float] | Sequence[Sequence[float]] | None = None, 

562 maxiter: int | None = None, 

563 mixer: dict | Mixer | None = None, 

564 nbands: int | str | None = None, 

565 occupations: dict | Occupations | None = None, 

566 parallel: dict | None = None, 

567 poissonsolver: dict | PoissonSolver | None = None, 

568 random: bool | None = None, 

569 setups: str | dict | None = None, 

570 soc: bool | None = None, 

571 spinpol: bool | None = None, 

572 symmetry: str | dict | Symmetry | None = None, 

573 xc: str | dict | XC | None = None): 

574 """DFT-parameters object. 

575 

576 >>> p = Parameters(mode=PW(400)) 

577 >>> p 

578 mode=PW(ecut=400) 

579 >>> p.charge 

580 0.0 

581 >>> p.xc 

582 XC(name='LDA') 

583 >>> from ase.build import molecule 

584 >>> atoms = molecule('H2', vacuum=3.0) 

585 >>> dft = p.dft_calculation(atoms, txt='h2.txt') 

586 >>> atoms.calc = dft.ase_calculator() 

587 

588 Parameters 

589 ========== 

590 mode: 

591 PW, LCAO or FD mode. 

592 basis: 

593 Basis-set. Used for LCAO calculations and wave-function initial 

594 guess for PW and FD calculations. Default is to use the PAW 

595 pseudo partial-waves. 

596 charge: 

597 Total charge of the system in units of `|e|`. 

598 convergence: 

599 SCF-convergence criteria. 

600 eigensolver: 

601 Eigensolver. Default for PW and FD mode is ``'davidson'``. 

602 environment: 

603 ... 

604 gpts: 

605 Number of real-space grid-points for wave-functions 

606 (three integers). 

607 h: 

608 grid-spaving for wave-function grid (Å). 

609 hund: 

610 Use Hund's rule for initial magnetic moments. 

611 experimental: 

612 Experimental stuff. 

613 extensions: 

614 Extensions (D3, ...). 

615 interpolation: 

616 ... 

617 kpts: 

618 Brilluin-zone sampling. Default is Γ-point only. 

619 magmoms: 

620 Initial magnetic moments for non-collinear calculations. 

621 maxiter: 

622 Maximum number of allowed SCF-iterations. Default is 333. 

623 mixer: 

624 Density-mixing scheme. 

625 nbands: 

626 Number of bands. 

627 occupations: 

628 ... 

629 parallel: 

630 Parallelization strategy. Example: Force parallelization 

631 over ``'kpt`` with ``{'band': 1, 'domain': 1}``. 

632 poissonsolver: 

633 ... 

634 random: 

635 Use random numbers for initial wave functions. 

636 setups: 

637 ... 

638 soc: 

639 Enable spin-orbit coupling. 

640 spinpol: 

641 Force spin-polarized calculation. 

642 symmetry: 

643 Use of symmetry. Default is to use ... 

644 xc: 

645 XC-functional. Default is PZ-LDA. 

646 """ 

647 soc, magmoms = _parse_experimental(experimental, soc, magmoms) 

648 self._non_defaults = [ 

649 key for key, value in locals().items() 

650 if value is not None and key != 'self'] 

651 

652 if h is not None and gpts is not None: 

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

654 

655 self.mode = Mode.from_param(mode) 

656 basis = basis or {} 

657 self.basis = ({'default': basis} if not isinstance(basis, dict) 

658 else basis) 

659 self.charge = charge or 0.0 

660 self.convergence = convergence or {} 

661 self.eigensolver = Eigensolver.from_param(eigensolver or {}) 

662 self.environment = Environment.from_param(environment) 

663 self.experimental = experimental or {} 

664 self.extensions = [Extension.from_param(ext) 

665 for ext in extensions or []] 

666 self.gpts = np.array(gpts) if gpts is not None else None 

667 self.h = h 

668 self.hund = hund or False 

669 self.interpolation = interpolation 

670 self.kpts = BZSampling.from_param((1, 1, 1) if kpts is None else kpts) 

671 self.magmoms = np.array(magmoms) if magmoms is not None else None 

672 self.maxiter = maxiter or 333 

673 self.mixer = Mixer.from_param(mixer or {}) 

674 self.nbands = nbands 

675 self.occupations = Occupations.from_param(occupations or {}) 

676 self.parallel = parallel or {} 

677 self.poissonsolver = PoissonSolver.from_param(poissonsolver or {}) 

678 self.random = random or False 

679 setups = setups or 'paw' 

680 self.setups = ({'default': setups} if isinstance(setups, str) 

681 else setups) 

682 self.soc = soc or False 

683 self.spinpol = spinpol or False 

684 self.symmetry = Symmetry.from_param(symmetry or 'on') 

685 self.xc = XC.from_param(xc or 'LDA') 

686 

687 _fix_legacy_stuff(self) 

688 

689 for key in self.parallel: 

690 if key not in PARALLEL_KEYS: 

691 raise ValueError( 

692 f'Unknown key: {key!r}. ' 

693 f'Must be one of {", ".join(PARALLEL_KEYS)}') 

694 

695 def __repr__(self) -> str: 

696 lines = [] 

697 for key in self._non_defaults: 

698 value = self._value(key) 

699 lines.append(f'{key}={value!r}') 

700 return ',\n'.join(lines) 

701 

702 def todict(self) -> dict: 

703 dct = {} 

704 for key in self._non_defaults: 

705 value = self._value(key) 

706 if hasattr(value, 'todict'): 

707 name = getattr(value, 'name', None) 

708 value = value.todict() 

709 if name is not None: 

710 value['name'] = name 

711 elif key == 'extensions': 

712 value = [{'name': x.name, **x.todict()} 

713 for x in self.extensions] 

714 dct[key] = value 

715 return dct 

716 

717 def _value(self, key: str) -> Any: 

718 value = self.__dict__[key] 

719 if key == 'basis': 

720 if list(value) == [None]: 

721 value = value[None] 

722 return value 

723 

724 def dft_component_builder(self, atoms, *, comm=None, log=None): 

725 return self.mode.dft_components_builder( 

726 atoms, self, comm=comm, log=log) 

727 

728 def dft_calculation(self, 

729 atoms, 

730 txt: str | Path | IO[str] | None = '-', 

731 communicator: MPIComm | Sequence[int] | None = None 

732 ) -> DFTCalculation: 

733 log = Logger(txt, communicator) 

734 return DFTCalculation.from_parameters(atoms, self, log.comm, log) 

735 

736 def dft_info(self, atoms): 

737 ... 

738 

739 

740def _parse_experimental(experimental: dict | None, 

741 soc: bool | None, 

742 magmoms) -> tuple: 

743 if experimental is None: 

744 return soc, magmoms 

745 if experimental.pop('niter_fixdensity', None) is not None: 

746 warnings.warn('Ignoring "niter_fixdensity".') 

747 if 'reuse_wfs_method' in experimental: 

748 del experimental['reuse_wfs_method'] 

749 warnings.warn('Ignoring "reuse_wfs_method".') 

750 if 'soc' in experimental: 

751 warnings.warn('Please use new "soc" parameter.', 

752 DeprecatedParameterWarning) 

753 assert soc is None 

754 soc = experimental.pop('soc') 

755 if 'magmoms' in experimental: 

756 warnings.warn('Please use new "magmoms" parameter.', 

757 DeprecatedParameterWarning) 

758 assert magmoms is None 

759 magmoms = experimental.pop('magmoms') 

760 unknown = experimental.keys() - {'backwards_compatible', 

761 'ccirs', 

762 'fast_pw_init'} 

763 if unknown: 

764 warnings.warn(f'Unknown experimental keyword(s): {unknown}', 

765 stacklevel=3) 

766 return soc, magmoms 

767 

768 

769def _fix_legacy_stuff(params: Parameters) -> None: 

770 if not isinstance(params.mode, Mode): 

771 dct = params.mode.todict() 

772 if 'interpolation' in dct: 

773 params.interpolation = dct.pop('interpolation') 

774 params.mode = Mode.from_param(dct) 

775 if not isinstance(params.eigensolver, Eigensolver): 

776 params.eigensolver = Eigensolver.from_param( 

777 params.eigensolver.todict()) 

778 if not isinstance(params.mixer, Mixer): 

779 params.mixer = Mixer.from_param(params.mixer.todict()) 

780 

781 

782def DFT( 

783 atoms: Atoms, 

784 *, 

785 mode: str | dict | Mode, 

786 basis: str | dict[str | int | None, str] | None = None, 

787 charge: float | None = None, 

788 convergence: dict | None = None, 

789 eigensolver: str | dict | Eigensolver | None = None, 

790 environment=None, 

791 experimental: dict | None = None, 

792 extensions: Sequence[Extension] | None = None, 

793 gpts: Sequence[int] | None = None, 

794 h: float | None = None, 

795 hund: bool | None = None, 

796 interpolation: int | None = None, 

797 kpts: KptsType | MonkhorstPack | None = None, 

798 magmoms: Sequence[float] | Sequence[Sequence[float]] | None = None, 

799 maxiter: int | None = None, 

800 mixer: dict | Mixer | None = None, 

801 nbands: int | str | None = None, 

802 occupations: dict | Occupations | None = None, 

803 parallel: dict | None = None, 

804 poissonsolver: dict | PoissonSolver | None = None, 

805 random: bool | None = None, 

806 setups: str | dict | None = None, 

807 soc: bool | None = None, 

808 spinpol: bool | None = None, 

809 symmetry: str | dict | Symmetry | None = None, 

810 xc: str | dict | XC | None = None, 

811 txt: str | Path | IO[str] | None = '-', 

812 communicator: MPIComm | Sequence[int] | None = None) -> DFTCalculation: 

813 """Create a DFTCalculation object. 

814 

815 See :class:`gpaw.dft.Parameters` for the complete list of parameters. 

816 

817 Parameters 

818 ========== 

819 atoms: 

820 ASE-Atoms object. 

821 txt: 

822 Text log-file. Use ``None`` for no loggin and ``'-'`` for using 

823 standard out. 

824 communicator: 

825 MPI-communicator. Default is to use ``gpaw.mpi.world``. 

826 

827 """ 

828 params = Parameters(**{k: v for k, v in locals().items() 

829 if k in PARAMETER_NAMES}) 

830 return params.dft_calculation(atoms, txt, communicator) 

831 

832 

833def GPAW( 

834 filename: str | Path | IO[str] | None = None, 

835 *, 

836 basis: str | dict[str | int | None, str] | None = None, 

837 charge: float | None = None, 

838 convergence: dict | None = None, 

839 eigensolver: str | dict | Eigensolver | None = None, 

840 environment=None, 

841 experimental: dict | None = None, 

842 extensions: Sequence[Extension] | None = None, 

843 gpts: Sequence[int] | None = None, 

844 h: float | None = None, 

845 hund: bool | None = None, 

846 interpolation: int | None = None, 

847 kpts: KptsType | MonkhorstPack | None = None, 

848 magmoms: Sequence[float] | Sequence[Sequence[float]] | None = None, 

849 maxiter: int | None = None, 

850 mixer: dict | Mixer | None = None, 

851 mode: str | dict | Mode | None = None, 

852 nbands: int | str | None = None, 

853 occupations: dict | Occupations | None = None, 

854 parallel: dict | None = None, 

855 poissonsolver: dict | PoissonSolver | None = None, 

856 random: bool | None = None, 

857 setups: str | dict | None = None, 

858 soc: bool | None = None, 

859 spinpol: bool | None = None, 

860 symmetry: str | dict | Symmetry | None = None, 

861 xc: str | dict | XC | None = None, 

862 txt: str | Path | IO[str] | None = '?', 

863 communicator: MPIComm | Sequence[int] | None = None, 

864 object_hooks=None) -> ASECalculator: 

865 """Create ASE-compatible GPAW calculator. 

866 

867 See :class:`gpaw.dft.Parameters` for the complete list of parameters. 

868 

869 Parameters 

870 ========== 

871 filename: 

872 Name of gpw-file to restart from. 

873 txt: 

874 Text log-file. Use ``None`` for no loggin and ``'-'`` 

875 for using standard out. 

876 communicator: 

877 MPI-communicator. Default is to use ``gpaw.mpi.world``. 

878 object_hooks: 

879 Dictionart of hook-functions to create custom parameter-objects. 

880 """ 

881 from gpaw.new.ase_interface import ASECalculator 

882 from gpaw.new.gpw import read_gpw 

883 

884 if txt == '?': 

885 txt = '-' if filename is None else None 

886 

887 log = Logger(txt, communicator) 

888 

889 if mode is None: 

890 del mode 

891 

892 kwargs = {key: value for key, value in locals().items() 

893 if key in PARAMETER_NAMES} 

894 

895 if filename is not None: 

896 args = Parameters(mode='pw', **kwargs)._non_defaults 

897 if set(args) > {'mode', 'parallel'}: 

898 raise ValueError( 

899 'Illegal argument(s) when reading from a file: ' 

900 f'{", ".join(args)}') 

901 atoms, dft, params, _ = read_gpw(filename, 

902 log=log, 

903 parallel=parallel, 

904 object_hooks=object_hooks) 

905 return ASECalculator(params, 

906 log=log, dft=dft, atoms=atoms) 

907 

908 params = Parameters(**kwargs) 

909 return ASECalculator(params, log=log)