Source code for ase.calculators.gaussian

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