diff --git a/docs/gammanu.ipynb b/docs/gammanu.ipynb index 6cb0a06..c58fbe4 100644 --- a/docs/gammanu.ipynb +++ b/docs/gammanu.ipynb @@ -16,6 +16,15 @@ "First we build up the integral as stated in equation 2.11 in the THO paper." ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!pip install pyquante2@git+https://github.com/rpmuller/pyquante2@pure\" " + ] + }, { "cell_type": "code", "execution_count": 2, @@ -1098,7 +1107,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.18" + "version": "3.11.10" }, "orig_nbformat": 4 }, diff --git a/docs/optim.ipynb b/docs/optim.ipynb index 3f5243e..7fa1453 100644 --- a/docs/optim.ipynb +++ b/docs/optim.ipynb @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -40,24 +40,43 @@ "hide-cell" ] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "/bin/bash: -c: line 1: unexpected EOF while looking for matching `\"'\n", + "/bin/bash: -c: line 2: syntax error: unexpected end of file\n" + ] + } + ], "source": [ "import sys\n", "\n", "if \"google.colab\" in sys.modules:\n", - " !pip install git+https://github.com/valence-labs/mess.git" + " !pip install git+https://github.com/valence-labs/mess.git\n", + "\n", + "!pip install pyquante2@git+https://github.com/rpmuller/pyquante2@pure\" " ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "metadata": { "id": "j7HdZdtPdEXU", "tags": [ "hide-cell" ] }, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "An NVIDIA GPU may be present on this machine, but a CUDA-enabled jaxlib is not installed. Falling back to cpu.\n" + ] + } + ], "source": [ "import jax\n", "import jax.numpy as jnp\n", @@ -82,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -94,10 +113,10 @@ "outputs": [ { "data": { - "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", + "application/3dmoljs_load.v0": "
\n

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n
\n", "text/html": [ - "
\n", - "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", + "
\n", + "

3Dmol.js failed to load for some reason. Please check your browser console for error messages.

