From eead47de47f058ef58ca80b823c29151f60b8b1e Mon Sep 17 00:00:00 2001 From: Aaron Virshup Date: Mon, 5 Jun 2017 14:14:19 -0400 Subject: [PATCH 1/5] Get symmetry working again (need to add tests) --- DockerMakefiles/SymMol.yml | 5 +++++ moldesign/_tests/test_symmetrizer.py | 0 moldesign/geom/symmetry.py | 8 +++++++- moldesign/interfaces/symmol_interface.py | 25 +++++++++++------------- 4 files changed, 23 insertions(+), 15 deletions(-) create mode 100644 moldesign/_tests/test_symmetrizer.py diff --git a/DockerMakefiles/SymMol.yml b/DockerMakefiles/SymMol.yml index 5ba955a..637fe7c 100644 --- a/DockerMakefiles/SymMol.yml +++ b/DockerMakefiles/SymMol.yml @@ -20,6 +20,11 @@ symmol_build: symmol: requires: - deploybase + build: | + RUN apt-get update \ + && apt-get install -y --no-install-recommends \ + gfortran libgfortran3\ + && cleanapt copy_from: symmol_build: /usr/local/bin/symmol: /usr/local/bin \ No newline at end of file diff --git a/moldesign/_tests/test_symmetrizer.py b/moldesign/_tests/test_symmetrizer.py new file mode 100644 index 0000000..e69de29 diff --git a/moldesign/geom/symmetry.py b/moldesign/geom/symmetry.py index d57c1dd..e643783 100644 --- a/moldesign/geom/symmetry.py +++ b/moldesign/geom/symmetry.py @@ -41,6 +41,12 @@ def __init__(self, symbol, matrix, **kwargs): for kw, val in kwargs.items(): setattr(self, kw, val) + def __str__(self): + return 'SymmetryElement %s' % self.symbol + + def __repr__(self): + return '<%s>' % self + def get_axis(self): """ Returns normal of the plane for Cs or axis of rotation for a Cn @@ -77,7 +83,7 @@ def __init__(self, mol, symbol, rms, self.mol = mol self.symbol = symbol self.rms = rms - self.orientation = mdt.utils.if_not_none(orientation, mol.atoms.position) + self.orientation = mdt.utils.if_not_none(orientation, mol.positions) self.elems = mdt.utils.if_not_none(elems, []) for kw, val in kwargs.items(): setattr(self, kw, val) diff --git a/moldesign/interfaces/symmol_interface.py b/moldesign/interfaces/symmol_interface.py index 5f338bd..0cc97bc 100644 --- a/moldesign/interfaces/symmol_interface.py +++ b/moldesign/interfaces/symmol_interface.py @@ -20,6 +20,7 @@ import fortranformat import numpy as np +import pyccc import moldesign as mdt from .. import units as u @@ -27,11 +28,10 @@ line_writer = fortranformat.FortranRecordWriter('(a6,i2,6f9.5)') +IMAGE = 'symmol' + #@doi('10.1107/S0021889898002180') -def run_symmol(mol, - tolerance=0.1 * u.angstrom, - image='symmol', - engine=None): +def run_symmol(mol, tolerance=0.1 * u.angstrom): infile = ['1.0 1.0 1.0 90.0 90.0 90.0', # line 1: indicates XYZ coordinates # line 2: numbers indicate: mass weighted moment of inertia, # tolerance interpretation, tolerance value, @@ -47,15 +47,11 @@ def run_symmol(mol, command = 'symmol < sym.in' inputs = {'sym.in': '\n'.join(infile)} - # TODO: this boilerplate has to go - engine = utils.if_not_none(engine, mdt.compute.get_engine()) - imagename = mdt.compute.get_image_path(image) - job = engine.launch(imagename, - command, - inputs=inputs, - name="symmol, %s" % mol.name) - mdt.display.display_log(job.get_display_object(), "symmol, %s"%mol.name) - job.wait() + job = pyccc.Job(image=mdt.compute.get_image_path(IMAGE), + command=command, + inputs=inputs, + name="symmol, %s" % mol.name) + job = mdt.compute.run_job(job) data = parse_output(job.get_output('symmol.out')) symm = mdt.geom.MolecularSymmetry( @@ -176,9 +172,10 @@ def _string_to_matrix(string): mat.append(row) return np.array(mat) + def get_aligned_coords(mol, data): com = mol.com - centerpos = mol.atoms.position - com + centerpos = mol.positions - com orthcoords = (centerpos.T.ldot(data.orthmat)).T return orthcoords From e43572099a765cdb2986ccf21c98997c62336b59 Mon Sep 17 00:00:00 2001 From: Aaron Virshup Date: Mon, 5 Jun 2017 16:43:44 -0400 Subject: [PATCH 2/5] Initial Psi4 interface calculates energies --- DockerMakefiles/PythonTools.yml | 6 +++ moldesign/interfaces/psi4_interface.py | 60 ++++++++++++++++++++++++++ moldesign/models/psi4.py | 51 ++++++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 moldesign/interfaces/psi4_interface.py create mode 100644 moldesign/models/psi4.py diff --git a/DockerMakefiles/PythonTools.yml b/DockerMakefiles/PythonTools.yml index d798b66..ea44a6a 100644 --- a/DockerMakefiles/PythonTools.yml +++ b/DockerMakefiles/PythonTools.yml @@ -14,6 +14,11 @@ notebook: && pip install backports.ssl_match_hostname backports.shutil_get_terminal_size +psi4: + requires: + - python_deploy_base + build: conda install -c psi4 psi4 + pyquante2: requires: - python_deploy_base @@ -34,6 +39,7 @@ chem_python_requirements: requires: - pyscf - chem_python_conda + - psi4 chem_python: requires: diff --git a/moldesign/interfaces/psi4_interface.py b/moldesign/interfaces/psi4_interface.py new file mode 100644 index 0000000..025be03 --- /dev/null +++ b/moldesign/interfaces/psi4_interface.py @@ -0,0 +1,60 @@ +from __future__ import print_function, absolute_import, division +from future.builtins import * +from future import standard_library +standard_library.install_aliases() + +# Copyright 2017 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. +import imp + +from moldesign import units as u +from ..utils import exports + + +try: + imp.find_module('psi4') +except (ImportError, OSError) as exc: + print('Psi4 not installed; using remote docker container') + force_remote = True +else: + force_remote = False + + +@exports +def mdt_to_psi4(mol): + import psi4 + lines = [] + for atom in mol.atoms: + x, y, z = atom.position.value_in(u.angstrom) + lines.append('%s %f %f %f' % (atom.symbol, x, y, z)) + + geom = psi4.geometry('\n'.join(lines)) + return geom + +@exports +def psi4_to_mdt(geom): + """ + + Args: + geom (psi4.core.Molecule): + + Returns: + + """ + geom.update_coords() + coords = geom.geometry().to_array() * u.bohr + + + + diff --git a/moldesign/models/psi4.py b/moldesign/models/psi4.py new file mode 100644 index 0000000..73dbd8a --- /dev/null +++ b/moldesign/models/psi4.py @@ -0,0 +1,51 @@ +from __future__ import print_function, absolute_import, division +from future.builtins import * +from future import standard_library + +standard_library.install_aliases() + +# Copyright 2017 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 ..compute import runsremotely +from ..interfaces import psi4_interface +from .base import QMBase + +THEORIES = {'rhf':'scf'} + +BASES = {} + + +class Psi4Potential(QMBase): + def prep(self): + return True + + @runsremotely(enable=psi4_interface.force_remote) + def calculate(self, requests): + import psi4 + geom = psi4_interface.mdt_to_psi4(self.mol) + psi4.set_options({'basis': BASES.get(self.params.basis, self.params.basis)}) + return {'potential_energy': self._get_runmethod(requests)(self._gettheorystring())} + + @staticmethod + def _get_runmethod(requests): + import psi4 + # TODO: actually deal with requests + return psi4.energy + + def _gettheorystring(self): + return THEORIES.get(self.params.theory, self.params.theory) + + + From c53f86606e387fcfc354711941f4f301a4b5b278 Mon Sep 17 00:00:00 2001 From: Aaron Virshup Date: Tue, 6 Jun 2017 14:27:33 -0400 Subject: [PATCH 3/5] Fix some random bugs --- DockerMakefiles/PythonTools.yml | 2 - moldesign/_tests/test_symmetrizer.py | 3 ++ moldesign/helpers/widgets.py | 4 +- moldesign/models/psi4.py | 4 +- moldesign/molecules/properties.py | 15 +++++++ moldesign/utils/callsigs.py | 60 ++++++++++++++++------------ 6 files changed, 58 insertions(+), 30 deletions(-) diff --git a/DockerMakefiles/PythonTools.yml b/DockerMakefiles/PythonTools.yml index ea44a6a..645efff 100644 --- a/DockerMakefiles/PythonTools.yml +++ b/DockerMakefiles/PythonTools.yml @@ -15,8 +15,6 @@ notebook: psi4: - requires: - - python_deploy_base build: conda install -c psi4 psi4 pyquante2: diff --git a/moldesign/_tests/test_symmetrizer.py b/moldesign/_tests/test_symmetrizer.py index e69de29..6964301 100644 --- a/moldesign/_tests/test_symmetrizer.py +++ b/moldesign/_tests/test_symmetrizer.py @@ -0,0 +1,3 @@ +import moldesign as mdt +import pytest + diff --git a/moldesign/helpers/widgets.py b/moldesign/helpers/widgets.py index 1b84124..ccdf2dd 100644 --- a/moldesign/helpers/widgets.py +++ b/moldesign/helpers/widgets.py @@ -24,8 +24,10 @@ imp.find_module('nbmolviz') except ImportError: nbmolviz_installed = False + disp_log = print else: nbmolviz_installed = True + from IPython.display import display as disp_log def _get_nbmethod(name): @@ -90,4 +92,4 @@ def display_log(obj, title=None, show=False): :param title: A name for the object (otherwise, str(obj) is used) :return: """ - print(obj) + disp_log(obj) diff --git a/moldesign/models/psi4.py b/moldesign/models/psi4.py index 73dbd8a..6597478 100644 --- a/moldesign/models/psi4.py +++ b/moldesign/models/psi4.py @@ -18,6 +18,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from .. import units as u from ..compute import runsremotely from ..interfaces import psi4_interface from .base import QMBase @@ -36,7 +37,8 @@ def calculate(self, requests): import psi4 geom = psi4_interface.mdt_to_psi4(self.mol) psi4.set_options({'basis': BASES.get(self.params.basis, self.params.basis)}) - return {'potential_energy': self._get_runmethod(requests)(self._gettheorystring())} + return {'potential_energy': + self._get_runmethod(requests)(self._gettheorystring()) * u.hartree} @staticmethod def _get_runmethod(requests): diff --git a/moldesign/molecules/properties.py b/moldesign/molecules/properties.py index 2880844..9072495 100644 --- a/moldesign/molecules/properties.py +++ b/moldesign/molecules/properties.py @@ -43,6 +43,21 @@ def copy(self, mol): props['mol'] = mol return self.__class__(**props) + def __getattr__(self, item): + val = super().__getattr__(item) + return self._tryconvert(val) + + def __getitem__(self, item): + val = super().__getitem__(item) + return self._tryconvert(val) + + def _tryconvert(self, val): + try: + return val.defunits() + except (TypeError, ValueError): + return val + + def geometry_matches(self, mol): """Returns: bool: True if the molecule's ``position`` is the same as these properties' ``position`` diff --git a/moldesign/utils/callsigs.py b/moldesign/utils/callsigs.py index 4359992..5b2c3d4 100644 --- a/moldesign/utils/callsigs.py +++ b/moldesign/utils/callsigs.py @@ -20,10 +20,12 @@ import inspect import os from functools import wraps - import collections +import warnings + import funcsigs + from .utils import if_not_none from .docparsers import GoogleDocArgumentInjector @@ -105,38 +107,44 @@ def args_from(original_function, def decorator(f): """Modify f's call signature (using the `__signature__` attribute)""" - if wraps: - fname = original_function.__name__ - f = functools.wraps(original_function)(f) - f.__name__ = fname # revert name change - else: - fname = f.__name__ - f.__signature__ = sig - - if update_docstring_args or inject_kwargs: - if not update_docstring_args: - argument_docstrings = GoogleDocArgumentInjector(f.__doc__).args - docs = GoogleDocArgumentInjector(f.__doc__) - docs.args = argument_docstrings - - if not hasattr(f, '__orig_docs'): - f.__orig_docs = [] - f.__orig_docs.append(f.__doc__) + try: + if wraps: + fname = original_function.__name__ + f = functools.wraps(original_function)(f) + f.__name__ = fname # revert name change + else: + fname = f.__name__ + f.__signature__ = sig + + if update_docstring_args or inject_kwargs: + if not update_docstring_args: + argument_docstrings = GoogleDocArgumentInjector(f.__doc__).args + docs = GoogleDocArgumentInjector(f.__doc__) + docs.args = argument_docstrings + + if not hasattr(f, '__orig_docs'): + f.__orig_docs = [] + f.__orig_docs.append(f.__doc__) + + f.__doc__ = docs.new_docstring() + + # Only for building sphinx documentation: + if os.environ.get('SPHINX_IS_BUILDING_DOCS', ""): + sigstring = '%s%s\n' % (fname, sig) + if hasattr(f, '__doc__') and f.__doc__ is not None: + f.__doc__ = sigstring + f.__doc__ + else: + f.__doc__ = sigstring - f.__doc__ = docs.new_docstring() + except Exception as exc: + warnings.warn('Failed to create call signature object for %s: %s' % (f, exc)) - # Only for building sphinx documentation: - if os.environ.get('SPHINX_IS_BUILDING_DOCS', ""): - sigstring = '%s%s\n' % (fname, sig) - if hasattr(f, '__doc__') and f.__doc__ is not None: - f.__doc__ = sigstring + f.__doc__ - else: - f.__doc__ = sigstring return f return decorator + def kwargs_from(reference_function, mod_docs=True): """ Replaces ``**kwargs`` in a call signature with keyword arguments from another function. From c74631e7e8e55506fed8bb4df712951b64edbd6c Mon Sep 17 00:00:00 2001 From: Aaron Virshup Date: Wed, 7 Jun 2017 20:39:09 -0400 Subject: [PATCH 4/5] Add cleanup, fix property access --- moldesign/models/psi4.py | 7 ++++--- moldesign/molecules/properties.py | 10 +++++----- moldesign/parameters.py | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/moldesign/models/psi4.py b/moldesign/models/psi4.py index 6597478..35473cd 100644 --- a/moldesign/models/psi4.py +++ b/moldesign/models/psi4.py @@ -37,8 +37,10 @@ def calculate(self, requests): import psi4 geom = psi4_interface.mdt_to_psi4(self.mol) psi4.set_options({'basis': BASES.get(self.params.basis, self.params.basis)}) - return {'potential_energy': - self._get_runmethod(requests)(self._gettheorystring()) * u.hartree} + props = {'potential_energy': + self._get_runmethod(requests)(self._gettheorystring()) * u.hartree} + psi4.core.clean() + psi4.core.clean_options() @staticmethod def _get_runmethod(requests): @@ -50,4 +52,3 @@ def _gettheorystring(self): return THEORIES.get(self.params.theory, self.params.theory) - diff --git a/moldesign/molecules/properties.py b/moldesign/molecules/properties.py index 9072495..d5a655e 100644 --- a/moldesign/molecules/properties.py +++ b/moldesign/molecules/properties.py @@ -51,15 +51,15 @@ def __getitem__(self, item): val = super().__getitem__(item) return self._tryconvert(val) - def _tryconvert(self, val): - try: + @staticmethod + def _tryconvert(val): + if hasattr(val, 'defunits'): return val.defunits() - except (TypeError, ValueError): + else: return val - def geometry_matches(self, mol): """Returns: bool: True if the molecule's ``position`` is the same as these properties' ``position`` """ - return np.array_equal(self.positions, mol.positions) \ No newline at end of file + return np.array_equal(self.positions, mol.positions) diff --git a/moldesign/parameters.py b/moldesign/parameters.py index d312a90..11ecd91 100644 --- a/moldesign/parameters.py +++ b/moldesign/parameters.py @@ -138,7 +138,7 @@ def named_dict(l): ]) -QMTHEORIES = ['rhf', 'rks', 'mp2', 'casscf', 'casci', 'fci'] +QMTHEORIES = ['rhf', 'rks', 'uhf', 'uks', 'mp2', 'casscf', 'casci', 'fci'] BASISSETS = ['3-21g', '4-31g', '6-31g', '6-31g*', '6-31g**', '6-311g', '6-311g*', '6-311g+', '6-311g*+', 'sto-3g', 'sto-6g', 'minao', 'weigend', From 2fdf47b1ba29d9cbefeede4ead9b83a8645a3fa9 Mon Sep 17 00:00:00 2001 From: Aaron Virshup Date: Fri, 9 Jun 2017 13:25:14 -0700 Subject: [PATCH 5/5] Actually return the calculated properties --- moldesign/models/psi4.py | 1 + 1 file changed, 1 insertion(+) diff --git a/moldesign/models/psi4.py b/moldesign/models/psi4.py index 35473cd..fdbbc2d 100644 --- a/moldesign/models/psi4.py +++ b/moldesign/models/psi4.py @@ -41,6 +41,7 @@ def calculate(self, requests): self._get_runmethod(requests)(self._gettheorystring()) * u.hartree} psi4.core.clean() psi4.core.clean_options() + return props @staticmethod def _get_runmethod(requests):