├── .editorconfig ├── .github └── workflows │ ├── build_conda.yml │ ├── build_wheels.yml │ ├── docs.yml │ └── paper.yml ├── .gitignore ├── COMPILATION.md ├── CONTRIBUTING.md ├── LICENSE ├── MANIFEST.in ├── README.md ├── bld.bat ├── build.sh ├── conda_build_config.yaml ├── deploy ├── Dockerfile-linux-anaconda ├── Dockerfile-linux-pypi ├── build_docker_linux_anaconda.sh ├── build_docker_linux_pypi.sh ├── docker_build_anaconda_local.sh ├── docker_build_pypi_local.sh ├── docker_setup_github.sh └── docker_test_github.sh ├── docs ├── Makefile ├── _static │ ├── css │ │ └── custom.css │ └── img │ │ ├── ch4_geomopt_ch_bond.png │ │ ├── ch4_geomopt_energy_rms_force.png │ │ ├── co.jpg │ │ ├── co_canonical_isosurfaces.jpg │ │ ├── co_fosterboys_isosurfaces.jpg │ │ ├── favicon.ico │ │ └── pyqint_logo_128.png ├── _templates │ └── breadcrumbs.html ├── community_guidelines.rst ├── conf.py ├── index.rst ├── installation.rst ├── make.bat └── user_interface.rst ├── environment.yml ├── examples ├── .gitignore ├── blender_render.py ├── cohp_co.py ├── cohp_lih.py ├── foster_boys.py ├── foster_boys_quick.py ├── molecular_orbitals_co.py └── molecular_orbitals_hco.py ├── img ├── co.jpg └── mo_h2o_1b2.png ├── meta.yaml ├── paper ├── .gitignore ├── compile.sh ├── img │ ├── co-coefficient-matrix.jpg │ ├── orbitals-co-contour.jpg │ ├── orbitals-co-isosurface.jpg │ └── orbitals-co-localized.jpg ├── paper.bib └── paper.md ├── pyproject.toml ├── pyqint ├── __init__.py ├── _version.py ├── basissets │ ├── p321.json │ ├── p631.json │ ├── sto3g.json │ └── sto6g.json ├── blender │ └── blender_render_molecule.py ├── blenderrender.py ├── cgf.cpp ├── cgf.h ├── cgf.py ├── cohp.py ├── element.py ├── factorials.h ├── foster_boys.py ├── gamma.cpp ├── gamma.h ├── geometry_optimization.py ├── gto.py ├── hf.py ├── integrals.cpp ├── integrals.h ├── isosurface.py ├── molecule.py ├── molecule_builder.py ├── molecules │ ├── benzene.xyz │ ├── bf3.xyz │ ├── ch4.xyz │ ├── co.xyz │ ├── co2.xyz │ ├── ethylene.xyz │ ├── h2.xyz │ ├── h2o.xyz │ ├── he.xyz │ ├── lih.xyz │ └── nh3.xyz ├── plotter.cpp ├── plotter.h ├── pyqint.pxd ├── pyqint.pyx ├── spherical_harmonics.py └── vec3.h ├── pytest.ini ├── setup.py ├── tests ├── results │ ├── ch4.xyz │ ├── h2_grad.npy │ ├── h2_wf.npy │ ├── h2o_dipole_x.npy │ ├── h2o_dipole_y.npy │ ├── h2o_dipole_z.npy │ ├── h2o_kinetic_p321.npy │ ├── h2o_kinetic_p631.npy │ ├── h2o_kinetic_sto3g.npy │ ├── h2o_kinetic_sto6g.npy │ ├── h2o_nuclear_p321.npy │ ├── h2o_nuclear_p631.npy │ ├── h2o_nuclear_sto3g.npy │ ├── h2o_nuclear_sto6g.npy │ ├── h2o_orb_1b2.npy │ ├── h2o_overlap_p321.npy │ ├── h2o_overlap_p631.npy │ ├── h2o_overlap_sto3g.npy │ ├── h2o_overlap_sto6g.npy │ ├── h2o_teint_p321.npy │ ├── h2o_teint_p631.npy │ ├── h2o_teint_sto3g.npy │ ├── h2o_teint_sto6g.npy │ └── nuclear_deriv_h2o.txt ├── test_cgf.py ├── test_cohp.py ├── test_custom_basis_set.py ├── test_derivatives_openmp.py ├── test_dipole.py ├── test_energy_decomposition.py ├── test_foster_boys.py ├── test_geometry_optimization.py ├── test_grad.py ├── test_gto.py ├── test_hf.py ├── test_hf_deriv.py ├── test_hf_molecules.py ├── test_hf_molecules_charged.py ├── test_integrals_openmp.py ├── test_kinetic.py ├── test_kinetic_deriv.py ├── test_molecule_builder.py ├── test_molecule_order.py ├── test_nuclear.py ├── test_nuclear_deriv.py ├── test_overlap.py ├── test_overlap_deriv.py ├── test_plot_data.py ├── test_repulsion.py ├── test_repulsion_deriv.py ├── test_safety_guards.py ├── test_str.py └── test_tolerance.py └── testversion.py /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | end_of_line = lf 11 | charset = utf-8 12 | 13 | # Docstrings and comments use max_line_length = 79 14 | [*.py] 15 | max_line_length = 119 16 | -------------------------------------------------------------------------------- /.github/workflows/build_conda.yml: -------------------------------------------------------------------------------- 1 | name: Conda pkg 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | - develop 10 | tags: 11 | - "v**" 12 | release: 13 | types: 14 | - published 15 | 16 | jobs: 17 | check-version-strings: 18 | runs-on: ubuntu-latest 19 | container: python:3.9.18-slim-bullseye 20 | 21 | steps: 22 | - name: Checkout repo 23 | uses: actions/checkout@v3 24 | - name: Install dependencies 25 | run: | 26 | pip install pyyaml 27 | - name: Test versions 28 | run: | 29 | python testversion.py 30 | 31 | #------------------------------------------------------------------------------- 32 | # Anaconda / Windows 33 | #------------------------------------------------------------------------------- 34 | build-anaconda-windows: 35 | needs: check-version-strings 36 | runs-on: windows-latest 37 | 38 | steps: 39 | - name: Checkout repo 40 | uses: actions/checkout@v3 41 | - name: Set-up miniconda 42 | uses: conda-incubator/setup-miniconda@v3 43 | with: 44 | activate-environment: test 45 | environment-file: environment.yml 46 | python-version: 3.8 47 | auto-activate-base: false 48 | - name: Set-up MSVC toolchain 49 | uses: ilammy/msvc-dev-cmd@v1 50 | with: 51 | arch: amd64 52 | - name: Build 53 | shell: bash -l {0} 54 | run: | 55 | echo $PATH 56 | conda build . --no-include-recipe 57 | - name: Archive packages 58 | uses: actions/upload-artifact@v4 59 | with: 60 | name: anaconda-windows-packages 61 | path: C:\Miniconda\envs\test\conda-bld\win-64\pyqint-*.tar.bz2 62 | 63 | anaconda-publish: 64 | name: Publish Anaconda / Windows 65 | if: startsWith(github.ref, 'refs/tags/v') 66 | needs: build-anaconda-windows 67 | runs-on: ubuntu-latest 68 | environment: 69 | name: anaconda 70 | url: https://anaconda.org/ifilot/pyqint 71 | steps: 72 | - name: Checkout repo 73 | uses: actions/checkout@v3 74 | - name: Set-up miniconda 75 | uses: conda-incubator/setup-miniconda@v3 76 | with: 77 | activate-environment: test 78 | environment-file: environment.yml 79 | python-version: 3.8 80 | auto-activate-base: false 81 | - name: Retrieve packages 82 | uses: actions/download-artifact@v4 83 | with: 84 | name: anaconda-windows-packages 85 | path: packages 86 | - name: publish-to-conda 87 | shell: bash -l {0} 88 | env: 89 | INPUT_ANACONDATOKEN: ${{ secrets.ANACONDA_TOKEN }} 90 | run: | 91 | export ANACONDA_API_TOKEN=$INPUT_ANACONDATOKEN 92 | anaconda upload packages/*.tar.bz2 -------------------------------------------------------------------------------- /.github/workflows/build_wheels.yml: -------------------------------------------------------------------------------- 1 | name: PyPI pkg 2 | 3 | on: 4 | workflow_dispatch: 5 | pull_request: 6 | push: 7 | branches: 8 | - master 9 | - develop 10 | tags: 11 | - "v**" 12 | release: 13 | types: 14 | - published 15 | 16 | env: 17 | # Build `universal2` and `arm64` wheels on an Intel runner. 18 | # Note that the `arm64` wheel and the `arm64` part of the `universal2` 19 | # wheel cannot be tested in this configuration. 20 | CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" 21 | 22 | jobs: 23 | check-version-strings: 24 | runs-on: ubuntu-latest 25 | container: python:3.9.18-slim-bullseye 26 | 27 | steps: 28 | - name: Checkout repo 29 | uses: actions/checkout@v3 30 | - name: Install dependencies 31 | run: | 32 | pip install pyyaml 33 | - name: Test versions 34 | run: | 35 | python testversion.py 36 | 37 | build_wheels: 38 | name: Build wheels on ${{ matrix.os }} 39 | needs: [check-version-strings] 40 | runs-on: ${{ matrix.os }} 41 | strategy: 42 | matrix: 43 | os: [ubuntu-latest, windows-latest, macos-latest] 44 | 45 | steps: 46 | - uses: actions/checkout@v4 47 | 48 | - name: Build wheels 49 | uses: pypa/cibuildwheel@v2.17.0 50 | 51 | - uses: actions/upload-artifact@v4 52 | with: 53 | name: wheels-${{ matrix.os }} 54 | path: ./wheelhouse/*.whl 55 | 56 | build_sdist: 57 | name: Build source distribution 58 | needs: [check-version-strings] 59 | runs-on: ubuntu-latest 60 | steps: 61 | - uses: actions/checkout@v4 62 | 63 | - name: Build sdist 64 | run: pipx run build --sdist 65 | 66 | - uses: actions/upload-artifact@v4 67 | with: 68 | name: distfiles 69 | path: dist/*.tar.gz 70 | 71 | upload_pypi: 72 | needs: [build_wheels, build_sdist] 73 | runs-on: ubuntu-latest 74 | environment: pypi 75 | permissions: 76 | id-token: write 77 | if: startsWith(github.ref, 'refs/tags/v') 78 | steps: 79 | - uses: actions/download-artifact@v4 80 | with: 81 | name: wheels-ubuntu-latest 82 | path: dist 83 | - uses: actions/download-artifact@v4 84 | with: 85 | name: wheels-windows-latest 86 | path: dist 87 | - uses: actions/download-artifact@v4 88 | with: 89 | name: wheels-macos-latest 90 | path: dist 91 | - uses: actions/download-artifact@v4 92 | with: 93 | name: distfiles 94 | path: dist 95 | 96 | - uses: pypa/gh-action-pypi-publish@release/v1 -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Deployment of documentation 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | permissions: 12 | contents: write # Grants write access to the repository contents 13 | 14 | jobs: 15 | build: 16 | name: Build and Deploy Sphinx Documentation 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Checkout Repository 21 | uses: actions/checkout@v3 22 | 23 | - name: Set up Python 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: 3.13.1 27 | 28 | - name: Install Dependencies 29 | run: | 30 | python -m pip install --upgrade pip 31 | pip install sphinx-rtd-theme \ 32 | sphinxcontrib-tikz \ 33 | pydata-sphinx-theme \ 34 | sphinx_design \ 35 | sphinx_subfigure \ 36 | myst_parser 37 | - name: Build Documentation 38 | run: | 39 | cd docs 40 | make html 41 | 42 | - name: Deploy to GitHub Pages 43 | uses: peaceiris/actions-gh-pages@v3 44 | with: 45 | github_token: ${{ secrets.GITHUB_TOKEN }} 46 | publish_dir: docs/_build/html -------------------------------------------------------------------------------- /.github/workflows/paper.yml: -------------------------------------------------------------------------------- 1 | name: paper 2 | 3 | on: [push] 4 | 5 | jobs: 6 | paper: 7 | runs-on: ubuntu-latest 8 | name: Paper Draft 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - name: Build draft PDF 13 | uses: openjournals/openjournals-draft-action@master 14 | with: 15 | journal: joss 16 | # This should be the path to the paper within your repo. 17 | paper-path: paper/paper.md 18 | - name: Upload 19 | uses: actions/upload-artifact@v4 20 | with: 21 | name: paper 22 | # This is the output path where Pandoc will write the compiled 23 | # PDF. Note, this should be the same directory as the input 24 | # paper.md 25 | path: paper/paper.pdf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | 134 | # pytype static type analyzer 135 | .pytype/ 136 | 137 | # Cython debug symbols 138 | cython_debug/ 139 | *.xyz 140 | 141 | # Spynx documentation 142 | docs/build/* 143 | 144 | # build folder 145 | build/* 146 | dist/* 147 | wheelhouse/* 148 | test.py 149 | nose_debug 150 | anaconda-upload/* 151 | *.vscode 152 | 153 | # managlyph .abo files 154 | *.abo 155 | .vscode 156 | -------------------------------------------------------------------------------- /COMPILATION.md: -------------------------------------------------------------------------------- 1 | # Compilation details 2 | 3 | ## Building 4 | 5 | To create a wheel (`whl`), run 6 | 7 | ```bash 8 | pipx run cibuildwheel --only cp312-manylinux_x86_64 9 | ``` 10 | 11 | To install the `whl` file 12 | 13 | ```bash 14 | pip3 install wheelhouse/pyqint-0.18.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl 15 | ``` 16 | 17 | and to locally test 18 | 19 | ```bash 20 | pytest-3 tests/*.py 21 | ``` 22 | 23 | For skipping unit testing when compiling with `cibuildwheel`, run 24 | 25 | ```bash 26 | CIBW_TEST_SKIP="cp312-manylinux_x86_64" pipx run cibuildwheel --only cp312-manylinux_x86_64 27 | ``` 28 | 29 | ### Uploading to PyPi 30 | 31 | This will place wheels in the `dist` folder. To upload these wheels 32 | to PyPi, make sure you have `twine` installed using 33 | 34 | ```bash 35 | pip install twine 36 | ``` 37 | 38 | To upload, run 39 | 40 | ```bash 41 | python -m twine upload wheelhouse/* 42 | ``` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | When contributing to this repository, please first discuss the change you wish to make via issue, 4 | email, or any other method with the owners of this repository before making a change. 5 | 6 | Please note we have a code of conduct, please follow it in all your interactions with the project. 7 | 8 | ## Pull Request Process 9 | 10 | 1. Ensure any install or build dependencies are removed before the end of the layer when doing a 11 | build. 12 | 2. Update the README.md with details of changes to the interface, this includes new environment 13 | variables, exposed ports, useful file locations and container parameters. 14 | 3. Increase the version numbers in any examples files and the README.md to the new version that this 15 | Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/). 16 | 4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you 17 | do not have permission to do that, you may request the second reviewer to merge it for you. 18 | 19 | ## Code of Conduct 20 | 21 | ### Our Pledge 22 | 23 | In the interest of fostering an open and welcoming environment, we as 24 | contributors and maintainers pledge to making participation in our project and 25 | our community a harassment-free experience for everyone, regardless of age, body 26 | size, disability, ethnicity, gender identity and expression, level of experience, 27 | nationality, personal appearance, race, religion, or sexual identity and 28 | orientation. 29 | 30 | ### Our Standards 31 | 32 | Examples of behavior that contributes to creating a positive environment 33 | include: 34 | 35 | * Using welcoming and inclusive language 36 | * Being respectful of differing viewpoints and experiences 37 | * Gracefully accepting constructive criticism 38 | * Focusing on what is best for the community 39 | * Showing empathy towards other community members 40 | 41 | Examples of unacceptable behavior by participants include: 42 | 43 | * The use of sexualized language or imagery and unwelcome sexual attention or 44 | advances 45 | * Trolling, insulting/derogatory comments, and personal or political attacks 46 | * Public or private harassment 47 | * Publishing others' private information, such as a physical or electronic 48 | address, without explicit permission 49 | * Other conduct which could reasonably be considered inappropriate in a 50 | professional setting 51 | 52 | ### Our Responsibilities 53 | 54 | Project maintainers are responsible for clarifying the standards of acceptable 55 | behavior and are expected to take appropriate and fair corrective action in 56 | response to any instances of unacceptable behavior. 57 | 58 | Project maintainers have the right and responsibility to remove, edit, or 59 | reject comments, commits, code, wiki edits, issues, and other contributions 60 | that are not aligned to this Code of Conduct, or to ban temporarily or 61 | permanently any contributor for other behaviors that they deem inappropriate, 62 | threatening, offensive, or harmful. 63 | 64 | ### Scope 65 | 66 | This Code of Conduct applies both within project spaces and in public spaces 67 | when an individual is representing the project or its community. Examples of 68 | representing a project or community include using an official project e-mail 69 | address, posting via an official social media account, or acting as an appointed 70 | representative at an online or offline event. Representation of a project may be 71 | further defined and clarified by project maintainers. 72 | 73 | ### Enforcement 74 | 75 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 76 | reported by contacting the project team at [INSERT EMAIL ADDRESS]. All 77 | complaints will be reviewed and investigated and will result in a response that 78 | is deemed necessary and appropriate to the circumstances. The project team is 79 | obligated to maintain confidentiality with regard to the reporter of an incident. 80 | Further details of specific enforcement policies may be posted separately. 81 | 82 | Project maintainers who do not follow or enforce the Code of Conduct in good 83 | faith may face temporary or permanent repercussions as determined by other 84 | members of the project's leadership. 85 | 86 | ### Attribution 87 | 88 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 89 | available at [http://contributor-covenant.org/version/1/4][version] 90 | 91 | [homepage]: http://contributor-covenant.org 92 | [version]: http://contributor-covenant.org/version/1/4/ -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include pyqint/basissets/*.json 2 | include pyqint/molecules/*.xyz -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyQInt 2 | 3 | [![status](https://jose.theoj.org/papers/2a73fa24200e8c1ec47fc6e37f818a54/status.svg)](https://jose.theoj.org/papers/2a73fa24200e8c1ec47fc6e37f818a54) 4 | [![Conda pkg](https://github.com/ifilot/pyqint/actions/workflows/build_conda.yml/badge.svg)](https://github.com/ifilot/pyqint/actions/workflows/build_conda.yml) 5 | [![PyPI pkg](https://github.com/ifilot/pyqint/actions/workflows/build_wheels.yml/badge.svg)](https://github.com/ifilot/pyqint/actions/workflows/build_wheels.yml) 6 | [![Anaconda-Server Badge](https://anaconda.org/ifilot/pyqint/badges/version.svg)](https://anaconda.org/ifilot/pyqint) 7 | [![PyPI](https://img.shields.io/pypi/v/pyqint?style=flat-square)](https://pypi.org/project/pyqint/) 8 | [![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0) 9 | 10 | ## Purpose 11 | 12 | PyQInt is a Python-based, teaching-oriented implementation of the Hartree-Fock 13 | method, designed to make the inner workings of electronic structure theory 14 | accessible and transparent. It provides a clear, readable interface to 15 | fundamental components such as molecular integrals over Gaussian basis 16 | functions, SCF procedures (with DIIS acceleration), orbital localization, and 17 | geometry optimization. 18 | 19 | What sets PyQInt apart is its educational design philosophy: all matrices, 20 | intermediate results, and algorithmic steps are exposed—allowing students, 21 | educators, and developers to inspect, understand, and experiment with every part 22 | of the computation. Whether you are learning how Hartree-Fock works, developing 23 | your own extensions, or teaching a course in computational chemistry, PyQInt 24 | offers a hands-on, exploratory platform. 25 | 26 | For students interested in the theoretical foundations and algorithmic 27 | implementation of Hartree–Fock, we recommend the open-access textbook Elements 28 | of [Electronic Structure Theory](https://ifilot.pages.tue.nl/elements-of-electronic-structure-theory/index.html). 29 | 30 | > [!NOTE] 31 | > PyQInt connects to a C++ backend for core numerical routines, but it 32 | > is not optimized for performance. It is best suited for learning, prototyping, 33 | > and small molecule calculations - **not** production-scale quantum chemistry. 34 | 35 | > [!TIP] 36 | > Interested in other **education** quantum chemical codes? Have a look at the 37 | > packages below. 38 | > * [PyDFT](https://github.com/ifilot/pydft) is a pure-Python density functional 39 | > theory code, built on top of PyQInt. 40 | > * [HFCXX](https://github.com/ifilot/hfcxx) is a full C++ code for performing 41 | > Hartree-Fock calculations. 42 | > * [DFTCXX](https://github.com/ifilot/dftcxx) is a full C++ code for performing 43 | > Density Functional Theory Calculations. 44 | 45 | ## Documentation 46 | 47 | PyQInt comes with detailed documentation and examples, which can be found 48 | at https://ifilot.github.io/pyqint/. 49 | 50 | ## Features 51 | 52 | The following molecular integrals are supported by PyQInt 53 | 54 | - [x] Overlap integral 55 | - [x] Kinetic integral 56 | - [x] Dipole integral 57 | - [x] Nuclear integral 58 | - [x] Two-electron repulsion integral 59 | 60 | as well as the following geometric derivatives 61 | 62 | - [x] Overlap integral 63 | - [x] Kinetic integral 64 | - [x] Nuclear integral 65 | - [x] Two-electron repulsion integral 66 | 67 | PyQInt offers additional features such as 68 | * Performing [restricted 69 | Hartree-Fock](https://en.wikipedia.org/wiki/Hartree%E2%80%93Fock_method) 70 | calculations using [DIIS](https://en.wikipedia.org/wiki/DIIS) 71 | * Calculation of [Crystal Orbital Hamilton Population](http://www.cohp.de/) 72 | coefficients 73 | * Construction of localized orbitals using the [Foster-Boys 74 | method](https://en.wikipedia.org/wiki/Localized_molecular_orbitals#Foster-Boys) 75 | * Geometry optimization using Conjugate Gradient 76 | * Visualization of molecular orbitals 77 | 78 | All routines are (automatically) tested and verified against several open-source 79 | as well as commercial programs that use cartesian Gaussian orbitals. 80 | Nevertheless, if you spot any mistake, please kindly open an 81 | [issue](https://github.com/ifilot/pyqint/issues) in this Github repository. 82 | 83 | In the image below, the (canonical) molecular orbitals as found using a 84 | restricted Hartree-Fock calculation for the CO molecule are shown. 85 | 86 | ![Molecular orbitals of CO](img/co.jpg) 87 | 88 | ## Community guidelines 89 | 90 | * Contributions to PyQInt are always welcome and appreciated. Before doing so, 91 | please first read the [CONTRIBUTING](CONTRIBUTING.md) guide. 92 | * For reporting issues or problems with the software, you are kindly invited to 93 | to open a [new issue with the bug 94 | label](https://github.com/ifilot/pyqint/issues/new?labels=bug). 95 | * If you seek support in using PyQInt, please [open an issue with the 96 | question](https://github.com/ifilot/pyqint/issues/new?labels=question) label. 97 | * If you wish to contact the developers, please send an e-mail to ivo@ivofilot.nl. 98 | 99 | ## License 100 | 101 | Unless otherwise stated, all code in this repository is provided under the GNU 102 | General Public License version 3. -------------------------------------------------------------------------------- /bld.bat: -------------------------------------------------------------------------------- 1 | "%PYTHON%" setup.py install 2 | if errorlevel 1 exit 1 3 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | $PYTHON setup.py install # Python command to install the script. -------------------------------------------------------------------------------- /conda_build_config.yaml: -------------------------------------------------------------------------------- 1 | python: 2 | - 3.8 3 | - 3.9 4 | - 3.10 5 | - 3.11 6 | - 3.12 7 | numpy: 8 | - 1.22 9 | - 1.22 10 | - 1.23 11 | - 1.26 12 | - 1.26 13 | zip_keys: 14 | - python 15 | - numpy 16 | -------------------------------------------------------------------------------- /deploy/Dockerfile-linux-anaconda: -------------------------------------------------------------------------------- 1 | FROM conda/miniconda3 2 | RUN apt update 3 | RUN apt -y install libeigen3-dev build-essential libboost-all-dev 4 | RUN conda install -y conda-build 5 | RUN apt -y install git 6 | -------------------------------------------------------------------------------- /deploy/Dockerfile-linux-pypi: -------------------------------------------------------------------------------- 1 | FROM quay.io/pypa/manylinux2010_x86_64 2 | RUN yum -y install eigen3-devel boost-devel libgomp 3 | RUN yum -y install mlocate 4 | RUN yum -y install gcc 5 | RUN updatedb 6 | RUN ls -alh /opt/python 7 | RUN /opt/python/cp36-cp36m/bin/python -m pip install numpy tqdm cython nose 8 | RUN /opt/python/cp37-cp37m/bin/python -m pip install numpy tqdm cython nose 9 | RUN /opt/python/cp38-cp38/bin/python -m pip install numpy tqdm cython nose 10 | RUN /opt/python/cp39-cp39/bin/python -m pip install numpy tqdm cython nose 11 | RUN /opt/python/cp310-cp310/bin/python -m pip install numpy tqdm cython nose -------------------------------------------------------------------------------- /deploy/build_docker_linux_anaconda.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # set path to root 4 | ROOT='//d//PROGRAMMING//PYTHON//pyqint' 5 | IMAGE='pyqint-anaconda' 6 | 7 | winpty docker run -i -t -v $ROOT://io -w //io -t $IMAGE .//docker//docker_run_anaconda.sh 8 | -------------------------------------------------------------------------------- /deploy/build_docker_linux_pypi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # clean any earlier distributions 4 | rm -rvf build/* 5 | rm -vf dist/*.whl wheelhouse/*.whl 6 | rm -rvf *.egg-info 7 | 8 | # set path to root 9 | ROOT='//d//PROGRAMMING//PYTHON//debian//pyqint' 10 | IMAGE='pyqint-pypi' 11 | 12 | # run compilation inside Docker 13 | winpty docker run -i -t -v $ROOT://io -w //io $IMAGE .//docker//docker_run_pypi.sh 14 | -------------------------------------------------------------------------------- /deploy/docker_build_anaconda_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # build the images 4 | conda build . 5 | 6 | # and copy the images back to the io folder so they can be uploaded 7 | rm -rvf /io/anaconda-upload 8 | mkdir /io/anaconda-upload 9 | cp -v /usr/local/conda-bld/linux-64/pyqint*.tar.bz2 /io/anaconda-upload 10 | -------------------------------------------------------------------------------- /deploy/docker_build_pypi_local.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u -x 4 | 5 | function repair_wheel { 6 | wheel="$1" 7 | if ! auditwheel show "$wheel"; then 8 | echo "Skipping non-platform wheel $wheel" 9 | else 10 | auditwheel repair "$wheel" -w /io/wheelhouse/ 11 | fi 12 | } 13 | 14 | # Compile wheels 15 | for PYBIN in /opt/python/cp3{7,8,9,10,11}-*/bin; do 16 | "${PYBIN}/python" /io/setup.py bdist_wheel 17 | done 18 | 19 | # Bundle external shared libraries into the wheels 20 | for whl in dist/*.whl; do 21 | repair_wheel "$whl" 22 | done 23 | -------------------------------------------------------------------------------- /deploy/docker_setup_github.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u -x 4 | 5 | function repair_wheel { 6 | wheel="$1" 7 | if ! auditwheel show "$wheel"; then 8 | echo "Skipping non-platform wheel $wheel" 9 | else 10 | auditwheel repair "$wheel" -w ./wheelhouse/ 11 | fi 12 | } 13 | 14 | # Compile wheels 15 | for PYBIN in /opt/python/cp3{7,8,9,10,11}-*/bin; do 16 | "${PYBIN}/python" setup.py bdist_wheel 17 | done 18 | 19 | # Bundle external shared libraries into the wheels 20 | for whl in dist/*.whl; do 21 | repair_wheel "$whl" 22 | done 23 | -------------------------------------------------------------------------------- /deploy/docker_test_github.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e -u -x 4 | 5 | # Install packages and test 6 | for PYBIN in /opt/python/cp3{7,8,9,10,11}-*/bin; do 7 | "${PYBIN}/python" -m pip install numpy pytest nose 8 | "${PYBIN}/pip" install pyqint --no-index -f ./wheelhouse 9 | ("${PYBIN}/pytest" --verbose ./tests/*.py) 10 | done 11 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | .tight-table td { 2 | white-space: normal !important; 3 | } 4 | 5 | .wy-side-nav-search { 6 | background: #e4f0e1; 7 | } 8 | 9 | .icon.icon-home { 10 | color: #7da275; 11 | } 12 | -------------------------------------------------------------------------------- /docs/_static/img/ch4_geomopt_ch_bond.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/ch4_geomopt_ch_bond.png -------------------------------------------------------------------------------- /docs/_static/img/ch4_geomopt_energy_rms_force.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/ch4_geomopt_energy_rms_force.png -------------------------------------------------------------------------------- /docs/_static/img/co.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/co.jpg -------------------------------------------------------------------------------- /docs/_static/img/co_canonical_isosurfaces.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/co_canonical_isosurfaces.jpg -------------------------------------------------------------------------------- /docs/_static/img/co_fosterboys_isosurfaces.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/co_fosterboys_isosurfaces.jpg -------------------------------------------------------------------------------- /docs/_static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/favicon.ico -------------------------------------------------------------------------------- /docs/_static/img/pyqint_logo_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/docs/_static/img/pyqint_logo_128.png -------------------------------------------------------------------------------- /docs/_templates/breadcrumbs.html: -------------------------------------------------------------------------------- 1 |
2 | Github 3 |
4 |
5 | -------------------------------------------------------------------------------- /docs/community_guidelines.rst: -------------------------------------------------------------------------------- 1 | .. _community_guidelines: 2 | .. index:: Community Guidelines 3 | 4 | Community guidelines 5 | ******************** 6 | 7 | * Contributions to :program:`PyQInt` are always welcome and appreciated. Before doing 8 | so, please first read the `CONTRIBUTING `_ 9 | guide. 10 | * For reporting issues or problems with the software, you are kindly invited to 11 | `to open a new issue on Github with the bug tag `_. 12 | * If you seek support in using :program:`PyQInt`, please 13 | `open an issue with the question tag `_. 14 | * If you wish to contact the developers, please send an e-mail to ivo@ivofilot.nl. 15 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('.')) 16 | import sphinx_rtd_theme 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'PyQInt' 21 | copyright = '2023, Inorganic Materials and Catalysis' 22 | author = 'Ivo Filot' 23 | 24 | 25 | # -- General configuration --------------------------------------------------- 26 | 27 | # Add any Sphinx extension module names here, as strings. They can be 28 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 29 | # ones. 30 | extensions = [ 31 | 'sphinx.ext.mathjax', 32 | 'sphinx.ext.autosectionlabel', 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.viewcode', 35 | 'sphinx.ext.napoleon', 36 | 'sphinx_rtd_theme' 37 | ] 38 | 39 | # Napoleon settings 40 | napoleon_numpy_docstring = True 41 | 42 | nitpicky = True 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['_templates'] 46 | 47 | # List of patterns, relative to source directory, that match files and 48 | # directories to ignore when looking for source files. 49 | # This pattern also affects html_static_path and html_extra_path. 50 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 51 | 52 | 53 | # -- Options for HTML output ------------------------------------------------- 54 | 55 | # The theme to use for HTML and HTML Help pages. See the documentation for 56 | # a list of builtin themes. 57 | # 58 | html_theme = 'sphinx_rtd_theme' 59 | 60 | # Add any paths that contain custom static files (such as style sheets) here, 61 | # relative to this directory. They are copied after the builtin static files, 62 | # so a file named "default.css" will overwrite the builtin "default.css". 63 | master_doc = 'index' 64 | html_static_path = ['_static'] 65 | # html_theme_options = { 66 | # 'display_version': True, 67 | # 'analytics_id': 'G-H71EPP6GVB' 68 | # } 69 | html_logo = "_static/img/pyqint_logo_128.png" 70 | html_favicon = "_static/img/favicon.ico" 71 | html_css_files = [ 72 | "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" 73 | ] 74 | 75 | # other options 76 | html_show_sourcelink = False 77 | 78 | def setup(app): 79 | app.add_css_file('css/custom.css') 80 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | PyQInt: a Python package for evaluating Gaussian integrals and performing electronic structure calculations 2 | =========================================================================================================== 3 | 4 | .. image:: https://jose.theoj.org/papers/2a73fa24200e8c1ec47fc6e37f818a54/status.svg 5 | :target: https://jose.theoj.org/papers/2a73fa24200e8c1ec47fc6e37f818a54 6 | :alt: status 7 | .. image:: https://img.shields.io/github/v/tag/ifilot/pyqint?label=version 8 | :alt: GitHub tag (latest SemVer) 9 | .. image:: https://github.com/ifilot/pyqint/actions/workflows/build_wheels.yml/badge.svg 10 | :target: https://github.com/ifilot/pyqint/actions/workflows/build_wheels.yml 11 | .. image:: https://github.com/ifilot/pyqint/actions/workflows/build_conda.yml/badge.svg 12 | :target: https://github.com/ifilot/pyqint/actions/workflows/build_conda.yml 13 | .. image:: https://img.shields.io/badge/License-GPLv3-blue.svg 14 | :target: https://www.gnu.org/licenses/gpl-3.0 15 | 16 | :program:`PyQInt` is a Python-based, teaching-oriented implementation of the 17 | Hartree-Fock method, designed to make the inner workings of electronic structure 18 | theory accessible and transparent. It provides a clear, readable interface to 19 | fundamental components such as molecular integrals over Gaussian basis 20 | functions, SCF procedures (with DIIS acceleration), orbital localization, and 21 | geometry optimization. 22 | 23 | What sets :program:`PyQInt` apart is its educational design philosophy: all 24 | matrices, intermediate results, and algorithmic steps are exposed—allowing 25 | students, educators, and developers to inspect, understand, and experiment with 26 | every part of the computation. Whether you are learning how Hartree-Fock works, 27 | developing your own extensions, or teaching a course in computational chemistry, 28 | PyQInt offers a hands-on, exploratory platform. 29 | 30 | .. admonition:: Tip 31 | 32 | For students interested in the theoretical foundations and algorithmic 33 | implementation of Hartree–Fock, we recommend the open-access textbook Elements 34 | of `Electronic Structure Theory `_. 35 | 36 | :program:`PyQInt` offers supporting scripts for facile visualization of result 37 | such as producing contour plots for the molecular orbitals. Below, an example 38 | is shown for the molecular orbitals of the CO molecule. 39 | 40 | .. figure:: _static/img/co.jpg 41 | 42 | Canonical molecular orbitals of CO visualized using contour plots. 43 | 44 | :program:`PyQInt` not only supports calculation of the canonical molecular orbitals 45 | via the (restricted) Hartee-Fock procedure, but can also be used to construct 46 | the localized molecular orbitals which is relevant for showing the similarity 47 | between modern electronic structure methods and classical Lewis theory. In the 48 | image below, one can observe the **canonical** molecular orbitals for the CO molecule 49 | as well as the **localized** molecular orbitals. 50 | 51 | .. figure:: _static/img/co_canonical_isosurfaces.jpg 52 | 53 | Canonical molecular orbitals of CO visualized using isosurfaces with an 54 | isovalue of +/-0.03. 55 | 56 | .. figure:: _static/img/co_fosterboys_isosurfaces.jpg 57 | 58 | Localized molecular orbitals of CO visualized using isosurfaces with an 59 | isovalue of +/-0.03. Note that the localization procedure has only been 60 | applied to the occupied molecular orbitals. Observe that the localized 61 | orbitals contain a triple-degenerate state corresponding to the triple 62 | bond and two lone pairs for C and O. 63 | 64 | :program:`PyQInt` has been developed at the Eindhoven University of Technology, 65 | Netherlands. :program:`PyQInt` and its development are hosted on `github 66 | `_. Bugs and feature 67 | requests are ideally submitted via the `github issue tracker 68 | `_. 69 | 70 | .. toctree:: 71 | :maxdepth: 3 72 | :caption: Contents: 73 | 74 | installation 75 | user_interface 76 | community_guidelines 77 | 78 | Indices and tables 79 | ------------------ 80 | 81 | * :ref:`genindex` 82 | * :ref:`search` 83 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | .. _installation: 2 | .. index:: Installation 3 | 4 | Installation 5 | ============ 6 | 7 | .. tip:: 8 | For Windows users with relatively little experience with Python, we warmly 9 | recommend to use the `Anaconda distribution `_. 10 | Anaconda is an all-in-one package containing the Python compiler, 11 | an integrated desktop environment (Spyder) and plenty of useful Python 12 | packages such as numpy and matplotlib. 13 | 14 | :program:`PyQInt` is distributed via both Anaconda package as well as PyPI. For 15 | Windows, it is recommended to install :program:`PyQInt` via Anaconda, while 16 | for Linux, we recommend to use PyPI. 17 | 18 | Windows / Anaconda 19 | ------------------ 20 | 21 | To install :program:`PyQInt` under Windows, open an Anaconda Prompt window 22 | and run:: 23 | 24 | conda install -c ifilot pyqint 25 | 26 | .. note:: 27 | Sometimes Anaconda is unable to resolve the package dependencies. This can 28 | be caused by a broken environment. An easy solution is to create a new 29 | environment. See the "Troubleshooting" section at the end of this page 30 | for more information. 31 | 32 | Linux / PyPI 33 | ------------ 34 | 35 | To install :program:`PyQInt` systemwide, run:: 36 | 37 | sudo pip install pyqint 38 | 39 | or to install :program:`PyQInt` only for the current user, run:: 40 | 41 | pip install pyqint 42 | 43 | Troubleshooting 44 | --------------- 45 | 46 | The Anaconda packaging system can sometimes be quite finicky and sometimes 47 | packages conflict with each other. A way to work around this issue is to create 48 | a separate environment and only use that environment for the electronic 49 | resources associated with this project. 50 | 51 | To create the new environment (called eoesc-env), run:: 52 | 53 | conda create -n eoesc-env python=3.11 54 | 55 | Next, open the environment with:: 56 | 57 | conda activate eoesc-env 58 | 59 | and install the required packages:: 60 | 61 | conda install -c ifilot pyqint pytessel 62 | 63 | Finally, you can install the IDE Spyder using:: 64 | 65 | conda install spyder matplotlib scipy pandas openpyxl 66 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: pyqint 2 | dependencies: 3 | - numpy 4 | - scipy 5 | - conda-build 6 | - conda-verify 7 | - anaconda-client 8 | - tqdm 9 | -------------------------------------------------------------------------------- /examples/.gitignore: -------------------------------------------------------------------------------- 1 | *.npy 2 | *.jpg 3 | *.ply 4 | *.png 5 | *.txt 6 | -------------------------------------------------------------------------------- /examples/blender_render.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, PyQInt, FosterBoys, GeometryOptimization, HF 2 | from pyqint import MoleculeBuilder, BlenderRender 3 | import pyqint 4 | import numpy as np 5 | import matplotlib.pyplot as plt 6 | import os 7 | import subprocess 8 | 9 | # 10 | # Plot the isosurfaces for a number of molecules, prior and after 11 | # orbital localization. Note that this script has been designed to be executed 12 | # in a Linux Ubuntu 22.04 LTS (WSL) container. 13 | # 14 | 15 | outpath = os.path.dirname(__file__) 16 | 17 | def main(): 18 | # build_orbitals_co() 19 | # build_orbitals_ch4() 20 | # build_orbitals_ethylene() 21 | build_orbitals_h2o() 22 | 23 | def build_orbitals_co(): 24 | """ 25 | Build a montage image of the canonical and localized molecular orbitals 26 | of CO 27 | """ 28 | molname = 'CO' 29 | mol = Molecule('CO') 30 | mol.add_atom('C', 0, 0, -1.08232106) 31 | mol.add_atom('O', 0, 0, 1.08232106) 32 | res = HF().rhf(mol, 'sto3g') 33 | resfb = FosterBoys(res).run() 34 | 35 | build(molname, res, resfb, nrows=2) 36 | 37 | def build_orbitals_h2o(): 38 | """ 39 | Build a montage image of the canonical and localized molecular orbitals 40 | of H2O 41 | """ 42 | molname = 'H2O' 43 | mol = MoleculeBuilder().from_name('h2o') 44 | res = GeometryOptimization().run(mol, 'sto3g')['data'] 45 | resfb = FosterBoys(res).run() 46 | 47 | build(molname, res, resfb, nrows=1) 48 | 49 | def build_orbitals_ch4(): 50 | """ 51 | Build a montage image of the canonical and localized molecular orbitals 52 | of CH4 53 | """ 54 | molname = 'CH4' 55 | mol = MoleculeBuilder().from_name('ch4') 56 | res = GeometryOptimization().run(mol, 'sto3g')['data'] 57 | resfb = FosterBoys(res).run() 58 | 59 | build(molname, res, resfb, nrows=3) 60 | 61 | def build_orbitals_ethylene(): 62 | """ 63 | Build a montage image of the canonical and localized molecular orbitals 64 | of CH4 65 | """ 66 | molname = 'ethylene' 67 | mol = MoleculeBuilder().from_name('ethylene') 68 | res = GeometryOptimization().run(mol, 'sto3g')['data'] 69 | resfb = FosterBoys(res).run() 70 | 71 | build(molname, res, resfb, nrows=2) 72 | 73 | def build(molname, res, resfb, nrows=2): 74 | """ 75 | Build isosurfaces, montage and print energies to a file 76 | 77 | :param molname: Name of the molecule 78 | :type molname: string 79 | :param res: Results of a Hartree-Fock calculation 80 | :type res: dictionary 81 | :param resfb: Results of a Foster-Boys localization 82 | :type resfb: dictionary 83 | :param nrows: Number of rows in the montage 84 | :type nrows: int 85 | """ 86 | build_isosurfaces(molname, res, resfb) 87 | montage(molname, nrows) 88 | store_energies(os.path.join(os.path.dirname(__file__), 'MO_%s_energies.txt' % molname), res['orbe'], resfb['orbe']) 89 | 90 | def build_isosurfaces(molname, res, resfb): 91 | """ 92 | Builds isosurfaces. 93 | 94 | :param molname: Name of the molecule 95 | :type molname: string 96 | :param res: Result of a Hartree-Fock calculation 97 | :type res: dictionary 98 | :param resfb: Result of a Foster-Boys localization 99 | :type resfb: dictionary 100 | """ 101 | br = BlenderRender() 102 | br.render_molecular_orbitals(res['mol'], res['cgfs'], res['orbc'], outpath, 103 | prefix='MO_CAN_%s' % molname) 104 | 105 | br.render_molecular_orbitals(resfb['mol'], res['cgfs'], resfb['orbc'], outpath, 106 | prefix='MO_FB_%s' % molname) 107 | 108 | def montage(molname, nrows=2): 109 | """ 110 | Produce a montage of the images 111 | 112 | :param molname: Name of the molecule 113 | :type molname: string 114 | :param nrows: Number of rows in the montage 115 | :type nrows: int 116 | """ 117 | out = subprocess.check_output( 118 | ['montage', 'MO_CAN_%s_????.png' % molname, '-tile', 'x%i' % nrows, '-geometry', '128x128+2+2', 'MO_%s_CAN.png' % molname], 119 | cwd=os.path.dirname(__file__) 120 | ) 121 | 122 | out = subprocess.check_output( 123 | ['montage', 'MO_FB_%s_????.png' % molname, '-tile', 'x%i' % nrows, '-geometry', '128x128+2+2', 'MO_%s_FB.png' % molname], 124 | cwd=os.path.dirname(__file__) 125 | ) 126 | 127 | def store_energies(filename, orbe, orbe_fb): 128 | """ 129 | Stores energies. 130 | 131 | :param filename: Name of the molecule 132 | :type filename: string 133 | :param orbe: Array of the canonical molecular orbital energies 134 | :type orbe: list or numpy array 135 | :param orbe_fb: Array of the localized molecular orbital energies 136 | :type orbe_fb: list or numpy array 137 | """ 138 | f = open(filename, 'w') 139 | f.write('id localized canonical\n') 140 | f.write('----------------------------\n') 141 | for i in range(len(orbe)): 142 | f.write("%02i %12.4f %12.4f\n" % (i+1, orbe[i], orbe_fb[i])) 143 | f.close() 144 | 145 | if __name__ == '__main__': 146 | main() 147 | -------------------------------------------------------------------------------- /examples/cohp_co.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, HF 2 | import numpy as np 3 | import scipy.optimize 4 | 5 | # 6 | # Perform a (simplified) geometry optimization on the CO molecule and 7 | # calculate the COHP coefficients for the molecule. 8 | # 9 | 10 | def main(): 11 | # find optimum for CO molecule 12 | res = scipy.optimize.minimize(optimize_co, [1.14], tol=1e-4) 13 | print('Optimal distance found at d = %f' % res.x) 14 | 15 | # calculate sto-3g coefficients for h2o 16 | result = calculate_co(res.x) 17 | 18 | energies = result['orbe'] 19 | coeff = result['orbc'] 20 | H = result['fock'] 21 | 22 | cohp = np.zeros(len(coeff)) 23 | for k in range(0, len(coeff)): 24 | for i in range(0,len(coeff)//2): # loop over orbitals on C 25 | for j in range(len(coeff)//2,len(coeff)): # loop over orbitals on O 26 | cohp[k] += 2.0 * H[i,j] * coeff[i,k] * coeff[j,k] 27 | 28 | for i,(chi,e) in enumerate(zip(cohp, energies)): 29 | print('%02i %+8.4f %+8.4f' % (i+1, e, chi)) 30 | 31 | def optimize_co(d): 32 | """ 33 | Optimization function for scipy.optimize.minimize 34 | """ 35 | mol = Molecule() 36 | mol.add_atom('C', 0.0, 0.0, -d[0]/2, unit='angstrom') 37 | mol.add_atom('O', 0.0, 0.0, d[0]/2, unit='angstrom') 38 | 39 | result = HF().rhf(mol, 'sto3g') 40 | 41 | return result['energy'] 42 | 43 | def calculate_co(d): 44 | """ 45 | Full function for evaluation 46 | """ 47 | mol = Molecule() 48 | mol.add_atom('C', 0.0, 0.0, -d[0]/2, unit='angstrom') 49 | mol.add_atom('O', 0.0, 0.0, d[0]/2, unit='angstrom') 50 | 51 | result = HF().rhf(mol, 'sto3g') 52 | 53 | return result 54 | 55 | if __name__ == '__main__': 56 | main() -------------------------------------------------------------------------------- /examples/cohp_lih.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, HF 2 | import numpy as np 3 | import scipy.optimize 4 | import matplotlib.pyplot as plt 5 | from matplotlib.patches import Rectangle 6 | 7 | # 8 | # Perform a (simplified) geometry optimization on the CO molecule and 9 | # calculate the COHP coefficients for the molecule. 10 | # 11 | 12 | def main(): 13 | # find optimum for CO molecule 14 | res = scipy.optimize.minimize(optimize_lih, [1.14], tol=1e-4) 15 | print('Optimal distance found at d = %f' % res.x) 16 | 17 | # calculate sto-3g coefficients for h2o 18 | result = calculate_lih(res.x) 19 | 20 | energies = result['orbe'] 21 | coeff = result['orbc'] 22 | H = result['fock'] 23 | 24 | cohp = np.zeros(len(coeff)) 25 | for k in range(0, len(coeff)): # loop over molecular orbitals 26 | for i in range(0,len(coeff)-1): # loop over orbitals on Li 27 | for j in range(len(coeff)-1,len(coeff)): # loop over orbitals on H 28 | cohp[k] += 2.0 * H[i,j] * coeff[i,k] * coeff[j,k] 29 | 30 | for i,(chi,e) in enumerate(zip(cohp, energies)): 31 | print('%02i %+8.4f %+8.4f' % (i+1, e, chi)) 32 | 33 | # labels = [ 34 | # 'Li,1s', 35 | # 'Li,2s', 36 | # 'Li,2p$_{x}$', 37 | # 'Li,2p$_{y}$', 38 | # 'Li,2p$_{z}$', 39 | # 'H,1s' 40 | # ] 41 | 42 | # # fig, ax = plt.subplots(1,2, dpi=300) 43 | # # plot_matrix(ax[0], H, xlabels=labels, ylabels=labels, 44 | # # title='Hamiltonian matrix') 45 | # # plot_matrix(ax[1], coeff, ylabels=labels, title='Coefficient matrix', 46 | # # orbe=energies) 47 | 48 | def optimize_lih(d): 49 | """ 50 | Optimization function for scipy.optimize.minimize 51 | """ 52 | mol = Molecule() 53 | mol.add_atom('Li', 0.0, 0.0, -d[0]/2, unit='angstrom') 54 | mol.add_atom('H', 0.0, 0.0, d[0]/2, unit='angstrom') 55 | 56 | result = HF().rhf(mol, 'sto3g') 57 | 58 | return result['energy'] 59 | 60 | def calculate_lih(d): 61 | """ 62 | Full function for evaluation 63 | """ 64 | mol = Molecule() 65 | mol.add_atom('Li', 0.0, 0.0, -d[0]/2, unit='angstrom') 66 | mol.add_atom('H', 0.0, 0.0, d[0]/2, unit='angstrom') 67 | 68 | result = HF().rhf(mol, 'sto3g') 69 | 70 | return result 71 | 72 | def plot_matrix(ax, mat, xlabels=None, ylabels=None, title = None, orbe=None): 73 | """ 74 | Produce plot of matrix 75 | """ 76 | ax.imshow(mat, vmin=-1, vmax=1, cmap='PiYG') 77 | for i in range(mat.shape[0]): 78 | for j in range(mat.shape[1]): 79 | ax.text(i, j, '%.3f' % mat[j,i], ha='center', va='center', 80 | fontsize=7, zorder=5) 81 | ax.set_xticks([]) 82 | ax.set_yticks([]) 83 | ax.hlines(np.arange(1, mat.shape[0])-0.5, -0.5, mat.shape[0] - 0.5, 84 | color='black', linestyle='--', linewidth=1) 85 | ax.vlines(np.arange(1, mat.shape[0])-0.5, -0.5, mat.shape[0] - 0.5, 86 | color='black', linestyle='--', linewidth=1) 87 | 88 | # add xlabels if available 89 | if xlabels: 90 | ax.set_xticks(np.arange(0, mat.shape[0])) 91 | ax.set_xticklabels(ylabels) 92 | ax.tick_params(axis='both', which='major', labelsize=7) 93 | 94 | # add basis functions as axes labels 95 | if ylabels: 96 | ax.set_yticks(np.arange(0, mat.shape[0])) 97 | ax.set_yticklabels(ylabels) 98 | ax.tick_params(axis='both', which='major', labelsize=7) 99 | 100 | if orbe is not None: 101 | orbe = ['(%i) / %.4f' % ((i+1),e) for i,e in enumerate(orbe)] 102 | ax.set_xticks(np.arange(0, mat.shape[0])) 103 | ax.set_xticklabels(orbe, rotation=90) 104 | 105 | # add title if supplied 106 | if title: 107 | ax.set_title(title) 108 | 109 | if __name__ == '__main__': 110 | main() 111 | -------------------------------------------------------------------------------- /examples/foster_boys.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, PyQInt, FosterBoys, GeometryOptimization 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | # 6 | # Plot the isosurfaces for the CO molecule 7 | # 8 | 9 | def main(): 10 | res = optimize_co() 11 | resfb = FosterBoys(res).run() 12 | energies = resfb['orbe'] 13 | coeff = resfb['orbc'] 14 | cgfs = res['cgfs'] 15 | 16 | fig, ax = plt.subplots(2, 5, dpi=144, figsize=(12,5)) 17 | for i,(chi,e) in enumerate(zip(coeff.transpose(), energies)): 18 | res, x, y = build_contourplot(cgfs, chi, sz=3, plane='xz') 19 | vmax = np.max(np.abs(res)) 20 | vmin = -vmax 21 | ax[i//5,i%5].contourf(x, y, res, levels=15, cmap='PiYG', 22 | vmin=vmin, vmax=vmax) 23 | ax[i//5,i%5].contour(x, y, res, levels=15, colors='black', 24 | vmin=vmin, vmax=vmax) 25 | ax[i//5,i%5].set_xlabel('x [Bohr]') 26 | ax[i//5,i%5].set_ylabel('z [Bohr]') 27 | ax[i//5,i%5].set_title('Energy = %.4f Ht' % e) 28 | ax[i//5,i%5].grid(linestyle='--', color='black', alpha=0.5) 29 | 30 | plt.tight_layout() 31 | plt.savefig('co_fb.jpg') 32 | 33 | def optimize_co(): 34 | """ 35 | Optimization function for scipy.optimize.minimize 36 | """ 37 | mol = Molecule() 38 | mol.add_atom('C', 0.0, 0.0, -0.6, unit='angstrom') 39 | mol.add_atom('O', 0.0, 0.0, 0.6, unit='angstrom') 40 | 41 | res = GeometryOptimization().run(mol, 'sto3g') 42 | 43 | print(res['data']['nuclei']) 44 | 45 | return res['data'] 46 | 47 | def build_contourplot(cgfs, coeff, sz=2, npts=50, plane='xy'): 48 | integrator = PyQInt() 49 | 50 | # build grid 51 | x = np.linspace(-sz, sz, 50) 52 | y = np.linspace(-sz, sz, 50) 53 | xx, yy = np.meshgrid(x,y) 54 | zz = np.zeros(len(x) * len(y)) 55 | 56 | if plane == 'xy': 57 | points = [xx.flatten(), yy.flatten(), zz] 58 | elif plane == 'xz': 59 | points = [xx.flatten(), zz, yy.flatten()] 60 | elif plane == 'yz': 61 | points = [zz, xx.flatten(), yy.flatten()] 62 | else: 63 | raise Exception('Unknown plane: %s' % plane) 64 | 65 | grid = np.vstack(points).reshape(3,-1).T 66 | res = integrator.plot_wavefunction(grid, coeff, cgfs).reshape((len(y), len(x))) 67 | 68 | return res, x, y 69 | 70 | if __name__ == '__main__': 71 | main() 72 | -------------------------------------------------------------------------------- /examples/foster_boys_quick.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, HF, COHP, FosterBoys 2 | import numpy as np 3 | 4 | d = 1.145414 5 | mol = Molecule() 6 | mol.add_atom('C', 0.0, 0.0, -d/2, unit='angstrom') 7 | mol.add_atom('O', 0.0, 0.0, d/2, unit='angstrom') 8 | 9 | res = HF().rhf(mol, 'sto3g') 10 | cohp = COHP(res).run(res['orbc'], 0, 1) 11 | 12 | resfb = FosterBoys(res).run(nr_runners=10) 13 | cohp_fb = COHP(res).run(resfb['orbc'], 0, 1) 14 | 15 | print('COHP values of canonical Hartree-Fock orbitals') 16 | for i,(e,chi) in enumerate(zip(res['orbe'], cohp)): 17 | print('%3i %12.4f %12.4f' % (i+1,e,chi)) 18 | print() 19 | 20 | print('COHP values after Foster-Boys localization') 21 | for i,(e,chi) in enumerate(zip(resfb['orbe'], cohp_fb)): 22 | print('%3i %12.4f %12.4f' % (i+1,e,chi)) 23 | print() 24 | 25 | print('Sum of COHP coefficient canonical orbitals: ', np.sum(cohp[:7])) 26 | print('Sum of COHP coefficient Foster-Boys orbitals: ', np.sum(cohp_fb[:7])) 27 | print('Result FB: ', resfb['r2start'], resfb['r2final']) 28 | -------------------------------------------------------------------------------- /examples/molecular_orbitals_co.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, HF, PyQInt, GeometryOptimization 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | # 6 | # Plot the isosurfaces for the CO molecule 7 | # 8 | 9 | def main(): 10 | # calculate sto-3g coefficients for co 11 | result = optimize_co() 12 | energies = result['orbe'] 13 | coeff = result['orbc'] 14 | 15 | fig, ax = plt.subplots(2, 5, dpi=144, figsize=(12,5)) 16 | for i,(chi,e) in enumerate(zip(coeff.transpose(), energies)): 17 | res, x, y = build_contourplot(result['cgfs'], chi, sz=3, plane='xz') 18 | vmax = np.max(np.abs(res)) 19 | vmin = -vmax 20 | ax[i//5,i%5].contourf(x, y, res, levels=15, cmap='PiYG', 21 | vmin=vmin, vmax=vmax) 22 | ax[i//5,i%5].contour(x, y, res, levels=15, colors='black', 23 | vmin=vmin, vmax=vmax) 24 | ax[i//5,i%5].set_xlabel('x [Bohr]') 25 | ax[i//5,i%5].set_ylabel('z [Bohr]') 26 | ax[i//5,i%5].set_title('Energy = %.4f Ht' % e) 27 | ax[i//5,i%5].grid(linestyle='--', color='black', alpha=0.5) 28 | 29 | plt.tight_layout() 30 | plt.savefig('co.jpg') 31 | 32 | def optimize_co(): 33 | """ 34 | Optimization function for scipy.optimize.minimize 35 | """ 36 | mol = Molecule() 37 | mol.add_atom('C', 0.0, 0.0, -0.6, unit='angstrom') 38 | mol.add_atom('O', 0.0, 0.0, 0.6, unit='angstrom') 39 | 40 | res = GeometryOptimization().run(mol, 'sto3g') 41 | 42 | return res['data'] 43 | 44 | def build_contourplot(cgfs, coeff, sz=2, npts=50, plane='xy'): 45 | integrator = PyQInt() 46 | 47 | # build grid 48 | x = np.linspace(-sz, sz, 50) 49 | y = np.linspace(-sz, sz, 50) 50 | xx, yy = np.meshgrid(x,y) 51 | zz = np.zeros(len(x) * len(y)) 52 | 53 | if plane == 'xy': 54 | points = [xx.flatten(), yy.flatten(), zz] 55 | elif plane == 'xz': 56 | points = [xx.flatten(), zz, yy.flatten()] 57 | elif plane == 'yz': 58 | points = [zz, xx.flatten(), yy.flatten()] 59 | else: 60 | raise Exception('Unknown plane: %s' % plane) 61 | 62 | grid = np.vstack(points).reshape(3,-1).T 63 | res = integrator.plot_wavefunction(grid, coeff, cgfs).reshape((len(y), len(x))) 64 | 65 | return res, x, y 66 | 67 | if __name__ == '__main__': 68 | main() 69 | -------------------------------------------------------------------------------- /examples/molecular_orbitals_hco.py: -------------------------------------------------------------------------------- 1 | from pyqint import Molecule, HF, PyQInt, GeometryOptimization 2 | import numpy as np 3 | import matplotlib.pyplot as plt 4 | 5 | # 6 | # Plot the isosurfaces for the CO molecule 7 | # 8 | 9 | def main(): 10 | # calculate sto-3g coefficients for co 11 | result = optimize_hco() 12 | energies = result['orbe'] 13 | coeff = result['orbc'] 14 | 15 | fig, ax = plt.subplots(2, 5, dpi=144, figsize=(12,5)) 16 | for i,(chi,e) in enumerate(zip(coeff.transpose(), energies)): 17 | 18 | if i >= 10: 19 | break 20 | 21 | res, x, y = build_contourplot(result['cgfs'], chi, sz=3, plane='xz') 22 | vmax = np.max(np.abs(res)) 23 | vmin = -vmax 24 | ax[i//5,i%5].contourf(x, y, res, levels=15, cmap='PiYG', 25 | vmin=vmin, vmax=vmax) 26 | ax[i//5,i%5].contour(x, y, res, levels=15, colors='black', 27 | vmin=vmin, vmax=vmax) 28 | ax[i//5,i%5].set_xlabel('x [Bohr]') 29 | ax[i//5,i%5].set_ylabel('z [Bohr]') 30 | ax[i//5,i%5].set_title('Energy = %.4f Ht' % e) 31 | ax[i//5,i%5].grid(linestyle='--', color='black', alpha=0.5) 32 | 33 | plt.tight_layout() 34 | plt.savefig('hco.jpg') 35 | 36 | def optimize_hco(): 37 | """ 38 | Optimization function for scipy.optimize.minimize 39 | """ 40 | mol = Molecule() 41 | mol.add_atom('O', -0.12770367, -0.00000000, 1.23419284) 42 | mol.add_atom('C', -0.50625665, 0.00000000, -1.09149431) 43 | mol.add_atom('H', 1.57882331, -0.00000000, -2.06681794) 44 | mol.set_charge(-1) 45 | 46 | res = GeometryOptimization().run(mol, 'p321') 47 | 48 | return res['data'] 49 | 50 | def build_contourplot(cgfs, coeff, sz=2, npts=50, plane='xy'): 51 | integrator = PyQInt() 52 | 53 | # build grid 54 | x = np.linspace(-sz, sz, 50) 55 | y = np.linspace(-sz, sz, 50) 56 | xx, yy = np.meshgrid(x,y) 57 | zz = np.zeros(len(x) * len(y)) 58 | 59 | if plane == 'xy': 60 | points = [xx.flatten(), yy.flatten(), zz] 61 | elif plane == 'xz': 62 | points = [xx.flatten(), zz, yy.flatten()] 63 | elif plane == 'yz': 64 | points = [zz, xx.flatten(), yy.flatten()] 65 | else: 66 | raise Exception('Unknown plane: %s' % plane) 67 | 68 | grid = np.vstack(points).reshape(3,-1).T 69 | res = integrator.plot_wavefunction(grid, coeff, cgfs).reshape((len(y), len(x))) 70 | 71 | return res, x, y 72 | 73 | if __name__ == '__main__': 74 | main() -------------------------------------------------------------------------------- /img/co.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/img/co.jpg -------------------------------------------------------------------------------- /img/mo_h2o_1b2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/img/mo_h2o_1b2.png -------------------------------------------------------------------------------- /meta.yaml: -------------------------------------------------------------------------------- 1 | package: 2 | name: "pyqint" 3 | version: "1.0.0" 4 | 5 | source: 6 | path: . 7 | 8 | requirements: 9 | build: 10 | - numpy 11 | - scipy 12 | - python {{ python }} 13 | 14 | host: 15 | - pip 16 | - python 17 | - setuptools 18 | - cython 19 | - numpy 20 | - scipy 21 | 22 | run: 23 | - python 24 | - numpy 25 | - scipy 26 | - tqdm 27 | 28 | test: 29 | requires: 30 | - numpy 31 | - scipy 32 | - setuptools 33 | - pytest 34 | source_files: 35 | - tests/*.py 36 | - tests/results/*.npy 37 | - tests/results/*.txt 38 | - tests/results/*.xyz 39 | commands: 40 | - pytest 41 | 42 | about: 43 | home: https://github.com/ifilot/pyqint 44 | license: GPL3 45 | license_family: GPL 46 | summary: Python package for evaluating integrals of Gaussian type orbitals in electronic structure calculations 47 | description: See the package README.md for more information. 48 | -------------------------------------------------------------------------------- /paper/.gitignore: -------------------------------------------------------------------------------- 1 | paper.jats 2 | paper.pdf 3 | <<<<<<< HEAD 4 | ======= 5 | jats/ 6 | >>>>>>> 7c9267227a66a7ad95712d3a19d40134857f93dd 7 | -------------------------------------------------------------------------------- /paper/compile.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Perform Docker compilation under Windows using Git BASH 4 | 5 | # provide the path to the location where the paper is stored; note that under 6 | # Windows Git Bash, two slashes need to be used instead of one 7 | PAPERPATH="$PWD" 8 | 9 | docker run --rm \ 10 | --volume $PAPERPATH:/data \ 11 | --user $(id -u):$(id -g) \ 12 | --env JOURNAL=jose \ 13 | openjournals/inara 14 | -------------------------------------------------------------------------------- /paper/img/co-coefficient-matrix.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/paper/img/co-coefficient-matrix.jpg -------------------------------------------------------------------------------- /paper/img/orbitals-co-contour.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/paper/img/orbitals-co-contour.jpg -------------------------------------------------------------------------------- /paper/img/orbitals-co-isosurface.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/paper/img/orbitals-co-isosurface.jpg -------------------------------------------------------------------------------- /paper/img/orbitals-co-localized.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/paper/img/orbitals-co-localized.jpg -------------------------------------------------------------------------------- /paper/paper.bib: -------------------------------------------------------------------------------- 1 | @article{roothaan:1951, 2 | title = {New Developments in Molecular Orbital Theory}, 3 | author = {Roothaan, C. C. J.}, 4 | journal = {Rev. Mod. Phys.}, 5 | volume = {23}, 6 | issue = {2}, 7 | pages = {69--89}, 8 | numpages = {0}, 9 | year = {1951}, 10 | month = {Apr}, 11 | publisher = {American Physical Society}, 12 | doi = {10.1103/RevModPhys.23.69}, 13 | url = {https://link.aps.org/doi/10.1103/RevModPhys.23.69} 14 | } 15 | 16 | @article{pople:1995, 17 | author = {Pople, John A. and Gill, Peter M. W. and Handy, Nicholas C.}, 18 | title = {Spin-unrestricted character of Kohn-Sham orbitals for open-shell systems}, 19 | journal = {International Journal of Quantum Chemistry}, 20 | volume = {56}, 21 | number = {4}, 22 | pages = {303-305}, 23 | doi = {10.1002/qua.560560414}, 24 | url = {https://onlinelibrary.wiley.com/doi/abs/10.1002/qua.560560414}, 25 | eprint = {https://onlinelibrary.wiley.com/doi/pdf/10.1002/qua.560560414}, 26 | year = {1995} 27 | } 28 | 29 | @article{pulay:1980, 30 | title = {Convergence acceleration of iterative sequences. the case of scf iteration}, 31 | journal = {Chemical Physics Letters}, 32 | volume = {73}, 33 | number = {2}, 34 | pages = {393-398}, 35 | year = {1980}, 36 | issn = {0009-2614}, 37 | doi = {10.1016/0009-2614(80)80396-4}, 38 | url = {https://www.sciencedirect.com/science/article/pii/0009261480803964}, 39 | author = {Péter Pulay}, 40 | } 41 | 42 | @article{huzinaga:1966, 43 | author = {Taketa ,Hiroshi and Huzinaga ,Sigeru and O-ohata ,Kiyosi}, 44 | title = {Gaussian-Expansion Methods for Molecular Integrals}, 45 | journal = {Journal of the Physical Society of Japan}, 46 | volume = {21}, 47 | number = {11}, 48 | pages = {2313-2324}, 49 | year = {1966}, 50 | doi = {10.1143/JPSJ.21.2313}, 51 | URL = {https://doi.org/10.1143/JPSJ.21.2313} 52 | } 53 | 54 | @article{dronskowski:1993, 55 | author = {Dronskowski, Richard and Bloechl, Peter E.}, 56 | title = {Crystal orbital Hamilton populations (COHP): energy-resolved visualization of chemical bonding in solids based on density-functional calculations}, 57 | journal = {The Journal of Physical Chemistry}, 58 | volume = {97}, 59 | number = {33}, 60 | pages = {8617-8624}, 61 | year = {1993}, 62 | doi = {10.1021/j100135a014}, 63 | } 64 | 65 | @article{stefani:2009, 66 | author = {Stefani, Christina and Tsaparlis, Georgios}, 67 | year = {2009}, 68 | month = {05}, 69 | pages = {520 - 536}, 70 | title = {Students' Levels of Explanations, Models, and Misconceptions in Basic Quantum Chemistry: A Phenomenographic Study}, 71 | volume = {46}, 72 | journal = {Journal of Research in Science Teaching}, 73 | doi = {10.1002/tea.20279} 74 | } 75 | 76 | @article{hulyadi:2023, 77 | author = {Hulyadi, Hulyadi and Muhali, Muhali and Gargazi, Gargazi}, 78 | year = {2023}, 79 | month = {12}, 80 | pages = {11207-11217}, 81 | title = {Reducing Student Misconceptions Through Problem-Based Learning with a Computational Chemistry-Assisted Question Map Approach}, 82 | volume = {09}, 83 | journal = {Jurnal Penelitian Pendidikan IPA}, 84 | doi = {10.29303/jppipa.v9i12.5936} 85 | } 86 | 87 | @article{becke:1993, 88 | author = {Becke, Axel D.}, 89 | title = {Density‐functional thermochemistry. III. The role of exact exchange}, 90 | journal = {The Journal of Chemical Physics}, 91 | volume = {98}, 92 | number = {7}, 93 | pages = {5648-5652}, 94 | year = {1993}, 95 | month = {04}, 96 | issn = {0021-9606}, 97 | doi = {10.1063/1.464913}, 98 | } 99 | 100 | @article{lee:1988, 101 | title = {Development of the Colle-Salvetti correlation-energy formula into a functional of the electron density}, 102 | author = {Lee, Chengteh and Yang, Weitao and Parr, Robert G.}, 103 | journal = {Phys. Rev. B}, 104 | volume = {37}, 105 | issue = {2}, 106 | pages = {785--789}, 107 | numpages = {0}, 108 | year = {1988}, 109 | month = {Jan}, 110 | publisher = {American Physical Society}, 111 | doi = {10.1103/PhysRevB.37.785}, 112 | url = {https://link.aps.org/doi/10.1103/PhysRevB.37.785} 113 | } 114 | 115 | @article{sousa:2007, 116 | author = {Sousa, S{\'e}rgio Filipe and Fernandes, Pedro Alexandrino and Ramos, Maria João}, 117 | title = {General Performance of Density Functionals}, 118 | journal = {The Journal of Physical Chemistry A}, 119 | volume = {111}, 120 | number = {42}, 121 | pages = {10439-10452}, 122 | year = {2007}, 123 | doi = {10.1021/jp0734474}, 124 | URL = {https://doi.org/10.1021/jp0734474}, 125 | } 126 | 127 | @article{boys:1960, 128 | title = {Construction of Some Molecular Orbitals to Be Approximately Invariant for Changes from One Molecule to Another}, 129 | author = {Boys, S. F.}, 130 | journal = {Rev. Mod. Phys.}, 131 | volume = {32}, 132 | issue = {2}, 133 | pages = {296--299}, 134 | numpages = {0}, 135 | year = {1960}, 136 | month = {Apr}, 137 | publisher = {American Physical Society}, 138 | doi = {10.1103/RevModPhys.32.296}, 139 | url = {https://link.aps.org/doi/10.1103/RevModPhys.32.296} 140 | } 141 | 142 | @article{gordon:2020, 143 | author = {Gordon, Mark S. and Windus, Theresa L.}, 144 | title = {Editorial: Modern Architectures and Their Impact on Electronic Structure Theory}, 145 | journal = {Chemical Reviews}, 146 | volume = {120}, 147 | number = {17}, 148 | pages = {9015-9020}, 149 | year = {2020}, 150 | doi = {10.1021/acs.chemrev.0c00700}, 151 | URL = {https://doi.org/10.1021/acs.chemrev.0c00700}, 152 | } 153 | 154 | @book{szabo, 155 | author = {Attila Szabo and Neil S. Ostlund}, 156 | year = {1996}, 157 | title = {Modern Quantum Chemistry: Introduction to Advanced Electronic Structure Theory}, 158 | publisher = {Dover}, 159 | address = {Mineola, New York} 160 | } 161 | 162 | @online{eoesbook, 163 | author = {Ivo A.W. Filot}, 164 | title = {Elements of Electronic Structure Theory}, 165 | year = {2025}, 166 | url = {https://ifilot.pages.tue.nl/elements-of-electronic-structure-theory/index.html}, 167 | urldate = {2025-04-24} 168 | } 169 | 170 | @Article{hunter:2007, 171 | Author = {Hunter, J. D.}, 172 | Title = {Matplotlib: A 2D graphics environment}, 173 | Journal = {Computing in Science \& Engineering}, 174 | Volume = {9}, 175 | Number = {3}, 176 | Pages = {90--95}, 177 | abstract = {Matplotlib is a 2D graphics package used for Python for 178 | application development, interactive scripting, and publication-quality 179 | image generation across user interfaces and operating systems.}, 180 | publisher = {IEEE COMPUTER SOC}, 181 | doi = {10.1109/MCSE.2007.55}, 182 | year = 2007 183 | } 184 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel", 5 | "Cython", 6 | "numpy", 7 | ] 8 | build-backend = "setuptools.build_meta" 9 | 10 | [tool.cibuildwheel] 11 | test-requires = "pytest" 12 | test-command = "pytest {project}/tests" 13 | 14 | # skip PyPy wheels and 32 bit builds 15 | skip = ["pp*", "*-win32", "*-manylinux_i686", "cp*-musllinux_*", "cp36-*", "cp37-*"] 16 | -------------------------------------------------------------------------------- /pyqint/__init__.py: -------------------------------------------------------------------------------- 1 | from .molecule import Molecule 2 | from .pyqint import PyQInt 3 | from .cgf import cgf 4 | from .gto import gto 5 | from .hf import HF 6 | from .foster_boys import FosterBoys 7 | from .cohp import COHP 8 | from .molecule_builder import MoleculeBuilder 9 | from .geometry_optimization import GeometryOptimization 10 | from .blenderrender import BlenderRender 11 | 12 | from ._version import __version__ 13 | -------------------------------------------------------------------------------- /pyqint/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "1.0.0" 2 | 3 | -------------------------------------------------------------------------------- /pyqint/cgf.py: -------------------------------------------------------------------------------- 1 | from .gto import gto 2 | from .pyqint import PyCGF 3 | from . import spherical_harmonics as sh 4 | 5 | class cgf: 6 | """ 7 | Contracted Gaussian Type Orbital 8 | """ 9 | def __init__(self, _p=[0,0,0]): 10 | """ 11 | Default constructor 12 | """ 13 | self.gtos = [] 14 | self.p = _p 15 | self.cgf = PyCGF(self.p) 16 | 17 | def __setstate__(self, d): 18 | """ 19 | Set the state using tuple d 20 | 21 | Rebuilds the PyCGF C++ class and 22 | adds gto object to the class 23 | """ 24 | self.p = d[0] 25 | self.gtos = d[1] 26 | self.cgf = PyCGF(self.p) 27 | for gto in self.gtos: 28 | self.cgf.add_gto(gto.c, gto.alpha, gto.l, gto.m, gto.n) 29 | 30 | def __reduce__(self): 31 | """ 32 | Used to pickle the class 33 | """ 34 | return (self.__class__, tuple([self.p]), (self.p, self.gtos)) 35 | 36 | def __str__(self): 37 | """ 38 | Get string representation of the Contracted Gaussian Functional 39 | """ 40 | res = "CGF; R=(%f,%f,%f)\n" % tuple(self.p) 41 | for i,gto in enumerate(self.gtos): 42 | res += " %02i | %s" % (i+1, str(gto)) 43 | return res 44 | 45 | def add_gto(self, c, alpha, l, m, n): 46 | """ 47 | Add Gaussian Type Orbital to Contracted Gaussian Function 48 | """ 49 | self.gtos.append(gto(c, self.p, alpha, l, m, n)) 50 | self.cgf.add_gto(c, alpha, l, m, n) 51 | 52 | def add_spherical_gto(self, c, alpha, l, m): 53 | """ 54 | Add Spherical Gaussian Type Orbital to Contracted Gaussian Function. 55 | l and m are the coefficients of the requested spherical harmonic function. 56 | l must be <= 6 and -l <= m <= l. 57 | """ 58 | if not l <= 6 or not abs(m) <=l: 59 | raise ValueError("l must be <= 6 and -l <= m <= l") 60 | for gto in sh.spherical_harmonics[l][m]: 61 | self.add_gto(gto[0] * c, alpha, gto[1][0], gto[1][1], gto[1][2]) 62 | 63 | def get_amp_f(self, x, y, z): 64 | """ 65 | Get the amplitude of the wave function at position r 66 | """ 67 | return self.cgf.get_amp_f(x, y, z) 68 | 69 | def get_amp(self, r): 70 | """ 71 | Get the amplitude of the wave function at position r 72 | """ 73 | return self.cgf.get_amp_f(r[0], r[1], r[2]) 74 | 75 | def get_grad_f(self, x, y, z): 76 | """ 77 | Get the gradient (3-vector) of the wave function at position r 78 | """ 79 | return self.cgf.get_grad_f(x, y, z) 80 | 81 | def get_grad(self, r): 82 | """ 83 | Get the gradient (3-vector) of the wave function at position r 84 | """ 85 | return self.cgf.get_grad_f(r[0], r[1], r[2]) 86 | -------------------------------------------------------------------------------- /pyqint/cohp.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from .pyqint import PyQInt 4 | import numpy as np 5 | 6 | class COHP: 7 | """ 8 | Class for performing Crystal Orbital Hamilton Population Analysis 9 | """ 10 | def __init__(self, res): 11 | # copy objects from Hartree-Fock result dictionary 12 | self.orbc_canonical = res['orbc'] 13 | self.orbe_canonical = res['orbe'] 14 | self.nelec = res['nelec'] 15 | self.nuclei = res['nuclei'] 16 | self.H = res['fock'] 17 | self.cgfs = res['cgfs'] 18 | self.maxiter = 1000 19 | self.occ = [1 if i < self.nelec//2 else 0 for i in range(0, len(self.cgfs))] 20 | 21 | def run(self, C, n1, n2): 22 | """ 23 | Run the COHP algorithm, take a coefficient matrix as input 24 | 25 | C: coefficient matrix 26 | n1: index of nucleus 1 27 | n2: index of nucleus 2 28 | """ 29 | if n1 == n2: 30 | raise Exception('Cannot perform COHP for the same atoms') 31 | 32 | # figure out which basis functions below to which nucleus 33 | cgfs1 = [] 34 | cgfs2 = [] 35 | nuc1 = self.nuclei[n1][0] 36 | nuc2 = self.nuclei[n2][0] 37 | for i,cgf in enumerate(self.cgfs): 38 | if np.linalg.norm(cgf.p - nuc1) < 1e-3: 39 | cgfs1.append(i) 40 | if np.linalg.norm(cgf.p - nuc2) < 1e-3: 41 | cgfs2.append(i) 42 | 43 | N = len(C) 44 | cohp = np.zeros(N) 45 | for k in range(0, N): # loop over molecular orbitals 46 | for i in cgfs1: # loop over atom A 47 | for j in cgfs2: # loop over atom B 48 | cohp[k] += 2.0 * self.H[i,j] * C[i,k] * C[j,k] 49 | 50 | return cohp 51 | -------------------------------------------------------------------------------- /pyqint/factorials.h: -------------------------------------------------------------------------------- 1 | #ifndef _FACTORIALS_H 2 | #define _FACTORIALS_H 3 | 4 | static double factorial(size_t n) { 5 | static const double ans[] = { 6 | 1,1,2,6,24,120,720,5040,40320,362880,3628800,39916800,479001600,6227020800,87178291200,1307674368000 7 | }; 8 | 9 | if(n > 15) { 10 | return n * factorial(n-1); 11 | } else { 12 | return ans[n]; 13 | } 14 | } 15 | 16 | static double double_factorial(size_t n) { 17 | static const double ans[] = { 18 | 1,1,2,3,8,15,48,105,384,945,3840,10395,46080,135135,645120,2027025 19 | }; 20 | 21 | if(n > 15) { 22 | return n * double_factorial(n-2); 23 | } else { 24 | return ans[n]; 25 | } 26 | } 27 | 28 | #endif -------------------------------------------------------------------------------- /pyqint/gamma.h: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * This file is part of PyQInt. * 3 | * * 4 | * Author: Ivo Filot * 5 | * * 6 | * PyQInt is free software: * 7 | * you can redistribute it and/or modify it under the terms of the * 8 | * GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the License, or (at your option) * 10 | * any later version. * 11 | * * 12 | * PyQInt is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 | * See the GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see http://www.gnu.org/licenses/. * 19 | * * 20 | **************************************************************************/ 21 | 22 | #pragma once 23 | 24 | /* 25 | * Incomplete Gamma Function 26 | * 27 | * Used in the evaluation of the two-electron integrals. 28 | * 29 | */ 30 | 31 | /* 32 | * The functions below were extracted from: 33 | * 34 | * Numerical Recipes 35 | * William H. Press, Saul A. Teukolsky, William T., 36 | * Vetterling and Brian P. Flannery 37 | * 3rd edition page 261 38 | * ISBN-13: 978-0521880688 39 | */ 40 | 41 | #include 42 | #include 43 | #include 44 | 45 | class GammaInc { 46 | public: 47 | double Fgamma(double m, double x) const; 48 | 49 | /* 50 | * @fn gamm_inc 51 | * @brief Calculates the incomplete gamma function P(a,x) 52 | * 53 | * gamma(a,x) 54 | * ---------- 55 | * G(a) 56 | * 57 | * @param a "squared width" of the IGF 58 | * @param x Upper bound of the integral in the IGF 59 | * 60 | * returns double value of the incomplete Gamma Function 61 | */ 62 | double gamm_inc(double a, double x) const; 63 | 64 | /* 65 | * @fn gamm_inc 66 | * @brief Calculates the incomplete gamma function P(a,x) 67 | * 68 | * This routine selects the best function to use in the 69 | * evaluation of the Incomplete Gamma Function (IGF). 70 | * 71 | * @param a "squared width" of the IGF 72 | * @param x Upper bound of the integral in the IGF 73 | * 74 | * returns double value of the incomplete Gamma Function 75 | */ 76 | double gammp(double m, double x) const; 77 | 78 | private: 79 | /* 80 | * @fn gser 81 | * @brief Gamma function P(a,x) evaluated by its series representation 82 | * 83 | * @param a "squared width" of the IGF 84 | * @param x Upper bound of the integral in the IGF 85 | * 86 | * returns double value of the incomplete Gamma Function 87 | */ 88 | double gser(double a, double x) const; 89 | 90 | 91 | double gammln(double xx) const; 92 | 93 | /* 94 | * @fn gcf 95 | * @brief Gamma function P(a,x) evaluated by its continued fraction representation 96 | * 97 | * @param a "squared width" of the IGF 98 | * @param x Upper bound of the integral in the IGF 99 | * 100 | * returns double value of the incomplete Gamma Function 101 | */ 102 | double gcf(double a, double x) const; 103 | 104 | /* 105 | * @fn gammpapprox 106 | * @brief Incomplete Gamma function P(a,x) or Q(a,x) evaluated by quadrature 107 | * 108 | * Returns P(a,x) or Q(a,x), when psig is 1 or 0, respectively. 109 | * 110 | * @param a "squared width" of the IGF 111 | * @param x Upper bound of the integral in the IGF 112 | * @param psig Whether to evaluate P(a,x) or Q(a,x) 113 | * 114 | * returns double value of the incomplete Gamma Function 115 | */ 116 | double gammpapprox(double a, double x, int psig) const; 117 | }; 118 | -------------------------------------------------------------------------------- /pyqint/geometry_optimization.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | from . import HF, Molecule 4 | import numpy as np 5 | import scipy.optimize 6 | 7 | class GeometryOptimization: 8 | """ 9 | Class to perform geometry optimizaton using the Conjugate Gradient algorithm 10 | as implemented in the scipy library 11 | """ 12 | def __init__(self, verbose=False): 13 | self.cinit = None 14 | self.P = None 15 | self.orbe = None 16 | self.verbose = verbose 17 | self.iter = 0 18 | self.energies_history = [] 19 | self.forces_history = [] 20 | self.coordinates_history = [] 21 | self.coord = None 22 | 23 | def run(self, mol, basis, gtol=1e-5): 24 | """ 25 | Perform geometry optimization 26 | """ 27 | x0 = self.__unpack(mol) # unpack coordinates from molecule class 28 | self.iter = 0 29 | 30 | # reset arrays 31 | self.energies_history = [] 32 | self.forces_history = [] 33 | self.coordinates_history = [] 34 | 35 | if self.verbose: 36 | self.__print_break(newline=False) 37 | print("START GEOMETRY OPTIMIZATION") 38 | print("USING CONJUGATE GRADIENT PROCEDURE") 39 | self.__print_break(newline=True) 40 | 41 | res_opt = scipy.optimize.minimize(self.energy, x0, args=(mol, basis), method='CG', 42 | jac=self.jacobian, options={'gtol':gtol}) 43 | 44 | res = { 45 | 'opt': res_opt, 46 | 'energies': self.energies_history, 47 | 'forces': self.forces_history, 48 | 'coordinates': self.coordinates_history, 49 | 'data': self.last_energy_run 50 | } 51 | 52 | return res 53 | 54 | def energy(self, x, mol, basis): 55 | self.iter += 1 56 | 57 | mol = self.__pack(mol, x) # pack positions into new molecule class 58 | 59 | if self.verbose: 60 | self.__print_break(newline=False) 61 | print(' START GEOMETRY OPTIMIZATION STEP %03i' % self.iter) 62 | self.__print_break() 63 | self.__print_positions(mol) 64 | print() # newline 65 | 66 | res = HF().rhf(mol, basis, orbc_init=self.cinit, calc_forces=True) 67 | 68 | if self.verbose: 69 | self.__print_energies(res) 70 | print() # print newline 71 | 72 | # cache matrices 73 | self.cinit = res['orbc'] 74 | self.P = res['density'] 75 | self.orbe = res['orbe'] 76 | self.forces = res['forces'] 77 | self.coord = x 78 | self.last_energy_run = res 79 | 80 | # append history 81 | self.forces_history.append(self.forces) 82 | self.coordinates_history.append(self.coord.reshape(len(mol.get_atoms()),3)) 83 | self.energies_history.append(res['energies'][-1]) 84 | 85 | if self.verbose: 86 | self.__print_forces(mol, self.forces) 87 | print() # print newline 88 | self.__print_break(newline=False) 89 | print(' END GEOMETRY OPTIMIZATION STEP %03i' % self.iter) 90 | self.__print_break(newline=True) 91 | 92 | return res['energies'][-1] 93 | 94 | def jacobian(self, x, mol, basis): 95 | """ 96 | Calculate the forces. Note that the forces are typically already 97 | calculated in the energy step so here only a simple check is done 98 | to see if the coordinates match. If so, the forces are simply returned. 99 | """ 100 | # check if forces are already calculated 101 | if self.coord is not None and np.max(x - self.coord) < 1e-5: 102 | return self.forces.flatten() 103 | else: 104 | # no forces have yet been calculated, indicative that no energy run has 105 | # yet been done 106 | res = HF().rhf(mol, basis, orbc_init=self.cinit, calc_forces=True) 107 | self.cinit = res['orbc'] 108 | self.P = res['density'] 109 | self.orbe = res['orbe'] 110 | self.forces = res['forces'] 111 | self.coord = x 112 | return res['forces'].flatten() 113 | 114 | def __unpack(self, mol): 115 | """ 116 | Unpack coordinates from molecule class 117 | """ 118 | coords = [] 119 | for atom in mol.get_atoms(): 120 | coords.append(atom[1]) 121 | 122 | return np.array(coords).flatten() 123 | 124 | def __pack(self, mol, coords): 125 | """ 126 | Pack coordinates into new molecule class 127 | """ 128 | 129 | newmol = Molecule(mol.name) 130 | newmol.set_charge(mol.get_charge()) 131 | coords = coords.reshape((len(mol.get_atoms()), 3)) 132 | for i,atom in enumerate(mol.get_atoms()): 133 | newmol.add_atom(mol.get_atoms()[i][0], coords[i][0], coords[i][1], coords[i][2]) 134 | 135 | return newmol 136 | 137 | def __print_positions(self, mol): 138 | """ 139 | Print atomic positions in nice formatting 140 | """ 141 | print('-------------') 142 | print(' POSITIONS ') 143 | print('-------------') 144 | for atom in mol.get_atoms(): 145 | print(' %2s %12.8f %12.8f %12.8f' % (atom[0], 146 | atom[1][0], 147 | atom[1][1], 148 | atom[1][2])) 149 | 150 | def __print_forces(self, mol, forces): 151 | """ 152 | Print forces using nice formatting 153 | """ 154 | print('----------') 155 | print(' FORCES ') 156 | print('----------') 157 | 158 | for atom,force in zip(mol.get_atoms(), forces): 159 | print(' %2s %12.4e %12.4e %12.4e' % (atom[0], 160 | force[0], 161 | force[1], 162 | force[2])) 163 | 164 | def __print_energies(self, res): 165 | """ 166 | Print the energy terms in each iteration 167 | """ 168 | print('------------') 169 | print(' ENERGIES ') 170 | print('------------') 171 | 172 | print(' Kinetic: %12.8f' % res['ekin']) 173 | print(' Nuclear: %12.8f' % res['enuc']) 174 | print(' Electron-electron repulsion: %12.8f' % res['erep']) 175 | print(' Exchange: %12.8f' % res['ex']) 176 | print(' Nuclear repulsion: %12.8f' % res['enucrep']) 177 | print(' TOTAL: %12.8f' % res['energies'][-1]) 178 | 179 | def __print_break(self, newline=True): 180 | print('=' * 80) 181 | if newline: 182 | print() 183 | -------------------------------------------------------------------------------- /pyqint/gto.py: -------------------------------------------------------------------------------- 1 | from .pyqint import PyGTO 2 | 3 | class gto: 4 | """ 5 | Primitive Gaussian Type Orbital 6 | """ 7 | def __init__(self, _c, _p, _alpha, _l, _m, _n): 8 | """ 9 | Default constructor 10 | """ 11 | self.c = _c 12 | self.p = _p 13 | self.alpha = _alpha 14 | self.l = _l 15 | self.m = _m 16 | self.n = _n 17 | 18 | self.gto = PyGTO(self.c, self.p, self.alpha, self.l, self.m, self.n) 19 | 20 | def __setstate__(self, d): 21 | """ 22 | Set the state using tuple d 23 | 24 | Rebuilds the PyGTO C++ class 25 | """ 26 | self.c = d[0] 27 | self.p = d[1] 28 | self.alpha = d[2] 29 | self.l = d[3] 30 | self.m = d[3] 31 | self.n = d[5] 32 | self.gto = PyGTO(self.c, self.p, self.alpha, self.l, self.m, self.n) 33 | 34 | def __str__(self): 35 | return "GTO : c=%f, alpha=%f, l=%i, m=%i, n=%i, R=(%f,%f,%f)\n" \ 36 | % (self.c, self.alpha, self.l, self.m, self.n, self.p[0], self.p[1], self.p[2]) 37 | 38 | def __reduce__(self): 39 | """ 40 | Used to pickle the class 41 | """ 42 | return (self.__class__, (self.c, self.p, self.alpha, self.l, self.m, self.n)) 43 | 44 | def get_amp(self, x, y, z): 45 | return self.gto.get_amp(x, y, z) 46 | 47 | def get_norm(self): 48 | """ 49 | Returns the normalization constant for the GTO 50 | """ 51 | return self.gto.get_norm() -------------------------------------------------------------------------------- /pyqint/isosurface.py: -------------------------------------------------------------------------------- 1 | from pytessel import PyTessel 2 | from . import PyQInt 3 | import numpy as np 4 | 5 | def build_isosurface(filename, mol, basis, coeff, isovalue, dim = 5.0): 6 | sz = 99 7 | integrator = PyQInt() 8 | grid = integrator.build_rectgrid3d(-dim, dim, sz) 9 | scalarfield = np.reshape(integrator.plot_wavefunction(grid, coeff, basis), (sz, sz, sz)) 10 | unitcell = np.diag(np.ones(3) * 10.0) 11 | 12 | pytessel = PyTessel() 13 | vertices_p, normals_p, indices_p = pytessel.marching_cubes(scalarfield.flatten(), scalarfield.shape, unitcell.flatten(), isovalue) 14 | vertices_n, normals_n, indices_n = pytessel.marching_cubes(scalarfield.flatten(), scalarfield.shape, unitcell.flatten(), -isovalue) 15 | 16 | build_abo(filename, mol, vertices_p, normals_p, indices_p, vertices_n, normals_n, indices_n) 17 | 18 | def build_abo(filename, mol, vertices_p, normals_p, indices_p, vertices_n, normals_n, indices_n): 19 | outfile = ('%s.abo' % filename) 20 | f = open(outfile, 'wb') 21 | 22 | # write number of frames 23 | f.write(int(1).to_bytes(2, byteorder='little')) 24 | 25 | # write frame index 26 | f.write(int(1).to_bytes(2, byteorder='little')) 27 | 28 | # write description 29 | description = 'Orbital generated by PyQInt / PyTessel' 30 | f.write(len(description).to_bytes(2, byteorder='little')) 31 | f.write(bytearray(description, encoding='utf8')) 32 | 33 | # write atoms 34 | f.write(len(mol.charges).to_bytes(2, byteorder='little')) 35 | 36 | # loop over atoms 37 | for idx,pos in zip(mol.charges, mol.atoms): 38 | f.write(idx.to_bytes(1, byteorder='little')) 39 | f.write((np.array(pos[1], dtype=np.float32) * 0.529177).tobytes()) 40 | 41 | # set colors 42 | color_pos = np.array([201, 2, 65, 0xF0]) / 255.0 43 | color_neg = np.array([2, 182, 201, 0xF0]) / 255.0 44 | 45 | # write number of models 46 | f.write(int(2).to_bytes(2, byteorder='little')) 47 | 48 | # write model index 49 | f.write(int(0).to_bytes(2, byteorder='little')) 50 | 51 | # write model color 52 | f.write(np.array(color_pos, dtype=np.float32).tobytes()) 53 | 54 | # write number of vertices 55 | f.write(len(vertices_p).to_bytes(4, byteorder='little')) 56 | 57 | # loop over vertices and normals 58 | for v,n in zip(vertices_p, normals_p): 59 | f.write(np.array(v, dtype=np.float32).tobytes()) 60 | f.write(np.array(n, dtype=np.float32).tobytes()) 61 | 62 | # write number of faces2 63 | f.write((int(len(indices_p) / 3)).to_bytes(4, byteorder='little')) 64 | 65 | # write faces 66 | f.write(np.array(indices_p, dtype=np.uint32).tobytes()) 67 | 68 | # write model index 69 | f.write(int(1).to_bytes(2, byteorder='little')) 70 | 71 | # write model color 72 | f.write(np.array(color_neg, dtype=np.float32).tobytes()) 73 | 74 | # write number of vertices 75 | f.write(len(vertices_n).to_bytes(4, byteorder='little')) 76 | 77 | # loop over vertices and normals 78 | for v,n in zip(vertices_n, normals_n): 79 | f.write(np.array(v, dtype=np.float32).tobytes()) 80 | f.write(np.array(n, dtype=np.float32).tobytes()) 81 | 82 | # write indices 83 | f.write((int(len(indices_n) / 3)).to_bytes(4, byteorder='little')) 84 | 85 | # write faces 86 | f.write(np.array(indices_n, dtype=np.uint32).tobytes()) 87 | 88 | f.close() 89 | -------------------------------------------------------------------------------- /pyqint/molecule.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import json 4 | import os 5 | import numpy as np 6 | from .cgf import cgf 7 | from .element import Element 8 | 9 | class Molecule: 10 | """ 11 | Molecule class 12 | """ 13 | def __init__(self, _name='unknown'): 14 | self.__atoms = [] 15 | self.__charges = [] 16 | self.name = _name 17 | self.__charge = 0 18 | self.__nelec = None 19 | 20 | def __str__(self): 21 | res = "Molecule: %s\n" % self.name 22 | for atom in self.__atoms: 23 | res += " %s (%f,%f,%f)\n" % (atom[0], atom[1][0], atom[1][1], atom[1][2]) 24 | 25 | return res 26 | 27 | def __len__(self): 28 | return len(self.__atoms) 29 | 30 | def get_nelec(self): 31 | """ 32 | Get the number of electrons 33 | """ 34 | if self.__nelec == None: 35 | raise Exception('You need to use build_basis() or get_nuclei() before using this function.') 36 | return self.__nelec - self.__charge 37 | 38 | def get_atoms(self): 39 | return self.__atoms 40 | 41 | def get_charge(self): 42 | return self.__charge 43 | 44 | def set_charge(self, charge): 45 | """ 46 | Set the charge of the molecule 47 | """ 48 | self.__charge = charge 49 | 50 | def add_atom(self, atom, x, y, z, unit='bohr'): 51 | """ 52 | Add an atom to the molecule 53 | """ 54 | ang2bohr = 1.8897259886 55 | 56 | x = float(x) 57 | y = float(y) 58 | z = float(z) 59 | 60 | if unit == "bohr": 61 | self.__atoms.append([atom, np.array([x, y, z])]) 62 | elif unit == "angstrom": 63 | self.__atoms.append([atom, np.array([x*ang2bohr, y*ang2bohr, z*ang2bohr])]) 64 | else: 65 | raise RuntimeError("Invalid unit encountered: %s. Accepted units are 'bohr' and 'angstrom'." % unit) 66 | 67 | self.__charges.append(0) 68 | 69 | def build_basis(self, name): 70 | """ 71 | Build a basis set from a label 72 | 73 | Returns list of CGFs and nuclei 74 | """ 75 | basis_filename = os.path.join(os.path.dirname(__file__), 'basissets', '%s.json' % name) 76 | f = open(basis_filename, 'r') 77 | basis = json.load(f) 78 | f.close() 79 | 80 | self.__cgfs = [] 81 | 82 | for aidx, atom in enumerate(self.__atoms): 83 | cgfs_template = basis[atom[0]] 84 | 85 | # store information about the nuclei 86 | self.__charges[aidx] = cgfs_template['atomic_number'] 87 | 88 | for cgf_t in cgfs_template['cgfs']: 89 | # s-orbitals 90 | if cgf_t['type'] == 'S': 91 | self.__cgfs.append(cgf(atom[1])) 92 | for gto in cgf_t['gtos']: 93 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 0, 0, 0) 94 | 95 | # p-orbitals 96 | if cgf_t['type'] == 'P': 97 | self.__cgfs.append(cgf(atom[1])) 98 | for gto in cgf_t['gtos']: 99 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 1, 0, 0) 100 | 101 | self.__cgfs.append(cgf(atom[1])) 102 | for gto in cgf_t['gtos']: 103 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 0, 1, 0) 104 | 105 | self.__cgfs.append(cgf(atom[1])) 106 | for gto in cgf_t['gtos']: 107 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 0, 0, 1) 108 | 109 | # d-orbitals 110 | if cgf_t['type'] == 'D': 111 | self.__cgfs.append(cgf(atom[1])) 112 | for gto in cgf_t['gtos']: 113 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 2, 0, 0) 114 | 115 | self.__cgfs.append(cgf(atom[1])) 116 | for gto in cgf_t['gtos']: 117 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 0, 2, 0) 118 | 119 | self.__cgfs.append(cgf(atom[1])) 120 | for gto in cgf_t['gtos']: 121 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 0, 0, 2) 122 | 123 | self.__cgfs.append(cgf(atom[1])) 124 | for gto in cgf_t['gtos']: 125 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 1, 1, 0) 126 | 127 | self.__cgfs.append(cgf(atom[1])) 128 | for gto in cgf_t['gtos']: 129 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 1, 0, 1) 130 | 131 | self.__cgfs.append(cgf(atom[1])) 132 | for gto in cgf_t['gtos']: 133 | self.__cgfs[-1].add_gto(gto['coeff'], gto['alpha'], 0, 1, 1) 134 | 135 | # build nuclei objects 136 | self.get_nuclei() 137 | self.__nelec = np.sum(self.__charges) 138 | 139 | return self.__cgfs, self.__nuclei 140 | 141 | def get_nuclei(self): 142 | """ 143 | Get the nuclei as a packed array 144 | """ 145 | el = Element() 146 | 147 | # reset list 148 | self.__nuclei = [] 149 | 150 | for aidx, atom in enumerate(self.__atoms): 151 | 152 | # store information about the nuclei 153 | self.__charges[aidx] = getattr(el, atom[0]) 154 | self.__nuclei.append([atom[1], self.__charges[aidx]]) 155 | 156 | # populate number of electrons 157 | self.__nelec = np.sum(self.__charges) 158 | 159 | return self.__nuclei -------------------------------------------------------------------------------- /pyqint/molecule_builder.py: -------------------------------------------------------------------------------- 1 | import os 2 | from .molecule import Molecule 3 | 4 | class MoleculeBuilder: 5 | """ 6 | Class that builds molecules from templates or from point group descriptions 7 | """ 8 | def __init__(self): 9 | pass 10 | 11 | def from_name(self, molname): 12 | """ 13 | Build molecule from molname 14 | """ 15 | fname = os.path.join(os.path.dirname(__file__), 'molecules', molname.lower() + '.xyz') 16 | return self.from_file(fname) 17 | 18 | def from_file(self, path, molname=None): 19 | """ 20 | Build molecule from file and return it 21 | """ 22 | with open(path, 'r') as f: 23 | lines = f.readlines() 24 | 25 | nratoms = int(lines[0].strip()) 26 | 27 | mol = Molecule(molname) 28 | for line in lines[2:2+nratoms]: 29 | pieces = line.split() 30 | mol.add_atom(pieces[0], float(pieces[1]), float(pieces[2]), float(pieces[3]), unit='angstrom') 31 | 32 | return mol 33 | 34 | def build_complex_td(self, r, at1, at2, unit='bohr'): 35 | mol = Molecule() 36 | mol.add_atom(at1, 0, 0, 0, unit=unit) 37 | mol.add_atom(at2, 1, 1, 1, unit=unit) 38 | mol.add_atom(at2, 1, -1, -1, unit=unit) 39 | mol.add_atom(at2, -1, -1, 1, unit=unit) 40 | mol.add_atom(at2, -1, 1, 1, unit=unit) 41 | 42 | return mol 43 | -------------------------------------------------------------------------------- /pyqint/molecules/benzene.xyz: -------------------------------------------------------------------------------- 1 | 12 2 | 3 | C 0.0000000 1.4024230 0.0000000 4 | C 1.2145340 0.7012110 0.0000000 5 | C 1.2145340 -0.7012110 0.0000000 6 | C 0.0000000 -1.4024230 0.0000000 7 | C -1.2145340 -0.7012110 0.0000000 8 | C -1.2145340 0.7012110 0.0000000 9 | H 0.0000000 2.5016720 0.0000000 10 | H 2.1665120 1.2508360 0.0000000 11 | H 2.1665120 -1.2508360 0.0000000 12 | H 0.0000000 -2.5016720 0.0000000 13 | H -2.1665120 -1.2508360 0.0000000 14 | H -2.1665120 1.2508360 0.0000000 15 | -------------------------------------------------------------------------------- /pyqint/molecules/bf3.xyz: -------------------------------------------------------------------------------- 1 | 4 2 | 3 | B 0.0000000 0.0000000 0.0000000 4 | F 0.0000000 1.3304260 0.0000000 5 | F 1.1521830 -0.6652130 0.0000000 6 | F -1.1521830 -0.6652130 0.0000000 7 | -------------------------------------------------------------------------------- /pyqint/molecules/ch4.xyz: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | C 0.0000000 0.0000000 0.0000000 4 | H 0.6327670 0.6327670 0.6327670 5 | H -0.6327670 -0.6327670 0.6327670 6 | H -0.6327670 0.6327670 -0.6327670 7 | H 0.6327670 -0.6327670 -0.6327670 8 | -------------------------------------------------------------------------------- /pyqint/molecules/co.xyz: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | O 0.0000000 0.0000000 0.5120840 4 | C 0.0000000 0.0000000 -0.6827790 5 | -------------------------------------------------------------------------------- /pyqint/molecules/co2.xyz: -------------------------------------------------------------------------------- 1 | 3 2 | 3 | C 0.0000000 0.0000000 0.0000000 4 | O 0.0000000 0.0000000 1.2292730 5 | O 0.0000000 0.0000000 -1.2292730 6 | -------------------------------------------------------------------------------- /pyqint/molecules/ethylene.xyz: -------------------------------------------------------------------------------- 1 | 6 2 | 3 | C 0.0000000 0.0000000 0.6655810 4 | H 0.0000000 0.9321350 1.2456070 5 | H 0.0000000 -0.9321350 1.2456070 6 | C 0.0000000 0.0000000 -0.6655810 7 | H 0.0000000 -0.9321350 -1.2456070 8 | H 0.0000000 0.9321350 -1.2456070 9 | -------------------------------------------------------------------------------- /pyqint/molecules/h2.xyz: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | H 0.0000000 0.0000000 0.3679020 4 | H 0.0000000 0.0000000 -0.3679020 5 | -------------------------------------------------------------------------------- /pyqint/molecules/h2o.xyz: -------------------------------------------------------------------------------- 1 | 3 2 | 3 | O 0.0000000 0.0000000 0.1368220 4 | H 0.0000000 0.7697660 -0.5472890 5 | H 0.0000000 -0.7697660 -0.5472890 6 | -------------------------------------------------------------------------------- /pyqint/molecules/he.xyz: -------------------------------------------------------------------------------- 1 | 1 2 | 3 | He 0.0 0.0 0.0 4 | -------------------------------------------------------------------------------- /pyqint/molecules/lih.xyz: -------------------------------------------------------------------------------- 1 | 2 2 | 3 | Li 0.0000000 0.0000000 0.3834270 4 | H 0.0000000 0.0000000 -1.1502800 5 | -------------------------------------------------------------------------------- /pyqint/molecules/nh3.xyz: -------------------------------------------------------------------------------- 1 | 4 2 | 3 | N 0.000 0.000 0.000 4 | H 0.000 -0.937 -0.3816 5 | H 0.812 0.469 -0.3816 6 | H -0.812 0.469 -0.3816 -------------------------------------------------------------------------------- /pyqint/plotter.cpp: -------------------------------------------------------------------------------- 1 | /************************************************************************** 2 | * This file is part of PyQInt. * 3 | * * 4 | * Author: Ivo Filot * 5 | * * 6 | * PyQInt is free software: * 7 | * you can redistribute it and/or modify it under the terms of the * 8 | * GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the License, or (at your option) * 10 | * any later version. * 11 | * * 12 | * PyQInt is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 | * See the GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see http://www.gnu.org/licenses/. * 19 | * * 20 | **************************************************************************/ 21 | 22 | #include "plotter.h" 23 | 24 | Plotter::Plotter() {} 25 | 26 | std::vector Plotter::plot_wavefunction(const std::vector& grid, 27 | const std::vector& coeff, 28 | const std::vector& cgfs) const { 29 | std::vector results(grid.size() / 3, 0.0); 30 | 31 | #pragma omp parallel for 32 | for(int i=0; i<(int)grid.size(); i+=3) { // have to use signed int for MSVC OpenMP here 33 | for(unsigned int j=0; j Plotter::plot_gradient(const std::vector& grid, 42 | const std::vector& coeff, 43 | const std::vector& cgfs) const { 44 | std::vector results(grid.size(), 0.0); 45 | 46 | #pragma omp parallel for 47 | for(int i=0; i<(int)grid.size(); i+=3) { // have to use signed int for MSVC OpenMP here 48 | for(unsigned int j=0; j * 5 | * * 6 | * PyQInt is free software: * 7 | * you can redistribute it and/or modify it under the terms of the * 8 | * GNU General Public License as published by the Free Software * 9 | * Foundation, either version 3 of the License, or (at your option) * 10 | * any later version. * 11 | * * 12 | * PyQInt is distributed in the hope that it will be useful, * 13 | * but WITHOUT ANY WARRANTY; without even the implied warranty * 14 | * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * 15 | * See the GNU General Public License for more details. * 16 | * * 17 | * You should have received a copy of the GNU General Public License * 18 | * along with this program. If not, see http://www.gnu.org/licenses/. * 19 | * * 20 | **************************************************************************/ 21 | 22 | #pragma once 23 | 24 | #include "cgf.h" 25 | 26 | class Plotter { 27 | private: 28 | 29 | public: 30 | Plotter(); 31 | 32 | std::vector plot_wavefunction(const std::vector& grid, 33 | const std::vector& coeff, 34 | const std::vector& cgfs) const; 35 | 36 | std::vector plot_gradient(const std::vector& grid, 37 | const std::vector& coeff, 38 | const std::vector& cgfs) const; 39 | 40 | private: 41 | }; 42 | -------------------------------------------------------------------------------- /pyqint/pyqint.pxd: -------------------------------------------------------------------------------- 1 | # distutils: language = c++ 2 | 3 | from libcpp.vector cimport vector 4 | from libcpp.string cimport string 5 | 6 | # Integrals 7 | cdef extern from "integrals.cpp": 8 | pass 9 | 10 | # Plotter 11 | cdef extern from "plotter.cpp": 12 | pass 13 | 14 | # contracted and primitive Gaussians 15 | cdef extern from "cgf.cpp": 16 | pass 17 | 18 | # Gamma and incomplete Gamma function 19 | cdef extern from "gamma.cpp": 20 | pass 21 | 22 | # Contracted Gaussian Functions class 23 | cdef extern from "cgf.h": 24 | cdef cppclass GTO: 25 | GTO() except + 26 | GTO(double, double, double, double, double, int, int, int) except + 27 | double get_amp(double, double, double) except + 28 | double get_norm() except+ 29 | 30 | cdef cppclass CGF: 31 | CGF() except + 32 | CGF(double, double, double) except + 33 | void add_gto(double, double, int, int, int) except + 34 | double get_amp(double, double, double) except + 35 | vector[double] get_grad(double, double, double) except + 36 | 37 | # Plotter class 38 | cdef extern from "plotter.h": 39 | cdef cppclass Plotter: 40 | Plotter() except + 41 | vector[double] plot_wavefunction(vector[double], vector[double], vector[CGF]) except+ 42 | vector[double] plot_gradient(vector[double], vector[double], vector[CGF]) except+ 43 | 44 | # Integrator class 45 | cdef extern from "integrals.h": 46 | cdef cppclass Integrator: 47 | Integrator() except + 48 | 49 | int get_num_threads() except + 50 | 51 | # overlap integrals 52 | double overlap_gto(GTO, GTO) except + 53 | double overlap(CGF, CGF) except + 54 | 55 | # overlap integral geometric derivatives 56 | double overlap_deriv(CGF, CGF, double, double, double, int) except + 57 | 58 | # dipole integrals 59 | double dipole(CGF, CGF, int, double) except + 60 | double dipole_gto(GTO, GTO, int, double) except + 61 | 62 | # kinetic integrals 63 | double kinetic_gto(GTO, GTO) except + 64 | double kinetic(CGF, CGF) except + 65 | 66 | # kinetic integral geometric derivatives 67 | double kinetic_deriv(CGF, CGF, double, double, double, int) except + 68 | 69 | # nuclear integrals 70 | double nuclear(CGF, CGF, double, double, double, int) except + 71 | double nuclear_gto(GTO, GTO, double, double, double) except + 72 | 73 | # nuclear integral geometric derivatives 74 | double nuclear_deriv(CGF, CGF, double, double, double, int, double, double, double, int) except + 75 | double nuclear_deriv_bf(GTO, GTO, double, double, double, int) except + 76 | double nuclear_deriv_op(GTO, GTO, double, double, double, int) except + 77 | 78 | # two-electron integrals 79 | double repulsion(CGF, CGF, CGF, CGF) except + 80 | double repulsion(GTO, GTO, GTO, GTO) except + 81 | 82 | # two-electron integral derivatives 83 | double repulsion_deriv(CGF, CGF, CGF, CGF, double, double, double, int) except + 84 | double repulsion_deriv(GTO, GTO, GTO, GTO, int) except + 85 | 86 | # two-electron indexing 87 | int teindex(int, int, int, int) except + 88 | 89 | # openmp routine for integral evaluation 90 | vector[double] evaluate_cgfs(vector[CGF], vector[int], vector[double], vector[double], vector[double]) except + 91 | 92 | # openmp routine for geometric derivatives evaluation 93 | vector[double] evaluate_geometric_derivatives(vector[CGF], vector[int], vector[double], vector[double], vector[double]) except + 94 | 95 | string get_compiler_version() except + 96 | string get_openmp_version() except + 97 | string get_compile_date() except + 98 | string get_compile_time() except + 99 | string get_compiler_type() except + 100 | -------------------------------------------------------------------------------- /pyqint/vec3.h: -------------------------------------------------------------------------------- 1 | #ifndef _VEC3_H 2 | #define _VEC3_H 3 | 4 | typedef double mat33[3][3]; 5 | 6 | /** 7 | * Custom 3-vector class 8 | */ 9 | class Vec3 { 10 | public: 11 | double x = 0.0; 12 | double y = 0.0; 13 | double z = 0.0; 14 | 15 | /** 16 | * Default constructor 17 | */ 18 | Vec3() {} 19 | 20 | /** 21 | * Initialization constructor 22 | */ 23 | Vec3(double _x, double _y, double _z) : x(_x), y(_y), z(_z) {} 24 | 25 | double& operator[](int n) { 26 | switch(n) { 27 | case 0: 28 | return this->x; 29 | case 1: 30 | return this->y; 31 | case 2: 32 | return this->z; 33 | default: // should never reach this 34 | return this->z; 35 | } 36 | } 37 | 38 | const double& operator[](int n) const { 39 | switch(n) { 40 | case 0: 41 | return this->x; 42 | case 1: 43 | return this->y; 44 | case 2: 45 | return this->z; 46 | default: // should never reach this 47 | return this->z; 48 | } 49 | } 50 | 51 | /** 52 | * Multiplication operation between matrix and vector 53 | */ 54 | friend Vec3 operator*(const mat33& lhs, const Vec3& rhs) { 55 | Vec3 r; 56 | r.x = lhs[0][0] * rhs.x + lhs[1][0] * rhs.y + lhs[2][0] * rhs.z; 57 | r.y = lhs[0][1] * rhs.x + lhs[1][1] * rhs.y + lhs[2][1] * rhs.z; 58 | r.z = lhs[0][2] * rhs.x + lhs[1][2] * rhs.y + lhs[2][2] * rhs.z; 59 | 60 | return r; 61 | } 62 | 63 | /** 64 | * Multiplication operation between scalar and vector 65 | */ 66 | friend Vec3 operator*(double v, const Vec3& rhs) { 67 | return Vec3(v * rhs.x, v * rhs.y, v * rhs.z); 68 | } 69 | 70 | /** 71 | * Multiplication operation between vector and scalar 72 | */ 73 | friend Vec3 operator*(const Vec3& rhs, double v) { 74 | return Vec3(v * rhs.x, v * rhs.y, v * rhs.z); 75 | } 76 | 77 | /** 78 | * Return normalized vector 79 | */ 80 | Vec3 normalized() const { 81 | double l = std::sqrt(this->x * this->x + this->y * this->y + this->z * this->z); 82 | return Vec3(this->x / l, this->y / l, this->z / l); 83 | } 84 | 85 | /** 86 | * Return dot product between two vectors 87 | */ 88 | double dot(const Vec3& rhs) const { 89 | return this->x * rhs.x + this->y * rhs.y + this->z * rhs.z; 90 | } 91 | 92 | /** 93 | * Addition operation between two vectors 94 | */ 95 | friend Vec3 operator+(const Vec3& lhs, const Vec3& rhs) { 96 | return Vec3(lhs.x + rhs.x, lhs.y + rhs.y, lhs.z + rhs.z); 97 | } 98 | 99 | /** 100 | * Subtraction operation between two vectors 101 | */ 102 | friend Vec3 operator-(const Vec3& lhs, const Vec3& rhs) { 103 | return Vec3(lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z); 104 | } 105 | 106 | /** 107 | * Addition assignment operation 108 | */ 109 | void operator+=(const Vec3& rhs) { 110 | this->x += rhs.x; 111 | this->y += rhs.y; 112 | this->z += rhs.z; 113 | } 114 | 115 | /** 116 | * Subtraction assignment operation 117 | */ 118 | void operator-=(const Vec3& rhs) { 119 | this->x -= rhs.x; 120 | this->y -= rhs.y; 121 | this->z -= rhs.z; 122 | } 123 | 124 | /** 125 | * Divide vector by a scalar operation 126 | */ 127 | friend Vec3 operator/(const Vec3& rhs, double v) { 128 | return Vec3(rhs.x / v, rhs.y / v, rhs.z / v); 129 | } 130 | 131 | /** 132 | * Calculate cross product between two vectors 133 | */ 134 | Vec3 cross(const Vec3& rhs) const { 135 | return Vec3( 136 | this->y * rhs.z - this->z * rhs.y, 137 | this->z * rhs.x - this->x * rhs.z, 138 | this->x * rhs.y - this->y * rhs.x 139 | ); 140 | } 141 | 142 | /** 143 | * Calculate squared sum of coefficients 144 | */ 145 | double norm2() const { 146 | return (this->x * this->x) + (this->y * this->y) + (this->z * this->z); 147 | } 148 | 149 | /** 150 | * Calculate product of coefficients 151 | */ 152 | double prod() const { 153 | return this->x * this->y * this->z; 154 | } 155 | }; 156 | 157 | #endif // _VEC3_H 158 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | # pytest.ini 2 | [pytest] 3 | 4 | testpaths = tests -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import Extension, setup 2 | from Cython.Build import cythonize 3 | import os 4 | import sys 5 | import re 6 | 7 | PKG = "pyqint" 8 | VERSIONFILE = os.path.join(os.path.dirname(__file__), PKG, "_version.py") 9 | verstr = "unknown" 10 | try: 11 | verstrline = open(VERSIONFILE, "rt").read() 12 | except EnvironmentError: 13 | pass # Okay, there is no version file. 14 | else: 15 | VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" 16 | mo = re.search(VSRE, verstrline, re.M) 17 | if mo: 18 | verstr = mo.group(1) 19 | else: 20 | print(r"Unable to find version in %s" % (VERSIONFILE,)) 21 | raise RuntimeError(r"If %s.py exists, it is required to be well-formed" % (VERSIONFILE,)) 22 | 23 | def find_windows_versions(): 24 | """ 25 | Autofind the msvc and winkit versions 26 | """ 27 | root = os.path.join('C:', os.sep,'Program Files', 'Microsoft Visual Studio', '2022', 'Community', 'VC', 'Tools', 'MSVC') 28 | 29 | # for Gitlab actions, the above folder does not exist and this is communicated 30 | # back by providing None as the result 31 | if not os.path.exists(root): 32 | return None, None 33 | 34 | for file in os.listdir(root): 35 | if os.path.isdir(os.path.join(root, file)): 36 | msvcver = file 37 | 38 | root = os.path.join('C:', os.sep,'Program Files (x86)', 'Windows Kits', '10', 'Include') 39 | for file in os.listdir(root): 40 | if os.path.isdir(os.path.join(root, file)): 41 | winkitver = file 42 | 43 | return msvcver, winkitver 44 | 45 | # specify paths on Windows to find compiler and libraries 46 | if os.name == 'nt': 47 | msvc_ver, winkit_ver = find_windows_versions() 48 | 49 | if msvc_ver and winkit_ver: 50 | # only proceed with setting the paths for local development, i.e. when the 51 | # msvc_ver and winkit_ver variables are *not* None 52 | os.environ['PATH'] += r";C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\%s\bin\Hostx64\x64" % msvc_ver 53 | os.environ['PATH'] += r";C:\Program Files (x86)\Windows Kits\10\bin\%s\x64" % winkit_ver 54 | 55 | # set path to include folders 56 | os.environ['INCLUDE'] += r";C:\Program Files (x86)\Windows Kits\10\Include\%s\ucrt" % winkit_ver 57 | os.environ['INCLUDE'] += r";C:\Program Files (x86)\Windows Kits\10\Include\%s\shared" % winkit_ver 58 | os.environ['INCLUDE'] += r";C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\%s\include" % msvc_ver 59 | 60 | # some references to libraries 61 | os.environ['LIB'] += r";C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\%s\lib\x64" % msvc_ver 62 | os.environ['LIB'] += r";C:\Program Files (x86)\Windows Kits\10\Lib\%s\um\x64" % winkit_ver 63 | os.environ['LIB'] += r";C:\Program Files (x86)\Windows Kits\10\Lib\%s\ucrt\x64" % winkit_ver 64 | else: 65 | # re-order paths to ensure that the MSVC toolchain is in front; this needs to be done 66 | # because the Git bin folder precedes the MSVC bin folder, resulting in the wrong link.exe 67 | # executable to be used in the linking step 68 | paths = os.environ['PATH'].split(";") 69 | newpaths = [] 70 | for path in paths: 71 | if "Microsoft Visual Studio" in path: 72 | newpaths = [path] + newpaths 73 | else: 74 | newpaths.append(path) 75 | os.environ['PATH'] = ";".join(newpaths) 76 | 77 | # specify compilation instructions for other platforms 78 | if os.name == 'posix' and sys.platform != 'darwin': 79 | extra_compile_args = ["-Wno-date-time", "-fopenmp", "-fPIC"] 80 | extra_link_args = ["-fopenmp"] 81 | elif os.name == 'nt': 82 | extra_compile_args = ["/openmp"] 83 | extra_link_args = [] 84 | elif sys.platform == 'darwin': 85 | extra_compile_args = ["-Wno-date-time", "-fPIC", "-std=c++14"] 86 | extra_link_args = [] 87 | 88 | ext_modules = [ 89 | Extension( 90 | "pyqint.pyqint", 91 | ["pyqint/pyqint.pyx"], 92 | extra_compile_args=extra_compile_args, # overrule some arguments 93 | extra_link_args=extra_link_args 94 | ), 95 | ] 96 | 97 | with open("README.md", "r", encoding="utf-8") as fh: 98 | long_description = fh.read() 99 | 100 | setup( 101 | name='pyqint', 102 | version=verstr, 103 | author="Ivo Filot", 104 | author_email="ivo@ivofilot.nl", 105 | description="Python package for evaluating integrals of Gaussian type orbitals in electronic structure calculations", 106 | long_description=long_description, 107 | long_description_content_type="text/markdown", 108 | url="https://github.com/ifilot/pyqint", 109 | ext_modules=cythonize(ext_modules[0], 110 | language_level = "3", 111 | build_dir="build"), 112 | packages=['pyqint', 'pyqint.basissets', 'pyqint.molecules', 'pyqint.blender'], 113 | include_package_data=True, 114 | classifiers=[ 115 | "Programming Language :: Python :: 3", 116 | "License :: OSI Approved :: MIT License", 117 | "Operating System :: POSIX", 118 | ], 119 | python_requires='>=3.5', 120 | install_requires=['numpy','scipy'], 121 | ) 122 | -------------------------------------------------------------------------------- /tests/results/ch4.xyz: -------------------------------------------------------------------------------- 1 | 5 2 | 3 | C 0.0000000 0.0000000 0.0000000 4 | H 0.6327670 0.6327670 0.6327670 5 | H -0.6327670 -0.6327670 0.6327670 6 | H -0.6327670 0.6327670 -0.6327670 7 | H 0.6327670 -0.6327670 -0.6327670 8 | -------------------------------------------------------------------------------- /tests/results/h2_grad.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2_grad.npy -------------------------------------------------------------------------------- /tests/results/h2_wf.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2_wf.npy -------------------------------------------------------------------------------- /tests/results/h2o_dipole_x.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_dipole_x.npy -------------------------------------------------------------------------------- /tests/results/h2o_dipole_y.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_dipole_y.npy -------------------------------------------------------------------------------- /tests/results/h2o_dipole_z.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_dipole_z.npy -------------------------------------------------------------------------------- /tests/results/h2o_kinetic_p321.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_kinetic_p321.npy -------------------------------------------------------------------------------- /tests/results/h2o_kinetic_p631.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_kinetic_p631.npy -------------------------------------------------------------------------------- /tests/results/h2o_kinetic_sto3g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_kinetic_sto3g.npy -------------------------------------------------------------------------------- /tests/results/h2o_kinetic_sto6g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_kinetic_sto6g.npy -------------------------------------------------------------------------------- /tests/results/h2o_nuclear_p321.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_nuclear_p321.npy -------------------------------------------------------------------------------- /tests/results/h2o_nuclear_p631.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_nuclear_p631.npy -------------------------------------------------------------------------------- /tests/results/h2o_nuclear_sto3g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_nuclear_sto3g.npy -------------------------------------------------------------------------------- /tests/results/h2o_nuclear_sto6g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_nuclear_sto6g.npy -------------------------------------------------------------------------------- /tests/results/h2o_orb_1b2.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_orb_1b2.npy -------------------------------------------------------------------------------- /tests/results/h2o_overlap_p321.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_overlap_p321.npy -------------------------------------------------------------------------------- /tests/results/h2o_overlap_p631.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_overlap_p631.npy -------------------------------------------------------------------------------- /tests/results/h2o_overlap_sto3g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_overlap_sto3g.npy -------------------------------------------------------------------------------- /tests/results/h2o_overlap_sto6g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_overlap_sto6g.npy -------------------------------------------------------------------------------- /tests/results/h2o_teint_p321.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_teint_p321.npy -------------------------------------------------------------------------------- /tests/results/h2o_teint_p631.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_teint_p631.npy -------------------------------------------------------------------------------- /tests/results/h2o_teint_sto3g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_teint_sto3g.npy -------------------------------------------------------------------------------- /tests/results/h2o_teint_sto6g.npy: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ifilot/pyqint/ecae5f0925ad41bdc1a835e1b5874130844d16aa/tests/results/h2o_teint_sto6g.npy -------------------------------------------------------------------------------- /tests/test_cgf.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule, cgf 3 | from copy import deepcopy 4 | import numpy as np 5 | import os 6 | 7 | class TestCGF(unittest.TestCase): 8 | 9 | def testFunctionsCGF(self): 10 | """ 11 | Test getting amplitude from CGF 12 | """ 13 | 14 | # construct integrator object 15 | integrator = PyQInt() 16 | 17 | # get compile info 18 | compile_info = integrator.get_compile_info() 19 | 20 | # build hydrogen molecule 21 | mol = Molecule() 22 | mol.add_atom('H', 0.0, 0.0, 0.0) 23 | cgfs, nuclei = mol.build_basis('sto3g') 24 | 25 | # test values at these coordinates 26 | coords = [] 27 | for x in np.linspace(0, 10, 10): 28 | coords.append([x, x, x]) 29 | 30 | # results 31 | ans = [6.2825e-01, 7.1229e-02, 6.8672e-03, 3.0000e-04, 3.7662e-06, 32 | 1.3536e-08, 1.3927e-11, 4.1021e-15, 3.4591e-19, 8.3505e-24] 33 | 34 | # test for each coord 35 | amps = [] 36 | for i,coord in enumerate(coords): 37 | amp = cgfs[0].get_amp(coord) 38 | amps.append(amp) 39 | 40 | np.testing.assert_almost_equal(amps, ans, 4) 41 | 42 | def testPlotGrid(self): 43 | """ 44 | Test plotting of 1b2 molecular orbital of H2O 45 | """ 46 | # coefficients 47 | coeff = [8.37612e-17, -2.73592e-16, -0.713011, -1.8627e-17, 9.53496e-17, -0.379323, 0.379323] 48 | 49 | # construct integrator object 50 | integrator = PyQInt() 51 | 52 | # build water molecule 53 | mol = Molecule('H2O') 54 | mol.add_atom('O', 0.0, 0.0, 0.0) 55 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 56 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 57 | cgfs, nuclei = mol.build_basis('sto3g') 58 | 59 | # build grid 60 | x = np.linspace(-2, 2, 50) 61 | y = np.linspace(-2, 2, 50) 62 | xx, yy = np.meshgrid(x,y) 63 | zz = np.zeros(len(x) * len(y)) 64 | grid = np.vstack([xx.flatten(), yy.flatten(), zz]).reshape(3,-1).T 65 | res = integrator.plot_wavefunction(grid, coeff, cgfs).reshape((len(y), len(x))) 66 | 67 | ans = np.load(os.path.join(os.path.dirname(__file__), 'results', 'h2o_orb_1b2.npy')) 68 | np.testing.assert_almost_equal(res, ans, 6) 69 | 70 | def testSphericalHarmonicsOnSite(self): 71 | """ 72 | Check if the overlap matrix of all spherical harmonics up to l=6 is the identity matrix 73 | """ 74 | basis_functions = [] 75 | p0 = [0.0, 0.0, 0.0] 76 | for l in range(7): 77 | for m in range(-l, l+1): 78 | orb = cgf(p0) 79 | orb.add_spherical_gto(1.0, 1.0, l, m) 80 | basis_functions.append(orb) 81 | # build overlap matrix 82 | om = np.zeros((len(basis_functions), len(basis_functions))) 83 | integrator = PyQInt() 84 | for i,orb1 in enumerate(basis_functions): 85 | for j,orb2 in enumerate(basis_functions): 86 | om[i,j] = integrator.overlap(orb1, orb2) 87 | np.testing.assert_almost_equal(om, np.eye(om.shape[0]), 6) 88 | 89 | if __name__ == '__main__': 90 | unittest.main() 91 | -------------------------------------------------------------------------------- /tests/test_cohp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF, COHP 3 | import numpy as np 4 | 5 | class TestCOHP(unittest.TestCase): 6 | 7 | def testCO(self): 8 | """ 9 | Test construction of localized orbitals using Foster-Boys procedure 10 | for the CO molecule 11 | """ 12 | d = 1.145414 13 | mol = Molecule() 14 | mol.add_atom('C', 0.0, 0.0, -d/2, unit='angstrom') 15 | mol.add_atom('O', 0.0, 0.0, d/2, unit='angstrom') 16 | 17 | res = HF().rhf(mol, 'sto3g') 18 | cohp = COHP(res).run(res['orbc'], 0, 1) 19 | 20 | cohp_ref = np.array([ 21 | 0.0399, 22 | 0.0104, 23 | -0.4365, 24 | 0.2051, 25 | -0.2918, 26 | -0.2918, 27 | 0.1098, 28 | 0.5029, 29 | 0.5029, 30 | 6.4827 31 | ]) 32 | 33 | # note that Foster-Boys optimization is somewhat random and thus 34 | # we use relatively loose testing criteria 35 | np.testing.assert_almost_equal(cohp, 36 | cohp_ref, 37 | decimal=4) 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /tests/test_custom_basis_set.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, cgf, HF, MoleculeBuilder 3 | import numpy as np 4 | 5 | class TestCustomBasisSet(unittest.TestCase): 6 | 7 | def test_custom_basis_set_h2(self): 8 | mol = Molecule() 9 | mol.add_atom('H', 0.0000, 0.0000, 0.3561150187, unit='angstrom') 10 | mol.add_atom('H', 0.0000, 0.0000, -0.3561150187, unit='angstrom') 11 | nuclei = mol.get_nuclei() 12 | 13 | cgfs = [] 14 | for n in nuclei: 15 | _cgf = cgf(n[0]) 16 | 17 | _cgf.add_gto(0.154329, 3.425251, 0, 0, 0) 18 | _cgf.add_gto(0.535328, 0.623914, 0, 0, 0) 19 | _cgf.add_gto(0.444635, 0.168855, 0, 0, 0) 20 | 21 | cgfs.append(_cgf) 22 | 23 | res = HF().rhf(mol, basis=cgfs) 24 | np.testing.assert_almost_equal(res['energy'], -1.1175059, 5) 25 | 26 | def test_custom_basis_set_co(self): 27 | mol = MoleculeBuilder().from_name('CO') 28 | cgfs, nuclei = mol.build_basis('sto3g') 29 | 30 | res = HF().rhf(mol, basis=cgfs) 31 | np.testing.assert_almost_equal(res['energy'], -111.2192571, 4) 32 | 33 | if __name__ == '__main__': 34 | unittest.main() 35 | -------------------------------------------------------------------------------- /tests/test_derivatives_openmp.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | import numpy as np 4 | 5 | class TestDerivativesOpenMP(unittest.TestCase): 6 | 7 | def test_hartree_fock_forces_h2o(self): 8 | """ 9 | Test Hartree-Fock calculation on water using STO-3G basis set 10 | """ 11 | mol = Molecule() 12 | mol.add_atom('O', 0.0, 0.1, 0.0) 13 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 14 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 15 | 16 | # calculate forces using analytical derivatives 17 | cgfs, nuclei = mol.build_basis('sto3g') 18 | N = len(cgfs) 19 | 20 | # build containers 21 | S = np.zeros((3,3,N,N)) 22 | T = np.zeros_like(S) 23 | V = np.zeros_like(S) 24 | 25 | # build integrator object 26 | integrator = PyQInt() 27 | 28 | ntei = integrator.teindex(N-1,N-1,N-1,N-1)+1 # calculate number of two-electron integrals 29 | teints = np.zeros((3,3,ntei)) 30 | 31 | for n,deriv_nucleus in enumerate(nuclei): 32 | for d in range(3): 33 | for i,cgf1 in enumerate(cgfs): 34 | for j,cgf2 in enumerate(cgfs): 35 | 36 | # derivative of overlap matrix 37 | S[n,d,i,j] += integrator.overlap_deriv(cgf1, cgf2, deriv_nucleus[0], d) 38 | 39 | # derivative of kinetic matrix 40 | T[n,d,i,j] += integrator.kinetic_deriv(cgf1, cgf2, deriv_nucleus[0], d) 41 | 42 | # derivative nuclear electron attraction 43 | for nucleus in nuclei: 44 | V[n,d,i,j] += integrator.nuclear_deriv(cgf1, cgf2, nucleus[0], nucleus[1], deriv_nucleus[0], d) 45 | 46 | ij = i*(i+1)//2+j; 47 | 48 | for k,cgf3 in enumerate(cgfs): 49 | for l,cgf4 in enumerate(cgfs): 50 | kl = k * (k+1)//2+l; 51 | if ij <= kl: 52 | idx = integrator.teindex(i,j,k,l) 53 | teints[n,d,idx] = integrator.repulsion_deriv(cgf1, cgf2, cgf3, cgf4, deriv_nucleus[0], d) 54 | 55 | 56 | S2, T2, V2, teints2 = integrator.build_geometric_derivatives_openmp(cgfs, nuclei) 57 | 58 | np.testing.assert_almost_equal(S, S2) 59 | np.testing.assert_almost_equal(T, T2) 60 | np.testing.assert_almost_equal(V, V2) 61 | np.testing.assert_almost_equal(teints, teints2) 62 | -------------------------------------------------------------------------------- /tests/test_dipole.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | import numpy as np 4 | import os 5 | 6 | class TestDipole(unittest.TestCase): 7 | 8 | def test_cgf_dipole(self): 9 | """ 10 | Test dipole integrals for contracted Gaussians 11 | 12 | Dijk = 13 | """ 14 | 15 | # construct integrator object 16 | integrator = PyQInt() 17 | 18 | # build water molecule 19 | mol = Molecule("H2O") 20 | mol.add_atom('O', 0.00000, -0.07579, 0.0000, unit='angstrom') 21 | mol.add_atom('H', 0.86681, 0.60144, 0.0000, unit='angstrom') 22 | mol.add_atom('H', -0.86681, 0.60144, 0.0000, unit='angstrom') 23 | cgfs, nuclei = mol.build_basis('sto3g') 24 | 25 | N = len(cgfs) 26 | D = np.zeros((N,N,3)) 27 | for i in range(N): 28 | for j in range(i,N): 29 | for k in range(0,3): 30 | D[i,j,k] = integrator.dipole(cgfs[i], cgfs[j], k) 31 | 32 | # test dipole integrals 33 | for i,cart in enumerate(['x','y','z']): 34 | exact = np.load(os.path.join(os.path.dirname(__file__), 35 | 'results', 36 | 'h2o_dipole_%s.npy' % cart)) 37 | np.testing.assert_almost_equal(D[:,:,i], exact, decimal=4) 38 | 39 | if __name__ == '__main__': 40 | unittest.main() 41 | -------------------------------------------------------------------------------- /tests/test_energy_decomposition.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import MoleculeBuilder, HF 3 | import numpy as np 4 | 5 | class TestEnergyDecomposition(unittest.TestCase): 6 | 7 | def test_hartree_fock_h2o(self): 8 | """ 9 | Test Hartree-Fock calculation on water using STO-3G basis set 10 | """ 11 | mol = MoleculeBuilder().from_name("h2o") 12 | 13 | res = HF().rhf(mol, 'sto3g') 14 | P = res['density'] 15 | T = res['kinetic'] 16 | V = res['nuclear'] 17 | H = res['fock'] 18 | enucrep = res['enucrep'] 19 | energy = res['energy'] 20 | 21 | np.testing.assert_almost_equal(0.5 * np.einsum('ji,ij', P, T+V+H) + enucrep, energy, decimal=16) 22 | 23 | if __name__ == '__main__': 24 | unittest.main() -------------------------------------------------------------------------------- /tests/test_foster_boys.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF, FosterBoys 3 | import numpy as np 4 | 5 | class TestFosterBoys(unittest.TestCase): 6 | 7 | def testCO(self): 8 | """ 9 | Test construction of localized orbitals using Foster-Boys procedure 10 | for the CO molecule 11 | """ 12 | d = 1.145414 13 | mol = Molecule() 14 | mol.add_atom('C', 0.0, 0.0, -d/2, unit='angstrom') 15 | mol.add_atom('O', 0.0, 0.0, d/2, unit='angstrom') 16 | 17 | res = HF().rhf(mol, 'sto3g') 18 | 19 | # note that a seed is given here for reproducibility purposes 20 | res_fb = FosterBoys(res, seed=0).run(nr_runners=5) 21 | 22 | orbe_ref = np.array([ 23 | -20.30750217, 24 | -11.0370294, 25 | -0.83093927, 26 | -0.8309353, 27 | -0.83084896, 28 | -0.81363734, 29 | -0.52411525, 30 | res['orbe'][7], 31 | res['orbe'][8], 32 | res['orbe'][9] 33 | ]) 34 | 35 | # note that Foster-Boys optimization is somewhat random and thus 36 | # we use relatively loose testing criteria 37 | np.testing.assert_almost_equal(res_fb['orbe'], 38 | orbe_ref, 39 | decimal=2) 40 | 41 | def testCH4(self): 42 | """ 43 | Test construction of localized orbitals using Foster-Boys procedure 44 | for the CH4 molecule 45 | """ 46 | mol = Molecule() 47 | dist = 1.78/2 48 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 49 | mol.add_atom('H', dist, dist, dist, unit='angstrom') 50 | mol.add_atom('H', -dist, -dist, dist, unit='angstrom') 51 | mol.add_atom('H', -dist, dist, -dist, unit='angstrom') 52 | mol.add_atom('H', dist, -dist, -dist, unit='angstrom') 53 | 54 | res = HF().rhf(mol, 'sto3g') 55 | 56 | # note that a seed is given here for reproducibility purposes 57 | res_fb = FosterBoys(res, seed=0).run(nr_runners=5) 58 | 59 | orbe_ref = np.array([ 60 | -11.050113, 61 | -0.47136919, 62 | -0.47136899, 63 | -0.47136892, 64 | -0.47136892, 65 | res['orbe'][5], 66 | res['orbe'][6], 67 | res['orbe'][7], 68 | res['orbe'][8] 69 | ]) 70 | 71 | # assert orbital energies 72 | np.testing.assert_almost_equal(res_fb['orbe'], orbe_ref, decimal=2) 73 | 74 | # specifically test for quadruple degenerate orbital 75 | for i in range(0,4): 76 | for j in range(i+1,4): 77 | # note that Foster-Boys optimization is somewhat random and thus 78 | # we use relatively loose testing criteria 79 | np.testing.assert_almost_equal(res_fb['orbe'][i+1], 80 | res_fb['orbe'][j+1], 81 | decimal=2) 82 | 83 | if __name__ == '__main__': 84 | unittest.main() 85 | -------------------------------------------------------------------------------- /tests/test_geometry_optimization.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule,GeometryOptimization 3 | import numpy as np 4 | import sys 5 | 6 | class TestGeometryOptimization(unittest.TestCase): 7 | """ 8 | Test the calculation of analytical gradients by comparing these 9 | with the finite difference results 10 | """ 11 | 12 | def test_optimization_h2_sto3g(self): 13 | """ 14 | Optimize dihydrogen molecule and assess that the energy corresponds to 15 | an optimized structure 16 | """ 17 | # create new H2 molecule with perturbed geometry 18 | mol = Molecule() 19 | mol.add_atom('H', 0.9, 0.0, 0.0) 20 | mol.add_atom('H', -0.9, 0.0, 0.0) 21 | 22 | res = GeometryOptimization(verbose=False).run(mol, 'sto3g') 23 | np.testing.assert_almost_equal(res['opt'].fun, -1.117506) 24 | 25 | self.assertEqual(len(res['energies']), len(res['forces'])) 26 | self.assertEqual(len(res['energies']), len(res['coordinates'])) 27 | self.assertEqual(res['coordinates'][0].shape, (len(mol.get_atoms()),3)) 28 | 29 | # test existence of data object 30 | N = len(res['data']['cgfs']) 31 | self.assertEqual(N,2) 32 | self.assertEqual(res['energies'][-1], res['data']['energies'][-1]) 33 | self.assertEqual(res['data']['overlap'].shape, (N, N)) 34 | self.assertEqual(res['data']['kinetic'].shape, (N, N)) 35 | self.assertEqual(res['data']['nuclear'].shape, (N, N)) 36 | self.assertEqual(res['data']['tetensor'].shape, (N, N, N, N)) 37 | 38 | @unittest.skip("Skipping H2 P321 test for receiving inconsistent results") 39 | def test_optimization_h2_p321(self): 40 | """ 41 | Optimize dihydrogen molecule and assess that the energy corresponds to 42 | an optimized structure 43 | """ 44 | # create new H2 molecule with perturbed geometry 45 | mol = Molecule() 46 | mol.add_atom('H', 0.9, 0.0, 0.0) 47 | mol.add_atom('H', -0.9, 0.0, 0.0) 48 | 49 | res = GeometryOptimization(verbose=False).run(mol, 'p321') 50 | np.testing.assert_almost_equal(res['opt'].fun, -1.1230, decimal=3) 51 | 52 | self.assertEqual(len(res['energies']), len(res['forces'])) 53 | self.assertEqual(len(res['energies']), len(res['coordinates'])) 54 | self.assertEqual(res['coordinates'][0].shape, (len(mol.get_atoms()),3)) 55 | 56 | # test existence of data object 57 | N = len(res['data']['cgfs']) 58 | self.assertEqual(N,4) 59 | self.assertEqual(res['energies'][-1], res['data']['energies'][-1]) 60 | self.assertEqual(res['data']['overlap'].shape, (N, N)) 61 | self.assertEqual(res['data']['kinetic'].shape, (N, N)) 62 | self.assertEqual(res['data']['nuclear'].shape, (N, N)) 63 | self.assertEqual(res['data']['tetensor'].shape, (N, N, N, N)) 64 | 65 | def test_optimization_ch4(self): 66 | """ 67 | Optimize methane molecule and assess that the energy corresponds to 68 | an optimized structure 69 | """ 70 | # create new CH4 molecule with perturbed geometry 71 | mol = Molecule() 72 | dist = 2.0/2 73 | mol.add_atom('C', 0.1, 0.0, 0.1, unit='angstrom') 74 | mol.add_atom('H', dist, dist, dist, unit='angstrom') 75 | mol.add_atom('H', -dist, -dist, dist, unit='angstrom') 76 | mol.add_atom('H', -dist, dist, -dist, unit='angstrom') 77 | mol.add_atom('H', dist, -dist, -dist, unit='angstrom') 78 | 79 | res = GeometryOptimization(verbose=False).run(mol, 'sto3g') 80 | np.testing.assert_almost_equal(res['opt'].fun, -39.72691085946399, decimal=4) 81 | 82 | self.assertEqual(len(res['energies']), len(res['forces'])) 83 | self.assertEqual(len(res['energies']), len(res['coordinates'])) 84 | self.assertEqual(res['coordinates'][0].shape, (len(mol.get_atoms()),3)) 85 | 86 | # test existence of data object 87 | N = len(res['data']['cgfs']) 88 | self.assertEqual(N,9) 89 | self.assertEqual(res['energies'][-1], res['data']['energies'][-1]) 90 | self.assertEqual(res['data']['overlap'].shape, (N, N)) 91 | self.assertEqual(res['data']['kinetic'].shape, (N, N)) 92 | self.assertEqual(res['data']['nuclear'].shape, (N, N)) 93 | self.assertEqual(res['data']['tetensor'].shape, (N, N, N, N)) 94 | 95 | def test_optimization_h2o(self): 96 | """ 97 | Optimize the water molecule and assess that the energy corresponds to 98 | an optimized structure 99 | """ 100 | # create new H2O molecule with perturbed geometry 101 | mol = Molecule() 102 | mol.add_atom('O', 0.0, 0.0, -0.2271310707, unit='angstrom') 103 | mol.add_atom('H', 0.0, -0.8580158822, 0.5085242828, unit='angstrom') 104 | mol.add_atom('H', 0.0, 0.8580158822, 0.5085242828, unit='angstrom') 105 | 106 | res = GeometryOptimization(verbose=False).run(mol, 'sto3g') 107 | np.testing.assert_almost_equal(res['opt'].fun, -74.96590347517174, decimal=4) 108 | 109 | self.assertEqual(len(res['energies']), len(res['forces'])) 110 | self.assertEqual(len(res['energies']), len(res['coordinates'])) 111 | self.assertEqual(res['coordinates'][0].shape, (len(mol.get_atoms()),3)) 112 | 113 | # test existence of data object 114 | N = len(res['data']['cgfs']) 115 | self.assertEqual(N,7) 116 | self.assertEqual(res['energies'][-1], res['data']['energies'][-1]) 117 | self.assertEqual(res['data']['overlap'].shape, (N, N)) 118 | self.assertEqual(res['data']['kinetic'].shape, (N, N)) 119 | self.assertEqual(res['data']['nuclear'].shape, (N, N)) 120 | self.assertEqual(res['data']['tetensor'].shape, (N, N, N, N)) 121 | 122 | @unittest.skipIf(sys.platform == "darwin", 123 | "skipping test for MacOS") 124 | def test_optimization_c2h4(self): 125 | """ 126 | Optimize ethylene molecule and assess that the energy corresponds to 127 | an optimized structure 128 | """ 129 | # create new C2H4 molecule with perturbed geometry 130 | mol = Molecule() 131 | mol.add_atom('C', -0.5530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 132 | mol.add_atom('C', 0.4530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 133 | mol.add_atom('H', -1.3288875372, -0.9556191261 ,0.1000000000, unit='angstrom') 134 | mol.add_atom('H', -1.1288875372, 0.9356191261 ,0.0000000000, unit='angstrom') 135 | mol.add_atom('H', 1.3288875372, 0.9556191261 ,0.0000000000, unit='angstrom') 136 | mol.add_atom('H', 1.1288875372, -0.9156191261 ,0.1000000000, unit='angstrom') 137 | 138 | res = GeometryOptimization(verbose=False).run(mol, 'sto3g') 139 | np.testing.assert_almost_equal(res['opt'].fun, -77.07396213047552, decimal=4) 140 | 141 | self.assertEqual(len(res['energies']), len(res['forces'])) 142 | self.assertEqual(len(res['energies']), len(res['coordinates'])) 143 | self.assertEqual(res['coordinates'][0].shape, (len(mol.get_atoms()),3)) 144 | 145 | # test existence of data object 146 | N = len(res['data']['cgfs']) 147 | self.assertEqual(N,14) 148 | self.assertEqual(res['energies'][-1], res['data']['energies'][-1]) 149 | self.assertEqual(res['data']['overlap'].shape, (N, N)) 150 | self.assertEqual(res['data']['kinetic'].shape, (N, N)) 151 | self.assertEqual(res['data']['nuclear'].shape, (N, N)) 152 | self.assertEqual(res['data']['tetensor'].shape, (N, N, N, N)) 153 | 154 | if __name__ == '__main__': 155 | unittest.main() 156 | -------------------------------------------------------------------------------- /tests/test_grad.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import cgf 3 | import numpy as np 4 | import itertools 5 | 6 | class TestGrad(unittest.TestCase): 7 | """ 8 | Test the calculation of analytical gradients by comparing these 9 | with the finite difference results 10 | """ 11 | 12 | def test_cgf_grad(self): 13 | """ 14 | Test gradient of basis functions 15 | """ 16 | h = 1e-4 # set step size for finite difference 17 | 18 | pp = list(itertools.product(range(4), repeat=3)) 19 | 20 | for exp in pp: 21 | for p in pp: 22 | l,m,n = exp 23 | cgft = cgf() 24 | cgft.add_gto(0.154329, 3.425251, l, m, n) 25 | cgft.add_gto(0.535328, 0.623914, l, m, n) 26 | cgft.add_gto(0.444635, 0.168855, l, m, n) 27 | 28 | grad = cgft.get_grad(p) 29 | np.testing.assert_almost_equal(grad, calculate_derivs_finite_diff(p, h, l,m,n), 4) 30 | 31 | def test_grad_density(self): 32 | """ 33 | Test the gradient of the density from the gradient of the basis functions 34 | """ 35 | h = 1e-4 # set step size for finite difference 36 | 37 | pp = list(itertools.product(range(4), repeat=3)) 38 | for exp in pp: 39 | for p in pp: 40 | l,m,n = exp 41 | cgft = cgf() 42 | cgft.add_gto(0.154329, 3.425251, l, m, n) 43 | cgft.add_gto(0.535328, 0.623914, l, m, n) 44 | cgft.add_gto(0.444635, 0.168855, l, m, n) 45 | 46 | # calculate gradient of the density using chain rule 47 | grad = 2.0 * cgft.get_amp(p) * np.array(cgft.get_grad(p)) 48 | np.testing.assert_almost_equal(grad, calculate_derivs_density_finite_diff(p, h, l,m,n), 4) 49 | 50 | def calculate_derivs_finite_diff(p, h, l=0, m=0, n=0): 51 | """ 52 | Determine the gradient using finite differences 53 | """ 54 | grad = [0,0,0] 55 | for i in range(0,3): 56 | r = np.zeros(3) 57 | 58 | r[i] = -h 59 | cgft = cgf() 60 | cgft.add_gto(0.154329, 3.425251, l, m, n) 61 | cgft.add_gto(0.535328, 0.623914, l, m, n) 62 | cgft.add_gto(0.444635, 0.168855, l, m, n) 63 | vl = cgft.get_amp(p + r) 64 | r[i] = h 65 | vr = cgft.get_amp(p + r) 66 | grad[i] = (vr - vl) / (2. * h) 67 | 68 | return grad 69 | 70 | def calculate_derivs_density_finite_diff(p, h, l=0, m=0, n=0): 71 | """ 72 | Determine the gradient using finite differences 73 | """ 74 | grad = [0,0,0] 75 | for i in range(0,3): 76 | r = np.zeros(3) 77 | 78 | r[i] = -h 79 | cgft = cgf() 80 | cgft.add_gto(0.154329, 3.425251, l, m, n) 81 | cgft.add_gto(0.535328, 0.623914, l, m, n) 82 | cgft.add_gto(0.444635, 0.168855, l, m, n) 83 | vl = cgft.get_amp(p + r)**2 # take square to get density 84 | r[i] = h 85 | vr = cgft.get_amp(p + r)**2 # take square to get density 86 | grad[i] = (vr - vl) / (2. * h) 87 | 88 | return grad 89 | 90 | if __name__ == '__main__': 91 | unittest.main() -------------------------------------------------------------------------------- /tests/test_gto.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import gto 3 | import numpy as np 4 | 5 | class testGTO(unittest.TestCase): 6 | 7 | def testNormalizationGTO(self): 8 | """ 9 | Test getting amplitude from CGF 10 | """ 11 | 12 | gto_h = gto(1.0, (0,0,0), 0.4166, 0, 0, 0) 13 | norm = gto_h.get_norm() 14 | np.testing.assert_almost_equal(norm, 0.36957240951430304, 4) 15 | 16 | if __name__ == '__main__': 17 | unittest.main() 18 | -------------------------------------------------------------------------------- /tests/test_hf.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF 3 | import numpy as np 4 | 5 | class TestHF(unittest.TestCase): 6 | 7 | def test_hartree_fock_h2o(self): 8 | """ 9 | Test Hartree-Fock calculation on water using STO-3G basis set 10 | """ 11 | mol = Molecule() 12 | mol.add_atom('O', 0.0, 0.0, 0.0) 13 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 14 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 15 | 16 | results = HF().rhf(mol, 'sto3g') 17 | 18 | # check that energy matches 19 | np.testing.assert_almost_equal(results['energy'], -73.21447132, 4) 20 | 21 | # verify that terms are being calculated 22 | np.testing.assert_almost_equal(results['density'], np.einsum('ik,jk,k->ij', results['orbc'], results['orbc'], [2,2,2,2,2,0,0]), decimal=5) 23 | np.testing.assert_almost_equal(results['ekin'] + results['enuc'] + results['erep'] + results['ex'] + results['enucrep'], results['energy'], decimal=5) 24 | 25 | def test_hartree_fock_ch4(self): 26 | """ 27 | Test Hartree-Fock calculation on water using STO-3G basis set 28 | """ 29 | mol = Molecule() 30 | dist = 1.78/2 31 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 32 | mol.add_atom('H', dist, dist, dist, unit='angstrom') 33 | mol.add_atom('H', -dist, -dist, dist, unit='angstrom') 34 | mol.add_atom('H', -dist, dist, -dist, unit='angstrom') 35 | mol.add_atom('H', dist, -dist, -dist, unit='angstrom') 36 | 37 | results = HF().rhf(mol, 'sto3g') 38 | 39 | # check that orbital energies are correctly approximated 40 | ans = np.array([-11.0707, 41 | -0.7392, 42 | -0.3752, 43 | -0.3752, 44 | -0.3752, 45 | 0.2865, 46 | 0.4092, 47 | 0.4092, 48 | 0.4092]) 49 | np.testing.assert_almost_equal(results['orbe'], ans, 4) 50 | 51 | en = -39.35007843284954 52 | np.testing.assert_almost_equal(results['energies'][-1], en, 4) 53 | 54 | def test_hartree_fock_ch4_symmetric(self): 55 | """ 56 | Test Hartree-Fock calculation on CH4 using an STO-3g basis set and 57 | symmetric orthogonalization 58 | """ 59 | mol = Molecule() 60 | dist = 1.78/2 61 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 62 | mol.add_atom('H', dist, dist, dist, unit='angstrom') 63 | mol.add_atom('H', -dist, -dist, dist, unit='angstrom') 64 | mol.add_atom('H', -dist, dist, -dist, unit='angstrom') 65 | mol.add_atom('H', dist, -dist, -dist, unit='angstrom') 66 | 67 | results = HF().rhf(mol, 'sto3g', ortho='symmetric') 68 | 69 | # check that orbital energies are correctly approximated 70 | ans = np.array([-11.0707, 71 | -0.7392, 72 | -0.3752, 73 | -0.3752, 74 | -0.3752, 75 | 0.2865, 76 | 0.4092, 77 | 0.4092, 78 | 0.4092]) 79 | np.testing.assert_almost_equal(results['orbe'], ans, 4) 80 | 81 | en = -39.35007843284954 82 | np.testing.assert_almost_equal(results['energies'][-1], en, 4) 83 | 84 | def test_hartree_fock_restart(self): 85 | """ 86 | Test Hartree-Fock calculation on water using STO-3G basis set 87 | """ 88 | mol = Molecule() 89 | dist = 1.78/2 90 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 91 | mol.add_atom('H', dist, dist, dist, unit='angstrom') 92 | mol.add_atom('H', -dist, -dist, dist, unit='angstrom') 93 | mol.add_atom('H', -dist, dist, -dist, unit='angstrom') 94 | mol.add_atom('H', dist, -dist, -dist, unit='angstrom') 95 | results1 = HF().rhf(mol, 'sto3g') 96 | 97 | # check that orbital energies are correctly approximated 98 | ans = np.array([-11.0707, 99 | -0.7392, 100 | -0.3752, 101 | -0.3752, 102 | -0.3752, 103 | 0.2865, 104 | 0.4092, 105 | 0.4092, 106 | 0.4092]) 107 | np.testing.assert_almost_equal(results1['orbe'], ans, 4) 108 | 109 | en = -39.35007843284954 110 | np.testing.assert_almost_equal(results1['energies'][-1], en, 4) 111 | 112 | # create new CH4 molecule with slight adjustment in geometry and 113 | # seed the calculation with the previous converged result 114 | mol = Molecule() 115 | dist = 1.78/2 116 | mol.add_atom('C', 0.0, 0.0, 0.1, unit='angstrom') 117 | mol.add_atom('H', dist, dist, dist, unit='angstrom') 118 | mol.add_atom('H', -dist, -dist, dist, unit='angstrom') 119 | mol.add_atom('H', -dist, dist, -dist, unit='angstrom') 120 | mol.add_atom('H', dist, -dist, -dist, unit='angstrom') 121 | 122 | # perform HF calculation with coefficient matrix from previous 123 | # calculation to speed up the convergence 124 | results2 = HF().rhf(mol, 'sto3g', orbc_init=results1['orbc']) 125 | 126 | # assess that the energy of the perturbed result is different 127 | # (and also higher) 128 | en = -39.34538546003782 129 | np.testing.assert_almost_equal(results2['energies'][-1], en, 3) 130 | 131 | # check that the convergence is quicker 132 | self.assertTrue(len(results1['energies']) > len(results2['energies'])) 133 | 134 | if __name__ == '__main__': 135 | unittest.main() 136 | -------------------------------------------------------------------------------- /tests/test_hf_deriv.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF 3 | from copy import deepcopy 4 | import numpy as np 5 | 6 | class TestHFDeriv(unittest.TestCase): 7 | 8 | def test_hartree_fock_forces_h2(self): 9 | """ 10 | Test Hartree-Fock calculation on H2 using STO-3G basis set 11 | """ 12 | for i in range(0,5): 13 | mol = Molecule() 14 | mol.add_atom('H', 0.0000, 0.0000, 0.30 + i * 0.50, unit='angstrom') 15 | mol.add_atom('H', 0.0000, 0.0000, -0.30 - i * 0.50, unit='angstrom') 16 | 17 | # calculate forces using analytical derivatives 18 | solver = HF() 19 | res = solver.rhf(mol, 'sto3g', calc_forces=True) 20 | 21 | # calculate forces using finite difference 22 | forces = calculate_forces_finite_difference(mol) 23 | 24 | np.testing.assert_almost_equal(res['forces'], forces, decimal=4) 25 | 26 | def test_hartree_fock_forces_h2o(self): 27 | """ 28 | Test Hartree-Fock calculation on water using STO-3G basis set 29 | """ 30 | mol = Molecule() 31 | mol.add_atom('O', 0.0, 0.1, 0.0) 32 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 33 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 34 | 35 | # calculate forces using analytical derivatives 36 | solver = HF() 37 | res = solver.rhf(mol, 'sto3g', calc_forces=True) 38 | 39 | # calculate forces using finite difference 40 | forces = calculate_forces_finite_difference(mol) 41 | 42 | np.testing.assert_almost_equal(res['forces'], forces, decimal=4) 43 | 44 | def test_hartree_fock_forces_ch4(self): 45 | """ 46 | Test Hartree-Fock calculation on CH4 using STO-3G basis set 47 | 48 | Note that the CH4 molecule is slightly perturbed 49 | """ 50 | mol = Molecule() 51 | mol.add_atom('C', 0.0, 0.1, 0.0, unit='angstrom') 52 | mol.add_atom('H', 0.1, 0.15, 1.0830098121, unit='angstrom') 53 | mol.add_atom('H', 0.0, -1.0210714424, -0.3610032723, unit='angstrom') 54 | mol.add_atom('H', -0.8842738057, 0.5105357237, -0.3610032748, unit='angstrom') 55 | mol.add_atom('H', 0.8842738117, 0.5105357203, -0.3610032651, unit='angstrom') 56 | 57 | # calculate forces using analytical derivatives 58 | solver = HF() 59 | res = solver.rhf(mol, 'sto3g', calc_forces=True, tolerance=1e-12) 60 | 61 | # calculate forces using finite difference 62 | forces = calculate_forces_finite_difference(mol) 63 | 64 | np.testing.assert_almost_equal(res['forces'], forces, decimal=3) 65 | 66 | def test_hartree_fock_forces_co2(self): 67 | """ 68 | Test Hartree-Fock calculation on CH4 using STO-3G basis set 69 | 70 | Note that the CO2 molecule is slightly perturbed 71 | """ 72 | mol = Molecule() 73 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 74 | mol.add_atom('O', 0.0, 0.0, 1.2879700928, unit='angstrom') 75 | mol.add_atom('O', 0.0, 0.0, -1.2879700928, unit='angstrom') 76 | 77 | # calculate forces using analytical derivatives 78 | solver = HF() 79 | res = solver.rhf(mol, 'sto3g', calc_forces=True) 80 | 81 | # calculate forces using finite difference 82 | forces = calculate_forces_finite_difference(mol) 83 | 84 | np.testing.assert_almost_equal(res['forces'], forces, decimal=3) 85 | 86 | def perform_hf(mol): 87 | sol = HF().rhf(mol, 'sto3g', tolerance=1e-12) 88 | return sol 89 | 90 | def calculate_forces_finite_difference(mol): 91 | """ 92 | Calculates the forces on each of the atoms using a finite difference 93 | approach. 94 | """ 95 | forces = np.zeros((len(mol.get_atoms()),3)) 96 | 97 | sz = 1e-3 98 | 99 | for i in range(0, len(mol.get_atoms())): # loop over nuclei 100 | for j in range(0, 3): # loop over directions 101 | mol1 = deepcopy(mol) 102 | mol1.get_atoms()[i][1][j] -= sz / 2 103 | mol2 = deepcopy(mol) 104 | mol2.get_atoms()[i][1][j] += sz / 2 105 | 106 | energy1 = perform_hf(mol1)['energy'] 107 | energy2 = perform_hf(mol2)['energy'] 108 | 109 | forces[i,j] = (energy2 - energy1) / sz 110 | 111 | return forces 112 | 113 | if __name__ == '__main__': 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /tests/test_hf_molecules.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF 3 | import numpy as np 4 | 5 | class TestHFMolecules(unittest.TestCase): 6 | 7 | def testH2(self): 8 | """ 9 | Test Hartree-Fock calculation for H2 10 | 11 | Data is compared to results obtained from Gaussian 12 | """ 13 | mol = Molecule() 14 | mol.add_atom('H', 0.0000, 0.0000, 0.3561150187, unit='angstrom') 15 | mol.add_atom('H', 0.0000, 0.0000, -0.3561150187, unit='angstrom') 16 | 17 | results = HF().rhf(mol, 'sto3g') 18 | 19 | # check that energy matches 20 | np.testing.assert_almost_equal(results['energy'], -1.1175059, 5) 21 | 22 | def testC2H4(self): 23 | """ 24 | Test Hartree-Fock calculation for Ethylene 25 | 26 | Data is compared to results obtained from Gaussian 27 | """ 28 | mol = Molecule() 29 | mol.add_atom('C', -0.6530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 30 | mol.add_atom('C', 0.6530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 31 | mol.add_atom('H', -1.2288875372, -0.9156191261 ,0.0000000000, unit='angstrom') 32 | mol.add_atom('H', -1.2288875372, 0.9156191261 ,0.0000000000, unit='angstrom') 33 | mol.add_atom('H', 1.2288875372, 0.9156191261 ,0.0000000000, unit='angstrom') 34 | mol.add_atom('H', 1.2288875372, -0.9156191261 ,0.0000000000, unit='angstrom') 35 | 36 | results = HF().rhf(mol, 'sto3g') 37 | 38 | # check that energy matches 39 | np.testing.assert_almost_equal(results['energy'], -77.0739726, 4) 40 | 41 | def testBF3(self): 42 | """ 43 | Test Hartree-Fock calculation for BF3 44 | 45 | Data is compared to results obtained from Gaussian 46 | """ 47 | mol = Molecule() 48 | mol.add_atom('B', 0.0, 0.0, 0.0, unit='angstrom') 49 | mol.add_atom('F', -1.1334295832, 0.654385875, 0.0, unit='angstrom') 50 | mol.add_atom('F', 1.1334295832, 0.654385875, 0.0, unit='angstrom') 51 | mol.add_atom('F', 0.0, -1.3087717499, 0.0, unit='angstrom') 52 | 53 | results = HF().rhf(mol, 'sto3g') 54 | 55 | # check that energy matches 56 | np.testing.assert_almost_equal(results['energy'], -318.6619373, 4) 57 | 58 | def testCH4(self): 59 | """ 60 | Test Hartree-Fock calculation for CH4 61 | 62 | Data is compared to results obtained from Gaussian 63 | """ 64 | mol = Molecule() 65 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 66 | mol.add_atom('H', -5.9e-09, -1.6e-09, 1.0830098121, unit='angstrom') 67 | mol.add_atom('H', 0.0, -1.0210714424, -0.3610032723, unit='angstrom') 68 | mol.add_atom('H', -0.8842738057, 0.5105357237, -0.3610032748, unit='angstrom') 69 | mol.add_atom('H', 0.8842738117, 0.5105357203, -0.3610032651, unit='angstrom') 70 | 71 | results = HF().rhf(mol, 'sto3g') 72 | 73 | # check that energy matches 74 | np.testing.assert_almost_equal(results['energy'], -39.7268637, 1) 75 | 76 | def testCO(self): 77 | """ 78 | Test Hartree-Fock calculation for CO 79 | 80 | Data is compared to results obtained from Gaussian 81 | """ 82 | mol = Molecule() 83 | mol.add_atom('O', 0.0, 0.0, 0.4909201273, unit='angstrom') 84 | mol.add_atom('C', 0.0, 0.0, -0.6545601698, unit='angstrom') 85 | 86 | results = HF().rhf(mol, 'sto3g') 87 | 88 | # check that energy matches 89 | np.testing.assert_almost_equal(results['energy'], -111.2254495, 4) 90 | 91 | def testCO2(self): 92 | """ 93 | Test Hartree-Fock calculation for CO2 94 | 95 | Data is compared to results obtained from Gaussian 96 | """ 97 | mol = Molecule() 98 | mol.add_atom('C', 0.0, 0.0, 0.0, unit='angstrom') 99 | mol.add_atom('O', 0.0, 0.0, 1.1879700928, unit='angstrom') 100 | mol.add_atom('O', 0.0, 0.0, -1.1879700928, unit='angstrom') 101 | 102 | results = HF().rhf(mol, 'sto3g') 103 | 104 | # check that energy matches 105 | np.testing.assert_almost_equal(results['energy'], -185.0683906, 5) 106 | 107 | def testH2O(self): 108 | """ 109 | Test Hartree-Fock calculation for H2O 110 | 111 | Data is compared to results obtained from Gaussian 112 | """ 113 | mol = Molecule() 114 | mol.add_atom('O', 0.0, 0.0, -0.1271310707, unit='angstrom') 115 | mol.add_atom('H', 0.0, -0.7580158822, 0.5085242828, unit='angstrom') 116 | mol.add_atom('H', 0.0, 0.7580158822, 0.5085242828, unit='angstrom') 117 | 118 | results = HF().rhf(mol, 'sto3g') 119 | 120 | # check that energy matches 121 | np.testing.assert_almost_equal(results['energy'], -74.9659012, 4) 122 | 123 | def testLIH(self): 124 | """ 125 | Test Hartree-Fock calculation for LIH 126 | 127 | Data is compared to results obtained from Gaussian 128 | """ 129 | mol = Molecule() 130 | mol.add_atom('Li', 0.0, 0.0, 0.377702853, unit='angstrom') 131 | mol.add_atom('H', 0.0, 0.0, -1.1331085589, unit='angstrom') 132 | 133 | results = HF().rhf(mol, 'sto3g') 134 | 135 | # check that energy matches 136 | np.testing.assert_almost_equal(results['energy'], -7.8633821, 5) 137 | 138 | def testNH3(self): 139 | """ 140 | Test Hartree-Fock calculation for NH3 141 | 142 | Note: a lower energy is found here as compared to Gaussian, so I reckon 143 | (might be wrong!) that this result is better. 144 | Using Gaussian: EHF = -55.7433617 Ht is found 145 | """ 146 | mol = Molecule() 147 | mol.add_atom('H', -3.4e-09, -2.6e-09, 1.1944430032, unit='angstrom') 148 | mol.add_atom('H', 1e-10, -1.1261316622, -0.3981476702, unit='angstrom') 149 | mol.add_atom('H', -0.9752586265, 0.5630658334, -0.3981476693, unit='angstrom') 150 | mol.add_atom('H', 0.9752586299, 0.5630658315, -0.3981476637, unit='angstrom') 151 | mol.add_atom('N', 0.0, 0.0, 0.0, unit='angstrom') 152 | 153 | results = HF().rhf(mol, 'sto3g') 154 | 155 | # check that energy matches 156 | np.testing.assert_almost_equal(results['energy'], -55.7998313, 5) 157 | 158 | def testC6H6(self): 159 | """ 160 | Test Hartree-Fock calculation for Ethylene 161 | 162 | Data is compared to results obtained from Gaussian 163 | """ 164 | mol = Molecule() 165 | mol.add_atom('C', 0.0000000015, -1.3868467444, 0.0000000000, unit='angstrom') 166 | mol.add_atom('C', 1.2010445126, -0.6934233709, 0.0000000000, unit='angstrom') 167 | mol.add_atom('C', 1.2010445111, 0.6934233735, 0.0000000000, unit='angstrom') 168 | mol.add_atom('C', -0.0000000015, 1.3868467444, 0.0000000000, unit='angstrom') 169 | mol.add_atom('C', -1.2010445126, 0.6934233709, 0.0000000000, unit='angstrom') 170 | mol.add_atom('C', -1.2010445111, -0.6934233735, 0.0000000000, unit='angstrom') 171 | mol.add_atom('H', 0.0000000027, -2.4694205285, 0.0000000000, unit='angstrom') 172 | mol.add_atom('H', 2.1385809117, -1.2347102619, 0.0000000000, unit='angstrom') 173 | mol.add_atom('H', 2.1385809090, 1.2347102666, 0.0000000000, unit='angstrom') 174 | mol.add_atom('H', -0.0000000027, 2.4694205285, 0.0000000000, unit='angstrom') 175 | mol.add_atom('H', -2.1385809117, 1.2347102619, 0.0000000000, unit='angstrom') 176 | mol.add_atom('H', -2.1385809090, -1.2347102666, 0.0000000000, unit='angstrom') 177 | 178 | results = HF().rhf(mol, 'sto3g') 179 | 180 | # check that energy matches 181 | np.testing.assert_almost_equal(results['energy'], -227.8913603, 5) 182 | 183 | if __name__ == '__main__': 184 | unittest.main() 185 | -------------------------------------------------------------------------------- /tests/test_hf_molecules_charged.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, GeometryOptimization 3 | import numpy as np 4 | 5 | class TestHFMoleculeCharged(unittest.TestCase): 6 | 7 | def testHCO(self): 8 | """ 9 | Test Hartree-Fock calculation for CO 10 | 11 | Data is compared to results obtained from Gaussian 12 | """ 13 | mol = Molecule() 14 | mol.add_atom('O', -0.12770367, -0.00000000, 1.23419284) 15 | mol.add_atom('C', -0.50625665, 0.00000000, -1.09149431) 16 | mol.add_atom('H', 1.57882331, -0.00000000, -2.06681794) 17 | mol.set_charge(-1) 18 | 19 | results = GeometryOptimization().run(mol, 'sto3g') 20 | 21 | # check that energy matches 22 | self.assertEqual(results['data']['nelec'], 16) 23 | np.testing.assert_almost_equal(results['energies'][-1], -111.523147758727, 5) 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /tests/test_kinetic.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, gto, Molecule 3 | import numpy as np 4 | 5 | class TestKinetic(unittest.TestCase): 6 | 7 | def test_gto_kinetic(self): 8 | """ 9 | Test kinetic integrals for primitive GTOs 10 | 11 | Tij = 12 | """ 13 | 14 | # construct integrator object 15 | integrator = PyQInt() 16 | 17 | # test GTO 18 | gto1 = gto(0.154329, [0.0, 0.0, 0.0], 3.425251, 0, 0, 0) 19 | gto2 = gto(0.535328, [0.0, 0.0, 0.0], 0.623914, 0, 0, 0) 20 | gto3 = gto(0.444635, [0.0, 0.0, 0.0], 0.168855, 0, 0, 0) 21 | kinetic = integrator.kinetic_gto(gto1, gto1) 22 | result = 1.595603108406067 23 | np.testing.assert_almost_equal(kinetic, result, 4) 24 | 25 | def test_cgf_kinetic(self): 26 | """ 27 | Test kinetic integrals for contracted Gaussians 28 | 29 | Tij = 30 | """ 31 | 32 | # construct integrator object 33 | integrator = PyQInt() 34 | 35 | # build hydrogen molecule 36 | mol = Molecule("H2") 37 | mol.add_atom('H', 0.0, 0.0, 0.0) 38 | mol.add_atom('H', 0.0, 0.0, 1.4) 39 | cgfs, nuclei = mol.build_basis('sto3g') 40 | 41 | T = np.zeros((2,2)) 42 | T[0,0] = integrator.kinetic(cgfs[0], cgfs[0]) 43 | T[0,1] = T[1,0] = integrator.kinetic(cgfs[0], cgfs[1]) 44 | T[1,1] = integrator.kinetic(cgfs[1], cgfs[1]) 45 | 46 | T11 = 0.7600315809249878 47 | T12 = 0.2364544570446014 48 | np.testing.assert_almost_equal(T[0,0], T11, 4) 49 | np.testing.assert_almost_equal(T[1,1], T11, 4) 50 | np.testing.assert_almost_equal(T[0,1], T12, 4) 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /tests/test_kinetic_deriv.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | from copy import deepcopy 4 | import numpy as np 5 | 6 | class TestKineticDeriv(unittest.TestCase): 7 | 8 | def testDerivH2O(self): 9 | """ 10 | Test Derivatives of dihydrogen 11 | """ 12 | 13 | # build integrator object 14 | integrator = PyQInt() 15 | 16 | # build hydrogen molecule 17 | mol = Molecule() 18 | mol.add_atom('O', 0.0, 0.0, 0.0) 19 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 20 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 21 | cgfs, nuclei = mol.build_basis('sto3g') 22 | 23 | # calculate derivative towards H1 in the x-direction 24 | fx1 = integrator.kinetic_deriv(cgfs[2], cgfs[2], nuclei[1][0], 0) # px 25 | fx2 = integrator.kinetic_deriv(cgfs[2], cgfs[3], nuclei[1][0], 0) # py 26 | 27 | ans1 = calculate_force_finite_difference(mol, 1, 2, 2, 0) 28 | ans2 = calculate_force_finite_difference(mol, 1, 3, 3, 0) 29 | 30 | # assert that the kinetic of two CGFs that spawn from 31 | # the same nucleus will not change in energy due to a 32 | # change of the nucleus coordinates 33 | np.testing.assert_almost_equal(fx1, ans1, 4) 34 | np.testing.assert_almost_equal(fx2, ans2, 4) 35 | 36 | # assert that the cross-terms will change 37 | fx3 = integrator.kinetic_deriv(cgfs[2], cgfs[5], nuclei[1][0], 0) 38 | fx4 = integrator.kinetic_deriv(cgfs[2], cgfs[5], nuclei[1][0], 0) 39 | 40 | ans3 = calculate_force_finite_difference(mol, 1, 2, 5, 0) 41 | ans4 = calculate_force_finite_difference(mol, 1, 2, 5, 0) 42 | 43 | np.testing.assert_almost_equal(fx3, ans3, 4) 44 | self.assertFalse(fx3 == 0.0) 45 | np.testing.assert_almost_equal(fx4, ans4, 4) 46 | self.assertFalse(fx4 == 0.0) 47 | 48 | def testDerivH2(self): 49 | """ 50 | Test Derivatives of dihydrogen 51 | """ 52 | 53 | # build integrator object 54 | integrator = PyQInt() 55 | 56 | # build hydrogen molecule 57 | mol = Molecule('H2') 58 | mol.add_atom('H', -0.5, 0.0, 0.0) 59 | mol.add_atom('H', 0.5, 0.0, 0.0) 60 | cgfs, nuclei = mol.build_basis('sto3g') 61 | 62 | # calculate derivative towards H1 in the x-direction 63 | fx1 = integrator.kinetic_deriv(cgfs[0], cgfs[0], nuclei[0][0], 0) 64 | fx2 = integrator.kinetic_deriv(cgfs[1], cgfs[1], nuclei[0][0], 0) 65 | 66 | ans1 = calculate_force_finite_difference(mol, 0, 0, 0, 0) 67 | ans2 = calculate_force_finite_difference(mol, 0, 1, 1, 0) 68 | 69 | # assert that the kinetic of two CGFs that spawn from 70 | # the same nucleus will not change in energy due to a 71 | # change of the nucleus coordinates 72 | np.testing.assert_almost_equal(fx1, ans1, 4) 73 | np.testing.assert_almost_equal(fx1, 0.0, 4) 74 | np.testing.assert_almost_equal(fx2, ans2, 4) 75 | np.testing.assert_almost_equal(fx2, 0.0, 4) 76 | 77 | # assert that the cross-terms will change 78 | fx3 = integrator.kinetic_deriv(cgfs[0], cgfs[1], nuclei[0][0], 0) 79 | fx4 = integrator.kinetic_deriv(cgfs[0], cgfs[1], nuclei[1][0], 0) 80 | fx5 = integrator.kinetic_deriv(cgfs[1], cgfs[0], nuclei[0][0], 0) 81 | fx6 = integrator.kinetic_deriv(cgfs[1], cgfs[0], nuclei[1][0], 0) 82 | 83 | ans3 = calculate_force_finite_difference(mol, 0, 0, 1, 0) 84 | ans4 = calculate_force_finite_difference(mol, 1, 0, 1, 0) 85 | ans5 = calculate_force_finite_difference(mol, 0, 1, 0, 0) 86 | ans6 = calculate_force_finite_difference(mol, 1, 1, 0, 0) 87 | 88 | np.testing.assert_almost_equal(fx3, ans3, 4) 89 | np.testing.assert_almost_equal(fx4, ans4, 4) 90 | np.testing.assert_almost_equal(fx5, ans5, 4) 91 | np.testing.assert_almost_equal(fx6, ans6, 4) 92 | 93 | def calculate_force_finite_difference(mol, nuc_id, cgf_id1, cgf_id2, coord): 94 | # build integrator object 95 | integrator = PyQInt() 96 | 97 | # distance 98 | diff = 0.00001 99 | 100 | mol1 = deepcopy(mol) 101 | mol1.get_atoms()[nuc_id][1][coord] -= diff / 2 102 | mol2 = deepcopy(mol) 103 | mol2.get_atoms()[nuc_id][1][coord] += diff / 2 104 | 105 | # build hydrogen molecule 106 | cgfs1, nuclei = mol1.build_basis('sto3g') 107 | left = integrator.kinetic(cgfs1[cgf_id1], cgfs1[cgf_id2]) 108 | cgfs2, nuclei = mol2.build_basis('sto3g') 109 | right = integrator.kinetic(cgfs2[cgf_id1], cgfs2[cgf_id2]) 110 | 111 | return (right - left) / diff 112 | 113 | if __name__ == '__main__': 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /tests/test_molecule_builder.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import MoleculeBuilder 3 | import numpy as np 4 | import os 5 | 6 | class TestMoleculeBuilder(unittest.TestCase): 7 | 8 | def test_construction(self): 9 | """ 10 | Build a molecule from point group 11 | """ 12 | mol = MoleculeBuilder().build_complex_td(1.0, 'C', 'H') 13 | 14 | def test_load_molecule_from_name(self): 15 | """ 16 | Build a molecule from filename 17 | """ 18 | mol = MoleculeBuilder().from_name('ch4') 19 | 20 | np.testing.assert_almost_equal(mol.get_atoms()[0][1], 21 | np.array([0.0,0.0,0.0], dtype=np.float64)) 22 | np.testing.assert_almost_equal(mol.get_atoms()[1][1], 23 | np.array([0.6327670,0.6327670,0.6327670]) * 1.8897259886) 24 | np.testing.assert_equal(mol.get_atoms()[0][0], 'C') 25 | 26 | mol.build_basis('sto3g') 27 | np.testing.assert_equal(mol.get_nelec(), 10) 28 | 29 | def test_load_molecule_from_file(self): 30 | """ 31 | Build a molecule from file 32 | """ 33 | fname = os.path.join(os.path.dirname(__file__), 'results', 'ch4.xyz') 34 | mol = MoleculeBuilder().from_file(fname) 35 | 36 | np.testing.assert_almost_equal(mol.get_atoms()[0][1], 37 | np.array([0.0,0.0,0.0], dtype=np.float64)) 38 | np.testing.assert_almost_equal(mol.get_atoms()[1][1], 39 | np.array([0.6327670,0.6327670,0.6327670]) * 1.8897259886) 40 | np.testing.assert_equal(mol.get_atoms()[0][0], 'C') 41 | 42 | mol.build_basis('sto3g') 43 | np.testing.assert_equal(mol.get_nelec(), 10) 44 | 45 | if __name__ == '__main__': 46 | unittest.main() 47 | -------------------------------------------------------------------------------- /tests/test_molecule_order.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF 3 | import numpy as np 4 | 5 | class TestMoleculeOrder(unittest.TestCase): 6 | 7 | def testOrder(self): 8 | """ 9 | Test whether the same calculation but with a different order in the 10 | atoms gives the same result. Note that this test does not pass when 11 | the energetic convergence criterium is set too loose (e.g. 1e-5) 12 | """ 13 | results1 = build_mol_order1() 14 | results2 = build_mol_order2() 15 | 16 | en1 = results1['energy'] 17 | en2 = results2['energy'] 18 | 19 | np.testing.assert_almost_equal(en1, en2) 20 | 21 | def build_mol_order1(): 22 | """ 23 | Test Hartree-Fock calculation for Ethylene 24 | 25 | Data is compared to results obtained from Gaussian 26 | """ 27 | mol = Molecule() 28 | mol.add_atom('C', -0.6530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 29 | mol.add_atom('C', 0.6530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 30 | mol.add_atom('H', -1.2288875372, -0.9156191261 ,0.0000000000, unit='angstrom') 31 | mol.add_atom('H', -1.2288875372, 0.9156191261 ,0.0000000000, unit='angstrom') 32 | mol.add_atom('H', 1.2288875372, 0.9156191261 ,0.0000000000, unit='angstrom') 33 | mol.add_atom('H', 1.2288875372, -0.9156191261 ,0.0000000000, unit='angstrom') 34 | 35 | results = HF().rhf(mol, basis='sto3g', tolerance=1e-12) 36 | 37 | return results 38 | 39 | def build_mol_order2(): 40 | """ 41 | Test Hartree-Fock calculation for Ethylene 42 | 43 | Data is compared to results obtained from Gaussian 44 | """ 45 | mol = Molecule() 46 | mol.add_atom('C', -0.6530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 47 | mol.add_atom('H', -1.2288875372, -0.9156191261 ,0.0000000000, unit='angstrom') 48 | mol.add_atom('H', -1.2288875372, 0.9156191261 ,0.0000000000, unit='angstrom') 49 | mol.add_atom('C', 0.6530176758, 0.0000000000 ,0.0000000000, unit='angstrom') 50 | mol.add_atom('H', 1.2288875372, 0.9156191261 ,0.0000000000, unit='angstrom') 51 | mol.add_atom('H', 1.2288875372, -0.9156191261 ,0.0000000000, unit='angstrom') 52 | 53 | results = HF().rhf(mol, basis='sto3g', tolerance=1e-12) 54 | 55 | return results 56 | 57 | if __name__ == '__main__': 58 | unittest.main() 59 | -------------------------------------------------------------------------------- /tests/test_nuclear.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, gto, Molecule 3 | import numpy as np 4 | 5 | class TestNuclear(unittest.TestCase): 6 | 7 | def test_gto_nuclear(self): 8 | """ 9 | Test nuclear attraction integral for GTOs 10 | 11 | V^{(c)}_ij = 12 | """ 13 | 14 | # construct integrator object 15 | integrator = PyQInt() 16 | 17 | # test GTO 18 | gto1 = gto(0.154329, [0.0, 0.0, 0.0], 3.425251, 0, 0, 0) 19 | gto2 = gto(0.535328, [0.0, 0.0, 0.0], 0.623914, 0, 0, 0) 20 | gto3 = gto(0.444635, [0.0, 0.0, 0.0], 0.168855, 0, 0, 0) 21 | nuclear = integrator.nuclear_gto(gto1, gto1, [0.0, 0.0, 1.0]) 22 | result = -0.31049036979675293 23 | np.testing.assert_almost_equal(nuclear, result, 4) 24 | 25 | def test_cgf_nuclear(self): 26 | """ 27 | Test nuclear attraction integrals for contracted Gaussians 28 | 29 | V^{(c)}_ij = 30 | """ 31 | 32 | integrator = PyQInt() 33 | 34 | # build hydrogen molecule 35 | mol = Molecule("H2") 36 | mol.add_atom('H', 0.0, 0.0, 0.0) 37 | mol.add_atom('H', 0.0, 0.0, 1.4) 38 | cgfs, nuclei = mol.build_basis('sto3g') 39 | 40 | V1 = np.zeros((2,2)) 41 | V1[0,0] = integrator.nuclear(cgfs[0], cgfs[0], cgfs[0].p, 1) 42 | V1[0,1] = V1[1,0] = integrator.nuclear(cgfs[0], cgfs[1], cgfs[0].p, 1) 43 | V1[1,1] = integrator.nuclear(cgfs[1], cgfs[1], cgfs[0].p, 1) 44 | 45 | V2 = np.zeros((2,2)) 46 | V2[0,0] = integrator.nuclear(cgfs[0], cgfs[0], cgfs[1].p, 1) 47 | V2[0,1] = V2[1,0] = integrator.nuclear(cgfs[0], cgfs[1], cgfs[1].p, 1) 48 | V2[1,1] = integrator.nuclear(cgfs[1], cgfs[1], cgfs[1].p, 1) 49 | 50 | V11 = -1.2266135215759277 51 | V12 = -0.5974172949790955 52 | V22 = -0.6538270711898804 53 | np.testing.assert_almost_equal(V1[0,0], V11, 4) 54 | np.testing.assert_almost_equal(V1[1,1], V22, 4) 55 | np.testing.assert_almost_equal(V1[0,1], V12, 4) 56 | np.testing.assert_almost_equal(V2[0,0], V22, 4) 57 | np.testing.assert_almost_equal(V2[1,1], V11, 4) 58 | np.testing.assert_almost_equal(V2[0,1], V12, 4) 59 | 60 | if __name__ == '__main__': 61 | unittest.main() 62 | -------------------------------------------------------------------------------- /tests/test_nuclear_deriv.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | from copy import deepcopy 4 | import numpy as np 5 | import os 6 | 7 | class TestNuclearDeriv(unittest.TestCase): 8 | 9 | def testNuclearRepulsionDerivatives(self): 10 | """ 11 | Test Hartree-Fock calculation on water using STO-3G basis set 12 | """ 13 | mol = Molecule() 14 | mol.add_atom('O', 0.0, 0.0, 0.0) 15 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 16 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 17 | 18 | cgfs, nuclei = mol.build_basis('sto3g') 19 | 20 | forces_fd = calculate_deriv_nuclear_repulsion_finite_difference(mol) 21 | 22 | forces = np.zeros(forces_fd.shape) 23 | for i in range(0, len(mol.get_atoms())): # loop over nuclei 24 | for j in range(0, 3): # loop over directions 25 | forces[i,j] = deriv_nuclear_repulsion(nuclei, i, j) 26 | 27 | np.testing.assert_almost_equal(forces, forces_fd, 4) 28 | 29 | def test_derivatives_h2o_fulltest(self): 30 | """ 31 | Test Derivatives of water 32 | """ 33 | # build integrator object 34 | integrator = PyQInt() 35 | 36 | # build hydrogen molecule 37 | mol = Molecule('H2O') 38 | mol.add_atom('O', 0.00000, -0.07579, 0.00000, unit='angstrom') 39 | mol.add_atom('H', 0.86681, 0.60144, 0.00000, unit='angstrom') 40 | mol.add_atom('H', -0.86681, 0.60144, 0.00000, unit='angstrom') 41 | cgfs, nuclei = mol.build_basis('sto3g') 42 | 43 | # get position and charge 44 | probe_atom = 0 45 | O = nuclei[probe_atom][0] 46 | Ochg = nuclei[probe_atom][1] 47 | 48 | # load results from file 49 | fname = os.path.join(os.path.dirname(__file__), 'results', 'nuclear_deriv_h2o.txt') 50 | vals = np.loadtxt(fname).reshape((len(cgfs), len(cgfs), 3, 3)) 51 | for i in range(0, len(cgfs)): # loop over cgfs 52 | for j in range(0, len(cgfs)): # loop over cgfs 53 | for k in range(0,3): # loop over nuclei 54 | for l in range(0,3): # loop over directions 55 | force = integrator.nuclear_deriv(cgfs[i], cgfs[j], O, Ochg, nuclei[k][0], l) 56 | np.testing.assert_almost_equal(force, vals[i,j,k,l], 4) 57 | 58 | def calculate_force_finite_difference(cgf_id1, cgf_id2, nuc_id, coord, probe_atom): 59 | # build integrator object 60 | integrator = PyQInt() 61 | 62 | # distance 63 | diff = 0.00001 64 | 65 | vals = np.zeros(2) 66 | for i,v in enumerate([-1,1]): 67 | # build hydrogen molecule 68 | mol = Molecule('H2O') 69 | mol.add_atom('O', 0.00000, -0.07579, 0.00000, unit='angstrom') 70 | mol.add_atom('H', 0.86681, 0.60144, 0.00000, unit='angstrom') 71 | mol.add_atom('H', -0.86681, 0.60144, 0.00000, unit='angstrom') 72 | 73 | # adjust molecule 74 | mol.get_atoms()[nuc_id][1][coord] += v * diff / 2 75 | 76 | # build basis 77 | cgfs, nuclei = mol.build_basis('sto3g') 78 | 79 | # calculate values 80 | O = nuclei[probe_atom][0] 81 | Ochg = nuclei[probe_atom][1] 82 | vals[i] = integrator.nuclear(cgfs[cgf_id1], cgfs[cgf_id2], O, Ochg) 83 | 84 | return (vals[1] - vals[0]) / diff 85 | 86 | def energy_nuclear_repulsion(nuclei): 87 | energy = 0.0 88 | for i in range(0, len(nuclei)): 89 | for j in range(i+1, len(nuclei)): 90 | r = np.linalg.norm(np.array(nuclei[i][0]) - np.array(nuclei[j][0])) 91 | energy += nuclei[i][1] * nuclei[j][1] / r 92 | return energy 93 | 94 | def deriv_nuclear_repulsion(nuclei, nucid, coord): 95 | Vnn = 0.0 96 | pc = nuclei[nucid][0] 97 | for i in range(0, len(nuclei)): 98 | if nucid != i: 99 | pi = nuclei[i][0] 100 | Vnn += nuclei[nucid][1] * nuclei[i][1] * (pi[coord] - pc[coord]) / np.linalg.norm(pi - pc)**3 101 | 102 | return Vnn 103 | 104 | def calculate_deriv_nuclear_repulsion_finite_difference(mol): 105 | forces = np.zeros((3,3)) 106 | 107 | sz = 0.0001 108 | 109 | for i in range(0, len(mol.get_atoms())): # loop over nuclei 110 | for j in range(0, 3): # loop over directions 111 | mol1 = deepcopy(mol) 112 | mol1.get_atoms()[i][1][j] -= sz / 2 113 | mol2 = deepcopy(mol) 114 | mol2.get_atoms()[i][1][j] += sz / 2 115 | 116 | cgfs, nuclei1= mol1.build_basis('sto3g') 117 | cgfs, nuclei2 = mol2.build_basis('sto3g') 118 | 119 | energy1 = energy_nuclear_repulsion(nuclei1) 120 | energy2 = energy_nuclear_repulsion(nuclei2) 121 | 122 | forces[i,j] = (energy2 - energy1) / sz 123 | 124 | return forces 125 | 126 | if __name__ == '__main__': 127 | unittest.main() 128 | -------------------------------------------------------------------------------- /tests/test_overlap.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, gto, Molecule 3 | import numpy as np 4 | 5 | class TestOverlap(unittest.TestCase): 6 | 7 | def test_gto_overlap(self): 8 | """ 9 | Test kinetic integrals for GTOs 10 | 11 | Sij = 12 | """ 13 | 14 | # construct integrator object 15 | integrator = PyQInt() 16 | 17 | # test GTO 18 | gto1 = gto(0.154329, [0.0, 0.0, 0.0], 3.425251, 0, 0, 0) 19 | gto2 = gto(0.535328, [0.0, 0.0, 0.0], 0.623914, 0, 0, 0) 20 | gto3 = gto(0.444635, [0.0, 0.0, 0.0], 0.168855, 0, 0, 0) 21 | overlap = integrator.overlap_gto(gto1, gto1) 22 | result = 0.31055691838264465 23 | np.testing.assert_almost_equal(overlap, result, 4) 24 | 25 | def test_cgf_overlap(self): 26 | """ 27 | Test kinetic integrals for contracted Gaussians 28 | 29 | Sij = 30 | """ 31 | 32 | # construct integrator object 33 | integrator = PyQInt() 34 | 35 | # build hydrogen molecule 36 | mol = Molecule("H2") 37 | mol.add_atom('H', 0.0, 0.0, 0.0) 38 | mol.add_atom('H', 0.0, 0.0, 1.4) 39 | cgfs, nuclei = mol.build_basis('sto3g') 40 | 41 | S = np.zeros((2,2)) 42 | S[0,0] = integrator.overlap(cgfs[0], cgfs[0]) 43 | S[0,1] = S[1,0] = integrator.overlap(cgfs[0], cgfs[1]) 44 | S[1,1] = integrator.overlap(cgfs[1], cgfs[1]) 45 | 46 | S11 = 1.0 47 | S12 = 0.65931845 48 | np.testing.assert_almost_equal(S[0,0], S11, 4) 49 | np.testing.assert_almost_equal(S[1,1], S11, 4) 50 | np.testing.assert_almost_equal(S[0,1], S12, 4) 51 | 52 | if __name__ == '__main__': 53 | unittest.main() 54 | -------------------------------------------------------------------------------- /tests/test_overlap_deriv.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | from copy import deepcopy 4 | import numpy as np 5 | 6 | class TestOverlapDeriv(unittest.TestCase): 7 | 8 | def testDerivH2O(self): 9 | """ 10 | Test Derivatives of dihydrogen 11 | """ 12 | 13 | # build integrator object 14 | integrator = PyQInt() 15 | 16 | # build hydrogen molecule 17 | mol = Molecule() 18 | mol.add_atom('O', 0.0, 0.0, 0.0) 19 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 20 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 21 | cgfs, nuclei = mol.build_basis('sto3g') 22 | 23 | # calculate derivative towards H1 in the x-direction 24 | fx1 = integrator.overlap_deriv(cgfs[2], cgfs[2], nuclei[1][0], 0) # px 25 | fx2 = integrator.overlap_deriv(cgfs[2], cgfs[3], nuclei[1][0], 0) # py 26 | 27 | ans1 = calculate_force_finite_difference(mol, 1, 2, 2, 0) 28 | ans2 = calculate_force_finite_difference(mol, 1, 3, 3, 0) 29 | 30 | # assert that the overlap of two CGFs that spawn from 31 | # the same nucleus will not change in energy due to a 32 | # change of the nucleus coordinates 33 | np.testing.assert_almost_equal(fx1, ans1, 4) 34 | np.testing.assert_almost_equal(fx2, ans2, 4) 35 | 36 | # assert that the cross-terms will change 37 | fx3 = integrator.overlap_deriv(cgfs[2], cgfs[5], nuclei[1][0], 0) 38 | fx4 = integrator.overlap_deriv(cgfs[2], cgfs[5], nuclei[1][0], 0) 39 | 40 | ans3 = calculate_force_finite_difference(mol, 1, 2, 5, 0) 41 | ans4 = calculate_force_finite_difference(mol, 1, 2, 5, 0) 42 | 43 | np.testing.assert_almost_equal(fx3, ans3, 4) 44 | self.assertFalse(fx3 == 0.0) 45 | np.testing.assert_almost_equal(fx4, ans4, 4) 46 | self.assertFalse(fx4 == 0.0) 47 | 48 | def testDerivH2(self): 49 | """ 50 | Test Derivatives of dihydrogen 51 | """ 52 | 53 | # build integrator object 54 | integrator = PyQInt() 55 | 56 | # build hydrogen molecule 57 | mol = Molecule('H2') 58 | mol.add_atom('H', -0.5, 0.0, 0.0) 59 | mol.add_atom('H', 0.5, 0.0, 0.0) 60 | cgfs, nuclei = mol.build_basis('sto3g') 61 | 62 | # calculate derivative towards H1 in the x-direction 63 | fx1 = integrator.overlap_deriv(cgfs[0], cgfs[0], nuclei[0][0], 0) 64 | fx2 = integrator.overlap_deriv(cgfs[1], cgfs[1], nuclei[0][0], 0) 65 | 66 | ans1 = calculate_force_finite_difference(mol, 0, 0, 0, 0) 67 | ans2 = calculate_force_finite_difference(mol, 0, 1, 1, 0) 68 | 69 | # assert that the overlap of two CGFs that spawn from 70 | # the same nucleus will not change in energy due to a 71 | # change of the nucleus coordinates 72 | np.testing.assert_almost_equal(fx1, ans1, 4) 73 | np.testing.assert_almost_equal(fx1, 0.0, 4) 74 | np.testing.assert_almost_equal(fx2, ans2, 4) 75 | np.testing.assert_almost_equal(fx2, 0.0, 4) 76 | 77 | # assert that the cross-terms will change 78 | fx3 = integrator.overlap_deriv(cgfs[0], cgfs[1], nuclei[0][0], 0) 79 | fx4 = integrator.overlap_deriv(cgfs[0], cgfs[1], nuclei[1][0], 0) 80 | fx5 = integrator.overlap_deriv(cgfs[1], cgfs[0], nuclei[0][0], 0) 81 | fx6 = integrator.overlap_deriv(cgfs[1], cgfs[0], nuclei[1][0], 0) 82 | 83 | ans3 = calculate_force_finite_difference(mol, 0, 0, 1, 0) 84 | ans4 = calculate_force_finite_difference(mol, 1, 0, 1, 0) 85 | ans5 = calculate_force_finite_difference(mol, 0, 1, 0, 0) 86 | ans6 = calculate_force_finite_difference(mol, 1, 1, 0, 0) 87 | 88 | np.testing.assert_almost_equal(fx3, ans3, 4) 89 | np.testing.assert_almost_equal(fx4, ans4, 4) 90 | np.testing.assert_almost_equal(fx5, ans5, 4) 91 | np.testing.assert_almost_equal(fx6, ans6, 4) 92 | 93 | def calculate_force_finite_difference(mol, nuc_id, cgf_id1, cgf_id2, coord): 94 | # build integrator object 95 | integrator = PyQInt() 96 | 97 | # distance 98 | diff = 0.00001 99 | 100 | mol1 = deepcopy(mol) 101 | mol1.get_atoms()[nuc_id][1][coord] -= diff / 2 102 | mol2 = deepcopy(mol) 103 | mol2.get_atoms()[nuc_id][1][coord] += diff / 2 104 | 105 | # build hydrogen molecule 106 | cgfs1, nuclei = mol1.build_basis('sto3g') 107 | left = integrator.overlap(cgfs1[cgf_id1], cgfs1[cgf_id2]) 108 | cgfs2, nuclei = mol2.build_basis('sto3g') 109 | right = integrator.overlap(cgfs2[cgf_id1], cgfs2[cgf_id2]) 110 | 111 | return (right - left) / diff 112 | 113 | if __name__ == '__main__': 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /tests/test_plot_data.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | import numpy as np 4 | import os 5 | 6 | class TestPlotData(unittest.TestCase): 7 | 8 | def test_plot_wavefunction(self): 9 | """ 10 | Test generation of wave function 11 | """ 12 | # build hydrogen molecule 13 | mol = Molecule("H2") 14 | mol.add_atom('H', 0.0, 0.00, -0.7) # distances in Bohr lengths 15 | mol.add_atom('H', 0.0, 0.00, 0.7) # distances in Bohr lengths 16 | cgfs, nuclei = mol.build_basis('sto3g') 17 | 18 | # construct integrator object 19 | integrator = PyQInt() 20 | 21 | # build grid points 22 | x = np.linspace(-2, 2, 6, endpoint=True) 23 | coord = np.flipud(np.vstack(np.meshgrid(x, x, x, indexing='ij')).reshape(3,-1)).T 24 | 25 | c = np.array([0.54893397, 0.54893397]) 26 | wf = integrator.plot_wavefunction(coord, c, cgfs) 27 | 28 | res = np.load(os.path.join(os.path.dirname(__file__), 'results', 'h2_wf.npy')) 29 | np.testing.assert_almost_equal(wf, res, 4) 30 | 31 | def test_plot_gradient(self): 32 | """ 33 | Test generation of wave function 34 | """ 35 | # build hydrogen molecule 36 | mol = Molecule("H2") 37 | mol.add_atom('H', 0.0, 0.00, -0.7) # distances in Bohr lengths 38 | mol.add_atom('H', 0.0, 0.00, 0.7) # distances in Bohr lengths 39 | cgfs, nuclei = mol.build_basis('sto3g') 40 | 41 | # construct integrator object 42 | integrator = PyQInt() 43 | 44 | c = np.array([0.54893397, 0.54893397]) 45 | 46 | # test couple of single points 47 | sc = np.array([[-2.,-2.,-2.]]) 48 | grad = integrator.plot_gradient(sc, c, cgfs) 49 | self.assertEqual(grad.shape[0], 1) 50 | self.assertEqual(grad.shape[1], 3) 51 | res = np.array([[0.00926244,0.00926244,0.0076777]]) 52 | np.testing.assert_almost_equal(grad, res, 4) 53 | 54 | sc = np.array([[-2., 2.,-2.]]) 55 | grad = integrator.plot_gradient(sc, c, cgfs) 56 | self.assertEqual(grad.shape[0], 1) 57 | self.assertEqual(grad.shape[1], 3) 58 | res = np.array([[0.00926244, -0.00926244, 0.0076777]]) 59 | np.testing.assert_almost_equal(grad, res, 4) 60 | 61 | # test two points 62 | tc = np.array([[-2.,-2.,-2.],[-2., 2.,-2.]]) 63 | grad = integrator.plot_gradient(tc, c, cgfs) 64 | self.assertEqual(grad.shape[0], 2) 65 | self.assertEqual(grad.shape[1], 3) 66 | res = np.array([[0.00926244,0.00926244,0.0076777],[0.00926244, -0.00926244, 0.0076777]]) 67 | np.testing.assert_almost_equal(grad, res, 4) 68 | 69 | # test grid of points 70 | # x = np.linspace(-2, 2, 6, endpoint=True) 71 | # coord = np.flipud(np.vstack(np.meshgrid(x, x, x, indexing='ij')).reshape(3,-1)).T 72 | coord = integrator.build_rectgrid3d(-2, 2, 6, endpoint=True) 73 | grad = integrator.plot_gradient(coord, c, cgfs) 74 | self.assertEqual(grad.shape[0], 6*6*6) 75 | self.assertEqual(grad.shape[1], 3) 76 | res = np.load(os.path.join(os.path.dirname(__file__), 'results', 'h2_grad.npy')) 77 | np.testing.assert_almost_equal(grad, res, 4) 78 | 79 | if __name__ == '__main__': 80 | unittest.main() 81 | -------------------------------------------------------------------------------- /tests/test_repulsion.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, gto, Molecule 3 | import numpy as np 4 | 5 | class TestRepulsion(unittest.TestCase): 6 | 7 | def test_gto_repulsion(self): 8 | """ 9 | Test two-electron integrals for primitive GTOs 10 | 11 | (ij|kl) = 12 | """ 13 | 14 | # construct integrator object 15 | integrator = PyQInt() 16 | 17 | # test GTO 18 | gto1 = gto(0.154329, [0.0, 0.0, 0.0], 3.425251, 0, 0, 0) 19 | gto2 = gto(0.535328, [0.0, 0.0, 0.0], 0.623914, 0, 0, 0) 20 | gto3 = gto(0.444635, [0.0, 0.0, 0.0], 0.168855, 0, 0, 0) 21 | repulsion = integrator.repulsion_gto(gto1, gto1, gto1, gto1) 22 | result = 0.20141123130697272 23 | np.testing.assert_almost_equal(repulsion, result, 4) 24 | 25 | def test_cgf_repulsion(self): 26 | """ 27 | Test two-electron integrals for contracted Gaussians 28 | 29 | (ij|kl) = 30 | """ 31 | 32 | # construct integrator object 33 | integrator = PyQInt() 34 | 35 | # build hydrogen molecule 36 | mol = Molecule("H2") 37 | mol.add_atom('H', 0.0, 0.0, 0.0) 38 | mol.add_atom('H', 0.0, 0.0, 1.4) 39 | cgfs, nuclei = mol.build_basis('sto3g') 40 | 41 | T1111 = integrator.repulsion(cgfs[0], cgfs[0], cgfs[0], cgfs[0]) 42 | T1122 = integrator.repulsion(cgfs[0], cgfs[0], cgfs[1], cgfs[1]) 43 | T1112 = integrator.repulsion(cgfs[0], cgfs[0], cgfs[0], cgfs[1]) 44 | T2121 = integrator.repulsion(cgfs[1], cgfs[0], cgfs[1], cgfs[0]) 45 | T1222 = integrator.repulsion(cgfs[0], cgfs[1], cgfs[1], cgfs[1]) 46 | T2211 = integrator.repulsion(cgfs[1], cgfs[1], cgfs[0], cgfs[0]) 47 | 48 | np.testing.assert_almost_equal(T1111, 0.7746056914329529, 4) 49 | np.testing.assert_almost_equal(T1122, 0.5696758031845093, 4) 50 | np.testing.assert_almost_equal(T1112, 0.4441076656879812, 4) 51 | np.testing.assert_almost_equal(T2121, 0.2970285713672638, 4) 52 | 53 | # test similarity between two-electron integrals 54 | np.testing.assert_almost_equal(T1222, T1112, 4) 55 | np.testing.assert_almost_equal(T1122, T2211, 4) 56 | 57 | def test_two_electron_indices(self): 58 | """ 59 | Test unique two-electron indices 60 | """ 61 | integrator = PyQInt() 62 | 63 | np.testing.assert_almost_equal(integrator.teindex(1,1,2,1), integrator.teindex(1,1,1,2), 4) 64 | np.testing.assert_almost_equal(integrator.teindex(1,1,2,1), integrator.teindex(2,1,1,1), 4) 65 | np.testing.assert_almost_equal(integrator.teindex(1,2,1,1), integrator.teindex(2,1,1,1), 4) 66 | np.testing.assert_almost_equal(integrator.teindex(1,1,1,2), integrator.teindex(1,1,2,1), 4) 67 | self.assertNotEqual(integrator.teindex(1,1,1,1), integrator.teindex(1,1,2,1)) 68 | self.assertNotEqual(integrator.teindex(1,1,2,1), integrator.teindex(1,1,2,2)) 69 | 70 | if __name__ == '__main__': 71 | unittest.main() 72 | -------------------------------------------------------------------------------- /tests/test_repulsion_deriv.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, gto, Molecule 3 | from copy import deepcopy 4 | import numpy as np 5 | 6 | class TestRepulsionDeriv(unittest.TestCase): 7 | 8 | def testDerivH2O(self): 9 | """ 10 | Test Derivatives of water 11 | """ 12 | 13 | # build integrator object 14 | integrator = PyQInt() 15 | 16 | # build hydrogen molecule 17 | mol = Molecule() 18 | mol.add_atom('O', 0.0, 0.0, 0.0) 19 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 20 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 21 | cgfs, nuclei = mol.build_basis('sto3g') 22 | 23 | # calculate derivative of 2s AO on oxygen towards H1 in the x-direction 24 | fx1 = integrator.repulsion_deriv(cgfs[2], cgfs[2], cgfs[2], cgfs[2], nuclei[1][0], 0) # px 25 | fx2 = integrator.repulsion_deriv(cgfs[2], cgfs[3], cgfs[3], cgfs[3], nuclei[1][0], 0) # py 26 | 27 | ans1 = calculate_force_finite_difference(mol, 1, 2, 2, 2, 2, 0) 28 | ans2 = calculate_force_finite_difference(mol, 1, 3, 3, 3, 3, 0) 29 | 30 | # assert that the repulsion of two CGFs that spawn from 31 | # the same nucleus will not change in energy due to a 32 | # change of the nucleus coordinates 33 | np.testing.assert_almost_equal(fx1, ans1, 4) 34 | np.testing.assert_almost_equal(fx2, ans2, 4) 35 | 36 | # assert that the cross-terms will change 37 | fx3 = integrator.repulsion_deriv(cgfs[3], cgfs[3], cgfs[5], cgfs[5], nuclei[0][0], 0) 38 | fx4 = integrator.repulsion_deriv(cgfs[3], cgfs[3], cgfs[5], cgfs[5], nuclei[1][0], 0) 39 | fx5 = integrator.repulsion_deriv(cgfs[5], cgfs[3], cgfs[5], cgfs[3], nuclei[0][0], 0) 40 | fx6 = integrator.repulsion_deriv(cgfs[3], cgfs[5], cgfs[3], cgfs[5], nuclei[1][0], 0) 41 | 42 | print(fx3) 43 | 44 | # mol | nuc_id | cgf_id1 | cgf_id2 | cgf_id3 | cgf_id4 | coord 45 | ans3 = calculate_force_finite_difference(mol, 0, 3, 3, 5, 5, 0) 46 | ans4 = calculate_force_finite_difference(mol, 1, 3, 3, 5, 5, 0) 47 | ans5 = calculate_force_finite_difference(mol, 0, 5, 3, 5, 3, 0) 48 | ans6 = calculate_force_finite_difference(mol, 1, 3, 5, 3, 5, 0) 49 | 50 | # assert that these are non-trivial tests 51 | self.assertFalse(ans3 == 0.0) 52 | self.assertFalse(ans4 == 0.0) 53 | self.assertFalse(ans5 == 0.0) 54 | self.assertFalse(ans6 == 0.0) 55 | 56 | np.testing.assert_almost_equal(fx3, ans3, 5) 57 | np.testing.assert_almost_equal(fx4, ans4, 5) 58 | np.testing.assert_almost_equal(fx5, ans5, 5) 59 | np.testing.assert_almost_equal(fx6, ans6, 5) 60 | 61 | # assert that the cross-terms will change 62 | fx7 = integrator.repulsion_deriv(cgfs[2], cgfs[3], cgfs[5], cgfs[6], nuclei[0][0], 0) 63 | fx8 = integrator.repulsion_deriv(cgfs[2], cgfs[3], cgfs[5], cgfs[6], nuclei[1][0], 1) 64 | fx9 = integrator.repulsion_deriv(cgfs[2], cgfs[3], cgfs[5], cgfs[6], nuclei[2][0], 1) 65 | 66 | # get answers 67 | ans7 = calculate_force_finite_difference(mol, 0, 2, 3, 5, 6, 0) 68 | ans8 = calculate_force_finite_difference(mol, 1, 2, 3, 5, 6, 1) 69 | ans9 = calculate_force_finite_difference(mol, 2, 2, 3, 5, 6, 1) 70 | 71 | # assert that these are non-trivial tests 72 | self.assertFalse(ans7 == 0.0) 73 | self.assertFalse(ans8 == 0.0) 74 | self.assertFalse(ans9 == 0.0) 75 | 76 | np.testing.assert_almost_equal(fx7, ans7, 5) 77 | np.testing.assert_almost_equal(fx8, ans8, 5) 78 | np.testing.assert_almost_equal(fx9, ans9, 5) 79 | 80 | def testDerivH2(self): 81 | """ 82 | Test Derivatives of dihydrogen 83 | """ 84 | 85 | # build integrator object 86 | integrator = PyQInt() 87 | 88 | # build hydrogen molecule 89 | mol = Molecule('H2') 90 | mol.add_atom('H', -0.5, 0.0, 0.0) 91 | mol.add_atom('H', 0.5, 0.0, 0.0) 92 | cgfs, nuclei = mol.build_basis('sto3g') 93 | 94 | # calculate derivative towards H1 in the x-direction 95 | fx1 = integrator.repulsion_deriv(cgfs[0], cgfs[0], cgfs[0], cgfs[0], nuclei[0][0], 0) 96 | fx2 = integrator.repulsion_deriv(cgfs[1], cgfs[1], cgfs[1], cgfs[1], nuclei[0][0], 0) 97 | 98 | # mol | nuc_id | cgf_id1 | cgf_id2 | cgf_id3 | cgf_id4 | coord 99 | ans1 = calculate_force_finite_difference(mol, 0, 0, 0, 0, 0, 0) 100 | ans2 = calculate_force_finite_difference(mol, 0, 1, 1, 1, 1, 0) 101 | 102 | # assert that the repulsion of two CGFs that spawn from 103 | # the same nucleus will not change in energy due to a 104 | # change of the nucleus coordinates 105 | np.testing.assert_almost_equal(fx1, ans1, 4) 106 | np.testing.assert_almost_equal(fx1, 0.0, 4) 107 | np.testing.assert_almost_equal(fx2, ans2, 4) 108 | np.testing.assert_almost_equal(fx2, 0.0, 4) 109 | 110 | # assert that the cross-terms will change 111 | fx3 = integrator.repulsion_deriv(cgfs[0], cgfs[0], cgfs[1], cgfs[1], nuclei[0][0], 0) 112 | fx4 = integrator.repulsion_deriv(cgfs[0], cgfs[0], cgfs[1], cgfs[1], nuclei[1][0], 0) 113 | fx5 = integrator.repulsion_deriv(cgfs[1], cgfs[0], cgfs[1], cgfs[0], nuclei[0][0], 0) 114 | fx6 = integrator.repulsion_deriv(cgfs[1], cgfs[0], cgfs[1], cgfs[0], nuclei[1][0], 0) 115 | 116 | # mol | nuc_id | cgf_id1 | cgf_id2 | cgf_id3 | cgf_id4 | coord 117 | ans3 = calculate_force_finite_difference(mol, 0, 0, 0, 1, 1, 0) 118 | ans4 = calculate_force_finite_difference(mol, 1, 0, 0, 1, 1, 0) 119 | ans5 = calculate_force_finite_difference(mol, 0, 1, 0, 1, 0, 0) 120 | ans6 = calculate_force_finite_difference(mol, 1, 1, 0, 1, 0, 0) 121 | 122 | np.testing.assert_almost_equal(fx3, ans3, 4) 123 | np.testing.assert_almost_equal(fx4, ans4, 4) 124 | np.testing.assert_almost_equal(fx5, ans5, 4) 125 | np.testing.assert_almost_equal(fx6, ans6, 4) 126 | 127 | def calculate_force_finite_difference(mol, nuc_id, cgf_id1, cgf_id2, cgf_id3, cgf_id4, coord): 128 | # build integrator object 129 | integrator = PyQInt() 130 | 131 | # distance 132 | diff = 0.00001 133 | 134 | mol1 = deepcopy(mol) 135 | mol1.get_atoms()[nuc_id][1][coord] -= diff / 2 136 | mol2 = deepcopy(mol) 137 | mol2.get_atoms()[nuc_id][1][coord] += diff / 2 138 | 139 | # build hydrogen molecule 140 | cgfs1, nuclei = mol1.build_basis('sto3g') 141 | left = integrator.repulsion(cgfs1[cgf_id1], cgfs1[cgf_id2], cgfs1[cgf_id3], cgfs1[cgf_id4]) 142 | cgfs2, nuclei = mol2.build_basis('sto3g') 143 | right = integrator.repulsion(cgfs2[cgf_id1], cgfs2[cgf_id2], cgfs2[cgf_id3], cgfs2[cgf_id4]) 144 | 145 | return (right - left) / diff 146 | 147 | def calculate_deriv_gto(gto1, gto2, gto3, gto4, coord): 148 | # build integrator object 149 | integrator = PyQInt() 150 | 151 | # distance 152 | diff = 0.000001 153 | p = np.zeros(3) 154 | p[coord] = diff 155 | 156 | gto1_new1 = gto(gto1.c, gto1.p - 0.5 * p, gto1.alpha, gto1.l, gto1.m, gto1.n) 157 | gto1_new2 = gto(gto1.c, gto1.p + 0.5 * p, gto1.alpha, gto1.l, gto1.m, gto1.n) 158 | 159 | # build hydrogen molecule 160 | left = integrator.repulsion_gto(gto1_new1, gto2, gto3, gto4) 161 | right = integrator.repulsion_gto(gto1_new2, gto2, gto3, gto4) 162 | 163 | return (right - left) / diff 164 | 165 | if __name__ == '__main__': 166 | unittest.main() 167 | -------------------------------------------------------------------------------- /tests/test_safety_guards.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import PyQInt, Molecule 3 | import numpy as np 4 | 5 | class TestSafetyGuards(unittest.TestCase): 6 | """ 7 | Tests certain safety features preventing user errors 8 | """ 9 | 10 | def test_safety_plot_wavefunction(self): 11 | """ 12 | Test for correct array sizes 13 | """ 14 | # build hydrogen molecule 15 | mol = Molecule("H2") 16 | mol.add_atom('H', 0.0, 0.00, -0.7) # distances in Bohr lengths 17 | mol.add_atom('H', 0.0, 0.00, 0.7) # distances in Bohr lengths 18 | cgfs, nuclei = mol.build_basis('sto3g') 19 | 20 | # construct integrator object 21 | integrator = PyQInt() 22 | 23 | # build grid points 24 | x = np.linspace(-2, 2, 6, endpoint=True) 25 | grid = np.flipud(np.vstack(np.meshgrid(x, x, x, indexing='ij')).reshape(3,-1)).T 26 | 27 | # put in matrix object -> should yield error 28 | c = np.identity(2) 29 | with self.assertRaises(TypeError) as raises_cm: 30 | integrator.plot_wavefunction(grid, c, cgfs) 31 | 32 | exception = raises_cm.exception 33 | self.assertTrue('arrays can be converted to Python scalars' in exception.args[0]) 34 | 35 | # put in matrix object -> should yield error 36 | c = np.identity(3) 37 | with self.assertRaises(Exception) as raises_cm: 38 | integrator.plot_wavefunction(grid, c, cgfs) 39 | 40 | exception = raises_cm.exception 41 | self.assertEqual(exception.args, ('Dimensions of cgf list and coefficient matrix do not match (2 != 3)',)) 42 | 43 | # put in matrix object -> should yield error 44 | c = np.ones(3) 45 | with self.assertRaises(Exception) as raises_cm: 46 | integrator.plot_wavefunction(grid, c, cgfs) 47 | 48 | exception = raises_cm.exception 49 | self.assertEqual(exception.args, ('Dimensions of cgf list and coefficient matrix do not match (2 != 3)',)) 50 | 51 | def test_safety_plot_gradient(self): 52 | """ 53 | Test for correct array sizes 54 | """ 55 | # build hydrogen molecule 56 | mol = Molecule("H2") 57 | mol.add_atom('H', 0.0, 0.00, -0.7) # distances in Bohr lengths 58 | mol.add_atom('H', 0.0, 0.00, 0.7) # distances in Bohr lengths 59 | cgfs, nuclei = mol.build_basis('sto3g') 60 | 61 | # construct integrator object 62 | integrator = PyQInt() 63 | 64 | # build grid points 65 | x = np.linspace(-2, 2, 6, endpoint=True) 66 | grid = np.flipud(np.vstack(np.meshgrid(x, x, x, indexing='ij')).reshape(3,-1)).T 67 | 68 | # put in matrix object -> should yield error 69 | c = np.identity(2) 70 | with self.assertRaises(TypeError) as raises_cm: 71 | integrator.plot_gradient(grid, c, cgfs) 72 | 73 | exception = raises_cm.exception 74 | self.assertEqual(type(exception), TypeError) 75 | 76 | # put in matrix object -> should yield error 77 | c = np.identity(3) 78 | with self.assertRaises(Exception) as raises_cm: 79 | integrator.plot_gradient(grid, c, cgfs) 80 | 81 | exception = raises_cm.exception 82 | self.assertEqual(exception.args, ('Dimensions of cgf list and coefficient matrix do not match (2 != 3)',)) 83 | 84 | # put in matrix object -> should yield error 85 | c = np.ones(3) 86 | with self.assertRaises(Exception) as raises_cm: 87 | integrator.plot_gradient(grid, c, cgfs) 88 | 89 | exception = raises_cm.exception 90 | self.assertEqual(exception.args, ('Dimensions of cgf list and coefficient matrix do not match (2 != 3)',)) 91 | 92 | if __name__ == '__main__': 93 | unittest.main() 94 | -------------------------------------------------------------------------------- /tests/test_str.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import pyqint 3 | from pyqint import PyQInt, Molecule 4 | 5 | class TestString(unittest.TestCase): 6 | 7 | def test_strings(self): 8 | """ 9 | Test automatic integral evaluation for hydrogen molecule 10 | """ 11 | 12 | # construct integrator object 13 | integrator = PyQInt() 14 | 15 | # build hydrogen molecule 16 | mol = Molecule('H2') 17 | mol.add_atom('H', 0.0, 0.0, 0.0) 18 | mol.add_atom('H', 0.0, 0.0, 1.4) 19 | cgfs, nuclei = mol.build_basis('sto3g') 20 | 21 | ans = "CGF; R=(0.000000,0.000000,0.000000)\n" 22 | ans += " 01 | GTO : c=0.154329, alpha=3.425251, l=0, m=0, n=0, R=(0.000000,0.000000,0.000000)\n" 23 | ans += " 02 | GTO : c=0.535328, alpha=0.623914, l=0, m=0, n=0, R=(0.000000,0.000000,0.000000)\n" 24 | ans += " 03 | GTO : c=0.444635, alpha=0.168855, l=0, m=0, n=0, R=(0.000000,0.000000,0.000000)\n" 25 | self.assertEqual(str(cgfs[0]), ans) 26 | 27 | ans = "Molecule: H2\n" 28 | ans += " H (0.000000,0.000000,0.000000)\n" 29 | ans += " H (0.000000,0.000000,1.400000)\n" 30 | self.assertEqual(str(mol), ans) 31 | 32 | def test_version(self): 33 | strver = pyqint.__version__.split('.') 34 | self.assertTrue(strver[0].isnumeric()) 35 | self.assertTrue(strver[1].isnumeric()) 36 | self.assertTrue(strver[2].isnumeric()) 37 | 38 | if __name__ == '__main__': 39 | unittest.main() 40 | -------------------------------------------------------------------------------- /tests/test_tolerance.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from pyqint import Molecule, HF 3 | import numpy as np 4 | 5 | class TestHF(unittest.TestCase): 6 | 7 | def testHartreeFockWater(self): 8 | """ 9 | Test Hartree-Fock calculation on water using STO-3G basis set 10 | """ 11 | mol = Molecule() 12 | mol.add_atom('O', 0.0, 0.0, 0.0) 13 | mol.add_atom('H', 0.7570, 0.5860, 0.0) 14 | mol.add_atom('H', -0.7570, 0.5860, 0.0) 15 | 16 | results = perform_hf(mol) 17 | 18 | # check that energy matches 19 | np.testing.assert_almost_equal(results['energy'], -73.21447132, 4) 20 | 21 | def perform_hf(mol): 22 | results = HF().rhf(mol, 'sto3g', tolerance=1e-9) 23 | return results 24 | 25 | if __name__ == '__main__': 26 | unittest.main() 27 | -------------------------------------------------------------------------------- /testversion.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import os 4 | import re 5 | 6 | ROOT = os.path.dirname(__file__) 7 | 8 | def main(): 9 | version_versionpy = get_version_versionpy() 10 | version_metayaml = get_version_metayaml() 11 | 12 | print('Version strings found:') 13 | print(version_versionpy) 14 | print(version_metayaml) 15 | 16 | try: 17 | for i in range(0,3): 18 | assert version_versionpy[i] == version_metayaml[i] 19 | except Exception as e: 20 | print(e) 21 | raise Exception('Invalid version strings encountered') 22 | 23 | def get_version_projecttoml(): 24 | """ 25 | Extract the version string from the pyproject.toml file 26 | """ 27 | pattern = re.compile(r'^version\s*=\s*"(\d+\.\d+.\d+)"\s*$') 28 | 29 | f = open(os.path.join(ROOT, 'pyproject.toml')) 30 | lines = f.readlines() 31 | for line in lines: 32 | match = re.match(pattern, line) 33 | if match: 34 | version = match.groups(1)[0] 35 | return [int(i) for i in version.split('.')] 36 | 37 | return None 38 | 39 | def get_version_versionpy(): 40 | """ 41 | Extract the version string from the _version.py file 42 | """ 43 | pattern = re.compile(r'^__version__\s*=\s*[\'"](\d+\.\d+.\d+)[\'"]\s*$') 44 | 45 | f = open(os.path.join(ROOT, 'pyqint', '_version.py')) 46 | lines = f.readlines() 47 | for line in lines: 48 | match = re.match(pattern, line) 49 | if match: 50 | version = match.groups(1)[0] 51 | return [int(i) for i in version.split('.')] 52 | 53 | return None 54 | 55 | def get_version_metayaml(): 56 | """ 57 | Extract the version string from the meta.yaml file 58 | """ 59 | pattern = re.compile(r'^\s*version\s*:\s*"(\d+\.\d+.\d+)"\s*$') 60 | 61 | f = open(os.path.join(ROOT, 'meta.yaml')) 62 | lines = f.readlines() 63 | for line in lines: 64 | match = re.match(pattern, line) 65 | if match: 66 | version = match.groups(1)[0] 67 | return [int(i) for i in version.split('.')] 68 | 69 | return None 70 | 71 | if __name__ == '__main__': 72 | main() --------------------------------------------------------------------------------