From b45f3a69a04b87b045b7c8067beafb64f45b0027 Mon Sep 17 00:00:00 2001 From: FNTwin Date: Wed, 25 Sep 2024 07:17:05 -0600 Subject: [PATCH 1/3] Removed pyquante, reworked pyproject.toml, dev versioning --- docs/optim.ipynb | 4 +++- mess/__init__.py | 7 +++++-- mess/integrals.py | 2 -- mess/interop.py | 19 +------------------ pyproject.toml | 14 +++++++++++++- test/test_benchmark.py | 14 -------------- 6 files changed, 22 insertions(+), 38 deletions(-) diff --git a/docs/optim.ipynb b/docs/optim.ipynb index 3f5243e..71a9b9d 100644 --- a/docs/optim.ipynb +++ b/docs/optim.ipynb @@ -45,7 +45,9 @@ "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\" " ] }, { diff --git a/mess/__init__.py b/mess/__init__.py index 4bd55c7..1436ccb 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..9c460de 100644 --- a/mess/interop.py +++ b/mess/interop.py @@ -1,11 +1,10 @@ -"""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 @@ -35,19 +34,3 @@ def from_pyscf(mol: "gto.Mole") -> Tuple[Structure, Basis]: basis = basisset(structure, basis_name=mol.basis) return structure, basis - - -def from_pyquante(name: str) -> Structure: - """Load molecular structure from pyquante2.geo.samples module - - Args: - name (str): Possible names include ch4, c6h6, aspirin, caffeine, hmx, petn, - prozan, rdx, taxol, tylenol, viagara, zoloft - - Returns: - Structure - """ - 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)] - return Structure(atomic_number, position) 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..35387f3 100644 --- a/test/test_benchmark.py +++ b/test/test_benchmark.py @@ -9,24 +9,10 @@ nuclear_basis, overlap_basis, ) -from mess.interop import from_pyquante from mess.structure import molecule -from mess.zeropad_integrals import overlap_basis_zeropad from conftest import is_mem_limited -@pytest.mark.parametrize("func", [overlap_basis, overlap_basis_zeropad, kinetic_basis]) -def test_benzene(func, benchmark): - mol = from_pyquante("c6h6") - basis = basisset(mol, "def2-TZVPPD") - basis = jax.device_put(basis) - - def harness(): - return func(basis).block_until_ready() - - benchmark(harness) - - @pytest.mark.parametrize("mol_name", ["h2", "water"]) @pytest.mark.parametrize( "func", [overlap_basis, kinetic_basis, nuclear_basis, eri_basis_sparse] From e923e495ec0a17a7f22065a9bb2634c56bbac478 Mon Sep 17 00:00:00 2001 From: FNTwin Date: Wed, 25 Sep 2024 07:22:17 -0600 Subject: [PATCH 2/3] rerun precommit --- docs/gammanu.ipynb | 11 ++++++++++- mess/__init__.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) 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/mess/__init__.py b/mess/__init__.py index 1436ccb..8f2cea9 100644 --- a/mess/__init__.py +++ b/mess/__init__.py @@ -10,7 +10,7 @@ try: __version__ = version("mess") -except PackageNotFoundError: # Package is not installed +except PackageNotFoundError: # Package is not installed __version__ = "dev" from mess.basis import basisset From 33bf20746e3e2571cc6d1776b2b909691e4361c4 Mon Sep 17 00:00:00 2001 From: FNTwin Date: Wed, 25 Sep 2024 08:37:31 -0600 Subject: [PATCH 3/3] Precommit --- docs/optim.ipynb | 53 ++++++++++++++------- mess/interop.py | 20 ++++++++ mess/package_utils.py | 103 +++++++++++++++++++++++++++++++++++++++++ test/test_benchmark.py | 18 +++++++ 4 files changed, 176 insertions(+), 18 deletions(-) create mode 100644 mess/package_utils.py diff --git a/docs/optim.ipynb b/docs/optim.ipynb index 71a9b9d..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,7 +40,16 @@ "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", @@ -52,14 +61,22 @@ }, { "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", @@ -84,7 +101,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -96,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", "" ] @@ -154,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" } @@ -347,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/interop.py b/mess/interop.py index 9c460de..18a1da6 100644 --- a/mess/interop.py +++ b/mess/interop.py @@ -9,6 +9,7 @@ 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": @@ -34,3 +35,22 @@ def from_pyscf(mol: "gto.Mole") -> Tuple[Structure, Basis]: basis = basisset(structure, basis_name=mol.basis) return structure, basis + + +@requires_package("pyquante2") +def from_pyquante(name: str) -> Structure: + """Load molecular structure from pyquante2.geo.samples module + + Args: + name (str): Possible names include ch4, c6h6, aspirin, caffeine, hmx, petn, + prozan, rdx, taxol, tylenol, viagara, zoloft + + 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)] + return Structure(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/test/test_benchmark.py b/test/test_benchmark.py index 35387f3..e3542da 100644 --- a/test/test_benchmark.py +++ b/test/test_benchmark.py @@ -3,6 +3,7 @@ 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, @@ -10,9 +11,26 @@ overlap_basis, ) from mess.structure import molecule +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") + basis = jax.device_put(basis) + + def harness(): + return func(basis).block_until_ready() + + benchmark(harness) + + @pytest.mark.parametrize("mol_name", ["h2", "water"]) @pytest.mark.parametrize( "func", [overlap_basis, kinetic_basis, nuclear_basis, eri_basis_sparse]