diff --git a/.gitignore b/.gitignore index ed7bae9..23c79ec 100644 --- a/.gitignore +++ b/.gitignore @@ -16,31 +16,60 @@ *.tmp *.autosave *.bak +*~ +*.py[cod] +*.so +*.cfg +!.isort.cfg +!setup.cfg +*.orig +*.log +*.pot +__pycache__/* +.cache/* +.*.swp +*/.ipynb_checkpoints/* +.DS_Store # These are directories used by IDEs for storing settings -.idea/ -.vscode/ - -# These are common Python virtual enviornment directory names -venv/ -docs/venv/ - -# This is where Jupyter/IPython store backup files -.ipynb_checkpoints/ - -# Byte-compiled Python -__pycache__/ - -# These are common build directory names -build*/ -docs/build -docs/source/_build -docs/doxyoutput -docs/source/api -*-build-*/ -_build/ -Debug/ -Release/ +.ropeproject +.project +.pydevproject +.settings +.idea +.vscode +tags + +# Package files +*.egg +*.eggs/ +.installed.cfg +*.egg-info + +# Unittest and coverage +htmlcov/* +.coverage +.coverage.* +.tox +junit*.xml +coverage.xml +.pytest_cache/ + +# Build and docs folder/files +build/* +dist/* +sdist/* +docs/source/api/* +!docs/source/api/modules.rst +docs/source/_rst/* +docs/build/* +cover/* +MANIFEST + +# Per-project virtualenvs +*venv*/ +.conda*/ +.python-version # Users commonly store their specific CMake settings in a toolchain file toolchain.cmake @@ -50,4 +79,4 @@ cache.db uuid.db # This is a generated file -src/python/friendzone/friends.py +# src/python/friendzone/friends.py diff --git a/.licenserc.yaml b/.licenserc.yaml index 3e2e6a5..713404b 100644 --- a/.licenserc.yaml +++ b/.licenserc.yaml @@ -20,12 +20,13 @@ header: paths-ignore: - .github/ - docs/Makefile + - docs/build/ - LICENSE - docs/requirements.txt - docs/source/bibliography/*.bib - docs/source/nitpick_exceptions - version.txt - - cmake/friends.py.in - build/ + - venv/ comment: never diff --git a/CMakeLists.txt b/CMakeLists.txt index c1415d8..e7ddbec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -27,18 +27,9 @@ include(get_cmaize) include(nwx_cxx_api_docs) ### Files and Paths ### -set(friends_template "${CMAKE_CURRENT_LIST_DIR}/cmake/friends.py.in") set(python_src_directory "${CMAKE_CURRENT_LIST_DIR}/src/python") # # Doxygen docs -if("${ONLY_BUILD_DOCS}") - # If we are only building the docs, we need to produce friends.py here - configure_file( - "${friends_template}" - "${python_src_directory}/friendzone/friends.py" - @ONLY - ) -endif() nwx_cxx_api_docs("${CMAKE_CURRENT_SOURCE_DIR}/src" "${CMAKE_CURRENT_SOURCE_DIR}/include") ### Options ### @@ -47,6 +38,7 @@ cmaize_option_list( BUILD_PYBIND11_PYBINDINGS ON "Use Pybind11 to build Python bindings?" ENABLE_EXPERIMENTAL_FEATURES OFF "Build features which are not 1.0-ed yet?" ENABLE_NWCHEM ON "Should we build support for friend: NWChem ?" + ENABLE_MOLSSI ON "Build support for the MolSSI interface?" ENABLE_ASE ON "Build support for the Atomic Simulation Environment?" ) @@ -70,18 +62,12 @@ set( ## Find friends ## include(ase) +include(molssi) include(nwchem) -## Configure file with enabled friends ## -configure_file( - "${friends_template}" # Input file - "${python_src_directory}/friendzone/friends.py" # Output file - @ONLY # Only replace @ variables -) - #TOOD: Replace cmaize_add_library when it supports Python add_library(${PROJECT_NAME} INTERFACE) -target_link_libraries(${PROJECT_NAME} INTERFACE simde ase nwchem) +target_link_libraries(${PROJECT_NAME} INTERFACE simde) if("${BUILD_TESTING}") include(CTest) diff --git a/cmake/ase.cmake b/cmake/ase.cmake index c8ced54..99d97ab 100644 --- a/cmake/ase.cmake +++ b/cmake/ase.cmake @@ -23,12 +23,10 @@ if("${BUILD_PYBIND11_PYBINDINGS}") #]] function(find_ase) assert_python_module("ase") - message("Found ASE: ${ASE_FOUND}") + message(STATUS "Found ASE: ${ASE_FOUND}") endfunction() if("${ENABLE_ASE}") find_ase() endif() endif() - -add_library(ase INTERFACE) diff --git a/cmake/friends.py.in b/cmake/friends.py.in deleted file mode 100644 index 10ec625..0000000 --- a/cmake/friends.py.in +++ /dev/null @@ -1,40 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# 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. - -# This file is auto-generated by FriendZone's configure step and will be -# overwritten next time configure is run. - - -def friends(): - """ Returns a dictionary of potential friends and whether they were enabled. - - :return: Key-value pairs where the key is the name of a potential - friend and the value is whether that friend was enabled or not - """ - return {'nwchem' : '@ENABLE_NWCHEM@', - 'ase' : '@ENABLE_ASE@' } - - -def is_friend_enabled(friend): - """ Wraps the process of determining if a particular friend was enabled. - - :return: True if FriendZone was configured with support for ``friend`` - and false otherwise. - :rtype: bool - """ - all_friends = friends() - if friend in all_friends: - status = all_friends[friend] - return status == 'ON' or status == 'on' or status == 'TRUE' or \ - status == 'true' diff --git a/cmake/molssi.cmake b/cmake/molssi.cmake new file mode 100644 index 0000000..3341ee5 --- /dev/null +++ b/cmake/molssi.cmake @@ -0,0 +1,36 @@ +# Copyright 2023 NWChemEx-Project +# +# 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. + +include_guard() + +if("${BUILD_PYBIND11_PYBINDINGS}") + include(python/python) + + #[[[ Determines if the MolSSI Python interface are installed. + # + # At present FriendZone can not install + #]] + function(find_molssi) + assert_python_module("qcelemental") + message(STATUS "Found qcelemental: ${QCELEMENTAL_FOUND}") + assert_python_module("qcengine") + message(STATUS "Found qcengine: ${QCENGINE_FOUND}") + assert_python_module("networkx") + message(STATUS "Found networkx: ${NETWORKX_FOUND}") + endfunction() + + if("${ENABLE_MOLSSI}") + find_molssi() + endif() +endif() diff --git a/cmake/nwchem.cmake b/cmake/nwchem.cmake index c5ab929..26f1879 100644 --- a/cmake/nwchem.cmake +++ b/cmake/nwchem.cmake @@ -15,23 +15,16 @@ include_guard() if("${BUILD_PYBIND11_PYBINDINGS}") - include(python/python) - #[[[ Determines if NWChem and the necessary Python interface are installed. # # At present FriendZone can not install #]] function(find_nwchem) find_program(NWCHEM_FOUND nwchem REQUIRED) - assert_python_module("qcelemental") - assert_python_module("qcengine") - assert_python_module("networkx") - message("Found nwchem: ${NWCHEM_FOUND}") + message(STATUS "Found nwchem: ${NWCHEM_FOUND}") endfunction() if("${ENABLE_NWCHEM}") find_nwchem() endif() endif() - -add_library(nwchem INTERFACE) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7e50ebe --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,91 @@ +# Copyright 2025 NWChemEx-Project +# +# 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. + +# This file defines the pip-installable Python package. + +[build-system] +requires = ["setuptools>=46.1.0", "setuptools_scm[toml]>=5"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] +# For smarter version schemes and other configuration options, +# check out https://github.com/pypa/setuptools_scm +version_scheme = "no-guess-dev" + +# To create a pip-installable package that uses CMake in the backend, +# scikit-build-core is used as the build backend, recommended by Pybind11. +# +# scikit-build-core: https://scikit-build-core.readthedocs.io/en/latest/ +# Pybind11 scikit-build-core example: https://github.com/pybind/scikit_build_example +# [build-system] +# requires = ["scikit-build-core>=0.11", "pybind11>=3.0"] +# build-backend = "scikit_build_core.build" + + +[project] +name = "friendzone" +license = "Apache-2.0" +license-files = ["LICENSE"] +description = "Provides SimDE compatible APIs so that NWChemEx can play nicely with its friends." +readme = "README.md" +authors = [ + { name = "zachcran", email = "zachcran@iastate.edu" }, + { name = "jwaldrop107", email = "jwaldrop@ameslab.gov" }, +] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", + "Private :: Do Not Upload", +] +# Dynamic project attributes +# +# Set the project version dynamically according to git tags. +# Git tag version info: https://github.com/pypa/setuptools-scm/blob/fb261332d9b46aa5a258042d85baa5aa7b9f4fa2/README.rst#default-versioning-scheme +dynamic = ["version"] +# NOTE: Invisible dependency for now until Python bindings at SimDE are +# packaged properly and available here +# dependencies = ["simde"] + + +[tool.setuptools] +# Cannot automatically find the friendzone namespace so we set it here +package-dir = { "" = "src/python" } + +# Optional dependencies represent optional features that can be enabled +# during installation +# Example: pip install friendzone[ase] +[project.optional-dependencies] +ase = ["ase"] +molssi = ["qcengine", "qcelemental", "networkx"] + +# Dependency groups are optional dependencies that are not intented to appear +# after packaging, usually used to help with testing or development +# Example: pip install --group dev +[dependency-groups] +test = ["pytest"] +dev = [{ include-group = "test" }, "tox", "pre-commit"] + +[tool.pytest.ini_options] +minversion = "8.0" +addopts = ["-ra", "--showlocals", "--strict-markers", "--strict-config"] +xfail_strict = true +log_cli_level = "INFO" +filterwarnings = ["error", "ignore::pytest.PytestCacheWarning"] +testpaths = ["tests"] diff --git a/src/python/friendzone/__init__.py b/src/python/friendzone/__init__.py index f19a2db..445b43e 100644 --- a/src/python/friendzone/__init__.py +++ b/src/python/friendzone/__init__.py @@ -13,24 +13,25 @@ # limitations under the License. from .nwx2ase import load_ase_modules -from .nwx2qcelemental import load_qcelemental_modules -from .nwx2qcengine import load_qcengine_modules +from .nwx2molssi import load_molssi_modules def load_modules(mm): - """Loads the collection of all modules provided by Friendzone. This function - calls the various friend specific module loading functions, including: + """Loads the collection of all modules provided by Friendzone. - * `load_ase_modules` - * `load_qcengine_modules` - * `load_qcelemental_modules` + This function calls the various friend specific module loading functions, + including: - Note some and/or all of these may be no-ops depending on what friends were - enabled. + * ``load_ase_modules`` + * ``load_molssi_modules`` + + .. note:: + + Some and/or all of these may be no-ops depending on what friends were + enabled. :param mm: The ModuleManager that the all Modules will be loaded into. :type mm: pluginplay.ModuleManager """ load_ase_modules(mm) - load_qcengine_modules(mm) - load_qcelemental_modules(mm) + load_molssi_modules(mm) diff --git a/src/python/friendzone/friends.py b/src/python/friendzone/friends.py new file mode 100644 index 0000000..c623247 --- /dev/null +++ b/src/python/friendzone/friends.py @@ -0,0 +1,46 @@ +# Copyright 2025 NWChemEx-Project +# +# 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 importlib.util import find_spec +from shutil import which + + +def is_ase_enabled(): + """Checks whether the ASE friend is enabled. + + :return: True if the ASE friend is enabled, False otherwise. + :rtype: bool + """ + return find_spec("ase") is not None + + +def is_molssi_enabled(): + """Checks whether the MolSSI friend is enabled. + + :return: True if the MolSSI friend is enabled, False otherwise. + :rtype: bool + """ + for req in ["qcelemental", "qcengine", "networkx"]: + if find_spec(req) is None: + return False + return True + + +def is_nwchem_enabled(): + """Checks whether the NWChem friend is enabled. + + :return: True if the NWChem friend is enabled, False otherwise. + :rtype: bool + """ + return which("nwchem") is not None diff --git a/src/python/friendzone/nwx2ase/__init__.py b/src/python/friendzone/nwx2ase/__init__.py index 7be0f96..e56e675 100644 --- a/src/python/friendzone/nwx2ase/__init__.py +++ b/src/python/friendzone/nwx2ase/__init__.py @@ -12,21 +12,27 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ..friends import is_friend_enabled +from ..friends import is_ase_enabled -if is_friend_enabled("ase"): - from .nwchem_via_ase import NWChemEnergyViaASE, NWChemGradientViaASE +if is_ase_enabled(): + from .nwchem_via_ase import load_nwchem_via_ase_modules def load_ase_modules(mm): - if not is_friend_enabled("ase"): - return - - if is_friend_enabled("nwchem"): - for method in ["SCF", "MP2", "CCSD", "CCSD(T)"]: - egy_key = "ASE(NWChem) : " + method - grad_key = egy_key + " gradient" - mm.add_module(egy_key, NWChemEnergyViaASE()) - mm.add_module(grad_key, NWChemGradientViaASE()) - for key in [egy_key, grad_key]: - mm.change_input(key, "method", method) + """Loads the collection of all ASE modules. + + This function calls the various submodule specific loading functions, + including: + + * ``load_nwchem_via_ase_modules`` + + .. note:: + + Some and/or all of these may be no-ops depending on what friends were + enabled. This function is a no-op if ASE is not installed. + + :param mm: The ModuleManager that the all Modules will be loaded into. + :type mm: pluginplay.ModuleManager + """ + if is_ase_enabled(): + load_nwchem_via_ase_modules(mm) diff --git a/src/python/friendzone/nwx2ase/nwchem_via_ase.py b/src/python/friendzone/nwx2ase/nwchem_via_ase.py index 554624c..fb72c27 100644 --- a/src/python/friendzone/nwx2ase/nwchem_via_ase.py +++ b/src/python/friendzone/nwx2ase/nwchem_via_ase.py @@ -21,6 +21,7 @@ from ase.calculators.nwchem import NWChem from simde import EnergyNuclearGradientStdVectorD, TotalEnergy +from ..friends import is_nwchem_enabled from ..utils.unwrap_inputs import unwrap_inputs from .chemical_system_conversions import chemical_system2atoms @@ -91,3 +92,26 @@ def run_(self, inputs, submods): augrad = [-1.0 * x / (au2eV / au2ang) for x in grad] rv = TotalEnergy().wrap_results(rv, egy) return pt.wrap_results(rv, augrad) + + +def load_nwchem_via_ase_modules(mm): + """Loads the collection of all ASE(NWChem) modules. + + .. note:: + + This function is a no-op if NWChem is not installed. + + :param mm: The ModuleManager that the all Modules will be loaded into. + :type mm: pluginplay.ModuleManager + """ + if is_nwchem_enabled(): + # Loop over methods and add energy and gradient modules for each + for method in ["SCF", "MP2", "CCSD", "CCSD(T)"]: + egy_key = "ASE(NWChem) : " + method + grad_key = egy_key + " gradient" + + mm.add_module(egy_key, NWChemEnergyViaASE()) + mm.add_module(grad_key, NWChemGradientViaASE()) + + for key in [egy_key, grad_key]: + mm.change_input(key, "method", method) diff --git a/src/python/friendzone/nwx2molssi/__init__.py b/src/python/friendzone/nwx2molssi/__init__.py new file mode 100644 index 0000000..8d5c3af --- /dev/null +++ b/src/python/friendzone/nwx2molssi/__init__.py @@ -0,0 +1,46 @@ +# Copyright 2024 NWChemEx +# +# 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 ..friends import is_molssi_enabled + +if is_molssi_enabled(): + from .nwchem_via_molssi import load_nwchem_via_molssi_modules + from .system_via_molssi import load_system_via_molssi_modules + + +def load_molssi_modules(mm): + """Loads the collection of all MolSSI modules. + + This function calls the various submodule specific loading functions, + including: + + * ``load_system_via_molssi_modules`` + * ``load_nwchem_via_molssi_modules`` + + .. note:: + + Some and/or all of these may be no-ops depending on what friends were + enabled. This entire function is a no-op if the following dependencies + are not installed: + + * ``qcelemental`` + * ``qcengine`` + * ``networkx`` + + :param mm: The ModuleManager that the all Modules will be loaded into. + :type mm: pluginplay.ModuleManager + """ + if is_molssi_enabled(): + load_system_via_molssi_modules(mm) + load_nwchem_via_molssi_modules(mm) diff --git a/src/python/friendzone/nwx2qcengine/call_qcengine.py b/src/python/friendzone/nwx2molssi/call_qcengine.py similarity index 97% rename from src/python/friendzone/nwx2qcengine/call_qcengine.py rename to src/python/friendzone/nwx2molssi/call_qcengine.py index 67e8177..a1edb39 100644 --- a/src/python/friendzone/nwx2qcengine/call_qcengine.py +++ b/src/python/friendzone/nwx2molssi/call_qcengine.py @@ -15,9 +15,7 @@ import qcelemental as qcel import qcengine as qcng -from ..nwx2qcelemental.chemical_system_conversions import ( - chemical_system2qc_mol, -) +from .chemical_system_conversions import chemical_system2qc_mol def call_qcengine(driver, mol, program, runtime, **kwargs): diff --git a/src/python/friendzone/nwx2qcelemental/chemical_system_conversions.py b/src/python/friendzone/nwx2molssi/chemical_system_conversions.py similarity index 100% rename from src/python/friendzone/nwx2qcelemental/chemical_system_conversions.py rename to src/python/friendzone/nwx2molssi/chemical_system_conversions.py diff --git a/src/python/friendzone/nwx2qcengine/__init__.py b/src/python/friendzone/nwx2molssi/nwchem_via_molssi.py similarity index 84% rename from src/python/friendzone/nwx2qcengine/__init__.py rename to src/python/friendzone/nwx2molssi/nwchem_via_molssi.py index ed9e0d3..124b250 100644 --- a/src/python/friendzone/nwx2qcengine/__init__.py +++ b/src/python/friendzone/nwx2molssi/nwchem_via_molssi.py @@ -1,4 +1,4 @@ -# Copyright 2024 NWChemEx-Project +# Copyright 2025 NWChemEx-Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,12 +12,13 @@ # See the License for the specific language governing permissions and # limitations under the License. + import numpy as np import pluginplay as pp import tensorwrapper as tw from simde import EnergyNuclearGradientStdVectorD, TotalEnergy -from ..friends import is_friend_enabled +from ..friends import is_nwchem_enabled from ..utils.unwrap_inputs import unwrap_inputs from .call_qcengine import call_qcengine @@ -95,7 +96,7 @@ def run_(self, inputs, submods): ) -def load_qcengine_modules(mm): +def load_nwchem_via_molssi_modules(mm): """Loads the collection of modules that wrap QCElemental calls. Currently, the friends exported by this function are: @@ -114,18 +115,20 @@ def load_qcengine_modules(mm): The final set of modules is the Cartesian product of all of the above. + .. note:: + + This function is a no-op if NWChem is not installed. + :param mm: The ModuleManager that the NWChem Modules will be loaded into. :type mm: pluginplay.ModuleManager """ - - for program in ["nwchem"]: - if is_friend_enabled(program): - for method in ["SCF", "B3LYP", "MP2", "CCSD", "CCSD(T)"]: - egy_key = program + " : " + method - grad_key = egy_key + " Gradient" - mm.add_module(egy_key, QCEngineEnergy()) - mm.add_module(grad_key, QCEngineGradient()) - - for key in [egy_key, grad_key]: - mm.change_input(key, "program", program) - mm.change_input(key, "method", method) + if is_nwchem_enabled(): + for method in ["SCF", "B3LYP", "MP2", "CCSD", "CCSD(T)"]: + egy_key = "nwchem" + " : " + method + grad_key = egy_key + " Gradient" + mm.add_module(egy_key, QCEngineEnergy()) + mm.add_module(grad_key, QCEngineGradient()) + + for key in [egy_key, grad_key]: + mm.change_input(key, "program", "nwchem") + mm.change_input(key, "method", method) diff --git a/src/python/friendzone/nwx2qcelemental/__init__.py b/src/python/friendzone/nwx2molssi/system_via_molssi.py similarity index 85% rename from src/python/friendzone/nwx2qcelemental/__init__.py rename to src/python/friendzone/nwx2molssi/system_via_molssi.py index 88a6d7e..93a5431 100644 --- a/src/python/friendzone/nwx2qcelemental/__init__.py +++ b/src/python/friendzone/nwx2molssi/system_via_molssi.py @@ -1,4 +1,4 @@ -# Copyright 2024 NWChemEx +# Copyright 2025 NWChemEx # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,13 +16,11 @@ import qcelemental from simde import MoleculeFromString -from ..nwx2qcelemental.chemical_system_conversions import qc_mol2molecule +from .chemical_system_conversions import qc_mol2molecule class SystemViaMolSSI(pp.ModuleBase): - """Creates an NWChemEx ChemicalSystem by going through MolSSI's string - parser. - """ + """Creates an NWChemEx ChemicalSystem by using MolSSI's string parser.""" def __init__(self): pp.ModuleBase.__init__(self) @@ -38,11 +36,10 @@ def run_(self, inputs, submods): return pt.wrap_results(rv, mol) -def load_qcelemental_modules(mm): +def load_system_via_molssi_modules(mm): """Loads the collection of modules that wrap QCElemental calls. :param mm: The ModuleManager that the NWChem Modules will be loaded into. :type mm: pluginplay.ModuleManager """ - mm.add_module("ChemicalSystem via QCElemental", SystemViaMolSSI()) diff --git a/tests/python/unit_tests/nwx2ase/test_chemical_system_conversions.py b/tests/python/unit_tests/nwx2ase/test_chemical_system_conversions.py index 63762c6..d9952b0 100644 --- a/tests/python/unit_tests/nwx2ase/test_chemical_system_conversions.py +++ b/tests/python/unit_tests/nwx2ase/test_chemical_system_conversions.py @@ -14,10 +14,10 @@ import unittest -from friendzone.friends import is_friend_enabled +from friendzone.friends import is_ase_enabled from molecules import make_h2, make_h2o -if is_friend_enabled("ase"): +if is_ase_enabled(): from ase import Atoms from friendzone.nwx2ase.chemical_system_conversions import ( chemical_system2atoms, @@ -68,5 +68,5 @@ def test_h2o(self): compare_ase(self, ase_mol, corr) def setUp(self): - if not is_friend_enabled("ase"): + if not is_ase_enabled(): self.skipTest("ASE is not enabled!") diff --git a/tests/python/unit_tests/nwx2ase/test_nwchem_via_ase.py b/tests/python/unit_tests/nwx2ase/test_nwchem_via_ase.py index 1b21461..5793380 100644 --- a/tests/python/unit_tests/nwx2ase/test_nwchem_via_ase.py +++ b/tests/python/unit_tests/nwx2ase/test_nwchem_via_ase.py @@ -15,7 +15,8 @@ import unittest import numpy as np -from friendzone import friends, load_modules +from friendzone import load_modules +from friendzone.friends import is_ase_enabled, is_nwchem_enabled from molecules import make_h2 from pluginplay import ModuleManager from simde import EnergyNuclearGradientStdVectorD, TotalEnergy @@ -65,10 +66,9 @@ def test_ccsd_t(self): self.assertAlmostEqual(np.array(egy), -1.122251361965036, places=4) def setUp(self): - nwchem_enabled = friends.is_friend_enabled("nwchem") - ase_enabled = friends.is_friend_enabled("ase") - - if not nwchem_enabled or not ase_enabled: + if not is_ase_enabled(): + self.skipTest("ASE is not enabled!") + elif not is_nwchem_enabled(): self.skipTest("NWChem backend is not enabled!") self.mm = ModuleManager() diff --git a/tests/python/unit_tests/nwx2qcelemental/__init__.py b/tests/python/unit_tests/nwx2molssi/__init__.py similarity index 100% rename from tests/python/unit_tests/nwx2qcelemental/__init__.py rename to tests/python/unit_tests/nwx2molssi/__init__.py diff --git a/tests/python/unit_tests/nwx2qcelemental/test_chemical_system_conversions.py b/tests/python/unit_tests/nwx2molssi/test_chemical_system_conversions.py similarity index 73% rename from tests/python/unit_tests/nwx2qcelemental/test_chemical_system_conversions.py rename to tests/python/unit_tests/nwx2molssi/test_chemical_system_conversions.py index e278979..84f3a32 100644 --- a/tests/python/unit_tests/nwx2qcelemental/test_chemical_system_conversions.py +++ b/tests/python/unit_tests/nwx2molssi/test_chemical_system_conversions.py @@ -14,14 +14,17 @@ import unittest -import qcelemental as qcel from compare_molecules import compare_molecules -from friendzone.nwx2qcelemental.chemical_system_conversions import ( - chemical_system2qc_mol, - qc_mol2molecule, -) +from friendzone.friends import is_molssi_enabled from molecules import make_h2 +if is_molssi_enabled(): + import qcelemental as qcel + from friendzone.nwx2molssi.chemical_system_conversions import ( + chemical_system2qc_mol, + qc_mol2molecule, + ) + class TestChemicalSystem2QC(unittest.TestCase): def test_h2(self): @@ -32,6 +35,10 @@ def test_h2(self): corr = qcel.models.Molecule.from_data(h2_as_str) self.assertEqual(qcel_mol, corr) + def setUp(self): + if not is_molssi_enabled(): + self.skipTest("MolSSI is not enabled!") + class TestQCMol2Molecule(unittest.TestCase): def test_h2(self): @@ -40,3 +47,7 @@ def test_h2(self): mol = qcel.models.Molecule.from_data(h2_as_str) result = qc_mol2molecule(mol) compare_molecules(self, result, corr) + + def setUp(self): + if not is_molssi_enabled(): + self.skipTest("MolSSI friend is not enabled!") diff --git a/tests/python/unit_tests/nwx2qcengine/test_nwchem.py b/tests/python/unit_tests/nwx2molssi/test_nwchem_via_molssi.py similarity index 90% rename from tests/python/unit_tests/nwx2qcengine/test_nwchem.py rename to tests/python/unit_tests/nwx2molssi/test_nwchem_via_molssi.py index 7224f4d..0665126 100644 --- a/tests/python/unit_tests/nwx2qcengine/test_nwchem.py +++ b/tests/python/unit_tests/nwx2molssi/test_nwchem_via_molssi.py @@ -15,13 +15,14 @@ import unittest import numpy as np -from friendzone import friends, load_modules +from friendzone import load_modules +from friendzone.friends import is_molssi_enabled, is_nwchem_enabled from molecules import make_h2 from pluginplay import ModuleManager from simde import EnergyNuclearGradientStdVectorD, TotalEnergy -class TestNWChem(unittest.TestCase): +class TestNWChemViaMolSSI(unittest.TestCase): def test_scf(self): mol = make_h2() key = "NWChem : SCF" @@ -65,7 +66,9 @@ def test_ccsd_t(self): self.assertAlmostEqual(np.array(egy), -1.122251361965036, places=4) def setUp(self): - if not friends.is_friend_enabled("nwchem"): + if not is_molssi_enabled(): + self.skipTest("MolSSI is not enabled!") + elif not is_nwchem_enabled(): self.skipTest("NWChem backend is not enabled!") self.mm = ModuleManager() diff --git a/tests/python/unit_tests/nwx2qcelemental/test_system_via_molssi.py b/tests/python/unit_tests/nwx2molssi/test_system_via_molssi.py similarity index 89% rename from tests/python/unit_tests/nwx2qcelemental/test_system_via_molssi.py rename to tests/python/unit_tests/nwx2molssi/test_system_via_molssi.py index c9d16ae..5e5bf23 100644 --- a/tests/python/unit_tests/nwx2qcelemental/test_system_via_molssi.py +++ b/tests/python/unit_tests/nwx2molssi/test_system_via_molssi.py @@ -16,6 +16,7 @@ from compare_molecules import compare_molecules from friendzone import load_modules +from friendzone.friends import is_molssi_enabled from molecules import make_h2 from pluginplay import ModuleManager from simde import MoleculeFromString @@ -31,6 +32,9 @@ def test_h2(self): compare_molecules(self, corr.molecule, mol) def setUp(self): + if not is_molssi_enabled(): + self.skipTest("MolSSI friend is not enabled!") + self.mm = ModuleManager() load_modules(self.mm) self.pt = MoleculeFromString() diff --git a/tests/python/unit_tests/nwx2qcengine/__init__.py b/tests/python/unit_tests/nwx2qcengine/__init__.py deleted file mode 100644 index cb2dc38..0000000 --- a/tests/python/unit_tests/nwx2qcengine/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2023 NWChemEx-Project -# -# 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. diff --git a/tests/python/unit_tests/test_friends.py b/tests/python/unit_tests/test_friends.py new file mode 100644 index 0000000..83f0370 --- /dev/null +++ b/tests/python/unit_tests/test_friends.py @@ -0,0 +1,58 @@ +# Copyright 2025 NWChemEx-Project +# +# 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 unittest +from unittest.mock import patch + +from friendzone import friends + + +@patch("friendzone.friends.find_spec") +@patch("friendzone.friends.which") +class TestFriends(unittest.TestCase): + def test_is_ase_enabled_when_detected(self, mock_which, mock_find_spec): + mock_find_spec.return_value = True + actual = friends.is_ase_enabled() + self.assertTrue(actual) + + def test_is_ase_enabled_when_not_detected( + self, mock_which, mock_find_spec + ): + mock_find_spec.return_value = None + actual = friends.is_ase_enabled() + self.assertFalse(actual) + + def test_is_molssi_enabled_when_detected(self, mock_which, mock_find_spec): + mock_find_spec.return_value = True + actual = friends.is_molssi_enabled() + self.assertTrue(actual) + + def test_is_molssi_enabled_when_not_detected( + self, mock_which, mock_find_spec + ): + mock_find_spec.return_value = None + actual = friends.is_molssi_enabled() + self.assertFalse(actual) + + def test_is_nwchem_enabled_when_detected(self, mock_which, mock_find_spec): + mock_which.return_value = True + actual = friends.is_nwchem_enabled() + self.assertTrue(actual) + + def test_is_nwchem_enabled_when_not_detected( + self, mock_which, mock_find_spec + ): + mock_which.return_value = None + actual = friends.is_nwchem_enabled() + self.assertFalse(actual)