Coverage for /builds/debichem-team/python-ase/ase/calculators/gaussian.py: 42.17%
83 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-03-06 04:00 +0000
1import copy
2import os
3from collections.abc import Iterable
4from typing import Dict, Optional
6from ase.calculators.calculator import FileIOCalculator
7from ase.io import read, write
10class GaussianDynamics:
11 calctype = 'optimizer'
12 delete = ['force']
13 keyword: Optional[str] = None
14 special_keywords: Dict[str, str] = {}
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__))
25 self.calc = self.atoms.calc
27 def todict(self):
28 return {'type': self.calctype,
29 'optimizer': self.__class__.__name__}
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)
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)
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))
48 kwargs[self.keyword] = args
50 def run(self, **kwargs):
51 calc_old = self.atoms.calc
52 params_old = copy.deepcopy(self.calc.parameters)
54 self.delete_keywords(kwargs)
55 self.delete_keywords(self.calc.parameters)
56 self.set_keywords(kwargs)
58 self.calc.set(**kwargs)
59 self.atoms.calc = self.calc
61 try:
62 self.atoms.get_potential_energy()
63 except OSError:
64 converged = False
65 else:
66 converged = True
68 atoms = read(self.calc.label + '.log')
69 self.atoms.cell = atoms.cell
70 self.atoms.positions = atoms.positions
72 self.calc.parameters = params_old
73 self.calc.reset()
74 if calc_old is not None:
75 self.atoms.calc = calc_old
77 return converged
80class GaussianOptimizer(GaussianDynamics):
81 keyword = 'opt'
82 special_keywords = {
83 'fmax': '{}',
84 'steps': 'maxcycle={}',
85 }
88class GaussianIRC(GaussianDynamics):
89 keyword = 'irc'
90 special_keywords = {
91 'direction': '{}',
92 'steps': 'maxpoints={}',
93 }
96class Gaussian(FileIOCalculator):
97 _legacy_default_command = 'g16 < PREFIX.com > PREFIX.log'
98 implemented_properties = ['energy', 'forces', 'dipole']
99 discard_results_on_any_change = True
101 fileio_rules = FileIOCalculator.ruleset(
102 stdin_name='{prefix}.com',
103 stdout_name='{prefix}.log')
105 def __init__(self, *args, label='Gaussian', **kwargs):
106 super().__init__(*args, label=label, **kwargs)
108 def write_input(self, atoms, properties=None, system_changes=None):
109 super().write_input(atoms, properties, system_changes)
110 write(self.label + '.com', atoms, properties=properties,
111 format='gaussian-in', parallel=False, **self.parameters)
113 def read_results(self):
114 output = read(self.label + '.log', format='gaussian-out')
115 self.calc = output.calc
116 self.results = output.calc.results
118 # Method(s) defined in the old calculator, added here for
119 # backwards compatibility
120 def clean(self):
121 for suffix in ['.com', '.chk', '.log']:
122 try:
123 os.remove(os.path.join(self.directory, self.label + suffix))
124 except OSError:
125 pass
127 def get_version(self):
128 raise NotImplementedError # not sure how to do this yet