Source code for ase.calculators.gaussian

import copy
import os
from collections.abc import Iterable
from typing import Dict, Optional

from ase.calculators.calculator import FileIOCalculator
from ase.io import read, write


class GaussianDynamics:
    calctype = 'optimizer'
    delete = ['force']
    keyword: Optional[str] = None
    special_keywords: Dict[str, str] = {}

    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): _legacy_default_command = 'g16 < PREFIX.com > PREFIX.log' implemented_properties = ['energy', 'forces', 'dipole'] discard_results_on_any_change = True fileio_rules = FileIOCalculator.ruleset( stdin_name='{prefix}.com', stdout_name='{prefix}.log') def __init__(self, *args, label='Gaussian', **kwargs): super().__init__(*args, label=label, **kwargs) def write_input(self, atoms, properties=None, system_changes=None): super().write_input(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