Coverage for /builds/kinetik161/ase/ase/calculators/gaussian.py: 40.74%

81 statements  

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

1import copy 

2import os 

3from collections.abc import Iterable 

4from typing import Dict, Optional 

5 

6from ase.calculators.calculator import FileIOCalculator 

7from ase.io import read, write 

8 

9 

10class GaussianDynamics: 

11 calctype = 'optimizer' 

12 delete = ['force'] 

13 keyword: Optional[str] = None 

14 special_keywords: Dict[str, str] = {} 

15 

16 def __init__(self, atoms, calc=None): 

17 self.atoms = atoms 

18 if calc is not None: 

19 self.calc = calc 

20 else: 

21 if self.atoms.calc is None: 

22 raise ValueError("{} requires a valid Gaussian calculator " 

23 "object!".format(self.__class__.__name__)) 

24 

25 self.calc = self.atoms.calc 

26 

27 def todict(self): 

28 return {'type': self.calctype, 

29 'optimizer': self.__class__.__name__} 

30 

31 def delete_keywords(self, kwargs): 

32 """removes list of keywords (delete) from kwargs""" 

33 for d in self.delete: 

34 kwargs.pop(d, None) 

35 

36 def set_keywords(self, kwargs): 

37 args = kwargs.pop(self.keyword, []) 

38 if isinstance(args, str): 

39 args = [args] 

40 elif isinstance(args, Iterable): 

41 args = list(args) 

42 

43 for key, template in self.special_keywords.items(): 

44 if key in kwargs: 

45 val = kwargs.pop(key) 

46 args.append(template.format(val)) 

47 

48 kwargs[self.keyword] = args 

49 

50 def run(self, **kwargs): 

51 calc_old = self.atoms.calc 

52 params_old = copy.deepcopy(self.calc.parameters) 

53 

54 self.delete_keywords(kwargs) 

55 self.delete_keywords(self.calc.parameters) 

56 self.set_keywords(kwargs) 

57 

58 self.calc.set(**kwargs) 

59 self.atoms.calc = self.calc 

60 

61 try: 

62 self.atoms.get_potential_energy() 

63 except OSError: 

64 converged = False 

65 else: 

66 converged = True 

67 

68 atoms = read(self.calc.label + '.log') 

69 self.atoms.cell = atoms.cell 

70 self.atoms.positions = atoms.positions 

71 

72 self.calc.parameters = params_old 

73 self.calc.reset() 

74 if calc_old is not None: 

75 self.atoms.calc = calc_old 

76 

77 return converged 

78 

79 

80class GaussianOptimizer(GaussianDynamics): 

81 keyword = 'opt' 

82 special_keywords = { 

83 'fmax': '{}', 

84 'steps': 'maxcycle={}', 

85 } 

86 

87 

88class GaussianIRC(GaussianDynamics): 

89 keyword = 'irc' 

90 special_keywords = { 

91 'direction': '{}', 

92 'steps': 'maxpoints={}', 

93 } 

94 

95 

96class Gaussian(FileIOCalculator): 

97 implemented_properties = ['energy', 'forces', 'dipole'] 

98 discard_results_on_any_change = True 

99 

100 def __init__(self, *args, label='Gaussian', **kwargs): 

101 super().__init__(*args, label=label, **kwargs) 

102 

103 def write_input(self, atoms, properties=None, system_changes=None): 

104 super().write_input(atoms, properties, system_changes) 

105 write(self.label + '.com', atoms, properties=properties, 

106 format='gaussian-in', parallel=False, **self.parameters) 

107 

108 def read_results(self): 

109 output = read(self.label + '.log', format='gaussian-out') 

110 self.calc = output.calc 

111 self.results = output.calc.results 

112 

113 # Method(s) defined in the old calculator, added here for 

114 # backwards compatibility 

115 def clean(self): 

116 for suffix in ['.com', '.chk', '.log']: 

117 try: 

118 os.remove(os.path.join(self.directory, self.label + suffix)) 

119 except OSError: 

120 pass 

121 

122 def get_version(self): 

123 raise NotImplementedError # not sure how to do this yet