# Copyright 2016 Autodesk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from cStringIO import StringIO
import numpy as np
import imp
import moldesign.units as u
from moldesign import compute
from moldesign.utils import if_not_none, redirect_stderr
from moldesign import orbitals
try:
imp.find_module('pyscf')
except (ImportError, OSError) as exc:
print 'PySCF not installed; using remote docker container'
force_remote = True
else:
force_remote = False
[docs]def mol_to_pyscf(mol, basis, symmetry=None, charge=0, positions=None):
"""Convert an MDT molecule to a PySCF "Mole" object"""
from pyscf import gto
pyscfmol = gto.Mole()
positions = if_not_none(positions, mol.atoms.position)
pyscfmol.atom = [[atom.elem, pos.value_in(u.angstrom)]
for atom, pos in zip(mol.atoms, positions)]
pyscfmol.basis = basis
pyscfmol.charge = charge
if symmetry is not None:
pyscfmol.symmetry = symmetry
with redirect_stderr(StringIO()) as builderr:
pyscfmol.build()
builderr.seek(0)
for line in builderr:
if line.strip() == 'Warn: Ipython shell catchs sys.args':
continue
else:
print 'PYSCF: ' + line
return pyscfmol
# PYSCF appears to have weird names for spherical components?
SPHERICAL_NAMES = {'y^3': (3, -3), 'xyz': (3, -2), 'yz^2': (3, -1), 'z^3': (3, 0),
'xz^2': (3, 1), 'zx^2': (3, 2), 'x^3': (3, 3),
'x2-y2': (2, 2)}
SPHERICAL_NAMES.update(orbitals.ANGULAR_NAME_TO_COMPONENT)
# TODO: need to handle parameters for max iterations,
# level shifts, requiring convergence, restarts, initial guesses
[docs]class StatusLogger(object):
LEN = 15
def __init__(self, description, columns, logger):
self.logger = logger
self.description = description
self.columns = columns
self._init = False
self._row_format = ("{:<%d}" % self.LEN) + ("{:>%d}" % self.LEN) * (len(columns) - 1)
def __call__(self, info):
if not self._init:
self.logger.status('Starting energy model calculation: %s' % self.description)
self.logger.status(self._row_format.format(*self.columns))
self.logger.status(self._row_format.format(*['-' * (self.LEN - 2) for i in self.columns]))
self._init = True
self.logger.status(self._row_format.format(*[info.get(c, 'n/a') for c in self.columns]))
@compute.runsremotely(enable=force_remote)
[docs]def get_eris_in_basis(basis, orbs):
""" Get electron repulsion integrals transformed in (in form eri[i,j,k,l] = (ij|kl))
"""
from pyscf import ao2mo
pmol = mol_to_pyscf(basis.wfn.molecule, basis=basis.basisname)
eri = ao2mo.full(pmol, orbs.T, compact=True) * u.hartree
eri.defunits_inplace()
return orbitals.ERI4FoldTensor(eri, orbs)
@compute.runsremotely(enable=force_remote)
[docs]def basis_values(mol, basis, coords, coeffs=None, positions=None):
""" Calculate the orbital's value at a position in space
Args:
mol (moldesign.Molecule): Molecule to attach basis set to
basis (moldesign.orbitals.BasisSet): set of basis functions
coords (Array[length]): List of coordinates (with shape ``(len(coords), 3)``)
coeffs (Vector): List of ao coefficients (optional; if not passed, all basis fn
values are returned)
Returns:
Array[length]: if ``coeffs`` is not passed, an array of basis fn values at each
coordinate. Otherwise, a list of orbital values at each coordinate
"""
from pyscf.dft import numint
# TODO: more than just create the basis by name ...
pmol = mol_to_pyscf(mol, basis=basis.basisname, positions=positions)
aovals = numint.eval_ao(pmol, np.ascontiguousarray(coords.value_in(u.bohr)))
if coeffs is None:
return aovals
else:
return aovals.dot(coeffs)