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
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 08:22 +0000
1# fmt: off
3import copy
4import os
5from collections.abc import Iterable
7from ase.calculators.calculator import FileIOCalculator
8from ase.io import read, write
11class GaussianDynamics:
12 calctype = 'optimizer'
13 delete = ['force']
14 keyword: str | None = None
15 special_keywords: dict[str, str] = {}
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__))
26 self.calc = self.atoms.calc
28 def todict(self):
29 return {'type': self.calctype,
30 'optimizer': self.__class__.__name__}
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)
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)
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))
49 kwargs[self.keyword] = args
51 def run(self, **kwargs):
52 calc_old = self.atoms.calc
53 params_old = copy.deepcopy(self.calc.parameters)
55 self.delete_keywords(kwargs)
56 self.delete_keywords(self.calc.parameters)
57 self.set_keywords(kwargs)
59 self.calc.set(**kwargs)
60 self.atoms.calc = self.calc
62 try:
63 self.atoms.get_potential_energy()
64 except OSError:
65 converged = False
66 else:
67 converged = True
69 atoms = read(self.calc.label + '.log')
70 self.atoms.cell = atoms.cell
71 self.atoms.positions = atoms.positions
73 self.calc.parameters = params_old
74 self.calc.reset()
75 if calc_old is not None:
76 self.atoms.calc = calc_old
78 return converged
81class GaussianOptimizer(GaussianDynamics):
82 keyword = 'opt'
83 special_keywords = {
84 'fmax': '{}',
85 'steps': 'maxcycle={}',
86 }
89class GaussianIRC(GaussianDynamics):
90 keyword = 'irc'
91 special_keywords = {
92 'direction': '{}',
93 'steps': 'maxpoints={}',
94 }
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
102 fileio_rules = FileIOCalculator.ruleset(
103 stdin_name='{prefix}.com',
104 stdout_name='{prefix}.log')
106 def __init__(self, *args, label='Gaussian', **kwargs):
107 super().__init__(*args, label=label, **kwargs)
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)
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
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
128 def get_version(self):
129 raise NotImplementedError # not sure how to do this yet