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")