Coverage for ase / calculators / gaussian.py: 41.46%

82 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-03-30 08:22 +0000

1# fmt: off 

2 

3import copy 

4import os 

5from collections.abc import Iterable 

6 

7from ase.calculators.calculator import FileIOCalculator 

8from ase.io import read, write 

9 

10 

11class GaussianDynamics: 

12 calctype = 'optimizer' 

13 delete = ['force'] 

14 keyword: str | None = None 

15 special_keywords: dict[str, str] = {} 

16 

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

18 self.atoms = atoms 

19 if calc is not None: 

20 self.calc = calc 

21 else: 

22 if self.atoms.calc is None: 

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

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

25 

26 self.calc = self.atoms.calc 

27 

28 def todict(self): 

29 return {'type': self.calctype, 

30 'optimizer': self.__class__.__name__} 

31 

32 def delete_keywords(self, kwargs): 

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

34 for d in self.delete: 

35 kwargs.pop(d, None) 

36 

37 def set_keywords(self, kwargs): 

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

39 if isinstance(args, str): 

40 args = [args] 

41 elif isinstance(args, Iterable): 

42 args = list(args) 

43 

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

45 if key in kwargs: 

46 val = kwargs.pop(key) 

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

48 

49 kwargs[self.keyword] = args 

50 

51 def run(self, **kwargs): 

52 calc_old = self.atoms.calc 

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

54 

55 self.delete_keywords(kwargs) 

56 self.delete_keywords(self.calc.parameters) 

57 self.set_keywords(kwargs) 

58 

59 self.calc.set(**kwargs) 

60 self.atoms.calc = self.calc 

61 

62 try: 

63 self.atoms.get_potential_energy() 

64 except OSError: 

65 converged = False 

66 else: 

67 converged = True 

68 

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

70 self.atoms.cell = atoms.cell 

71 self.atoms.positions = atoms.positions 

72 

73 self.calc.parameters = params_old 

74 self.calc.reset() 

75 if calc_old is not None: 

76 self.atoms.calc = calc_old 

77 

78 return converged 

79 

80 

81class GaussianOptimizer(GaussianDynamics): 

82 keyword = 'opt' 

83 special_keywords = { 

84 'fmax': '{}', 

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

86 } 

87 

88 

89class GaussianIRC(GaussianDynamics): 

90 keyword = 'irc' 

91 special_keywords = { 

92 'direction': '{}', 

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

94 } 

95 

96 

97class Gaussian(FileIOCalculator): 

98 _legacy_default_command = 'g16 < PREFIX.com > PREFIX.log' 

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

100 discard_results_on_any_change = True 

101 

102 fileio_rules = FileIOCalculator.ruleset( 

103 stdin_name='{prefix}.com', 

104 stdout_name='{prefix}.log') 

105 

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

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

108 

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

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

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

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

113 

114 def read_results(self): 

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

116 self.calc = output.calc 

117 self.results = output.calc.results 

118 

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

120 # backwards compatibility 

121 def clean(self): 

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

123 try: 

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

125 except OSError: 

126 pass 

127 

128 def get_version(self): 

129 raise NotImplementedError # not sure how to do this yet