\n", "
\n", "" ] @@ -152,7 +171,7 @@ "Structure(atomic_number=i64[5](numpy), position=f64[5,3](numpy))" ] }, - "execution_count": 4, + "execution_count": 3, "metadata": {}, "output_type": "execute_result" } @@ -345,7 +364,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.14" + "version": "3.11.10" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/mess/__init__.py b/mess/__init__.py index 4bd55c7..8f2cea9 100644 --- a/mess/__init__.py +++ b/mess/__init__.py @@ -6,9 +6,12 @@ * ``MESS_CACHE_DIR``: JIT compilation cache location. Defaults to ``~/.cache/mess`` """ -import importlib.metadata +from importlib.metadata import PackageNotFoundError, version -__version__ = importlib.metadata.version("mess") +try: + __version__ = version("mess") +except PackageNotFoundError: # Package is not installed + __version__ = "dev" from mess.basis import basisset from mess.hamiltonian import Hamiltonian, minimise diff --git a/mess/integrals.py b/mess/integrals.py index 1b50b81..ef05a8d 100644 --- a/mess/integrals.py +++ b/mess/integrals.py @@ -15,8 +15,6 @@ open-ended approach for one-electron integrals with Gaussian bases. Journal of computational chemistry. 1990 Jan;11(1):105-11. - -[2] PyQuante: """ from dataclasses import asdict diff --git a/mess/interop.py b/mess/interop.py index 79187c4..18a1da6 100644 --- a/mess/interop.py +++ b/mess/interop.py @@ -1,15 +1,15 @@ -"""Interoperation tools for working across MESS, PySCF, and PyQuante.""" +"""Interoperation tools for working across MESS, PySCF.""" from typing import Tuple import numpy as np from periodictable import elements from pyscf import gto -from pyquante2.geo import samples from mess.basis import Basis, basisset from mess.structure import Structure from mess.units import to_bohr +from mess.package_utils import requires_package def to_pyscf(structure: Structure, basis_name: str = "sto-3g") -> "gto.Mole": @@ -37,6 +37,7 @@ def from_pyscf(mol: "gto.Mole") -> Tuple[Structure, Basis]: return structure, basis +@requires_package("pyquante2") def from_pyquante(name: str) -> Structure: """Load molecular structure from pyquante2.geo.samples module @@ -47,6 +48,8 @@ def from_pyquante(name: str) -> Structure: Returns: Structure """ + from pyquante2.geo import samples + pqmol = getattr(samples, name) atomic_number, position = zip(*[(a.Z, a.r) for a in pqmol]) atomic_number, position = [np.asarray(x) for x in (atomic_number, position)] diff --git a/mess/package_utils.py b/mess/package_utils.py new file mode 100644 index 0000000..990f6cb --- /dev/null +++ b/mess/package_utils.py @@ -0,0 +1,103 @@ +import importlib +from functools import wraps +from typing import Any, Callable, TypeVar + +F = TypeVar("F", bound=Callable[..., Any]) + + +class MissingOptionalDependencyError(BaseException): + """ + An exception raised when an optional dependency is required + but cannot be found. + + Attributes + ---------- + library_name + The name of the missing library. + """ + + def __init__(self, library_name: str): + """ + + Parameters + ---------- + library_name + The name of the missing library. + license_issue + Whether the library was importable but was unusable due + to a missing license. + """ + + message = f"The required {library_name} module could not be imported." + + super(MissingOptionalDependencyError, self).__init__(message) + + self.library_name = library_name + + +def has_package(package_name: str) -> bool: + """ + Helper function to generically check if a Python package is installed. + Intended to be used to check for optional dependencies. + + Parameters + ---------- + package_name : str + The name of the Python package to check the availability of + + Returns + ------- + package_available : bool + Boolean indicator if the package is available or not + + Examples + -------- + >>> has_numpy = has_package('numpy') + >>> has_numpy + True + >>> has_foo = has_package('other_non_installed_package') + >>> has_foo + False + """ + try: + importlib.import_module(package_name) + except ModuleNotFoundError: + return False + return True + + +def requires_package(package_name: str) -> Callable[..., Any]: + """ + Helper function to denote that a funciton requires some optional + dependency. A function decorated with this decorator will raise + `MissingOptionalDependencyError` if the package is not found by + `importlib.import_module()`. + + Parameters + ---------- + package_name : str + The name of the module to be imported. + + Raises + ------ + MissingOptionalDependencyError + + """ + + def inner_decorator(function: F) -> F: + @wraps(function) + def wrapper(*args, **kwargs): + import importlib + + try: + importlib.import_module(package_name) + except ImportError: + raise MissingOptionalDependencyError(library_name=package_name) + except Exception as e: + raise e + + return function(*args, **kwargs) + + return wrapper + + return inner_decorator diff --git a/pyproject.toml b/pyproject.toml index b6259e1..4462008 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "py3Dmol", "basis_set_exchange", "sympy", - "pyquante2@git+https://github.com/rpmuller/pyquante2@pure", + "importlib-resources", ] dynamic = ["version"] @@ -47,6 +47,18 @@ dev = [ "seaborn", ] +[project.urls] +Website = "https://github.com/valence-labs/mess" +"Source Code" = "https://github.com/valence-labs/mess" +"Bug Tracker" = "https://github.com/valence-labs/mess/issues" +Documentation = "https://valence-labs.github.io/mess/" + +[tool.setuptools] +include-package-data = true + +[tool.setuptools_scm] +fallback_version = "dev" + [tool.pytest.ini_options] addopts = "-s -v --durations=10" filterwarnings = [ diff --git a/test/test_benchmark.py b/test/test_benchmark.py index 187a092..e3542da 100644 --- a/test/test_benchmark.py +++ b/test/test_benchmark.py @@ -3,19 +3,23 @@ from mess.basis import basisset from mess.hamiltonian import Hamiltonian, minimise +from mess.zeropad_integrals import overlap_basis_zeropad from mess.integrals import ( eri_basis_sparse, kinetic_basis, nuclear_basis, overlap_basis, ) -from mess.interop import from_pyquante from mess.structure import molecule -from mess.zeropad_integrals import overlap_basis_zeropad +from mess.interop import from_pyquante +from mess.package_utils import has_package from conftest import is_mem_limited @pytest.mark.parametrize("func", [overlap_basis, overlap_basis_zeropad, kinetic_basis]) +@pytest.mark.skipif( + not has_package("pyquante2"), reason="Missing Optional Dependency: pyquante2" +) def test_benzene(func, benchmark): mol = from_pyquante("c6h6") basis = basisset(mol, "def2-TZVPPD")