/ase/calculators/gaussian.py
Python | 138 lines | 105 code | 30 blank | 3 comment | 19 complexity | 0cc1ba2634572ba4d2dace18c8287ee7 MD5 | raw file
- 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={}',
- }
- 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