diff --git a/.github/.licenserc.yaml b/.github/.licenserc.yaml new file mode 100644 index 0000000..98318b9 --- /dev/null +++ b/.github/.licenserc.yaml @@ -0,0 +1,15 @@ +header: + license: + spdx-id: Apache-2.0 + copyright-owner: NWChemEx-Project + + paths-ignore: + - .github/ + - docs/Makefile + - LICENSE + - docs/requirements.txt + - docs/source/bibliography/*.bib + - version.txt + - cmake/friends.py.in + + comment: never diff --git a/.github/workflows/merge.yaml b/.github/workflows/merge.yaml new file mode 100644 index 0000000..883e863 --- /dev/null +++ b/.github/workflows/merge.yaml @@ -0,0 +1,14 @@ +name: .github Merge Workflow + +on: + push: + branches: + - master + +jobs: + Common-Merge: + uses: NWChemEx-Project/.github/.github/workflows/common_merge.yaml@master + with: + doc_target: 'Sphinx' + generate_module_docs: false + secrets: inherit diff --git a/.github/workflows/pull_request.yaml b/.github/workflows/pull_request.yaml new file mode 100644 index 0000000..b1213ad --- /dev/null +++ b/.github/workflows/pull_request.yaml @@ -0,0 +1,16 @@ +name: .github Pull Request Workflow + +on: + pull_request: + branches: + - master + +jobs: + Common-Pull-Request: + uses: NWChemEx-Project/.github/.github/workflows/common_pull_request.yaml@master + with: + config_file: '.github/.licenserc.yaml' + source_dir: '' + compilers: '' + doc_target: 'Sphinx' + secrets: inherit diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..2532ba1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,94 @@ +cmake_minimum_required(VERSION 3.14) +set(VERSION 1.0.0) #TODO: get from git +project(structurefinder VERSION "${VERSION}" LANGUAGES CXX) + +include(FetchContent) +FetchContent_Declare( + nwx_cmake + GIT_REPOSITORY https://github.com/NWChemEx-Project/NWXCMake +) +FetchContent_MakeAvailable(nwx_cmake) +list(APPEND CMAKE_MODULE_PATH "${nwx_cmake_SOURCE_DIR}/cmake") + +set( + CMAKE_MODULE_PATH "${CMAKE_MODULE_PATH}" "${PROJECT_SOURCE_DIR}/cmake" + CACHE STRING "" FORCE +) + +include(get_cmaize) +include(nwx_versions) + +### Options ### +option(BUILD_TESTING "Should we build the tests?" OFF) + +## Build StructureFinder's dependencies ## +cpp_find_or_build_dependency( + nwchemex + URL github.com/NWChemEx-Project/NWChemEx + PRIVATE TRUE + VERSION master + BUILD_TARGET nwchemex + FIND_TARGET nwx::nwchemex + CMAKE_ARGS BUILD_TESTING=OFF +) + +if("${BUILD_TESTING}") + include(CTest) + include(nwx_pybind11) + +# override +function(nwx_pybind11_tests npt_name npt_driver) + if(NOT "${BUILD_PYBIND11_PYBINDINGS}") + return() + endif() + + set(_npt_options "") + set(_npt_one_val "") + set(_npt_lists SUBMODULES) + cmake_parse_arguments( + "_npt" "${_npt_options}" "${_npt_one_val}" "${_npt_lists}" ${ARGN} + ) + + if("${BUILD_TESTING}") + include(CTest) + find_package(Python COMPONENTS Interpreter REQUIRED) + + # Build the PYTHONPATH for the test + set(_npt_py_path "PYTHONPATH=${NWX_MODULE_DIRECTORY}") + set(_npt_py_path "${_npt_py_path}:${CMAKE_BINARY_DIR}") + foreach(_npt_submod ${_npt_SUBMODULES}) + set(_npt_dep_dir "${CMAKE_BINARY_DIR}/_deps/${_npt_submod}-build") + set(_npt_py_path "${_npt_py_path}:${_npt_dep_dir}") + endforeach() + if(NOT "${NWX_PYTHON_EXTERNALS}" STREQUAL "") + set(_npt_py_path "${_npt_py_path}:${NWX_PYTHON_EXTERNALS}") + endif() + + add_test( + NAME "${npt_name}" + COMMAND "${Python_EXECUTABLE}" "${npt_driver}" + ) + + set_tests_properties( + "${npt_name}" + PROPERTIES ENVIRONMENT "${_npt_py_path}" + ) + endif() +endfunction() + +# + + set(PYTHON_TEST_DIR "${CMAKE_CURRENT_LIST_DIR}/tests/python") + + message("calling nwx_pybind11_tests") + nwx_pybind11_tests( + pyStructureFinder_unit_tests + "${PYTHON_TEST_DIR}/unit_tests/test_structurefinder.py" + SUBMODULES nwchemex simde chemist pluginplay parallelzone + ) +endif() + +install( + DIRECTORY "${python_src_directory}/structurefinder" + DESTINATION "${NWX_MODULE_DIRECTORY}" +) diff --git a/README.md b/README.md index 3f2c16b..f69af32 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,18 @@ + + # StructureFinder Repo for geometry optimization, transition states, etc. Much of this repo will be Python based, providing interfaces to geomeTRIC, pyBerny, ASE NEB, etc. diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..50f4f85 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +SPHINXPROJ = structurefinder +SOURCEDIR = source +BUILDDIR = build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d4af8f8 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,18 @@ + + +General instructions for building documentation found throughout the NWChemEx project are available at: +https://github.com/NWChemEx-Project/NWChemEx/blob/master/docs/README.md diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..c453480 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +sphinx +sphinx_rtd_theme==1.3.0 +sphinxcontrib-bibtex +sphinx_tabs +sphinx-autoapi==3.0.0 diff --git a/docs/source/conf.py b/docs/source/conf.py new file mode 100644 index 0000000..176f6ee --- /dev/null +++ b/docs/source/conf.py @@ -0,0 +1,174 @@ +# -*- coding: utf-8 -*- +# 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. + +# +# Configuration file for the Sphinx documentation builder. +# +# This file does only contain a selection of the most common options. For a +# full list see the documentation: +# http://www.sphinx-doc.org/en/master/config + +import os +import sys + +# -- Project information ----------------------------------------------------- + +project = u'structurefinder' +copyright = u'2023, NWChemEx Team' +author = u'NWChemEx Team' + +# Get the version from version.txt +version = '1.0.0' +# The full version, including alpha/beta/rc tags +release = version + +# -- General configuration --------------------------------------------------- + +# We use numref which is introduced in Sphinx 1.3 +needs_sphinx = '1.3' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', 'sphinx.ext.autosummary', 'sphinx_tabs.tabs', + 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.coverage', + 'sphinx.ext.mathjax', 'sphinx.ext.githubpages', 'autoapi.extension' +] +dir_path = os.path.dirname(os.path.realpath(__file__)) +doc_path = os.path.dirname(dir_path) +root_path = os.path.dirname(doc_path) + +# Add any paths that contain templates here, relative to this directory. +#templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +# source_suffix = ['.rst', '.md'] +source_suffix = '.rst' + +# The master toctree document. +master_doc = 'index' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# +# This is also used if you do content translation via gettext catalogs. +# Usually you set "language" from the command line for these cases. +language = 'en' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path . +exclude_patterns = [] + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# Should figures be numbered? +numfig = True + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'sphinx_rtd_theme' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# +# html_theme_options = {} + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +#html_static_path = ['_static'] + +# Custom sidebar templates, must be a dictionary that maps document names +# to template names. +# +# The default sidebars (for documents that don't match any pattern) are +# defined by theme itself. Builtin themes are using these templates by +# default: ``['localtoc.html', 'relations.html', 'sourcelink.html', +# 'searchbox.html']``. +# +# html_sidebars = {} + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = project + 'doc' + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', + + # Latex figure (float) alignment + # + # 'figure_align': 'htbp', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (master_doc, project + '.tex', project + ' Documentation', author, + 'manual'), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(master_doc, project.lower(), project + ' Documentation', + [author], 1)] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (master_doc, project, project + ' Documentation', author, project, + 'One line description of project.', 'Miscellaneous'), +] + +# -- Extension configuration ------------------------------------------------- +autoapi_dirs = ['../../src', '../../tests'] +autoapi_add_toctree_entry = False + +# -- Options for intersphinx extension --------------------------------------- + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/': None} + +# -- Options for todo extension ---------------------------------------------- + +# If true, `todo` and `todoList` produce output, else they produce nothing. +todo_include_todos = True diff --git a/docs/source/index.rst b/docs/source/index.rst new file mode 100644 index 0000000..72af3c0 --- /dev/null +++ b/docs/source/index.rst @@ -0,0 +1,17 @@ +.. 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. + +############### +StructureFinder +############### diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6bad103 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +numpy +scipy diff --git a/src/python/structurefinder/__init__.py b/src/python/structurefinder/__init__.py new file mode 100644 index 0000000..4851bf4 --- /dev/null +++ b/src/python/structurefinder/__init__.py @@ -0,0 +1,14 @@ +# 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/src/python/structurefinder/chemist_tools.py b/src/python/structurefinder/chemist_tools.py new file mode 100644 index 0000000..c5423ae --- /dev/null +++ b/src/python/structurefinder/chemist_tools.py @@ -0,0 +1,227 @@ +# 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. + +import chemist +import itertools + +element_symbols = [ + 'X', 'H', 'He', + 'Li', 'Be', 'B', 'C', 'N', 'O', 'F', 'Ne', + 'Na', 'Mg', 'Al', 'Si', 'P', 'S', 'Cl', 'Ar' + 'K', 'Ca', 'Sc', 'Ti', 'V', 'Cr', 'Mn', 'Fe', 'Co', 'Ni', 'Cu', 'Zn', 'Ga', 'Ge', 'As', 'Se', 'Br', 'Kr', + 'Rb', 'Sr', 'Y', 'Zr', 'Nb', 'Mo', 'Tc', 'Ru', 'Rh', 'Pd', 'Ag', 'Cd', 'In', 'Sn', 'Sb', 'Te', 'I', 'Xe' +] + +atomic_masses = [ + 0.0, # Dummy + 1.0079, # Hydrogen + 4.0026, # Helium + 6.94, # Lithium + 9.0122, # Beryllium + 10.81, # Boron + 12.011, # Carbon + 14.007, # Nitrogen + 15.999, # Oxygen + 18.998, # Fluorine + 20.180, # Neon + 22.990, # Sodium + 24.305, # Magnesium + 26.982, # Aluminium + 28.085, # Silicon + 30.974, # Phosphorus + 32.06, # Sulfur + 35.45, # Chlorine + 39.948, # Argon + 39.098, # Potassium + 40.078, # Calcium + 44.956, # Scandium + 47.867, # Titanium + 50.942, # Vanadium + 51.996, # Chromium + 54.938, # Manganese + 55.845, # Iron + 58.933, # Cobalt + 58.693, # Nickel + 63.546, # Copper + 65.38, # Zinc + 69.723, # Gallium + 72.630, # Germanium + 74.922, # Arsenic + 78.971, # Selenium + 79.904, # Bromine + 83.798, # Krypton + 85.468, # Rubidium + 87.62, # Strontium + 88.906, # Yttrium + 91.224, # Zirconium + 92.906, # Niobium + 95.95, # Molybdenum + 97.907, # Technetium + 101.07, # Ruthenium + 102.91, # Rhodium + 106.42, # Palladium + 107.87, # Silver + 112.41, # Cadmium + 114.82, # Indium + 118.71, # Tin + 121.76, # Antimony + 127.60, # Tellurium + 126.90, # Iodine + 131.29 # Xenon +] + +def get_atomic_mass(x): + """Return the atomic mass of an element or atom. + + Parameters + ---------- + x : str or int + Element symbol or atomic number + + Returns + ------- + mass : float + Atomic mass of the atom + """ + mass = 0.0 + if type(x) == str: + atomic_number = get_atomic_number(x) + mass = atomic_masses[atomic_number] + elif type(x) == int: + mass = atomic_masses[x] + else: + print("Error: Invalid input type") + return mass + + +def get_atomic_number(symbol): + """Return the atomic number for a given element symbol. + + Parameters + ---------- + symbol : str + Element symbol (case insensitive) + + Returns + ------- + atomic_number : int + Atomic number of the element + >>> print(get_atomic_number('H')) + 1 + """ + symbol = symbol.capitalize() + return element_symbols.index(symbol) + + +def get_molecule(symbols, coordinates): + """Return a chemist.Molecule object from a list of symbols and coordinates. + + Parameters + ---------- + symbols : list of str + List of element symbols + coordinates : list of lists of floats + List of lists containing the coordinates of the atoms in the molecule. + The inner lists contain the x, y, and z coordinates of each atom. + + Returns + ------- + mol : chemist.Molecule + chemist.Molecule object + """ + natom = len(symbols) + mol = chemist.Molecule() + if type(coordinates[0]) == list or str(type(coordinates[0])).startswith('numpy'): + coordinates = list(itertools.chain(*coordinates)) + for i in range(natom): + x, y, z = [float(x) for x in coordinates[3 * i:3 * i + 3]] + symbol = symbols[i] + atomic_number = get_atomic_number(symbol) + mass = get_atomic_mass(atomic_number) + atom = chemist.Atom(symbol, atomic_number, mass, x, y, z) + mol.push_back(atom) + return mol + + +def get_molecule_coordinates(mol): + """Return the coordinates of a molecule as a list of lists. + + Parameters + ---------- + mol : chemist.Molecule + chemist.Molecule object + + Returns + ------- + coordinates : list of lists + List of lists containing the coordinates of the atoms in the molecule. + The inner lists contain the x, y, and z coordinates of each atom. + """ + return [[mol.at(i).x, mol.at(i).y, mol.at(i).z] for i in range(mol.size())] + + +def get_molecule_symbols(mol): + """Return the symbols of a molecule as a list. + + Parameters + ---------- + mol : chemist.Molecule + chemist.Molecule object + + Returns + ------- + symbols : list of str + List of element symbols of the atoms in the molecule. + """ + natom = mol.size() + symbols = [] + for i in range(natom): + symbols.append(get_symbol(mol.at(i).Z)) + return symbols + + +def get_periodic_table(): + """Return the periodic table as a list. + Returns + ------- + element_symbols : list of str + + Notes + ----- + Up to elements with atomic number 54 (Xenon). + """ + return element_symbols + + +def get_symbol(atomic_number): + """Returns the element symbol for a given atomic number. + + Parameters + ---------- + atomic_number : int + Atomic number of the element + + Returns + ------- + symbol : str + Element symbol + + Notes + ----- + Returns 'X' for atomic_number=0 + >>> print(get_symbol(1)) + H + """ + return element_symbols[atomic_number] + diff --git a/src/python/structurefinder/optimizer_modules.py b/src/python/structurefinder/optimizer_modules.py new file mode 100644 index 0000000..af37cf4 --- /dev/null +++ b/src/python/structurefinder/optimizer_modules.py @@ -0,0 +1,78 @@ +# 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. + +from pluginplay import ModuleBase +from simde import AOEnergy, Energy, MolecularBasisSet +from chemist import AOSpaceD, ChemicalSystem +from . import chemist_tools as ct +from numpy import ravel +from scipy.optimize import minimize + +class Scipy_optimizer_aoenergy(ModuleBase): + """Optimization module based on scipy.optimize.minimize. + Satisfies simde.AOEnergy property type.""" + ptype = AOEnergy() + + def __init__(self): + ModuleBase.__init__(self) + self.description(self.__doc__) + self.satisfies_property_type(self.ptype) + self.add_submodule(AOEnergy(), "AOEnergy") + + def run_(self, inputs, submods): + [aos, system_in] = self.ptype.unwrap_inputs(inputs) + mol = system_in.molecule + coords = ct.get_molecule_coordinates(mol) + symbols = ct.get_molecule_symbols(mol) + def _get_energy(coords): + m = ct.get_molecule(symbols, coords) + chem_sys = ChemicalSystem(m) + E = submods["AOEnergy"].run_as(self.ptype, aos, chem_sys) + return E + result = minimize(_get_energy, ravel(coords)) + E = result.fun + r = self.results() + return self.ptype.wrap_results(r, E) + + +class Scipy_optimizer_energy(ModuleBase): + """Optimization module based on scipy.optimize.minimize. Satisfies + simde.Energy property type. Has two submodules that satisfy + simde.MolecularBasisSet and simde.AOEnergy property types. + """ + ptype = Energy() + + def __init__(self): + ModuleBase.__init__(self) + self.description(self.__doc__) + self.satisfies_property_type(self.ptype) + self.add_submodule(MolecularBasisSet(), "MolecularBasisSet") + self.add_submodule(AOEnergy(), "AOEnergy") + + def run_(self, inputs, submods): + system_in, = self.ptype.unwrap_inputs(inputs) + mol = system_in.molecule + coords = ct.get_molecule_coordinates(mol) + symbols = ct.get_molecule_symbols(mol) + aobasis = submods["MolecularBasisSet"].run_as(MolecularBasisSet(), mol) + aos = AOSpaceD(aobasis) + def _get_energy(coords): + m = ct.get_molecule(symbols, coords) + chem_sys = ChemicalSystem(m) + E = submods["AOEnergy"].run_as(AOEnergy(), aos, chem_sys) + return E + result = minimize(_get_energy, ravel(coords)) + E = result.fun + r = self.results() + return self.ptype.wrap_results(r, E) \ No newline at end of file diff --git a/tests/python/unit_tests/test_chemist_tools.py b/tests/python/unit_tests/test_chemist_tools.py new file mode 100644 index 0000000..d5d3b83 --- /dev/null +++ b/tests/python/unit_tests/test_chemist_tools.py @@ -0,0 +1,59 @@ +# 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. + +import structurefinder.chemist_tools as ct +import unittest + +class Test_chemist_tools(unittest.TestCase): + def setUp(self): + self.mol = ct.get_molecule(['H', 'H'], [[0.0, 0.0, 0.0], [0.0, 0.0, 0.74]]) + + def test_get_atomic_mass(self): + self.assertEqual(ct.get_atomic_mass(1), 1.0079) + self.assertEqual(ct.get_atomic_mass('H'), 1.0079) + self.assertEqual(ct.get_atomic_mass(6), 12.011) + self.assertEqual(ct.get_atomic_mass('C'), 12.011) + + def test_get_atomic_number(self): + self.assertEqual(ct.get_atomic_number('H'), 1) + self.assertEqual(ct.get_atomic_number('C'), 6) + + def test_get_molecule(self): + self.assertEqual(self.mol.size(), 2) + self.assertEqual(self.mol.at(0).name, 'H') + self.assertEqual(self.mol.at(1).name, 'H') + self.assertEqual(self.mol.at(0).x, 0.0) + self.assertEqual(self.mol.at(0).y, 0.0) + self.assertEqual(self.mol.at(0).z, 0.0) + self.assertEqual(self.mol.at(1).x, 0.0) + self.assertEqual(self.mol.at(1).y, 0.0) + self.assertEqual(self.mol.at(1).z, 0.74) + + def test_get_molecule_coordinates(self): + coords = ct.get_molecule_coordinates(self.mol) + self.assertEqual(coords, [[0.0, 0.0, 0.0], [0.0, 0.0, 0.74]]) + + def test_get_molecule_symbols(self): + symbols = ct.get_molecule_symbols(self.mol) + self.assertEqual(symbols, ['H', 'H']) + + def test_get_periodic_table(self): + pt = ct.get_periodic_table() + self.assertEqual(len(pt), 54) + self.assertEqual(pt[1], 'H') + self.assertEqual(pt[6], 'C') + + def test_get_symbol(self): + self.assertEqual(ct.get_symbol(1), 'H') + self.assertEqual(ct.get_symbol(6), 'C') \ No newline at end of file diff --git a/tests/python/unit_tests/test_optimizer_modules.py b/tests/python/unit_tests/test_optimizer_modules.py new file mode 100644 index 0000000..3793d4b --- /dev/null +++ b/tests/python/unit_tests/test_optimizer_modules.py @@ -0,0 +1,53 @@ +# 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. + +import structurefinder.chemist_tools as ct +from structurefinder.optimizer_modules import Scipy_optimizer_aoenergy, Scipy_optimizer_energy +import unittest +from nwchemex import load_modules +from pluginplay import ModuleManager +from simde import AOEnergy, Energy, MolecularBasisSet +from chemist import AOSpaceD, ChemicalSystem + +class Test_scipy_optimizer_aoenergy(unittest.TestCase): + def setUp(self): + self.mol = ct.get_molecule(['H', 'H'], [[0.0, 0.0, 0.0], [0.0, 0.0, 0.74]]) + self.mm = ModuleManager() + load_modules(self.mm) + self.mod_key = 'scipy_optimizer_aoenergy' + self.mm.add_module(self.mod_key, Scipy_optimizer_aoenergy()) + + def test_scf(self): + self.mm.change_submod(self.mod_key, 'AOEnergy', 'SCF Energy') + bs = self.mm.at("sto-3g").run_as(MolecularBasisSet(), self.mol) + aos = AOSpaceD(bs) + chem_sys = ChemicalSystem(self.mol) + energy = self.mm.at(self.mod_key).run_as(AOEnergy(), aos, chem_sys) + self.assertAlmostEqual(energy, -1.0787343257869195, places=5) + + +class Test_scipy_optimizer_energy(unittest.TestCase): + def setUp(self): + self.mol = ct.get_molecule(['H', 'H'], [[0.0, 0.0, 0.0], [0.0, 0.0, 0.74]]) + self.mm = ModuleManager() + load_modules(self.mm) + self.mod_key = 'scipy_optimizer_energy' + self.mm.add_module(self.mod_key, Scipy_optimizer_energy()) + + def test_scf(self): + self.mm.change_submod(self.mod_key, 'AOEnergy', 'SCF Energy') + self.mm.change_submod(self.mod_key, 'MolecularBasisSet', 'sto-3g') + chem_sys = ChemicalSystem(self.mol) + energy = self.mm.at(self.mod_key).run_as(Energy(), chem_sys) + self.assertAlmostEqual(energy, -1.0787343257869195, places=5) diff --git a/tests/python/unit_tests/test_structurefinder.py b/tests/python/unit_tests/test_structurefinder.py new file mode 100644 index 0000000..e4e554f --- /dev/null +++ b/tests/python/unit_tests/test_structurefinder.py @@ -0,0 +1,35 @@ +# 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. + +import os +import parallelzone as pz +import sys +import unittest + + + +if __name__ == '__main__': + rv = pz.runtime.RuntimeView() + + my_dir = os.path.dirname(os.path.realpath(__file__)) + root_dir = os.path.dirname(os.path.dirname(os.path.dirname(my_dir))) + src_dir = os.path.join(root_dir, 'src', 'python') + sys.path.append(src_dir) + + loader = unittest.TestLoader() + tests = loader.discover(my_dir) + testrunner = unittest.runner.TextTestRunner() + ret = not testrunner.run(tests).wasSuccessful() + sys.exit(ret) +