├── .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 | [](https://jose.theoj.org/papers/2a73fa24200e8c1ec47fc6e37f818a54)
4 | [](https://github.com/ifilot/pyqint/actions/workflows/build_conda.yml)
5 | [](https://github.com/ifilot/pyqint/actions/workflows/build_wheels.yml)
6 | [](https://anaconda.org/ifilot/pyqint)
7 | [](https://pypi.org/project/pyqint/)
8 | [](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 | 
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 |
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()
--------------------------------------------------------------------------------