1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138
|
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', **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
|