Coverage for /builds/ase/ase/ase/calculators/gaussian.py: 42.17%
83 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-08-02 00:12 +0000
1# fmt: off
3import copy
4import os
5from collections.abc import Iterable
6from typing import Dict, Optional
8from ase.calculators.calculator import FileIOCalculator
9from ase.io import read, write
12class GaussianDynamics:
13 calctype = 'optimizer'
14 delete = ['force']
15 keyword: Optional[str] = None
16 special_keywords: Dict[str, str] = {}
18 def __init__(self, atoms, calc=None):
19 self.atoms = atoms
20 if calc is not None:
21 self.calc = calc
22 else:
23 if self.atoms.calc is None:
24 raise ValueError("{} requires a valid Gaussian calculator "
25 "object!".format(self.__class__.__name__))
27 self.calc = self.atoms.calc
29 def todict(self):
30 return {'type': self.calctype,
31 'optimizer': self.__class__.__name__}
33 def delete_keywords(self, kwargs):
34 """removes list of keywords (delete) from kwargs"""
35 for d in self.delete:
36 kwargs.pop(d, None)
38 def set_keywords(self, kwargs):
39 args = kwargs.pop(self.keyword, [])
40 if isinstance(args, str):
41 args = [args]
42 elif isinstance(args, Iterable):
43 args = list(args)
45 for key, template in self.special_keywords.items():
46 if key in kwargs:
47 val = kwargs.pop(key)
48 args.append(template.format(val))
50 kwargs[self.keyword] = args
52 def run(self, **kwargs):
53 calc_old = self.atoms.calc
54 params_old = copy.deepcopy(self.calc.parameters)
56 self.delete_keywords(kwargs)
57 self.delete_keywords(self.calc.parameters)
58 self.set_keywords(kwargs)
60 self.calc.set(**kwargs)
61 self.atoms.calc = self.calc
63 try:
64 self.atoms.get_potential_energy()
65 except OSError:
66 converged = False
67 else:
68 converged = True
70 atoms = read(self.calc.label + '.log')
71 self.atoms.cell = atoms.cell
72 self.atoms.positions = atoms.positions
74 self.calc.parameters = params_old
75 self.calc.reset()
76 if calc_old is not None:
77 self.atoms.calc = calc_old
79 return converged
82class GaussianOptimizer(GaussianDynamics):
83 keyword = 'opt'
84 special_keywords = {
85 'fmax': '{}',
86 'steps': 'maxcycle={}',
87 }
90class GaussianIRC(GaussianDynamics):
91 keyword = 'irc'
92 special_keywords = {
93 'direction': '{}',
94 'steps': 'maxpoints={}',
95 }
98class Gaussian(FileIOCalculator):
99 _legacy_default_command = 'g16 < PREFIX.com > PREFIX.log'
100 implemented_properties = ['energy', 'forces', 'dipole']
101 discard_results_on_any_change = True
103 fileio_rules = FileIOCalculator.ruleset(
104 stdin_name='{prefix}.com',
105 stdout_name='{prefix}.log')
107 def __init__(self, *args, label='Gaussian', **kwargs):
108 super().__init__(*args, label=label, **kwargs)
110 def write_input(self, atoms, properties=None, system_changes=None):
111 super().write_input(atoms, properties, system_changes)
112 write(self.label + '.com', atoms, properties=properties,
113 format='gaussian-in', parallel=False, **self.parameters)
115 def read_results(self):
116 output = read(self.label + '.log', format='gaussian-out')
117 self.calc = output.calc
118 self.results = output.calc.results
120 # Method(s) defined in the old calculator, added here for
121 # backwards compatibility
122 def clean(self):
123 for suffix in ['.com', '.chk', '.log']:
124 try:
125 os.remove(os.path.join(self.directory, self.label + suffix))
126 except OSError:
127 pass
129 def get_version(self):
130 raise NotImplementedError # not sure how to do this yet