import os
import copy
from collections.abc import Iterable
from shutil import which
from typing import Dict, Optional
from ase.io import read, write
from ase.calculators.calculator import FileIOCalculator, EnvironmentError
class GaussianDynamics:
calctype = 'optimizer'
delete = ['force']
keyword: Optional[str] = None
special_keywords: Dict[str, str] = dict()
def __init__(self, atoms, calc=None):
self.atoms = atoms
if calc is not None:
self.calc = calc
else:
if self.atoms.calc is None:
raise ValueError("{} requires a valid Gaussian calculator "
"object!".format(self.__class__.__name__))
self.calc = self.atoms.calc
def todict(self):
return {'type': self.calctype,
'optimizer': self.__class__.__name__}
def delete_keywords(self, kwargs):
"""removes list of keywords (delete) from kwargs"""
for d in self.delete:
kwargs.pop(d, None)
def set_keywords(self, kwargs):
args = kwargs.pop(self.keyword, [])
if isinstance(args, str):
args = [args]
elif isinstance(args, Iterable):
args = list(args)
for key, template in self.special_keywords.items():
if key in kwargs:
val = kwargs.pop(key)
args.append(template.format(val))
kwargs[self.keyword] = args
def run(self, **kwargs):
calc_old = self.atoms.calc
params_old = copy.deepcopy(self.calc.parameters)
self.delete_keywords(kwargs)
self.delete_keywords(self.calc.parameters)
self.set_keywords(kwargs)
self.calc.set(**kwargs)
self.atoms.calc = self.calc
try:
self.atoms.get_potential_energy()
except OSError:
converged = False
else:
converged = True
atoms = read(self.calc.label + '.log')
self.atoms.cell = atoms.cell
self.atoms.positions = atoms.positions
self.calc.parameters = params_old
self.calc.reset()
if calc_old is not None:
self.atoms.calc = calc_old
return converged
class GaussianOptimizer(GaussianDynamics):
keyword = 'opt'
special_keywords = {
'fmax': '{}',
'steps': 'maxcycle={}',
}
class GaussianIRC(GaussianDynamics):
keyword = 'irc'
special_keywords = {
'direction': '{}',
'steps': 'maxpoints={}',
}
[docs]class Gaussian(FileIOCalculator):
implemented_properties = ['energy', 'forces', 'dipole']
command = 'GAUSSIAN < PREFIX.com > PREFIX.log'
discard_results_on_any_change = True
def __init__(self, *args, label='Gaussian', **kwargs):
FileIOCalculator.__init__(self, *args, label=label, **kwargs)
def calculate(self, *args, **kwargs):
gaussians = ('g16', 'g09', 'g03')
if 'GAUSSIAN' in self.command:
for gau in gaussians:
if which(gau):
self.command = self.command.replace('GAUSSIAN', gau)
break
else:
raise EnvironmentError('Missing Gaussian executable {}'
.format(gaussians))
FileIOCalculator.calculate(self, *args, **kwargs)
def write_input(self, atoms, properties=None, system_changes=None):
FileIOCalculator.write_input(self, atoms, properties, system_changes)
write(self.label + '.com', atoms, properties=properties,
format='gaussian-in', parallel=False, **self.parameters)
def read_results(self):
output = read(self.label + '.log', format='gaussian-out')
self.calc = output.calc
self.results = output.calc.results
# Method(s) defined in the old calculator, added here for
# backwards compatibility
def clean(self):
for suffix in ['.com', '.chk', '.log']:
try:
os.remove(os.path.join(self.directory, self.label + suffix))
except OSError:
pass
def get_version(self):
raise NotImplementedError # not sure how to do this yet