Source code for berny.solvers
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import division
import os
import shutil
import subprocess
import tempfile
import numpy as np
from .coords import angstrom
__all__ = ['MopacSolver']
[docs]def MopacSolver(cmd='mopac', method='PM7', workdir=None):
"""
Crate a solver that wraps `MOPAC <http://openmopac.net>`_.
Mopac needs to be installed on the system.
:param str cmd: MOPAC executable
:param str method: model to calculate energy
"""
kcal = 1 / 627.503
tmpdir = workdir or tempfile.mkdtemp()
try:
atoms, lattice = yield
while True:
mopac_input = '{} 1SCF GRADIENTS\n\n\n'.format(method) + '\n'.join(
'{} {} 1 {} 1 {} 1'.format(el, *coord) for el, coord in atoms
)
if lattice is not None:
mopac_input += '\n' + '\n'.join(
'Tv {} 1 {} 1 {} 1'.format(*vec) for vec in lattice
)
input_file = os.path.join(tmpdir, 'job.mop')
with open(input_file, 'w') as f:
f.write(mopac_input)
subprocess.check_call([cmd, input_file])
with open(os.path.join(tmpdir, 'job.out')) as f:
energy = float(
next(l for l in f if 'FINAL HEAT OF FORMATION' in l).split()[5]
)
next(l for l in f if 'FINAL POINT AND DERIVATIVES' in l)
next(f)
next(f)
gradients = np.array(
[
[float(next(f).split()[6]) for _ in range(3)]
for _ in range(len(atoms) + (0 if lattice is None else 3))
]
)
atoms, lattice = yield energy * kcal, gradients * kcal / angstrom
finally:
if tmpdir != workdir:
shutil.rmtree(tmpdir)
def GenericSolver(f, *args, **kwargs):
delta = kwargs.pop('delta', 1e-3)
atoms, lattice = yield
while True:
energy = f(atoms, lattice, *args, **kwargs)
coords = np.array([coord for _, coord in atoms])
gradients = np.zeros(coords.shape)
for i_atom in range(coords.shape[0]):
for i_xyz in range(3):
ene = {}
for step in [-2, -1, 1, 2]:
coords_diff = coords.copy()
coords_diff[i_atom, i_xyz] += step * delta
atoms_diff = list(zip([sp for sp, _, in atoms], coords_diff))
ene[step] = f(atoms_diff, lattice, *args, **kwargs)
gradients[i_atom, i_xyz] = _diff5(ene, delta)
if lattice is not None:
lattice_grads = np.zeros((3, 3))
for i_vec in range(3):
for i_xyz in range(3):
ene = {}
for step in [-2, -1, 1, 2]:
lattice_diff = lattice.copy()
lattice_diff[i_vec, i_xyz] += step * delta
ene[step] = f(atoms, lattice_diff, *args, **kwargs)
lattice_grads[i_vec, i_xyz] = _diff5(ene, delta)
gradients = np.vstack((gradients, lattice_grads))
atoms, lattice = yield energy, gradients / angstrom
def _diff5(x, delta):
return (1 / 12 * x[-2] - 2 / 3 * x[-1] + 2 / 3 * x[1] - 1 / 12 * x[2]) / delta